|
wx_BGI_Graphics
Classic BGI-compatible graphics API with modern OpenGL extension API
|
wx_bgi_wx is an optional static library that lets you embed the BGI OpenGL drawing surface – cameras, viewports, DDS scene graph – inside a wxWidgets wxFrame alongside menus, toolbars, status bars, and other controls.
The library includes a standalone wx window API modelled on wxPython's wx.App / wx.Frame / wx.App.MainLoop() pattern. It lets Python, Pascal, plain C, or any ctypes-compatible language open a wx window and draw with the full BGI API — without writing a single line of wxWidgets C++.
Note: Do not mix
wxbgi_wx_app_create()withwxIMPLEMENT_APPorwx_bgi_wx.libin the same program. For C++ apps that want a customwxFrame, usewx_bgi_wx.lib+WxBgiCanvasdirectly.
WxbgiFrameCallback is defined in wx_bgi_ext.h:
wxbgi_wx_app_create() allocates a minimal BgiStandaloneApp (subclass of wxApp) and calls wxInitialize().wxbgi_wx_frame_create() creates a BgiStandaloneFrame hosting a single WxBgiCanvas, calls wxbgi_wx_init_for_canvas(w, h) to set up the BGI CPU page buffers, then shows the frame. All BGI drawing calls made after this point write into the page buffer and are displayed on the next paint. On Linux and macOS, after Show(true) the function calls wxApp::Yield() and WxBgiCanvas::Render() to ensure the GL context is fully initialised (including the DLL-local GLEW function pointer table) before returning. This makes GL extension functions such as wxbgi_write_pixels_rgba8 safe to call immediately — without needing wxbgi_wx_app_main_loop to run first.wxbgi_wx_app_main_loop() runs a Win32 message pump (Windows) or a wxEventLoop (Linux / macOS). When the frame is closed (timer, user click, or wxbgi_wx_close_frame()), the pump exits and the function returns.GTK on Linux and macOS loads librsvg for SVG icon rendering. librsvg (via libxml2) performs floating-point operations (NaN / denormal arithmetic) that violate FreePascal's default strict x87 exception mask, producing:
All FreePascal programs that call wxbgi_wx_app_create must mask FPU exceptions at program start:
This has no effect on Windows (where GTK is not used) and is safe to include unconditionally.
The existing Pascal example in this section does not open a wx window, so it is unaffected. The full test programs in examples/demoFreePascal/ already include this call.
Or when building wx_bgi itself — wx is ON by default, no flag needed:
To build without wxWidgets (GLFW-only, smaller DLL):
When WXBGI_ENABLE_WX=ON the build system automatically downloads wxWidgets 3.2.5 via FetchContent (requires network access at configure time, ~170 MB git shallow clone). No manual wxWidgets installation is needed.
The CMakeLists.txt fetches wxWidgets with these settings:
CMake targets provided by wx: wxcore, wxgl, wxbase.
| Target | Type | Purpose |
|---|---|---|
wx_bgi_wx | STATIC lib | Link into your app |
wx_bgi_app | Executable | Interactive 2-D demo (mouse crosshair, key feedback) |
wx_bgi_3d_app | Executable | Interactive 3-D orbit demo (camera, solids) |
wx_multi_scene_demo | Executable | Multi-scene / multi-camera demo with menubar + statusbar |
wx_bgi_solids_test | Executable | 3-second automated test |
WxBgiCanvas uses a lazy-initialization pattern following the official wxWidgets OpenGL sample (cube.cpp):
wxGLContext creation. Just sets up event bindings.Render() call (triggered by first OnPaint) – creates wxGLContext, calls glewInit(), calls wxbgi_wx_init_for_canvas(w, h). This sets BgiState::wxEmbedded = true, allocates CPU page buffers, creates the default "default" camera (pixel-space orthographic), and the default "world" UCS. GLFW is never started in wx mode.This pattern avoids the DLL-boundary wxWidgets initialisation crash described in the Architecture Note below.
Render() makes the wxGLContext current (shared across all WxBgiCanvas instances in the application), runs the three-phase composite pipeline, then calls SwapBuffers().
Why the two-pass approach? 3-D solid geometry is rendered directly by OpenGL into the framebuffer (depth-tested). Visual aids (grid, UCS axes, concentric circles, selection cursor) live in the BGI pixel buffer and must appear in front of all solid geometry. PostBlit / renderPageAsTextureAlpha achieve this by:
alpha = 0 for all background pixels and alpha = 255 for overlay pixels.WxBgiCanvas is compiled into the **wx_bgi_wx STATIC library**, not the wx_bgi_opengl.dll. This is critical on Windows:
When a DLL statically links wxWidgets it gets its own private copy of all wx globals (
wxTheApp, GL factory, timer factory, ...). That copy has nowxAppregistered (only the EXE'swxIMPLEMENT_APPsetswxTheApp). Any wx assertion from inside the DLL's message handler causes aSTATUS_FATAL_USER_CALLBACK_EXCEPTION(0xC000041D) crash.
By placing WxBgiCanvas in the STATIC lib it compiles into the EXE's address space where wxTheApp is valid. The DLL exposes only plain C functions (wxbgi_wx_render_page_gl, wxbgi_wx_key_event, ...) that WxBgiCanvas calls through the normal import table – no wx objects cross the DLL boundary.
OnSize calls wxbgi_wx_resize(w, h) to reallocate page buffers while keeping camera viewports intact.
All classic BGI drawing functions work identically:
Difference from GLFW mode:
| Behaviour | GLFW mode | wx mode |
|---|---|---|
| Event loop | wxbgi_poll_events() | wx event loop (automatic) |
wxbgi_poll_events() | pumps GLFW | no-op, returns 0 |
| Page flush | flushToScreen() or swapbuffers() | WxBgiCanvas::OnPaint() |
| Double-buffering | setactivepage() / setvisualpage() | unchanged |
glfwSwapBuffers() | called | never called |
Use canvas->Render() to force an immediate repaint, or rely on the timer set with SetAutoRefreshHz().
WxBgiCanvas translates wx events into the same internal state updates that the GLFW callbacks perform in standalone mode.
| wx Event | BGI Internal State | User Hook Fired |
|---|---|---|
wxEVT_KEY_DOWN | gState.keyDown[glfwKey] = 1; key queue push | userKeyHook (action=PRESS) |
wxEVT_KEY_UP | gState.keyDown[glfwKey] = 0 | userKeyHook (action=RELEASE) |
wxEVT_CHAR | gState.keyQueue push (unicode) | userCharHook |
wxEVT_MOTION | gState.mouseX/Y; mouseMoved=true | userCursorPosHook |
wxEVT_LEFT_DOWN/UP | – | userMouseButtonHook (button=LEFT) |
wxEVT_RIGHT_DOWN/UP | – | userMouseButtonHook (button=RIGHT) |
wxEVT_MIDDLE_DOWN/UP | – | userMouseButtonHook (button=MIDDLE) |
wxEVT_MOUSEWHEEL | gState.scrollDeltaX/Y accumulate | userScrollHook |
wx key codes are translated to GLFW key constants for keyDown[] indexing. The mapping is defined in WxBgiCanvas::WxKeyToGlfw() inside src/wx/wx_bgi_canvas.cpp. GLFW headers are not available in wx_bgi_wx – constants are defined locally.
All hook registration functions work identically in wx mode:
The WXBGI_DEFAULT_* bypass flags apply equally to wx mode and GLFW mode. See InputsProcessing.md for the full bypass API.
All camera, UCS, world-draw, and solid APIs work in wx mode:
| Constant | Description |
|---|---|
WXBGI_SOLID_WIREFRAME | Edges only, using the current line colour. |
WXBGI_SOLID_SOLID / WXBGI_SOLID_FLAT | Filled faces with flat Phong shading (GPU pass pending). |
WXBGI_SOLID_SMOOTH | Smooth per-vertex Phong shading (GPU pass pending). |
Configure the key/fill lights and material parameters for solid shading (effective once the GPU solid pass, GL-5, is enabled):
WxBgiCanvas automatically calls wxbgi_gl_pass_destroy() in its destructor. If you manage a custom wxGLContext, call it with the context current before destroying the context to avoid GPU driver crashes on Windows:
wxbgi_* and classic BGI functions acquire gMutex internally.WxBgiCanvas methods run on the wx main thread – no additional locking needed.wxbgi_* function from within a hook callback – that will deadlock. Use flags or queues to communicate back to the main thread instead.The WxBgiCanvas implementation creates one shared wxGLContext for the entire application. All canvas instances (panels) use SetCurrent() with the same context object.
This design is required because:
glewInit.wx_bgi_opengl.dll has a single global GL state (bgi::gState). Using a single shared context keeps that state consistent.Each panel runs its own PreBlit → wxbgi_wx_render_page_gl_vp → PostBlit pipeline sequentially on the wx main thread. There is no concurrent access to the pixel buffer. A representative 4-panel setup:
See the 4-Panel Camera Demo for a complete working example.
examples/wx/wx_bgi_solids_test.cpp is an automated CTest entry (wx_bgi_solids_test, 15-second timeout). It:
SolidsFrame with a WxBgiCanvas (640x480)."testcam".Run it:
Source: examples/wx/wx_bgi_app.cpp
Demonstrates live mouse tracking and key feedback with the classic BGI 2-D API.
| Input | Effect |
|---|---|
| Mouse move | Magenta crosshair follows the cursor; coordinates shown in HUD |
R G B C Y W | Change the triangle fill colour |
| Any other key | Key name shown in HUD |
Source: examples/wx/wx_bgi_3d_app.cpp
Demonstrates an orbiting perspective camera with solid 3-D primitives: box (red), sphere (cyan), cylinder (yellow), cone (magenta), torus (light green), a ground grid, and XYZ axes (red/green/blue).
| Input | Effect |
|---|---|
| Arrow keys | Orbit camera (azimuth / elevation) |
+ / - | Zoom in / out |
W | Toggle wireframe / solid |
R | Reset camera to default position |
| Left-drag | Orbit (azimuth x elevation) |
| Mouse scroll | Zoom |
Camera state (azimuth, elevation, distance) is displayed in the status bar.
Implementation pattern: The scene is built once into the DDS (retained-mode scene graph) via wxbgi_solid_* calls. On each camera change only wxbgi_render_dds("cam3d") is called after updating the camera eye position – there is no need to re-submit geometry every frame.
Source: examples/wx/wx_multi_scene_demo.cpp
A full wxIMPLEMENT_APP application that demonstrates the Multi-Scene Management feature with a 3-panel split view, menubar, and statusbar.
Layout:
Both Camera A and Camera B view the same "main" scene graph (sphere + box + solid primitives) from independently-controlled viewpoints. Camera C is assigned to the "secondary" scene, which contains entirely different objects (bar-chart elements drawn with classic BGI calls).
Menu bar:
| Menu | Items |
|---|---|
| File | Exit |
| Camera A | Smooth (radio), Flat (radio), Wireframe (radio), Reset Yaw |
| Camera B | Smooth (radio), Flat (radio), Wireframe (radio), Reset Pitch |
| Help | Controls…, About |
Status bar (2 fields): Cam A: yaw=…° | Smooth and Cam B: pitch=…° | Flat
Keyboard controls:
| Key | Effect |
|---|---|
| ← / → | Orbit Camera A (yaw) |
| ↑ / ↓ | Orbit Camera B (pitch) |
S / F / W | Camera A shading: Smooth / Flat / Wireframe |
1 / 2 / 3 | Camera B shading: Smooth / Flat / Wireframe |
Key implementation details:
OnFirstPaint → CallAfter pattern ensures the GL context is ready before wxbgi_cam_create is called.wxEVT_IDLE + held-key booleans (m_keyLeft/Right/Up/Down); evt.RequestMore(anyKeyHeld) keeps idle events flowing only when keys are held (CPU-friendly).wxbgi_dds_set_solid_draw_mode(mode) called immediately before each wxbgi_render_dds(cam) — no separate state per camera needed in the DDS itself.setCamAShading() / setCamBShading() helpers.Source: examples/cpp/wxbgi_camera_demo.cpp
A four-panel interactive demo that shows the same DDS scene graph rendered simultaneously through four independent cameras, each in its own WxBgiCanvas panel. All four panels are hosted inside a single wxFrame using a wxGridSizer.
Layout:
All four panels share the single DDS scene graph "scene_3d" containing: a solid box (red), solid sphere (cyan), solid cylinder (yellow), solid cone (magenta), solid torus (light green), a ground grid, and XYZ axes (red / green / blue line segments).
Keyboard controls (any panel focused):
| Key | Effect |
|---|---|
| ← / → | Orbit cam3d (azimuth) |
| ↑ / ↓ | Orbit cam3d (elevation) |
+ / - | Zoom cam3d in / out |
S | Solid shading (WXBGI_SOLID_SMOOTH) |
F | Flat shading (WXBGI_SOLID_FLAT) |
W | Wireframe (WXBGI_SOLID_WIREFRAME) |
R | Reset cam3d to default position |
| Mouse scroll (cam2d panel) | Zoom 2D camera |
Overlay system per panel:
Each CameraPanel overrides both PreBlit and PostBlit:
PreBlit: calls cleardevice() + wxbgi_render_dds(camName) to fill the page buffer with scene content for the assigned camera.PostBlit: calls wxbgi_wx_render_overlays_for_camera(camName, ...) to composite the visual-aids overlays (grid, UCS axes, concentric circles, selection cursor) in front of all 3-D solid geometry.OS-level redraw safety:
All drawing primitives are stored in the DDS scene graph. On any OS repaint event (minimize/maximize/resize) each panel's Render() is called automatically, PreBlit re-renders the DDS, and the scene reappears correctly.
See also: README.md · InputsProcessing.md · DDS.md · VisualAids.md