test(3d): screenshot-baseline TDD suite for the 3D viewer OpenGL->WebGL port — 47 native goldens + red-state WebGL harness
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>
This commit is contained in:
parent
1a24c2894e
commit
ce1473c9ab
85 changed files with 6797 additions and 1 deletions
168
tests/3d-regression/native/CMakeLists.txt
Normal file
168
tests/3d-regression/native/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
# Native golden-baseline generator for the 3D-renderer regression suite.
|
||||
# Compiles REAL KiCad 3D-viewer sources against desktop OpenGL (macOS 2.1
|
||||
# compatibility context via the vendored glad loader — no GLEW).
|
||||
# Modeled on tests/gal-regression/native/CMakeLists.txt.
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(scene3d_native_test)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(APPLE)
|
||||
add_compile_definitions(GL_SILENCE_DEPRECATION)
|
||||
endif()
|
||||
|
||||
# KiCad builds clipper2 with USINGZ (PUBLIC), so every TU touching clipper
|
||||
# types must agree.
|
||||
add_compile_definitions(USINGZ)
|
||||
|
||||
# System wxWidgets (homebrew)
|
||||
set(wxWidgets_CONFIG_EXECUTABLE "/opt/homebrew/bin/wx-config")
|
||||
find_package(wxWidgets REQUIRED COMPONENTS core base gl)
|
||||
include(${wxWidgets_USE_FILE})
|
||||
|
||||
# OpenGL.framework provides GL and GLU on macOS
|
||||
find_package(OpenGL REQUIRED)
|
||||
|
||||
# KiCad source root
|
||||
set(KICAD_ROOT ${CMAKE_SOURCE_DIR}/../../../kicad)
|
||||
|
||||
# Vendored glad loader (the fork's kicad_gl/kiglad.h routes to it natively)
|
||||
set(GLAD_SOURCES ${KICAD_ROOT}/thirdparty/glad/src/gl.c)
|
||||
|
||||
# Real KiCad 3D-viewer TUs — Stage 1 (standalone, no RENDER_3D_OPENGL members).
|
||||
# Stage 2 adds render_3d_opengl.cpp / create_scene.cpp + the board_adapter /
|
||||
# settings test impls (see the suite README).
|
||||
set(KICAD_3D_SOURCES
|
||||
${KICAD_ROOT}/3d-viewer/common_ogl/ogl_utils.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/image.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/buffers_debug.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/color_rgba.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/track_ball.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/trackball.cpp
|
||||
${KICAD_ROOT}/common/gal/3d/camera.cpp
|
||||
# display lists / plates / stencil subtraction
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/opengl/layer_triangles.cpp
|
||||
# immediate-mode helpers (arrows, segments, bbox, half-cylinder)
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/opengl/opengl_utils.cpp
|
||||
# 3D model VBO path + navigator gizmo
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/opengl/3d_model.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/opengl/3d_spheres_gizmo.cpp
|
||||
# shapes2D objects the scenarios construct (ROUND_SEGMENT_2D et al.)
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/raytracing/ray.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/raytracing/accelerators/container_2d.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/raytracing/shapes2D/object_2d.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/raytracing/shapes2D/bbox_2d.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/raytracing/shapes2D/round_segment_2d.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/raytracing/shapes2D/filled_circle_2d.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/raytracing/shapes3D/bbox_3d.cpp
|
||||
# kimath bits the above reference (RotatePoint in DrawHalfOpenCylinder)
|
||||
${KICAD_ROOT}/libs/kimath/src/trigo.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/eda_angle.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/seg.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/math/util.cpp
|
||||
|
||||
# ---- Stage 2: RENDER_3D_OPENGL itself ----
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/opengl/render_3d_opengl.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/opengl/create_scene.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/render_3d_base.cpp
|
||||
# remaining shapes2D types the generators take as inputs
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/raytracing/shapes2D/ring_2d.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/raytracing/shapes2D/triangle_2d.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/raytracing/shapes2D/4pt_polygon_2d.cpp
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering/raytracing/shapes2D/polygon_2d.cpp
|
||||
# kimath polygon set + triangulation (createBoard/generateLayerList)
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/shape_poly_set.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/shape_line_chain.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/shape.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/shape_arc.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/vertex_set.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/geometry_utils.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/convert_basic_shapes_to_polygon.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/md5_hash.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/bezier_curves.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/circle.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/arc_chord_params.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/corner_operations.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/shape_collisions.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/shape_compound.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/shape_rect.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/shape_nearest_points.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/half_line.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/shape_utils.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/roundrect.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/line.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/geometry/shape_segment.cpp
|
||||
${KICAD_ROOT}/libs/kimath/src/math/vector2.cpp
|
||||
# small common/core TUs referenced by the render TUs
|
||||
${KICAD_ROOT}/common/layer_id.cpp
|
||||
${KICAD_ROOT}/common/gal/color4d.cpp
|
||||
${KICAD_ROOT}/common/kicad_gl/gl_context_mgr.cpp
|
||||
${KICAD_ROOT}/common/lset.cpp
|
||||
${KICAD_ROOT}/libs/core/utf8.cpp
|
||||
# vendored clipper2 (SHAPE_POLY_SET booleans)
|
||||
${KICAD_ROOT}/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp
|
||||
${KICAD_ROOT}/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp
|
||||
${KICAD_ROOT}/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp
|
||||
)
|
||||
|
||||
set(SCENARIO_SOURCES
|
||||
${CMAKE_SOURCE_DIR}/../scenarios/scene3d_test_scenarios.cpp
|
||||
${CMAKE_SOURCE_DIR}/../scenarios/scene3d_test_ctx.cpp
|
||||
${CMAKE_SOURCE_DIR}/../scenarios/test_board_data.cpp
|
||||
${CMAKE_SOURCE_DIR}/../scenarios/scenario_tier1_utils.cpp
|
||||
${CMAKE_SOURCE_DIR}/../scenarios/scenario_tier1_tdl.cpp
|
||||
${CMAKE_SOURCE_DIR}/../scenarios/scenario_tier1_model.cpp
|
||||
${CMAKE_SOURCE_DIR}/../scenarios/scenario_tier2_generators.cpp
|
||||
${CMAKE_SOURCE_DIR}/../scenarios/scenario_tier3_composite.cpp
|
||||
)
|
||||
|
||||
add_executable(scene3d_native_test
|
||||
scene3d_native_test.cpp
|
||||
fbo_capture.cpp
|
||||
kicad_stubs_3d.cpp
|
||||
board_adapter_test_impl.cpp
|
||||
settings_3d_stub.cpp
|
||||
render3d_test_accessor.cpp
|
||||
${SCENARIO_SOURCES}
|
||||
${KICAD_3D_SOURCES}
|
||||
${GLAD_SOURCES}
|
||||
)
|
||||
|
||||
target_include_directories(scene3d_native_test PRIVATE
|
||||
${CMAKE_SOURCE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/../scenarios
|
||||
|
||||
# KiCad includes
|
||||
${KICAD_ROOT}/include # kicad_gl/, gal/3d/camera.h, plugins/3dapi/
|
||||
${KICAD_ROOT}/3d-viewer # "3d_rendering/...", common_ogl/, 3d_math.h
|
||||
${KICAD_ROOT}/3d-viewer/3d_rendering # trackball.cpp does #include <trackball.h>
|
||||
${KICAD_ROOT}/3d-viewer/3d_viewer # create_scene.cpp: <eda_3d_viewer_frame.h>
|
||||
${KICAD_ROOT}/pcbnew # pad.h et al. (Stage 2 headers)
|
||||
${KICAD_ROOT}/common
|
||||
|
||||
# KiCad libs
|
||||
${KICAD_ROOT}/libs/kimath/include
|
||||
${KICAD_ROOT}/libs/core/include
|
||||
|
||||
# KiCad thirdparty
|
||||
${KICAD_ROOT}/thirdparty/glad/include
|
||||
${KICAD_ROOT}/thirdparty/clipper2/Clipper2Lib/include
|
||||
${KICAD_ROOT}/thirdparty/dynamic_bitset
|
||||
${KICAD_ROOT}/thirdparty/rtree # geometry/rtree.h (shape_poly_set.cpp)
|
||||
${KICAD_ROOT}/thirdparty/magic_enum/magic_enum # magic_enum.hpp (layer_id.cpp)
|
||||
${KICAD_ROOT}/thirdparty/expected/include # tl/expected.hpp (library_table.h)
|
||||
${KICAD_ROOT}/thirdparty
|
||||
|
||||
# Homebrew (glm)
|
||||
/opt/homebrew/include
|
||||
)
|
||||
|
||||
target_link_libraries(scene3d_native_test
|
||||
${wxWidgets_LIBRARIES}
|
||||
OpenGL::GL
|
||||
)
|
||||
|
||||
message(STATUS "KiCad root: ${KICAD_ROOT}")
|
||||
message(STATUS "Building scene3d_native_test with actual KiCad 3D-viewer sources")
|
||||
521
tests/3d-regression/native/board_adapter_test_impl.cpp
Normal file
521
tests/3d-regression/native/board_adapter_test_impl.cpp
Normal file
|
|
@ -0,0 +1,521 @@
|
|||
/**
|
||||
* Test-harness definitions of BOARD_ADAPTER's out-of-line members.
|
||||
*
|
||||
* The real board_adapter.cpp / create_layer_items.cpp drag the whole pcbnew
|
||||
* board model + settings machinery in — none of which exists in this harness.
|
||||
* Instead WE define the declared members: an out-of-line definition of a
|
||||
* declared member function has full private access, so this TU is both the
|
||||
* stub layer and the synthetic-board-data injection seam (InitSettings).
|
||||
*
|
||||
* Bodies marked "verbatim" are copied from board_adapter.cpp and must behave
|
||||
* identically; bodies marked "test" are simplified board-less variants (any
|
||||
* behavioral drift shows up as a baseline change and is reviewed there).
|
||||
*/
|
||||
|
||||
#include "kicad_stubs_3d.h"
|
||||
|
||||
#include "3d_canvas/board_adapter.h"
|
||||
#include "3d_rendering/raytracing/shapes2D/filled_circle_2d.h"
|
||||
#include "3d_rendering/raytracing/shapes2D/round_segment_2d.h"
|
||||
#include "3d_viewer/eda_3d_viewer_settings.h"
|
||||
|
||||
#include <board_design_settings.h>
|
||||
#include <convert_basic_shapes_to_polygon.h>
|
||||
#include <geometry/geometry_utils.h> // GetArcToSegmentCount
|
||||
|
||||
// Same values as board_adapter.cpp:51-57 (defined there as TU-local macros).
|
||||
#define DEFAULT_BOARD_THICKNESS pcbIUScale.mmToIU( 1.6 )
|
||||
#define DEFAULT_COPPER_THICKNESS pcbIUScale.mmToIU( 0.035 )
|
||||
#define DEFAULT_TECH_LAYER_THICKNESS pcbIUScale.mmToIU( 0.025 )
|
||||
#define SOLDERPASTE_LAYER_THICKNESS pcbIUScale.mmToIU( 0.04 )
|
||||
|
||||
#include "../scenarios/test_board_data.h"
|
||||
|
||||
// Statics (board_adapter.cpp:60-89, verbatim).
|
||||
CUSTOM_COLORS_LIST BOARD_ADAPTER::g_SilkColors;
|
||||
CUSTOM_COLORS_LIST BOARD_ADAPTER::g_MaskColors;
|
||||
CUSTOM_COLORS_LIST BOARD_ADAPTER::g_PasteColors;
|
||||
CUSTOM_COLORS_LIST BOARD_ADAPTER::g_FinishColors;
|
||||
CUSTOM_COLORS_LIST BOARD_ADAPTER::g_BoardColors;
|
||||
|
||||
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultBackgroundTop;
|
||||
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultBackgroundBot;
|
||||
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultSilkscreen;
|
||||
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultSolderMask;
|
||||
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultSolderPaste;
|
||||
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultSurfaceFinish;
|
||||
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultBoardBody;
|
||||
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultComments;
|
||||
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultECOs;
|
||||
|
||||
const wxChar* BOARD_ADAPTER::m_logTrace = wxT( "KI_TRACE_EDA_CINFO3D_VISU" );
|
||||
|
||||
// The raytracer bevel global lives in board_adapter.cpp too.
|
||||
float g_BevelThickness3DU = 0.0f;
|
||||
|
||||
|
||||
// test: same default block as the real ctor (board_adapter.cpp:92-157) minus
|
||||
// ReloadColorSettings() (our version below is settings-free) and the custom
|
||||
// stackup color tables (not needed — GetLayerColors below is default-based).
|
||||
BOARD_ADAPTER::BOARD_ADAPTER() :
|
||||
m_Cfg( nullptr ),
|
||||
m_IsBoardView( true ),
|
||||
m_MousewheelPanning( true ),
|
||||
m_IsPreviewer( false ),
|
||||
m_board( nullptr ),
|
||||
m_3dModelManager( nullptr ),
|
||||
m_layerZcoordTop(),
|
||||
m_layerZcoordBottom()
|
||||
{
|
||||
m_boardPos = VECTOR2I();
|
||||
m_boardSize = VECTOR2I();
|
||||
m_boardCenter = SFVEC3F( 0.0f );
|
||||
|
||||
m_boardBoundingBox.Reset();
|
||||
|
||||
m_TH_IDs.Clear();
|
||||
m_TH_ODs.Clear();
|
||||
m_viaAnnuli.Clear();
|
||||
|
||||
m_copperLayersCount = 2;
|
||||
|
||||
m_biuTo3Dunits = 1.0;
|
||||
m_boardBodyThickness3DU = DEFAULT_BOARD_THICKNESS * m_biuTo3Dunits;
|
||||
m_frontCopperThickness3DU = DEFAULT_COPPER_THICKNESS * m_biuTo3Dunits;
|
||||
m_backCopperThickness3DU = DEFAULT_COPPER_THICKNESS * m_biuTo3Dunits;
|
||||
m_nonCopperLayerThickness3DU = DEFAULT_TECH_LAYER_THICKNESS * m_biuTo3Dunits;
|
||||
m_frontMaskThickness3DU = DEFAULT_TECH_LAYER_THICKNESS * m_biuTo3Dunits;
|
||||
m_backMaskThickness3DU = DEFAULT_TECH_LAYER_THICKNESS * m_biuTo3Dunits;
|
||||
m_solderPasteLayerThickness3DU = SOLDERPASTE_LAYER_THICKNESS * m_biuTo3Dunits;
|
||||
|
||||
m_trackCount = 0;
|
||||
m_viaCount = 0;
|
||||
m_averageViaHoleDiameter = 0.0f;
|
||||
m_holeCount = 0;
|
||||
m_averageHoleDiameter = 0.0f;
|
||||
m_averageTrackWidth = 0.0f;
|
||||
|
||||
m_BgColorBot = SFVEC4F( 0.4, 0.4, 0.5, 1.0 );
|
||||
m_BgColorTop = SFVEC4F( 0.8, 0.8, 0.9, 1.0 );
|
||||
m_BoardBodyColor = SFVEC4F( 0.4, 0.4, 0.5, 0.9 );
|
||||
m_SolderMaskColorTop = SFVEC4F( 0.1, 0.2, 0.1, 0.83 );
|
||||
m_SolderMaskColorBot = SFVEC4F( 0.1, 0.2, 0.1, 0.83 );
|
||||
m_SolderPasteColor = SFVEC4F( 0.4, 0.4, 0.4, 1.0 );
|
||||
m_SilkScreenColorTop = SFVEC4F( 0.9, 0.9, 0.9, 1.0 );
|
||||
m_SilkScreenColorBot = SFVEC4F( 0.9, 0.9, 0.9, 1.0 );
|
||||
m_CopperColor = SFVEC4F( 0.75, 0.61, 0.23, 1.0 );
|
||||
m_UserDrawingsColor = SFVEC4F( 0.85, 0.85, 0.85, 1.0 );
|
||||
m_UserCommentsColor = SFVEC4F( 0.85, 0.85, 0.85, 1.0 );
|
||||
m_ECO1Color = SFVEC4F( 0.70, 0.10, 0.10, 1.0 );
|
||||
m_ECO2Color = SFVEC4F( 0.70, 0.10, 0.10, 1.0 );
|
||||
|
||||
for( int ii = 0; ii < 45; ++ii )
|
||||
m_UserDefinedLayerColor[ii] = SFVEC4F( 0.70, 0.10, 0.10, 1.0 );
|
||||
|
||||
m_platedPadsFront = nullptr;
|
||||
m_platedPadsBack = nullptr;
|
||||
m_offboardPadsFront = nullptr;
|
||||
m_offboardPadsBack = nullptr;
|
||||
|
||||
m_frontPlatedCopperPolys = nullptr;
|
||||
m_backPlatedCopperPolys = nullptr;
|
||||
|
||||
ReloadColorSettings();
|
||||
|
||||
g_DefaultBackgroundTop = COLOR4D( 0.80, 0.80, 0.90, 1.0 );
|
||||
g_DefaultBackgroundBot = COLOR4D( 0.40, 0.40, 0.50, 1.0 );
|
||||
g_DefaultSilkscreen = COLOR4D( 0.94, 0.94, 0.94, 1.0 );
|
||||
g_DefaultSolderMask = COLOR4D( 0.08, 0.20, 0.14, 0.83 );
|
||||
g_DefaultSolderPaste = COLOR4D( 0.50, 0.50, 0.50, 1.0 );
|
||||
g_DefaultSurfaceFinish = COLOR4D( 0.75, 0.61, 0.23, 1.0 );
|
||||
g_DefaultBoardBody = COLOR4D( 0.4, 0.4, 0.5, 0.9 );
|
||||
g_DefaultComments = COLOR4D( 0.85, 0.85, 0.85, 1.0 );
|
||||
g_DefaultECOs = COLOR4D( 0.70, 0.10, 0.10, 1.0 );
|
||||
}
|
||||
|
||||
|
||||
BOARD_ADAPTER::~BOARD_ADAPTER()
|
||||
{
|
||||
destroyLayers();
|
||||
}
|
||||
|
||||
|
||||
// test: frees exactly what our InitSettings allocates.
|
||||
void BOARD_ADAPTER::destroyLayers()
|
||||
{
|
||||
for( auto& [layer, container] : m_layerMap )
|
||||
delete container;
|
||||
|
||||
m_layerMap.clear();
|
||||
|
||||
for( auto& [layer, container] : m_layerHoleMap )
|
||||
delete container;
|
||||
|
||||
m_layerHoleMap.clear();
|
||||
|
||||
for( auto& [layer, poly] : m_layers_poly )
|
||||
delete poly;
|
||||
|
||||
m_layers_poly.clear();
|
||||
|
||||
for( auto& [layer, poly] : m_layerHoleOdPolys )
|
||||
delete poly;
|
||||
|
||||
m_layerHoleOdPolys.clear();
|
||||
|
||||
for( auto& [layer, poly] : m_layerHoleIdPolys )
|
||||
delete poly;
|
||||
|
||||
m_layerHoleIdPolys.clear();
|
||||
|
||||
delete m_platedPadsFront;
|
||||
delete m_platedPadsBack;
|
||||
delete m_offboardPadsFront;
|
||||
delete m_offboardPadsBack;
|
||||
m_platedPadsFront = m_platedPadsBack = nullptr;
|
||||
m_offboardPadsFront = m_offboardPadsBack = nullptr;
|
||||
|
||||
delete m_frontPlatedCopperPolys;
|
||||
delete m_backPlatedCopperPolys;
|
||||
m_frontPlatedCopperPolys = nullptr;
|
||||
m_backPlatedCopperPolys = nullptr;
|
||||
|
||||
m_TH_ODs.Clear();
|
||||
m_TH_IDs.Clear();
|
||||
m_viaAnnuli.Clear();
|
||||
m_viaTH_ODs.Clear();
|
||||
|
||||
m_board_poly.RemoveAllContours();
|
||||
m_TH_ODPolys.RemoveAllContours();
|
||||
m_NPTH_ODPolys.RemoveAllContours();
|
||||
m_viaTH_ODPolys.RemoveAllContours();
|
||||
m_viaAnnuliPolys.RemoveAllContours();
|
||||
}
|
||||
|
||||
|
||||
// test: settings-free — the board-editor color table gets a neutral default
|
||||
// (the real one loads COLOR_SETTINGS; scenarios don't use per-PCB-layer colors).
|
||||
void BOARD_ADAPTER::ReloadColorSettings() noexcept
|
||||
{
|
||||
for( int layer = F_Cu; layer < PCB_LAYER_ID_COUNT; ++layer )
|
||||
m_BoardEditorColors[layer] = COLOR4D( 0.75, 0.61, 0.23, 1.0 );
|
||||
}
|
||||
|
||||
|
||||
// verbatim (board_adapter.cpp:243-288) minus the m_board branches (no board).
|
||||
bool BOARD_ADAPTER::Is3dLayerEnabled( PCB_LAYER_ID aLayer,
|
||||
const std::bitset<LAYER_3D_END>& aVisibilityFlags ) const
|
||||
{
|
||||
wxASSERT( aLayer < PCB_LAYER_ID_COUNT );
|
||||
|
||||
switch( aLayer )
|
||||
{
|
||||
case B_Cu: return aVisibilityFlags.test( LAYER_3D_COPPER_BOTTOM );
|
||||
case F_Cu: return aVisibilityFlags.test( LAYER_3D_COPPER_TOP );
|
||||
case B_Adhes: return aVisibilityFlags.test( LAYER_3D_ADHESIVE );
|
||||
case F_Adhes: return aVisibilityFlags.test( LAYER_3D_ADHESIVE );
|
||||
case B_Paste: return aVisibilityFlags.test( LAYER_3D_SOLDERPASTE );
|
||||
case F_Paste: return aVisibilityFlags.test( LAYER_3D_SOLDERPASTE );
|
||||
case B_SilkS: return aVisibilityFlags.test( LAYER_3D_SILKSCREEN_BOTTOM );
|
||||
case F_SilkS: return aVisibilityFlags.test( LAYER_3D_SILKSCREEN_TOP );
|
||||
case B_Mask: return aVisibilityFlags.test( LAYER_3D_SOLDERMASK_BOTTOM );
|
||||
case F_Mask: return aVisibilityFlags.test( LAYER_3D_SOLDERMASK_TOP );
|
||||
case Dwgs_User: return aVisibilityFlags.test( LAYER_3D_USER_DRAWINGS );
|
||||
case Cmts_User: return aVisibilityFlags.test( LAYER_3D_USER_COMMENTS );
|
||||
case Eco1_User: return aVisibilityFlags.test( LAYER_3D_USER_ECO1 );
|
||||
case Eco2_User: return aVisibilityFlags.test( LAYER_3D_USER_ECO2 );
|
||||
default:
|
||||
return false; // test: no board -> unmapped layers hidden
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// test: previews/boards not modeled — footprints always "shown".
|
||||
bool BOARD_ADAPTER::IsFootprintShown( const FOOTPRINT* aFootprint ) const
|
||||
{
|
||||
return aFootprint != nullptr;
|
||||
}
|
||||
|
||||
|
||||
// verbatim no-board branch (board_adapter.cpp:315-320).
|
||||
int BOARD_ADAPTER::GetHolePlatingThickness() const noexcept
|
||||
{
|
||||
return DEFAULT_COPPER_THICKNESS;
|
||||
}
|
||||
|
||||
|
||||
// verbatim (board_adapter.cpp:322-328).
|
||||
unsigned int BOARD_ADAPTER::GetCircleSegmentCount( float aDiameter3DU ) const
|
||||
{
|
||||
wxASSERT( aDiameter3DU > 0.0f );
|
||||
|
||||
return GetCircleSegmentCount( (int) ( aDiameter3DU / m_biuTo3Dunits ) );
|
||||
}
|
||||
|
||||
|
||||
// test: like board_adapter.cpp:330-336 with the BOARD_DESIGN_SETTINGS default
|
||||
// max error (ARC_HIGH_DEF) instead of a live board's setting.
|
||||
unsigned int BOARD_ADAPTER::GetCircleSegmentCount( int aDiameterBIU ) const
|
||||
{
|
||||
wxASSERT( aDiameterBIU > 0 );
|
||||
|
||||
return GetArcToSegmentCount( aDiameterBIU / 2, ARC_HIGH_DEF, FULL_CIRCLE );
|
||||
}
|
||||
|
||||
|
||||
// verbatim non-previewer path (board_adapter.cpp:808-905) minus FOLLOW_PCB
|
||||
// (needs a board).
|
||||
std::bitset<LAYER_3D_END> BOARD_ADAPTER::GetVisibleLayers() const
|
||||
{
|
||||
std::bitset<LAYER_3D_END> ret;
|
||||
|
||||
ret.set( LAYER_3D_BOARD, m_Cfg->m_Render.show_board_body );
|
||||
ret.set( LAYER_3D_PLATED_BARRELS, m_Cfg->m_Render.show_plated_barrels );
|
||||
ret.set( LAYER_3D_COPPER_TOP, m_Cfg->m_Render.show_copper_top );
|
||||
ret.set( LAYER_3D_COPPER_BOTTOM, m_Cfg->m_Render.show_copper_bottom );
|
||||
ret.set( LAYER_3D_SILKSCREEN_TOP, m_Cfg->m_Render.show_silkscreen_top );
|
||||
ret.set( LAYER_3D_SILKSCREEN_BOTTOM, m_Cfg->m_Render.show_silkscreen_bottom );
|
||||
ret.set( LAYER_3D_SOLDERMASK_TOP, m_Cfg->m_Render.show_soldermask_top );
|
||||
ret.set( LAYER_3D_SOLDERMASK_BOTTOM, m_Cfg->m_Render.show_soldermask_bottom );
|
||||
ret.set( LAYER_3D_SOLDERPASTE, m_Cfg->m_Render.show_solderpaste );
|
||||
ret.set( LAYER_3D_ADHESIVE, m_Cfg->m_Render.show_adhesive );
|
||||
ret.set( LAYER_3D_USER_COMMENTS, m_Cfg->m_Render.show_comments );
|
||||
ret.set( LAYER_3D_USER_DRAWINGS, m_Cfg->m_Render.show_drawings );
|
||||
ret.set( LAYER_3D_USER_ECO1, m_Cfg->m_Render.show_eco1 );
|
||||
ret.set( LAYER_3D_USER_ECO2, m_Cfg->m_Render.show_eco2 );
|
||||
|
||||
for( int layer = LAYER_3D_USER_1; layer <= LAYER_3D_USER_45; ++layer )
|
||||
ret.set( layer, m_Cfg->m_Render.show_user[layer - LAYER_3D_USER_1] );
|
||||
|
||||
ret.set( LAYER_FP_REFERENCES, m_Cfg->m_Render.show_fp_references );
|
||||
ret.set( LAYER_FP_VALUES, m_Cfg->m_Render.show_fp_values );
|
||||
ret.set( LAYER_FP_TEXT, m_Cfg->m_Render.show_fp_text );
|
||||
|
||||
ret.set( LAYER_3D_TH_MODELS, m_Cfg->m_Render.show_footprints_normal );
|
||||
ret.set( LAYER_3D_SMD_MODELS, m_Cfg->m_Render.show_footprints_insert );
|
||||
ret.set( LAYER_3D_VIRTUAL_MODELS, m_Cfg->m_Render.show_footprints_virtual );
|
||||
ret.set( LAYER_3D_MODELS_NOT_IN_POS, m_Cfg->m_Render.show_footprints_not_in_posfile );
|
||||
ret.set( LAYER_3D_MODELS_MARKED_DNP, m_Cfg->m_Render.show_footprints_dnp );
|
||||
|
||||
ret.set( LAYER_3D_BOUNDING_BOXES, m_Cfg->m_Render.show_model_bbox );
|
||||
ret.set( LAYER_3D_OFF_BOARD_SILK, m_Cfg->m_Render.show_off_board_silk );
|
||||
ret.set( LAYER_3D_NAVIGATOR, m_Cfg->m_Render.show_navigator );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// test: no board -> board-editor copper colors never apply.
|
||||
bool BOARD_ADAPTER::GetUseBoardEditorCopperLayerColors() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// verbatim (board_adapter.cpp:1039-1053).
|
||||
float BOARD_ADAPTER::GetFootprintZPos( bool aIsFlipped ) const
|
||||
{
|
||||
if( aIsFlipped )
|
||||
{
|
||||
if( auto it = m_layerZcoordBottom.find( B_Paste ); it != m_layerZcoordBottom.end() )
|
||||
return it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( auto it = m_layerZcoordTop.find( F_Paste ); it != m_layerZcoordTop.end() )
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
|
||||
// verbatim (board_adapter.cpp:1056-1070); user-layer remap dropped (unused here).
|
||||
SFVEC4F BOARD_ADAPTER::GetLayerColor( int aLayerId ) const
|
||||
{
|
||||
wxASSERT( aLayerId < PCB_LAYER_ID_COUNT );
|
||||
|
||||
return GetColor( m_BoardEditorColors.at( aLayerId ) );
|
||||
}
|
||||
|
||||
|
||||
SFVEC4F BOARD_ADAPTER::GetColor( const COLOR4D& aColor ) const
|
||||
{
|
||||
return SFVEC4F( aColor.r, aColor.g, aColor.b, aColor.a );
|
||||
}
|
||||
|
||||
|
||||
// test: default-color scheme only (no COLOR_SETTINGS machinery).
|
||||
std::map<int, COLOR4D> BOARD_ADAPTER::GetDefaultColors() const
|
||||
{
|
||||
std::map<int, COLOR4D> colors;
|
||||
|
||||
colors[LAYER_3D_BACKGROUND_TOP] = g_DefaultBackgroundTop;
|
||||
colors[LAYER_3D_BACKGROUND_BOTTOM] = g_DefaultBackgroundBot;
|
||||
colors[LAYER_3D_BOARD] = g_DefaultBoardBody;
|
||||
colors[LAYER_3D_COPPER_TOP] = g_DefaultSurfaceFinish;
|
||||
colors[LAYER_3D_COPPER_BOTTOM] = g_DefaultSurfaceFinish;
|
||||
colors[LAYER_3D_SILKSCREEN_TOP] = g_DefaultSilkscreen;
|
||||
colors[LAYER_3D_SILKSCREEN_BOTTOM] = g_DefaultSilkscreen;
|
||||
colors[LAYER_3D_SOLDERMASK_TOP] = g_DefaultSolderMask;
|
||||
colors[LAYER_3D_SOLDERMASK_BOTTOM] = g_DefaultSolderMask;
|
||||
colors[LAYER_3D_SOLDERPASTE] = g_DefaultSolderPaste;
|
||||
colors[LAYER_3D_USER_DRAWINGS] = g_DefaultComments;
|
||||
colors[LAYER_3D_USER_COMMENTS] = g_DefaultComments;
|
||||
colors[LAYER_3D_USER_ECO1] = g_DefaultECOs;
|
||||
colors[LAYER_3D_USER_ECO2] = g_DefaultECOs;
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
|
||||
std::map<int, COLOR4D> BOARD_ADAPTER::GetLayerColors() const
|
||||
{
|
||||
return GetDefaultColors();
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// THE SEAM: synthetic test-board data instead of a real BOARD.
|
||||
//
|
||||
// A 40 x 30 mm two-layer board. Layer Z stacking follows the real
|
||||
// InitSettings maths (board body centered on Z=0, copper plated on top/bottom,
|
||||
// tech layers above copper). All values in BIU (nm) scaled by m_biuTo3Dunits.
|
||||
// ---------------------------------------------------------------------------
|
||||
void BOARD_ADAPTER::InitSettings( REPORTER* aStatusReporter, REPORTER* aWarningReporter )
|
||||
{
|
||||
(void) aStatusReporter;
|
||||
(void) aWarningReporter;
|
||||
|
||||
destroyLayers();
|
||||
|
||||
const int boardW = pcbIUScale.mmToIU( 40 );
|
||||
const int boardH = pcbIUScale.mmToIU( 30 );
|
||||
|
||||
m_boardSize = VECTOR2I( boardW, boardH );
|
||||
m_boardPos = VECTOR2I( 0, 0 );
|
||||
m_copperLayersCount = 2;
|
||||
|
||||
// Same scale maths as the real InitSettings (board_adapter.cpp:382-387):
|
||||
// no BOARD -> the "footprint holder" zoom hack applies.
|
||||
m_biuTo3Dunits = RANGE_SCALE_3D / std::max( m_boardSize.x, m_boardSize.y );
|
||||
m_biuTo3Dunits *= 1.6f;
|
||||
|
||||
m_boardBodyThickness3DU = DEFAULT_BOARD_THICKNESS * m_biuTo3Dunits;
|
||||
m_frontCopperThickness3DU = DEFAULT_COPPER_THICKNESS * m_biuTo3Dunits;
|
||||
m_backCopperThickness3DU = DEFAULT_COPPER_THICKNESS * m_biuTo3Dunits;
|
||||
m_nonCopperLayerThickness3DU = DEFAULT_TECH_LAYER_THICKNESS * m_biuTo3Dunits;
|
||||
m_frontMaskThickness3DU = DEFAULT_TECH_LAYER_THICKNESS * m_biuTo3Dunits;
|
||||
m_backMaskThickness3DU = DEFAULT_TECH_LAYER_THICKNESS * m_biuTo3Dunits;
|
||||
m_solderPasteLayerThickness3DU = SOLDERPASTE_LAYER_THICKNESS * m_biuTo3Dunits;
|
||||
|
||||
// Layer Z coordinates (board body spans -body/2 .. +body/2).
|
||||
const float bodyTop = m_boardBodyThickness3DU / 2.0f;
|
||||
const float bodyBot = -m_boardBodyThickness3DU / 2.0f;
|
||||
|
||||
m_layerZcoordBottom[F_Cu] = bodyTop;
|
||||
m_layerZcoordTop[F_Cu] = bodyTop + m_frontCopperThickness3DU;
|
||||
m_layerZcoordBottom[B_Cu] = bodyBot;
|
||||
m_layerZcoordTop[B_Cu] = bodyBot - m_backCopperThickness3DU;
|
||||
|
||||
m_layerZcoordBottom[F_Mask] = m_layerZcoordTop[F_Cu];
|
||||
m_layerZcoordTop[F_Mask] = m_layerZcoordBottom[F_Mask] + m_frontMaskThickness3DU;
|
||||
m_layerZcoordBottom[B_Mask] = m_layerZcoordTop[B_Cu];
|
||||
m_layerZcoordTop[B_Mask] = m_layerZcoordBottom[B_Mask] - m_backMaskThickness3DU;
|
||||
|
||||
m_layerZcoordBottom[F_SilkS] = m_layerZcoordTop[F_Mask];
|
||||
m_layerZcoordTop[F_SilkS] = m_layerZcoordBottom[F_SilkS] + m_nonCopperLayerThickness3DU;
|
||||
m_layerZcoordBottom[B_SilkS] = m_layerZcoordTop[B_Mask];
|
||||
m_layerZcoordTop[B_SilkS] = m_layerZcoordBottom[B_SilkS] - m_nonCopperLayerThickness3DU;
|
||||
|
||||
m_layerZcoordBottom[F_Paste] = m_layerZcoordTop[F_Cu];
|
||||
m_layerZcoordTop[F_Paste] = m_layerZcoordBottom[F_Paste] + m_solderPasteLayerThickness3DU;
|
||||
m_layerZcoordBottom[B_Paste] = m_layerZcoordTop[B_Cu];
|
||||
m_layerZcoordTop[B_Paste] = m_layerZcoordBottom[B_Paste] - m_solderPasteLayerThickness3DU;
|
||||
|
||||
m_boardCenter = SFVEC3F( 0.0f, 0.0f, 0.0f );
|
||||
|
||||
m_boardBoundingBox.Set( SFVEC3F( -boardW / 2 * m_biuTo3Dunits, -boardH / 2 * m_biuTo3Dunits,
|
||||
bodyBot ),
|
||||
SFVEC3F( boardW / 2 * m_biuTo3Dunits, boardH / 2 * m_biuTo3Dunits,
|
||||
bodyTop ) );
|
||||
|
||||
// Board outline: plain rectangle.
|
||||
m_board_poly.RemoveAllContours();
|
||||
m_board_poly.NewOutline();
|
||||
m_board_poly.Append( -boardW / 2, -boardH / 2 );
|
||||
m_board_poly.Append( boardW / 2, -boardH / 2 );
|
||||
m_board_poly.Append( boardW / 2, boardH / 2 );
|
||||
m_board_poly.Append( -boardW / 2, boardH / 2 );
|
||||
m_board_poly.Outline( 0 ).SetClosed( true );
|
||||
|
||||
// Copper: a few tracks + round pads per side; silkscreen: a frame; the
|
||||
// BVH containers own the objects (they delete them).
|
||||
auto addTrack = [&]( BVH_CONTAINER_2D* aDst, double aX1mm, double aY1mm, double aX2mm,
|
||||
double aY2mm, double aWidthMm )
|
||||
{
|
||||
aDst->Add( new ROUND_SEGMENT_2D(
|
||||
SFVEC2F( pcbIUScale.mmToIU( aX1mm ) * m_biuTo3Dunits,
|
||||
pcbIUScale.mmToIU( aY1mm ) * m_biuTo3Dunits ),
|
||||
SFVEC2F( pcbIUScale.mmToIU( aX2mm ) * m_biuTo3Dunits,
|
||||
pcbIUScale.mmToIU( aY2mm ) * m_biuTo3Dunits ),
|
||||
pcbIUScale.mmToIU( aWidthMm ) * m_biuTo3Dunits, DummyBoardItem() ) );
|
||||
};
|
||||
|
||||
auto addCircle = [&]( BVH_CONTAINER_2D* aDst, double aXmm, double aYmm, double aRmm )
|
||||
{
|
||||
aDst->Add( new FILLED_CIRCLE_2D( SFVEC2F( pcbIUScale.mmToIU( aXmm ) * m_biuTo3Dunits,
|
||||
pcbIUScale.mmToIU( aYmm ) * m_biuTo3Dunits ),
|
||||
pcbIUScale.mmToIU( aRmm ) * m_biuTo3Dunits,
|
||||
DummyBoardItem() ) );
|
||||
};
|
||||
|
||||
BVH_CONTAINER_2D* frontCu = new BVH_CONTAINER_2D;
|
||||
addTrack( frontCu, -15, -10, 15, -10, 1.0 );
|
||||
addTrack( frontCu, -15, -10, -15, 10, 1.0 );
|
||||
addTrack( frontCu, -15, 10, 0, 10, 0.6 );
|
||||
addTrack( frontCu, 0, 10, 8, 2, 0.6 );
|
||||
addCircle( frontCu, -15, -10, 1.5 );
|
||||
addCircle( frontCu, 15, -10, 1.5 );
|
||||
addCircle( frontCu, 8, 2, 1.2 );
|
||||
frontCu->BuildBVH();
|
||||
m_layerMap[F_Cu] = frontCu;
|
||||
|
||||
BVH_CONTAINER_2D* backCu = new BVH_CONTAINER_2D;
|
||||
addTrack( backCu, 15, -10, 15, 10, 1.2 );
|
||||
addTrack( backCu, 15, 10, -8, 10, 1.2 );
|
||||
addCircle( backCu, -15, -10, 1.5 );
|
||||
addCircle( backCu, 15, -10, 1.5 );
|
||||
backCu->BuildBVH();
|
||||
m_layerMap[B_Cu] = backCu;
|
||||
|
||||
BVH_CONTAINER_2D* frontSilk = new BVH_CONTAINER_2D;
|
||||
addTrack( frontSilk, -17, -13, 17, -13, 0.3 );
|
||||
addTrack( frontSilk, 17, -13, 17, 13, 0.3 );
|
||||
addTrack( frontSilk, 17, 13, -17, 13, 0.3 );
|
||||
addTrack( frontSilk, -17, 13, -17, -13, 0.3 );
|
||||
addCircle( frontSilk, -12, 6, 0.8 );
|
||||
frontSilk->BuildBVH();
|
||||
m_layerMap[F_SilkS] = frontSilk;
|
||||
|
||||
// Through holes: the two big pads are plated through.
|
||||
auto addHolePoly = [&]( SHAPE_POLY_SET& aPolys, double aXmm, double aYmm, double aRmm )
|
||||
{
|
||||
TransformCircleToPolygon( aPolys,
|
||||
VECTOR2I( pcbIUScale.mmToIU( aXmm ), pcbIUScale.mmToIU( aYmm ) ),
|
||||
pcbIUScale.mmToIU( aRmm ), ARC_HIGH_DEF, ERROR_INSIDE );
|
||||
};
|
||||
|
||||
addCircle( &m_TH_ODs, -15, -10, 0.8 );
|
||||
addCircle( &m_TH_ODs, 15, -10, 0.8 );
|
||||
m_TH_ODs.BuildBVH();
|
||||
|
||||
addCircle( &m_TH_IDs, -15, -10, 0.65 );
|
||||
addCircle( &m_TH_IDs, 15, -10, 0.65 );
|
||||
m_TH_IDs.BuildBVH();
|
||||
|
||||
addHolePoly( m_TH_ODPolys, -15, -10, 0.8 );
|
||||
addHolePoly( m_TH_ODPolys, 15, -10, 0.8 );
|
||||
|
||||
m_TH_ODPolys.Simplify();
|
||||
}
|
||||
26
tests/3d-regression/native/config.h
Normal file
26
tests/3d-regression/native/config.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Minimal config.h for native GAL test
|
||||
// Based on KiCad's generated config
|
||||
#ifndef KICAD_CONFIG_H
|
||||
#define KICAD_CONFIG_H
|
||||
|
||||
// Version info
|
||||
#define KICAD_MAJOR_VERSION 8
|
||||
#define KICAD_MINOR_VERSION 0
|
||||
#define KICAD_PATCH_VERSION 0
|
||||
|
||||
// Enable OpenGL
|
||||
#define KICAD_USE_OCC 0
|
||||
#define KICAD_USE_EGL 0
|
||||
|
||||
// Platform detection
|
||||
#ifdef __APPLE__
|
||||
#define KICAD_MACOS 1
|
||||
#endif
|
||||
|
||||
// Math
|
||||
#define KICAD_USE_STDROUND 1
|
||||
|
||||
// Ensure types are properly sized
|
||||
#include <cstdint>
|
||||
|
||||
#endif // KICAD_CONFIG_H
|
||||
146
tests/3d-regression/native/fbo_capture.cpp
Normal file
146
tests/3d-regression/native/fbo_capture.cpp
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#include "fbo_capture.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
// Core-vs-EXT selection: Apple's 2.1 context exports the ARB entry points on
|
||||
// all modern renderers, but keep the EXT fallback the compositor path proves
|
||||
// works (see plan §7 risk 2). Enum values are shared between core and EXT.
|
||||
static PFNGLGENFRAMEBUFFERSPROC s_glGenFramebuffers = nullptr;
|
||||
static PFNGLBINDFRAMEBUFFERPROC s_glBindFramebuffer = nullptr;
|
||||
static PFNGLFRAMEBUFFERTEXTURE2DPROC s_glFramebufferTexture2D = nullptr;
|
||||
static PFNGLGENRENDERBUFFERSPROC s_glGenRenderbuffers = nullptr;
|
||||
static PFNGLBINDRENDERBUFFERPROC s_glBindRenderbuffer = nullptr;
|
||||
static PFNGLRENDERBUFFERSTORAGEPROC s_glRenderbufferStorage = nullptr;
|
||||
static PFNGLFRAMEBUFFERRENDERBUFFERPROC s_glFramebufferRenderbuffer = nullptr;
|
||||
static PFNGLCHECKFRAMEBUFFERSTATUSPROC s_glCheckFramebufferStatus = nullptr;
|
||||
static PFNGLDELETEFRAMEBUFFERSPROC s_glDeleteFramebuffers = nullptr;
|
||||
static PFNGLDELETERENDERBUFFERSPROC s_glDeleteRenderbuffers = nullptr;
|
||||
|
||||
static bool resolveFboEntryPoints()
|
||||
{
|
||||
if( s_glGenFramebuffers )
|
||||
return true;
|
||||
|
||||
if( glad_glGenFramebuffers )
|
||||
{
|
||||
s_glGenFramebuffers = glad_glGenFramebuffers;
|
||||
s_glBindFramebuffer = glad_glBindFramebuffer;
|
||||
s_glFramebufferTexture2D = glad_glFramebufferTexture2D;
|
||||
s_glGenRenderbuffers = glad_glGenRenderbuffers;
|
||||
s_glBindRenderbuffer = glad_glBindRenderbuffer;
|
||||
s_glRenderbufferStorage = glad_glRenderbufferStorage;
|
||||
s_glFramebufferRenderbuffer = glad_glFramebufferRenderbuffer;
|
||||
s_glCheckFramebufferStatus = glad_glCheckFramebufferStatus;
|
||||
s_glDeleteFramebuffers = glad_glDeleteFramebuffers;
|
||||
s_glDeleteRenderbuffers = glad_glDeleteRenderbuffers;
|
||||
return true;
|
||||
}
|
||||
|
||||
if( glad_glGenFramebuffersEXT )
|
||||
{
|
||||
std::fprintf( stderr, "[fbo] core FBO entry points missing; using EXT fallback\n" );
|
||||
s_glGenFramebuffers = glad_glGenFramebuffersEXT;
|
||||
s_glBindFramebuffer = glad_glBindFramebufferEXT;
|
||||
s_glFramebufferTexture2D = glad_glFramebufferTexture2DEXT;
|
||||
s_glGenRenderbuffers = glad_glGenRenderbuffersEXT;
|
||||
s_glBindRenderbuffer = glad_glBindRenderbufferEXT;
|
||||
s_glRenderbufferStorage = glad_glRenderbufferStorageEXT;
|
||||
s_glFramebufferRenderbuffer = glad_glFramebufferRenderbufferEXT;
|
||||
s_glCheckFramebufferStatus = glad_glCheckFramebufferStatusEXT;
|
||||
s_glDeleteFramebuffers = glad_glDeleteFramebuffersEXT;
|
||||
s_glDeleteRenderbuffers = glad_glDeleteRenderbuffersEXT;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::fprintf( stderr, "[fbo] no framebuffer object support in this context\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool FBO_CAPTURE::Create( int aWidth, int aHeight )
|
||||
{
|
||||
if( !resolveFboEntryPoints() )
|
||||
return false;
|
||||
|
||||
m_width = aWidth;
|
||||
m_height = aHeight;
|
||||
|
||||
// Same sequence as EDA_3D_CANVAS::RenderToFrameBuffer (eda_3d_canvas.cpp:736-772).
|
||||
s_glGenFramebuffers( 1, &m_fbo );
|
||||
s_glBindFramebuffer( GL_FRAMEBUFFER, m_fbo );
|
||||
|
||||
glGenTextures( 1, &m_colorTexture );
|
||||
glBindTexture( GL_TEXTURE_2D, m_colorTexture );
|
||||
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, aWidth, aHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
nullptr );
|
||||
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
||||
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
||||
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
|
||||
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
|
||||
s_glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
m_colorTexture, 0 );
|
||||
glBindTexture( GL_TEXTURE_2D, 0 );
|
||||
|
||||
s_glGenRenderbuffers( 1, &m_depthStencil );
|
||||
s_glBindRenderbuffer( GL_RENDERBUFFER, m_depthStencil );
|
||||
s_glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, aWidth, aHeight );
|
||||
s_glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
|
||||
m_depthStencil );
|
||||
|
||||
const GLenum status = s_glCheckFramebufferStatus( GL_FRAMEBUFFER );
|
||||
|
||||
if( status != GL_FRAMEBUFFER_COMPLETE )
|
||||
{
|
||||
std::fprintf( stderr, "[fbo] framebuffer incomplete: 0x%04X\n", status );
|
||||
Destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void FBO_CAPTURE::Bind()
|
||||
{
|
||||
s_glBindFramebuffer( GL_FRAMEBUFFER, m_fbo );
|
||||
glViewport( 0, 0, m_width, m_height );
|
||||
}
|
||||
|
||||
|
||||
bool FBO_CAPTURE::ReadPixels( std::vector<uint8_t>& aOut )
|
||||
{
|
||||
if( !m_fbo )
|
||||
return false;
|
||||
|
||||
s_glBindFramebuffer( GL_FRAMEBUFFER, m_fbo );
|
||||
glFinish();
|
||||
|
||||
aOut.resize( static_cast<size_t>( m_width ) * m_height * 4 );
|
||||
glPixelStorei( GL_PACK_ALIGNMENT, 1 );
|
||||
glReadPixels( 0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, aOut.data() );
|
||||
|
||||
const GLenum err = glGetError();
|
||||
|
||||
if( err != GL_NO_ERROR )
|
||||
{
|
||||
std::fprintf( stderr, "[fbo] glReadPixels error: 0x%04X\n", err );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void FBO_CAPTURE::Destroy()
|
||||
{
|
||||
if( m_fbo )
|
||||
s_glDeleteFramebuffers( 1, &m_fbo );
|
||||
|
||||
if( m_colorTexture )
|
||||
glDeleteTextures( 1, &m_colorTexture );
|
||||
|
||||
if( m_depthStencil )
|
||||
s_glDeleteRenderbuffers( 1, &m_depthStencil );
|
||||
|
||||
m_fbo = m_colorTexture = m_depthStencil = 0;
|
||||
}
|
||||
46
tests/3d-regression/native/fbo_capture.h
Normal file
46
tests/3d-regression/native/fbo_capture.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Offscreen FBO for deterministic fixed-size capture, mirroring
|
||||
* EDA_3D_CANVAS::RenderToFrameBuffer (eda_3d_canvas.cpp:736-772):
|
||||
* GL_RGBA8 color texture + GL_DEPTH24_STENCIL8 renderbuffer on
|
||||
* GL_DEPTH_STENCIL_ATTACHMENT (the stencil is required by
|
||||
* OPENGL_RENDER_LIST::DrawCulled hole cutting).
|
||||
*
|
||||
* Rendering into an FBO makes the output independent of window size and
|
||||
* Retina scaling — pixels are exactly aWidth x aHeight.
|
||||
*/
|
||||
|
||||
#ifndef FBO_CAPTURE_H
|
||||
#define FBO_CAPTURE_H
|
||||
|
||||
#include <kicad_gl/kiglad.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
class FBO_CAPTURE
|
||||
{
|
||||
public:
|
||||
/// Create the FBO; returns false if the framebuffer is incomplete.
|
||||
/// Falls back to the EXT entry points if the core ARB ones didn't load
|
||||
/// (Apple GL 2.1 exposes both; glad loads by symbol name).
|
||||
bool Create( int aWidth, int aHeight );
|
||||
|
||||
void Bind();
|
||||
|
||||
/// glFinish + glReadPixels(GL_RGBA); aOut is resized to w*h*4.
|
||||
bool ReadPixels( std::vector<uint8_t>& aOut );
|
||||
|
||||
void Destroy();
|
||||
|
||||
int Width() const { return m_width; }
|
||||
int Height() const { return m_height; }
|
||||
|
||||
private:
|
||||
int m_width = 0;
|
||||
int m_height = 0;
|
||||
GLuint m_fbo = 0;
|
||||
GLuint m_colorTexture = 0;
|
||||
GLuint m_depthStencil = 0;
|
||||
};
|
||||
|
||||
#endif // FBO_CAPTURE_H
|
||||
303
tests/3d-regression/native/kicad_stubs_3d.cpp
Normal file
303
tests/3d-regression/native/kicad_stubs_3d.cpp
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
/**
|
||||
* Link stubs for the native 3D-renderer test build.
|
||||
*
|
||||
* The harness compiles real KiCad 3D-viewer TUs; the symbols those TUs
|
||||
* reference from elsewhere in KiCad but only reach through dead branches
|
||||
* (m_board / model-cache / app machinery is never populated here) are defined
|
||||
* as safe no-ops. One stub per linker error, each annotated with the
|
||||
* referencing TU. Seeded from tests/gal-regression/native/kicad_stubs.cpp.
|
||||
*/
|
||||
|
||||
#include "kicad_stubs_3d.h"
|
||||
|
||||
#include <advanced_config.h>
|
||||
#include <pgm_base.h>
|
||||
#include <singleton.h>
|
||||
#include <kicad_gl/gl_context_mgr.h>
|
||||
|
||||
// PGM_BASE holds unique_ptrs to these — complete types needed to define
|
||||
// its ctor/dtor here.
|
||||
#include <background_jobs_monitor.h>
|
||||
#include <notifications_manager.h>
|
||||
#include <settings/settings_manager.h>
|
||||
#include <wx/snglinst.h>
|
||||
|
||||
#include <board.h>
|
||||
#include <pad.h>
|
||||
#include <pcb_track.h>
|
||||
|
||||
#include <libraries/library_manager.h>
|
||||
#include <project_pcb.h>
|
||||
|
||||
#include "3d_cache/3d_cache.h"
|
||||
|
||||
//=============================================================================
|
||||
// PGM_BASE / Pgm() — render_3d_opengl.cpp uses
|
||||
// Pgm().GetGLContextManager()->RunWithoutCtxLock() during reload().
|
||||
//=============================================================================
|
||||
|
||||
class PGM_BASE_TEST : public PGM_BASE
|
||||
{
|
||||
public:
|
||||
PGM_BASE_TEST() { m_singleton.m_GLContextManager = new GL_CONTEXT_MANAGER(); }
|
||||
|
||||
void MacOpenFile( const wxString& ) override {}
|
||||
};
|
||||
|
||||
PGM_BASE& Pgm()
|
||||
{
|
||||
static PGM_BASE_TEST* s_pgm = nullptr;
|
||||
|
||||
if( !s_pgm )
|
||||
s_pgm = new PGM_BASE_TEST();
|
||||
|
||||
return *s_pgm;
|
||||
}
|
||||
|
||||
// PGM_BASE out-of-line methods (pgm_base.cpp is not compiled) — same stub set
|
||||
// as the GAL harness.
|
||||
PGM_BASE::PGM_BASE()
|
||||
{
|
||||
}
|
||||
|
||||
PGM_BASE::~PGM_BASE()
|
||||
{
|
||||
}
|
||||
|
||||
wxApp& PGM_BASE::App()
|
||||
{
|
||||
static wxApp* app = nullptr;
|
||||
return *app;
|
||||
}
|
||||
|
||||
COMMON_SETTINGS* PGM_BASE::GetCommonSettings() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const wxString& PGM_BASE::GetExecutablePath() const
|
||||
{
|
||||
static wxString s;
|
||||
return s;
|
||||
}
|
||||
|
||||
ENV_VAR_MAP& PGM_BASE::GetLocalEnvVariables() const
|
||||
{
|
||||
static ENV_VAR_MAP map;
|
||||
return map;
|
||||
}
|
||||
|
||||
bool PGM_BASE::SetLanguage( wxString&, bool )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const wxString& PGM_BASE::GetTextEditor( bool )
|
||||
{
|
||||
static wxString s;
|
||||
return s;
|
||||
}
|
||||
|
||||
void PGM_BASE::SetTextEditor( const wxString& )
|
||||
{
|
||||
}
|
||||
|
||||
wxString PGM_BASE::GetLanguageTag()
|
||||
{
|
||||
return wxString();
|
||||
}
|
||||
|
||||
void PGM_BASE::SetLanguagePath()
|
||||
{
|
||||
}
|
||||
|
||||
void PGM_BASE::ReadPdfBrowserInfos()
|
||||
{
|
||||
}
|
||||
|
||||
bool PGM_BASE::SetLocalEnvVariable( const wxString&, const wxString& )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void PGM_BASE::SetLocalEnvVariables()
|
||||
{
|
||||
}
|
||||
|
||||
void PGM_BASE::WritePdfBrowserInfos()
|
||||
{
|
||||
}
|
||||
|
||||
void PGM_BASE::SetLanguageIdentifier( int )
|
||||
{
|
||||
}
|
||||
|
||||
const wxString PGM_BASE::AskUserForPreferredEditor( const wxString& )
|
||||
{
|
||||
return wxString();
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// ADVANCED_CFG singleton (advanced_config.cpp is not compiled)
|
||||
//=============================================================================
|
||||
|
||||
const ADVANCED_CFG& ADVANCED_CFG::GetCfg()
|
||||
{
|
||||
static ADVANCED_CFG instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
ADVANCED_CFG::ADVANCED_CFG()
|
||||
{
|
||||
// Only fields the compiled TUs actually read; KiCad default DPI.
|
||||
m_ScreenDPI = 91;
|
||||
m_3DRT_BevelHeight_um = 30;
|
||||
m_3DRT_BevelExtentFactor = 1.0 / 16.0;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// Profiling clock — deterministic zero for the test harness.
|
||||
//=============================================================================
|
||||
|
||||
int64_t GetRunningMicroSecs()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// Destructors of app-machinery members PGM_BASE owns (their real TUs would
|
||||
// drag the whole settings/library world in; nothing here ever populates them).
|
||||
//=============================================================================
|
||||
|
||||
KICAD_SINGLETON::~KICAD_SINGLETON()
|
||||
{
|
||||
delete m_GLContextManager;
|
||||
m_GLContextManager = nullptr;
|
||||
}
|
||||
|
||||
LIBRARY_MANAGER::~LIBRARY_MANAGER() = default;
|
||||
|
||||
SETTINGS_MANAGER::~SETTINGS_MANAGER() = default;
|
||||
|
||||
//=============================================================================
|
||||
// STATUSBAR_REPORTER (reporter.cpp drags fontconfig; Redraw() takes REPORTER*
|
||||
// but the harness always passes nullptr).
|
||||
//=============================================================================
|
||||
|
||||
#include <reporter.h>
|
||||
|
||||
REPORTER& STATUSBAR_REPORTER::Report( const wxString&, SEVERITY )
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// BOARD / BOARD_ITEM / PAD / PCB_VIA — referenced from create_scene.cpp and
|
||||
// render_3d_opengl.cpp branches that only run with a real BOARD loaded
|
||||
// (m_board stays nullptr in this harness).
|
||||
//=============================================================================
|
||||
|
||||
int BOARD::GetCopperLayerCount() const
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
const EMBEDDED_FILES* BOARD::GetEmbeddedFiles() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const wxString BOARD::GetLayerName( PCB_LAYER_ID aLayer ) const
|
||||
{
|
||||
return LayerName( aLayer );
|
||||
}
|
||||
|
||||
int BOARD_ITEM::GetMaxError() const
|
||||
{
|
||||
return ARC_HIGH_DEF;
|
||||
}
|
||||
|
||||
bool PAD::TransformHoleToPolygon( SHAPE_POLY_SET&, int, int, ERROR_LOC ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int PCB_VIA::GetDrillValue() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PCB_VIA::LayerPair( PCB_LAYER_ID* aTopLayer, PCB_LAYER_ID* aBottomLayer ) const
|
||||
{
|
||||
if( aTopLayer )
|
||||
*aTopLayer = F_Cu;
|
||||
|
||||
if( aBottomLayer )
|
||||
*aBottomLayer = B_Cu;
|
||||
}
|
||||
|
||||
std::optional<int> PCB_VIA::GetSecondaryDrillSize() const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int> PCB_VIA::GetTertiaryDrillSize() const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
FILLING_MODE PCB_VIA::GetFillingMode() const
|
||||
{
|
||||
return static_cast<FILLING_MODE>( 0 );
|
||||
}
|
||||
|
||||
CAPPING_MODE PCB_VIA::GetCappingMode() const
|
||||
{
|
||||
return static_cast<CAPPING_MODE>( 0 );
|
||||
}
|
||||
|
||||
PLUGGING_MODE PCB_VIA::GetFrontPluggingMode() const
|
||||
{
|
||||
return static_cast<PLUGGING_MODE>( 0 );
|
||||
}
|
||||
|
||||
PLUGGING_MODE PCB_VIA::GetBackPluggingMode() const
|
||||
{
|
||||
return static_cast<PLUGGING_MODE>( 0 );
|
||||
}
|
||||
|
||||
COVERING_MODE PCB_VIA::GetFrontCoveringMode() const
|
||||
{
|
||||
return static_cast<COVERING_MODE>( 0 );
|
||||
}
|
||||
|
||||
COVERING_MODE PCB_VIA::GetBackCoveringMode() const
|
||||
{
|
||||
return static_cast<COVERING_MODE>( 0 );
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// Footprint-model loading chain (Load3dModelsIfNeeded is never called).
|
||||
//=============================================================================
|
||||
|
||||
FOOTPRINT_LIBRARY_ADAPTER* PROJECT_PCB::FootprintLibAdapter( PROJECT* )
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
wxString LIBRARY_MANAGER::GetFullURI( const LIBRARY_TABLE_ROW*, bool )
|
||||
{
|
||||
return wxString();
|
||||
}
|
||||
|
||||
std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER_ADAPTER::GetRow( const wxString&,
|
||||
LIBRARY_TABLE_SCOPE ) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
S3DMODEL* S3D_CACHE::GetModel( const wxString&, const wxString&,
|
||||
std::vector<const EMBEDDED_FILES*> )
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
15
tests/3d-regression/native/kicad_stubs_3d.h
Normal file
15
tests/3d-regression/native/kicad_stubs_3d.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Include shim for the native 3D-renderer test build.
|
||||
* kiglad.h must precede wx/glcanvas.h so GL symbols come from the vendored
|
||||
* glad loader instead of the (deprecated) Apple GL headers.
|
||||
*/
|
||||
|
||||
#ifndef KICAD_STUBS_3D_H
|
||||
#define KICAD_STUBS_3D_H
|
||||
|
||||
#include <kicad_gl/kiglad.h>
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/glcanvas.h>
|
||||
|
||||
#endif // KICAD_STUBS_3D_H
|
||||
273
tests/3d-regression/native/render3d_test_accessor.cpp
Normal file
273
tests/3d-regression/native/render3d_test_accessor.cpp
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
#include "kicad_stubs_3d.h"
|
||||
|
||||
#include "render3d_test_accessor.h"
|
||||
|
||||
#include "3d_rendering/opengl/render_3d_opengl.h"
|
||||
|
||||
// Member-pointer private-access technique, same as gal_test_accessor.cpp
|
||||
// (https://bloglitb.blogspot.com/2010/07/access-to-private-members-thats-easy.html).
|
||||
|
||||
template <typename Tag>
|
||||
struct result
|
||||
{
|
||||
typedef typename Tag::type type;
|
||||
static type ptr;
|
||||
};
|
||||
|
||||
template <typename Tag>
|
||||
typename result<Tag>::type result<Tag>::ptr;
|
||||
|
||||
template <typename Tag, typename Tag::type p>
|
||||
struct rob : result<Tag>
|
||||
{
|
||||
struct filler
|
||||
{
|
||||
filler() { result<Tag>::ptr = p; }
|
||||
};
|
||||
static filler filler_obj;
|
||||
};
|
||||
|
||||
template <typename Tag, typename Tag::type p>
|
||||
typename rob<Tag, p>::filler rob<Tag, p>::filler_obj;
|
||||
|
||||
// Tags — the typedef's signature picks the right overload of the member.
|
||||
struct R3D_initializeOpenGL
|
||||
{
|
||||
typedef bool ( RENDER_3D_OPENGL::*type )();
|
||||
};
|
||||
template struct rob<R3D_initializeOpenGL, &RENDER_3D_OPENGL::initializeOpenGL>;
|
||||
|
||||
struct R3D_generateCylinder
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )( const SFVEC2F&, float, float, float, float,
|
||||
unsigned int, TRIANGLE_DISPLAY_LIST* );
|
||||
};
|
||||
template struct rob<R3D_generateCylinder, &RENDER_3D_OPENGL::generateCylinder>;
|
||||
|
||||
struct R3D_generateInvCone
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )( const SFVEC2F&, float, float, float, float,
|
||||
unsigned int, TRIANGLE_DISPLAY_LIST*, EDA_ANGLE );
|
||||
};
|
||||
template struct rob<R3D_generateInvCone, &RENDER_3D_OPENGL::generateInvCone>;
|
||||
|
||||
struct R3D_generateDisk
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )( const SFVEC2F&, float, float, unsigned int,
|
||||
TRIANGLE_DISPLAY_LIST*, bool );
|
||||
};
|
||||
template struct rob<R3D_generateDisk, &RENDER_3D_OPENGL::generateDisk>;
|
||||
|
||||
struct R3D_generateDimple
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )( const SFVEC2F&, float, float, float, unsigned int,
|
||||
TRIANGLE_DISPLAY_LIST*, bool );
|
||||
};
|
||||
template struct rob<R3D_generateDimple, &RENDER_3D_OPENGL::generateDimple>;
|
||||
|
||||
struct R3D_generateRing
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )( const SFVEC2F&, float, float, unsigned int,
|
||||
std::vector<SFVEC2F>&, std::vector<SFVEC2F>&,
|
||||
bool );
|
||||
};
|
||||
template struct rob<R3D_generateRing, &RENDER_3D_OPENGL::generateRing>;
|
||||
|
||||
struct R3D_addObj_Circle
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )( const FILLED_CIRCLE_2D*, TRIANGLE_DISPLAY_LIST*,
|
||||
float, float );
|
||||
};
|
||||
template struct rob<R3D_addObj_Circle, &RENDER_3D_OPENGL::addObjectTriangles>;
|
||||
|
||||
struct R3D_addObj_Ring
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )( const RING_2D*, TRIANGLE_DISPLAY_LIST*, float,
|
||||
float );
|
||||
};
|
||||
template struct rob<R3D_addObj_Ring, &RENDER_3D_OPENGL::addObjectTriangles>;
|
||||
|
||||
struct R3D_addObj_Poly4
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )( const POLYGON_4PT_2D*, TRIANGLE_DISPLAY_LIST*,
|
||||
float, float );
|
||||
};
|
||||
template struct rob<R3D_addObj_Poly4, &RENDER_3D_OPENGL::addObjectTriangles>;
|
||||
|
||||
struct R3D_addObj_Tri
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )( const TRIANGLE_2D*, TRIANGLE_DISPLAY_LIST*, float,
|
||||
float );
|
||||
};
|
||||
template struct rob<R3D_addObj_Tri, &RENDER_3D_OPENGL::addObjectTriangles>;
|
||||
|
||||
struct R3D_addObj_Seg
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )( const ROUND_SEGMENT_2D*, TRIANGLE_DISPLAY_LIST*,
|
||||
float, float );
|
||||
};
|
||||
template struct rob<R3D_addObj_Seg, &RENDER_3D_OPENGL::addObjectTriangles>;
|
||||
|
||||
struct R3D_appendPostMachining
|
||||
{
|
||||
typedef bool ( RENDER_3D_OPENGL::*type )( TRIANGLE_DISPLAY_LIST*, const SFVEC2F&,
|
||||
PAD_DRILL_POST_MACHINING_MODE, int, int, float,
|
||||
float, bool, float, float, float* );
|
||||
};
|
||||
template struct rob<R3D_appendPostMachining, &RENDER_3D_OPENGL::appendPostMachiningGeometry>;
|
||||
|
||||
struct R3D_createBoard
|
||||
{
|
||||
typedef OPENGL_RENDER_LIST* ( RENDER_3D_OPENGL::*type )( const SHAPE_POLY_SET&,
|
||||
const BVH_CONTAINER_2D* );
|
||||
};
|
||||
template struct rob<R3D_createBoard, &RENDER_3D_OPENGL::createBoard>;
|
||||
|
||||
struct R3D_generate3dGrid
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )( GRID3D_TYPE );
|
||||
};
|
||||
template struct rob<R3D_generate3dGrid, &RENDER_3D_OPENGL::generate3dGrid>;
|
||||
|
||||
struct R3D_setupMaterials
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )();
|
||||
};
|
||||
template struct rob<R3D_setupMaterials, &RENDER_3D_OPENGL::setupMaterials>;
|
||||
|
||||
struct R3D_setLayerMaterial
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )( PCB_LAYER_ID );
|
||||
};
|
||||
template struct rob<R3D_setLayerMaterial, &RENDER_3D_OPENGL::setLayerMaterial>;
|
||||
|
||||
struct R3D_setArrowMaterial
|
||||
{
|
||||
typedef void ( RENDER_3D_OPENGL::*type )();
|
||||
};
|
||||
template struct rob<R3D_setArrowMaterial, &RENDER_3D_OPENGL::setArrowMaterial>;
|
||||
|
||||
struct R3D_m_grid
|
||||
{
|
||||
typedef GLuint RENDER_3D_OPENGL::*type;
|
||||
};
|
||||
template struct rob<R3D_m_grid, &RENDER_3D_OPENGL::m_grid>;
|
||||
|
||||
// ---- public wrappers ----
|
||||
|
||||
bool R3D_InitializeOpenGL( RENDER_3D_OPENGL& aRenderer )
|
||||
{
|
||||
return ( aRenderer.*result<R3D_initializeOpenGL>::ptr )();
|
||||
}
|
||||
|
||||
void R3D_GenerateCylinder( RENDER_3D_OPENGL& aRenderer, const SFVEC2F& aCenter,
|
||||
float aInnerRadius, float aOuterRadius, float aZtop, float aZbot,
|
||||
unsigned int aNrSides, TRIANGLE_DISPLAY_LIST* aDst )
|
||||
{
|
||||
( aRenderer.*result<R3D_generateCylinder>::ptr )( aCenter, aInnerRadius, aOuterRadius, aZtop,
|
||||
aZbot, aNrSides, aDst );
|
||||
}
|
||||
|
||||
void R3D_GenerateInvCone( RENDER_3D_OPENGL& aRenderer, const SFVEC2F& aCenter,
|
||||
float aInnerRadius, float aOuterRadius, float aZtop, float aZbot,
|
||||
unsigned int aNrSides, TRIANGLE_DISPLAY_LIST* aDst, EDA_ANGLE aAngle )
|
||||
{
|
||||
( aRenderer.*result<R3D_generateInvCone>::ptr )( aCenter, aInnerRadius, aOuterRadius, aZtop,
|
||||
aZbot, aNrSides, aDst, aAngle );
|
||||
}
|
||||
|
||||
void R3D_GenerateDisk( RENDER_3D_OPENGL& aRenderer, const SFVEC2F& aCenter, float aRadius,
|
||||
float aZ, unsigned int aNrSides, TRIANGLE_DISPLAY_LIST* aDst, bool aTop )
|
||||
{
|
||||
( aRenderer.*result<R3D_generateDisk>::ptr )( aCenter, aRadius, aZ, aNrSides, aDst, aTop );
|
||||
}
|
||||
|
||||
void R3D_GenerateDimple( RENDER_3D_OPENGL& aRenderer, const SFVEC2F& aCenter, float aRadius,
|
||||
float aZ, float aDepth, unsigned int aNrSides,
|
||||
TRIANGLE_DISPLAY_LIST* aDst, bool aTop )
|
||||
{
|
||||
( aRenderer.*result<R3D_generateDimple>::ptr )( aCenter, aRadius, aZ, aDepth, aNrSides, aDst,
|
||||
aTop );
|
||||
}
|
||||
|
||||
void R3D_GenerateRing( RENDER_3D_OPENGL& aRenderer, const SFVEC2F& aCenter, float aInnerRadius,
|
||||
float aOuterRadius, unsigned int aNrSides,
|
||||
std::vector<SFVEC2F>& aInnerContour, std::vector<SFVEC2F>& aOuterContour,
|
||||
bool aInvertOrder )
|
||||
{
|
||||
( aRenderer.*result<R3D_generateRing>::ptr )( aCenter, aInnerRadius, aOuterRadius, aNrSides,
|
||||
aInnerContour, aOuterContour, aInvertOrder );
|
||||
}
|
||||
|
||||
void R3D_AddObjTriangles( RENDER_3D_OPENGL& aRenderer, const FILLED_CIRCLE_2D* aCircle,
|
||||
TRIANGLE_DISPLAY_LIST* aDst, float aZtop, float aZbot )
|
||||
{
|
||||
( aRenderer.*result<R3D_addObj_Circle>::ptr )( aCircle, aDst, aZtop, aZbot );
|
||||
}
|
||||
|
||||
void R3D_AddObjTriangles( RENDER_3D_OPENGL& aRenderer, const RING_2D* aRing,
|
||||
TRIANGLE_DISPLAY_LIST* aDst, float aZtop, float aZbot )
|
||||
{
|
||||
( aRenderer.*result<R3D_addObj_Ring>::ptr )( aRing, aDst, aZtop, aZbot );
|
||||
}
|
||||
|
||||
void R3D_AddObjTriangles( RENDER_3D_OPENGL& aRenderer, const POLYGON_4PT_2D* aPoly,
|
||||
TRIANGLE_DISPLAY_LIST* aDst, float aZtop, float aZbot )
|
||||
{
|
||||
( aRenderer.*result<R3D_addObj_Poly4>::ptr )( aPoly, aDst, aZtop, aZbot );
|
||||
}
|
||||
|
||||
void R3D_AddObjTriangles( RENDER_3D_OPENGL& aRenderer, const TRIANGLE_2D* aTri,
|
||||
TRIANGLE_DISPLAY_LIST* aDst, float aZtop, float aZbot )
|
||||
{
|
||||
( aRenderer.*result<R3D_addObj_Tri>::ptr )( aTri, aDst, aZtop, aZbot );
|
||||
}
|
||||
|
||||
void R3D_AddObjTriangles( RENDER_3D_OPENGL& aRenderer, const ROUND_SEGMENT_2D* aSeg,
|
||||
TRIANGLE_DISPLAY_LIST* aDst, float aZtop, float aZbot )
|
||||
{
|
||||
( aRenderer.*result<R3D_addObj_Seg>::ptr )( aSeg, aDst, aZtop, aZbot );
|
||||
}
|
||||
|
||||
bool R3D_AppendPostMachining( RENDER_3D_OPENGL& aRenderer, TRIANGLE_DISPLAY_LIST* aDst,
|
||||
const SFVEC2F& aHoleCenter, PAD_DRILL_POST_MACHINING_MODE aMode,
|
||||
int aSizeIU, int aDepthIU, float aHoleInnerRadius, float aZSurface,
|
||||
bool aIsFront, float aPlatingThickness3d, float aUnitScale,
|
||||
float* aZEnd )
|
||||
{
|
||||
return ( aRenderer.*result<R3D_appendPostMachining>::ptr )(
|
||||
aDst, aHoleCenter, aMode, aSizeIU, aDepthIU, aHoleInnerRadius, aZSurface, aIsFront,
|
||||
aPlatingThickness3d, aUnitScale, aZEnd );
|
||||
}
|
||||
|
||||
OPENGL_RENDER_LIST* R3D_CreateBoard( RENDER_3D_OPENGL& aRenderer,
|
||||
const SHAPE_POLY_SET& aBoardPoly,
|
||||
const BVH_CONTAINER_2D* aThroughHoles )
|
||||
{
|
||||
return ( aRenderer.*result<R3D_createBoard>::ptr )( aBoardPoly, aThroughHoles );
|
||||
}
|
||||
|
||||
void R3D_Generate3dGrid( RENDER_3D_OPENGL& aRenderer, GRID3D_TYPE aGridType )
|
||||
{
|
||||
( aRenderer.*result<R3D_generate3dGrid>::ptr )( aGridType );
|
||||
}
|
||||
|
||||
unsigned int R3D_GetGridList( RENDER_3D_OPENGL& aRenderer )
|
||||
{
|
||||
return aRenderer.*result<R3D_m_grid>::ptr;
|
||||
}
|
||||
|
||||
void R3D_SetupMaterials( RENDER_3D_OPENGL& aRenderer )
|
||||
{
|
||||
( aRenderer.*result<R3D_setupMaterials>::ptr )();
|
||||
}
|
||||
|
||||
void R3D_SetLayerMaterial( RENDER_3D_OPENGL& aRenderer, PCB_LAYER_ID aLayerID )
|
||||
{
|
||||
( aRenderer.*result<R3D_setLayerMaterial>::ptr )( aLayerID );
|
||||
}
|
||||
|
||||
void R3D_SetArrowMaterial( RENDER_3D_OPENGL& aRenderer )
|
||||
{
|
||||
( aRenderer.*result<R3D_setArrowMaterial>::ptr )();
|
||||
}
|
||||
83
tests/3d-regression/native/render3d_test_accessor.h
Normal file
83
tests/3d-regression/native/render3d_test_accessor.h
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* Access to RENDER_3D_OPENGL's private geometry generators / material setters
|
||||
* for the Tier-2 scenarios, using the same member-pointer technique as
|
||||
* tests/gal-regression/native/gal_test_accessor.cpp (no KiCad header edits).
|
||||
* Overloads are disambiguated by the tag's member-function-pointer typedef.
|
||||
*/
|
||||
|
||||
#ifndef RENDER3D_TEST_ACCESSOR_H
|
||||
#define RENDER3D_TEST_ACCESSOR_H
|
||||
|
||||
#include <plugins/3dapi/xv3d_types.h>
|
||||
|
||||
#include <3d_enums.h>
|
||||
#include <geometry/eda_angle.h>
|
||||
#include <layer_ids.h>
|
||||
#include <padstack.h> // PAD_DRILL_POST_MACHINING_MODE
|
||||
|
||||
#include <vector>
|
||||
|
||||
class RENDER_3D_OPENGL;
|
||||
class TRIANGLE_DISPLAY_LIST;
|
||||
class OPENGL_RENDER_LIST;
|
||||
class FILLED_CIRCLE_2D;
|
||||
class RING_2D;
|
||||
class POLYGON_4PT_2D;
|
||||
class TRIANGLE_2D;
|
||||
class ROUND_SEGMENT_2D;
|
||||
class SHAPE_POLY_SET;
|
||||
class BVH_CONTAINER_2D;
|
||||
|
||||
bool R3D_InitializeOpenGL( RENDER_3D_OPENGL& aRenderer );
|
||||
|
||||
void R3D_GenerateCylinder( RENDER_3D_OPENGL& aRenderer, const SFVEC2F& aCenter,
|
||||
float aInnerRadius, float aOuterRadius, float aZtop, float aZbot,
|
||||
unsigned int aNrSides, TRIANGLE_DISPLAY_LIST* aDst );
|
||||
|
||||
void R3D_GenerateInvCone( RENDER_3D_OPENGL& aRenderer, const SFVEC2F& aCenter,
|
||||
float aInnerRadius, float aOuterRadius, float aZtop, float aZbot,
|
||||
unsigned int aNrSides, TRIANGLE_DISPLAY_LIST* aDst, EDA_ANGLE aAngle );
|
||||
|
||||
void R3D_GenerateDisk( RENDER_3D_OPENGL& aRenderer, const SFVEC2F& aCenter, float aRadius,
|
||||
float aZ, unsigned int aNrSides, TRIANGLE_DISPLAY_LIST* aDst, bool aTop );
|
||||
|
||||
void R3D_GenerateDimple( RENDER_3D_OPENGL& aRenderer, const SFVEC2F& aCenter, float aRadius,
|
||||
float aZ, float aDepth, unsigned int aNrSides,
|
||||
TRIANGLE_DISPLAY_LIST* aDst, bool aTop );
|
||||
|
||||
void R3D_GenerateRing( RENDER_3D_OPENGL& aRenderer, const SFVEC2F& aCenter, float aInnerRadius,
|
||||
float aOuterRadius, unsigned int aNrSides,
|
||||
std::vector<SFVEC2F>& aInnerContour, std::vector<SFVEC2F>& aOuterContour,
|
||||
bool aInvertOrder );
|
||||
|
||||
void R3D_AddObjTriangles( RENDER_3D_OPENGL& aRenderer, const FILLED_CIRCLE_2D* aCircle,
|
||||
TRIANGLE_DISPLAY_LIST* aDst, float aZtop, float aZbot );
|
||||
void R3D_AddObjTriangles( RENDER_3D_OPENGL& aRenderer, const RING_2D* aRing,
|
||||
TRIANGLE_DISPLAY_LIST* aDst, float aZtop, float aZbot );
|
||||
void R3D_AddObjTriangles( RENDER_3D_OPENGL& aRenderer, const POLYGON_4PT_2D* aPoly,
|
||||
TRIANGLE_DISPLAY_LIST* aDst, float aZtop, float aZbot );
|
||||
void R3D_AddObjTriangles( RENDER_3D_OPENGL& aRenderer, const TRIANGLE_2D* aTri,
|
||||
TRIANGLE_DISPLAY_LIST* aDst, float aZtop, float aZbot );
|
||||
void R3D_AddObjTriangles( RENDER_3D_OPENGL& aRenderer, const ROUND_SEGMENT_2D* aSeg,
|
||||
TRIANGLE_DISPLAY_LIST* aDst, float aZtop, float aZbot );
|
||||
|
||||
bool R3D_AppendPostMachining( RENDER_3D_OPENGL& aRenderer, TRIANGLE_DISPLAY_LIST* aDst,
|
||||
const SFVEC2F& aHoleCenter, PAD_DRILL_POST_MACHINING_MODE aMode,
|
||||
int aSizeIU, int aDepthIU, float aHoleInnerRadius, float aZSurface,
|
||||
bool aIsFront, float aPlatingThickness3d, float aUnitScale,
|
||||
float* aZEnd );
|
||||
|
||||
OPENGL_RENDER_LIST* R3D_CreateBoard( RENDER_3D_OPENGL& aRenderer,
|
||||
const SHAPE_POLY_SET& aBoardPoly,
|
||||
const BVH_CONTAINER_2D* aThroughHoles = nullptr );
|
||||
|
||||
void R3D_Generate3dGrid( RENDER_3D_OPENGL& aRenderer, GRID3D_TYPE aGridType );
|
||||
|
||||
/// The compiled grid display-list id (private m_grid).
|
||||
unsigned int R3D_GetGridList( RENDER_3D_OPENGL& aRenderer );
|
||||
|
||||
void R3D_SetupMaterials( RENDER_3D_OPENGL& aRenderer );
|
||||
void R3D_SetLayerMaterial( RENDER_3D_OPENGL& aRenderer, PCB_LAYER_ID aLayerID );
|
||||
void R3D_SetArrowMaterial( RENDER_3D_OPENGL& aRenderer );
|
||||
|
||||
#endif // RENDER3D_TEST_ACCESSOR_H
|
||||
299
tests/3d-regression/native/scene3d_native_test.cpp
Normal file
299
tests/3d-regression/native/scene3d_native_test.cpp
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
/**
|
||||
* Native 3D-renderer test application — golden-baseline generator.
|
||||
*
|
||||
* Renders the shared scenarios (tests/3d-regression/scenarios/) through the
|
||||
* REAL KiCad 3D-viewer OpenGL code on a desktop GL 2.1 compatibility context,
|
||||
* captures each into a fixed-size offscreen FBO and writes PNGs. These PNGs
|
||||
* are the committed goldens the WebGL port will be compared against with the
|
||||
* pixelmatch engine (tests/tools/screenshots/compare-dirs.ts).
|
||||
*
|
||||
* Modeled on tests/gal-regression/native/gal_native_test.cpp.
|
||||
*/
|
||||
|
||||
#include "kicad_stubs_3d.h" // kiglad before wx
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "stb_image_write.h"
|
||||
|
||||
#include "fbo_capture.h"
|
||||
#include "scene3d_test_ctx.h"
|
||||
#include "scene3d_test_scenarios.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// Fixed capture size — must match the WebGL harness canvas (wasm/3d_webgl_test.html)
|
||||
// and manifest.json. FBO capture makes this independent of window size / Retina.
|
||||
static const int CAPTURE_WIDTH = 800;
|
||||
static const int CAPTURE_HEIGHT = 600;
|
||||
|
||||
static std::string g_outputDir;
|
||||
static std::string g_manifestPath;
|
||||
static std::string g_filter;
|
||||
static bool g_showWindow = false;
|
||||
|
||||
|
||||
static bool SavePng( const std::string& aPath, std::vector<uint8_t>& aPixels, int aWidth,
|
||||
int aHeight )
|
||||
{
|
||||
// Force alpha opaque: the browser canvas the WebGL side screenshots is
|
||||
// composited opaque, while the FBO keeps partial alpha (same rationale as
|
||||
// gal_native_test.cpp SaveScreenshot).
|
||||
for( size_t i = 3; i < aPixels.size(); i += 4 )
|
||||
aPixels[i] = 255;
|
||||
|
||||
// OpenGL rows are bottom-up.
|
||||
stbi_flip_vertically_on_write( 1 );
|
||||
|
||||
return stbi_write_png( aPath.c_str(), aWidth, aHeight, 4, aPixels.data(), aWidth * 4 ) != 0;
|
||||
}
|
||||
|
||||
|
||||
static bool WriteManifest( const std::string& aPath )
|
||||
{
|
||||
std::ofstream out( aPath );
|
||||
|
||||
if( !out )
|
||||
{
|
||||
std::cerr << "Failed to write manifest: " << aPath << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
out << "{\n \"width\": " << CAPTURE_WIDTH << ",\n \"height\": " << CAPTURE_HEIGHT
|
||||
<< ",\n \"scenarios\": [\n";
|
||||
|
||||
for( int i = 0; i < Scene3DTest::GetScenarioCount(); i++ )
|
||||
{
|
||||
out << " \"" << Scene3DTest::GetScenarioName( i ) << "\""
|
||||
<< ( i + 1 < Scene3DTest::GetScenarioCount() ? "," : "" ) << "\n";
|
||||
}
|
||||
|
||||
out << " ]\n}\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
class TEST_GL_CANVAS : public wxGLCanvas
|
||||
{
|
||||
public:
|
||||
explicit TEST_GL_CANVAS( wxWindow* aParent, const wxGLAttributes& aAttrs ) :
|
||||
wxGLCanvas( aParent, aAttrs, wxID_ANY, wxDefaultPosition,
|
||||
wxSize( CAPTURE_WIDTH, CAPTURE_HEIGHT ) )
|
||||
{
|
||||
wxGLContextAttrs ctxAttrs; // default: legacy compatibility profile —
|
||||
ctxAttrs.PlatformDefaults().EndList(); // required for immediate mode + display lists
|
||||
m_context = new wxGLContext( this, nullptr, &ctxAttrs );
|
||||
}
|
||||
|
||||
~TEST_GL_CANVAS() override { delete m_context; }
|
||||
|
||||
bool MakeCurrent() { return SetCurrent( *m_context ); }
|
||||
|
||||
private:
|
||||
wxGLContext* m_context;
|
||||
};
|
||||
|
||||
|
||||
class SCENE3D_TEST_FRAME : public wxFrame
|
||||
{
|
||||
public:
|
||||
SCENE3D_TEST_FRAME() :
|
||||
wxFrame( nullptr, wxID_ANY, "3D Renderer Native Test", wxDefaultPosition,
|
||||
wxSize( CAPTURE_WIDTH, CAPTURE_HEIGHT ) )
|
||||
{
|
||||
wxGLAttributes attrs;
|
||||
attrs.PlatformDefaults().RGBA().DoubleBuffer().Depth( 24 ).Stencil( 8 ).EndList();
|
||||
|
||||
m_canvas = new TEST_GL_CANVAS( this, attrs );
|
||||
|
||||
CallAfter( &SCENE3D_TEST_FRAME::RunScenarios );
|
||||
}
|
||||
|
||||
void RunScenarios()
|
||||
{
|
||||
std::vector<int> toRun;
|
||||
|
||||
for( int i = 0; i < Scene3DTest::GetScenarioCount(); i++ )
|
||||
{
|
||||
const std::string name = Scene3DTest::GetScenarioName( i );
|
||||
|
||||
if( g_filter.empty() || name.find( g_filter ) != std::string::npos )
|
||||
toRun.push_back( i );
|
||||
}
|
||||
|
||||
m_total = static_cast<int>( toRun.size() );
|
||||
|
||||
if( !m_canvas->MakeCurrent() )
|
||||
{
|
||||
std::cerr << "Failed to make GL context current\n";
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
const int gladVersion = gladLoaderLoadGL();
|
||||
|
||||
if( !gladVersion )
|
||||
{
|
||||
std::cerr << "gladLoaderLoadGL failed\n";
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "GL_VERSION: " << (const char*) glGetString( GL_VERSION ) << "\n";
|
||||
std::cout << "GL_RENDERER: " << (const char*) glGetString( GL_RENDERER ) << "\n";
|
||||
|
||||
// The FFP renderer needs a compatibility context: display lists must exist.
|
||||
if( !glad_glGenLists )
|
||||
{
|
||||
std::cerr << "glGenLists did not load — not a compatibility context?\n";
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
FBO_CAPTURE fbo;
|
||||
|
||||
if( !fbo.Create( CAPTURE_WIDTH, CAPTURE_HEIGHT ) )
|
||||
{
|
||||
std::cerr << "FBO creation failed\n";
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
fs::create_directories( g_outputDir );
|
||||
|
||||
SCENE3D_CTX ctx( CAPTURE_WIDTH, CAPTURE_HEIGHT );
|
||||
ctx.InitOnce(); // initializeOpenGL()-equivalent state + circle texture
|
||||
|
||||
std::vector<uint8_t> pixels;
|
||||
|
||||
for( int i : toRun )
|
||||
{
|
||||
const std::string name = Scene3DTest::GetScenarioName( i );
|
||||
|
||||
std::cout << "Scenario " << i << ": " << name << "... " << std::flush;
|
||||
|
||||
fbo.Bind();
|
||||
Scene3DTest::RenderScenario( ctx, i );
|
||||
|
||||
const std::string path = g_outputDir + "/3d-" + name + ".png";
|
||||
|
||||
if( fbo.ReadPixels( pixels ) && SavePng( path, pixels, fbo.Width(), fbo.Height() ) )
|
||||
{
|
||||
std::cout << "OK\n";
|
||||
m_passed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "FAILED\n";
|
||||
}
|
||||
}
|
||||
|
||||
fbo.Destroy();
|
||||
|
||||
if( !g_manifestPath.empty() )
|
||||
{
|
||||
if( WriteManifest( g_manifestPath ) )
|
||||
std::cout << "Manifest: " << g_manifestPath << "\n";
|
||||
else
|
||||
m_passed = -1;
|
||||
}
|
||||
|
||||
std::cout << "\nResults: " << m_passed << "/" << m_total << " scenarios saved\n";
|
||||
|
||||
if( !g_showWindow )
|
||||
Close();
|
||||
}
|
||||
|
||||
int GetPassed() const { return m_passed; }
|
||||
int GetTotal() const { return m_total; }
|
||||
|
||||
private:
|
||||
TEST_GL_CANVAS* m_canvas;
|
||||
int m_passed = 0;
|
||||
int m_total = 0;
|
||||
};
|
||||
|
||||
|
||||
class SCENE3D_TEST_APP : public wxApp
|
||||
{
|
||||
public:
|
||||
bool OnInit() override
|
||||
{
|
||||
m_frame = new SCENE3D_TEST_FRAME();
|
||||
m_frame->Show( true );
|
||||
return true;
|
||||
}
|
||||
|
||||
int OnExit() override
|
||||
{
|
||||
if( m_frame )
|
||||
return ( m_frame->GetPassed() == m_frame->GetTotal() ) ? 0 : 1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private:
|
||||
SCENE3D_TEST_FRAME* m_frame = nullptr;
|
||||
};
|
||||
|
||||
wxIMPLEMENT_APP_NO_MAIN( SCENE3D_TEST_APP );
|
||||
|
||||
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
for( int i = 1; i < argc; i++ )
|
||||
{
|
||||
const std::string arg = argv[i];
|
||||
|
||||
if( arg == "--output" && i + 1 < argc )
|
||||
{
|
||||
g_outputDir = argv[++i];
|
||||
}
|
||||
else if( arg == "--manifest" && i + 1 < argc )
|
||||
{
|
||||
g_manifestPath = argv[++i];
|
||||
}
|
||||
else if( arg == "--filter" && i + 1 < argc )
|
||||
{
|
||||
g_filter = argv[++i];
|
||||
}
|
||||
else if( arg == "--show" )
|
||||
{
|
||||
g_showWindow = true;
|
||||
}
|
||||
else if( arg == "--list" )
|
||||
{
|
||||
for( int s = 0; s < Scene3DTest::GetScenarioCount(); s++ )
|
||||
std::cout << s << ": " << Scene3DTest::GetScenarioName( s ) << "\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Usage: scene3d_native_test --output <dir> [options]\n"
|
||||
" --output <dir> Output directory for 3d-<name>.png\n"
|
||||
" --manifest <file> Write the scenario manifest JSON\n"
|
||||
" --filter <substr> Only run scenarios whose name contains <substr>\n"
|
||||
" --list Print scenario names and exit\n"
|
||||
" --show Keep the window open after rendering\n";
|
||||
return arg == "--help" ? 0 : 2;
|
||||
}
|
||||
}
|
||||
|
||||
if( g_outputDir.empty() )
|
||||
{
|
||||
std::cerr << "--output is required (or use --list)\n";
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::cout << "3D Renderer Native Test - RENDER_3D_OPENGL Baseline Generator\n";
|
||||
std::cout << "==============================================================\n\n";
|
||||
|
||||
return wxEntry( argc, argv );
|
||||
}
|
||||
159
tests/3d-regression/native/settings_3d_stub.cpp
Normal file
159
tests/3d-regression/native/settings_3d_stub.cpp
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* Test-harness stub of the EDA_3D_VIEWER_SETTINGS / APP_SETTINGS_BASE /
|
||||
* JSON_SETTINGS chain. The renderer only reads m_Render/m_Camera plain
|
||||
* fields; none of the JSON load/store machinery ever runs, so every virtual
|
||||
* is a no-op and the ctor fills the render settings with the upstream
|
||||
* defaults (deterministic test values, documented deviations flagged).
|
||||
*/
|
||||
|
||||
#include "kicad_stubs_3d.h"
|
||||
|
||||
#include "3d_viewer/eda_3d_viewer_settings.h"
|
||||
#include "common_ogl/ogl_attr_list.h" // ANTIALIASING_MODE (fwd-declared in the settings header)
|
||||
|
||||
#include <settings/json_settings_internals.h>
|
||||
|
||||
// ---- JSON_SETTINGS ----
|
||||
|
||||
JSON_SETTINGS::JSON_SETTINGS( const wxString& aFilename, SETTINGS_LOC aLocation,
|
||||
int aSchemaVersion, bool aCreateIfMissing, bool aCreateIfDefault,
|
||||
bool aWriteFile ) :
|
||||
m_filename( aFilename ),
|
||||
m_legacy_filename( "" ),
|
||||
m_location( aLocation ),
|
||||
m_createIfMissing( aCreateIfMissing ),
|
||||
m_createIfDefault( aCreateIfDefault ),
|
||||
m_writeFile( aWriteFile ),
|
||||
m_modified( false ),
|
||||
m_deleteLegacyAfterMigration( false ),
|
||||
m_resetParamsIfMissing( true ),
|
||||
m_schemaVersion( aSchemaVersion ),
|
||||
m_manager( nullptr )
|
||||
{
|
||||
m_internals = std::make_unique<JSON_SETTINGS_INTERNALS>();
|
||||
}
|
||||
|
||||
JSON_SETTINGS::~JSON_SETTINGS() = default;
|
||||
|
||||
void JSON_SETTINGS::Load() {}
|
||||
bool JSON_SETTINGS::Store() { return false; }
|
||||
bool JSON_SETTINGS::LoadFromFile( const wxString& ) { return false; }
|
||||
bool JSON_SETTINGS::SaveToFile( const wxString&, bool ) { return false; }
|
||||
std::map<std::string, nlohmann::json> JSON_SETTINGS::GetFileHistories() { return {}; }
|
||||
bool JSON_SETTINGS::MigrateFromLegacy( wxConfigBase* ) { return false; }
|
||||
|
||||
// ---- APP_SETTINGS_BASE ----
|
||||
|
||||
APP_SETTINGS_BASE::APP_SETTINGS_BASE( const std::string& aFilename, int aSchemaVersion ) :
|
||||
JSON_SETTINGS( aFilename, SETTINGS_LOC::USER, aSchemaVersion, true, true, true ),
|
||||
m_CrossProbing(),
|
||||
m_FindReplace(),
|
||||
m_Graphics(),
|
||||
m_ColorPicker(),
|
||||
m_LibTree(),
|
||||
m_Printing(),
|
||||
m_SearchPane(),
|
||||
m_System(),
|
||||
m_Window(),
|
||||
m_appSettingsSchemaVersion( aSchemaVersion )
|
||||
{
|
||||
}
|
||||
|
||||
bool APP_SETTINGS_BASE::MigrateFromLegacy( wxConfigBase* ) { return false; }
|
||||
|
||||
// ---- EDA_3D_VIEWER_SETTINGS ----
|
||||
|
||||
EDA_3D_VIEWER_SETTINGS::EDA_3D_VIEWER_SETTINGS() :
|
||||
APP_SETTINGS_BASE( "3d_viewer", 0 ),
|
||||
m_Render(),
|
||||
m_Camera()
|
||||
{
|
||||
RENDER_SETTINGS& r = m_Render;
|
||||
|
||||
// Upstream defaults (eda_3d_viewer_settings.cpp PARAM defaults), with two
|
||||
// determinism-driven deviations: engine is OPENGL (the renderer under
|
||||
// test) and AA is NONE (context is single-sample anyway).
|
||||
r.engine = RENDER_ENGINE::OPENGL;
|
||||
r.grid_type = GRID3D_TYPE::NONE;
|
||||
r.opengl_AA_mode = ANTIALIASING_MODE::AA_NONE;
|
||||
r.material_mode = MATERIAL_MODE::NORMAL;
|
||||
|
||||
r.opengl_AA_disableOnMove = false;
|
||||
r.opengl_thickness_disableOnMove = false;
|
||||
r.opengl_microvias_disableOnMove = false;
|
||||
r.opengl_holes_disableOnMove = false;
|
||||
r.opengl_render_bbox_only_OnMove = false;
|
||||
r.opengl_copper_thickness = true;
|
||||
r.show_model_bbox = false;
|
||||
r.show_off_board_silk = false;
|
||||
r.highlight_on_rollover = false;
|
||||
r.opengl_selection_color = KIGFX::COLOR4D( 0.0, 1.0, 0.0, 1.0 );
|
||||
|
||||
r.raytrace_anti_aliasing = false;
|
||||
r.raytrace_backfloor = false;
|
||||
r.raytrace_post_processing = false;
|
||||
r.raytrace_procedural_textures = false;
|
||||
r.raytrace_reflections = false;
|
||||
r.raytrace_refractions = false;
|
||||
r.raytrace_shadows = false;
|
||||
r.raytrace_nrsamples_shadows = 0;
|
||||
r.raytrace_nrsamples_reflections = 0;
|
||||
r.raytrace_nrsamples_refractions = 0;
|
||||
r.raytrace_spread_shadows = 0.0f;
|
||||
r.raytrace_spread_reflections = 0.0f;
|
||||
r.raytrace_spread_refractions = 0.0f;
|
||||
r.raytrace_recursivelevel_reflections = 0;
|
||||
r.raytrace_recursivelevel_refractions = 0;
|
||||
r.raytrace_lightColorCamera = KIGFX::COLOR4D( 0.2, 0.2, 0.2, 1.0 );
|
||||
r.raytrace_lightColorTop = KIGFX::COLOR4D( 0.247, 0.247, 0.247, 1.0 );
|
||||
r.raytrace_lightColorBottom = KIGFX::COLOR4D( 0.247, 0.247, 0.247, 1.0 );
|
||||
|
||||
r.show_adhesive = true;
|
||||
r.show_navigator = false;
|
||||
r.show_board_body = true;
|
||||
r.show_plated_barrels = true;
|
||||
r.show_comments = true;
|
||||
r.show_drawings = true;
|
||||
r.show_eco1 = true;
|
||||
r.show_eco2 = true;
|
||||
|
||||
for( bool& user : r.show_user )
|
||||
user = false;
|
||||
|
||||
r.show_footprints_insert = true;
|
||||
r.show_footprints_normal = true;
|
||||
r.show_footprints_virtual = true;
|
||||
r.show_footprints_not_in_posfile = true;
|
||||
r.show_footprints_dnp = true;
|
||||
r.show_silkscreen_top = true;
|
||||
r.show_silkscreen_bottom = true;
|
||||
r.show_soldermask_top = true;
|
||||
r.show_soldermask_bottom = true;
|
||||
r.show_solderpaste = true;
|
||||
r.show_copper_top = true;
|
||||
r.show_copper_bottom = true;
|
||||
r.show_zones = true;
|
||||
r.show_fp_references = true;
|
||||
r.show_fp_values = true;
|
||||
r.show_fp_text = true;
|
||||
r.subtract_mask_from_silk = false;
|
||||
r.clip_silk_on_via_annuli = true;
|
||||
r.differentiate_plated_copper = true;
|
||||
r.use_board_editor_copper_colors = false;
|
||||
r.preview_show_board_body = true;
|
||||
|
||||
m_Camera.animation_enabled = false;
|
||||
m_Camera.moving_speed_multiplier = 3;
|
||||
m_Camera.rotation_increment = 10.0;
|
||||
m_Camera.projection_mode = 0;
|
||||
}
|
||||
|
||||
LAYER_PRESET_3D* EDA_3D_VIEWER_SETTINGS::FindPreset( const wxString& )
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool EDA_3D_VIEWER_SETTINGS::MigrateFromLegacy( wxConfigBase* )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
1724
tests/3d-regression/native/stb_image_write.h
Normal file
1724
tests/3d-regression/native/stb_image_write.h
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue