- Add submodule setup instructions to Phase 3 docs - Explains nested submodule config.sub requirements - Documents manual copy step after cloning - Add WXWIDGETS-WASM-PORT-PROGRESS.md tracking all modifications 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
17 KiB
Phase 3: wxWidgets wxUniversal for WebAssembly
Overview
Port wxWidgets with wxUniversal backend to WebAssembly, enabling the full KiCad GUI to run in browsers. This builds on the core library already compiled to WASM (kimath, sexpr).
Current State (from Phase 2)
Already working in WASM:
libkimath.a- Geometry library (5.1MB)libsexpr.a- S-expression parser (159KB)libclipper2.a- Polygon operations (1.2MB)libkicad_core_utils.a- Core utilities (106KB)- wxBase (non-GUI utilities) - via existing build script
Goal
Build wxWidgets with wxUniversal backend for Emscripten, enabling:
- Full wxWidgets GUI rendered to HTML5 canvas
- wxGLCanvas for KiCad's GAL (Graphics Abstraction Layer)
- Event handling (mouse, keyboard, touch)
First Milestone: Any .kicad_pcb file renders in browser
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ HTML5 Canvas│ │ WebGL │ │ DOM Events │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
└─────────┼────────────────┼────────────────┼─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ wxWidgets (wxUniversal backend) │
│ - Draws all widgets to canvas (no native widgets) │
│ - wxGLCanvas → WebGL context │
│ - Event translation (browser → wx events) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ KiCad Application │
│ - GAL (Graphics Abstraction Layer) → uses wxGLCanvas │
│ - Board data model (already in WASM) │
│ - PCB Painter │
└─────────────────────────────────────────────────────────────┘
Strategy: Create Your Own wxWidgets Fork
Rather than using the abandoned wxWidgets-wasm fork (based on old wx 3.0.x), create a fresh fork from modern wxWidgets 3.2.6 and apply the ~10 required patches.
Reference: wxWidgets-wasm Commits
From ahilss/wxWidgets-wasm:
| Commit | Purpose | Likely Files |
|---|---|---|
| Suppress locale warnings | Browser env | src/common/intl.cpp |
| Touch → mouse events | Web input | src/univ/topluniv.cpp |
| Font size in pixels | Web rendering | src/univ/themes/*.cpp |
| Size top window before run | WASM init | src/univ/topluniv.cpp |
| Mouse window crash fix | Event stability | src/common/wincmn.cpp |
Configure Flags (from wxWidgets-wasm)
--host=emscripten \
--with-cxx=17 \
--enable-utf8 \
--enable-universal \ # Key: Use wxUniversal backend
--disable-shared \
--disable-exceptions \
--disable-richtext \
--without-libtiff \
--disable-xlocale \
--with-opengl # Enable wxGLCanvas for WebGL
Implementation Steps
Step 1: Fork wxWidgets
- Fork wxWidgets on GitHub (e.g.,
VV-EE/wxWidgets) - Clone locally
- Checkout tag
v3.2.6 - Create branch
wasm-port
git clone git@github.com:VV-EE/wxWidgets.git
cd wxWidgets
git checkout v3.2.6
git checkout -b wasm-port
Step 2: Study wxWidgets-wasm Patches
Clone wxWidgets-wasm and identify the specific changes:
git clone https://github.com/ahilss/wxWidgets-wasm.git wxwidgets-wasm-ref
cd wxwidgets-wasm-ref
# Find commits that differ from upstream
git log --oneline | head -20
For each WASM-specific commit:
- Identify changed files
- Understand the change
- Create equivalent patch for 3.2.6
Step 3: Apply Patches to Your Fork
Create patch files and apply:
# In your wxWidgets fork
git apply ../patches/0001-suppress-locale-warnings.patch
git apply ../patches/0002-touch-to-mouse-events.patch
# etc.
git commit -m "Add Emscripten/WASM support"
Step 4: Submodule Setup After Cloning
The wxwidgets submodule is a fork (VV-EE/wxWidgets) with all WASM changes already committed. However, wxWidgets has nested submodules (pcre, expat, jpeg, png, tiff, zlib) that need config.sub modifications for Emscripten support. These nested submodules are separate repositories, so their changes are NOT tracked by the wxwidgets fork.
After cloning kicad-wasm, you must:
git clone <kicad-wasm-repo>
cd kicad-wasm
# 1. Initialize wxwidgets submodule
git submodule update --init wxwidgets
# 2. Initialize wxwidgets' nested submodules
cd wxwidgets
git submodule update --init --recursive
# 3. Copy config.sub to nested submodules (required for Emscripten)
# The main wxwidgets/config.sub already has emscripten/wasm32 support.
# Copy it to all nested submodule locations:
cp config.sub 3rdparty/pcre/config.sub
cp config.sub src/expat/expat/conftools/config.sub
cp config.sub src/jpeg/config.sub
cp config.sub src/png/config.sub
cp config.sub src/tiff/config/config.sub
cd ..
Why is this needed?
- wxWidgets bundles libraries (pcre, expat, jpeg, png, tiff) as git submodules
- Each submodule has its own
config.subthat must recognizeemscriptenandwasm32hosts - These submodules point to upstream repos, so we can't commit changes to them
- The same modified config.sub must be copied to all 5 locations after every fresh clone
Reproducibility Testing (optional):
A patch-based build system exists for validation:
# Generate patches from current wxwidgets state
./scripts/generate-wxwidgets-patches.sh
# Build from clean clone + patches (validates reproducibility)
./scripts/build-wxwidgets-wasm-clean.sh --clean
Step 5: Create Build Script
Create scripts/build-wxuniversal-wasm.sh:
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-universal"
WX_SOURCE="$PROJECT_ROOT/wxwidgets"
# Environment setup
export CFLAGS="-I$EMSDK/upstream/emscripten/system/local/include"
export CXXFLAGS="-I$EMSDK/upstream/emscripten/system/local/include"
export LDFLAGS="-L$EMSDK/upstream/emscripten/system/local/lib -sERROR_ON_UNDEFINED_SYMBOLS=0"
CONFIGURE_ARGS="--host=emscripten \
--with-cxx=17 \
--enable-utf8 \
--enable-universal \
--disable-shared \
--disable-exceptions \
--disable-richtext \
--without-libtiff \
--disable-xlocale \
--with-opengl"
if [ "$1" = "--clean" ]; then
rm -rf "$BUILD_DIR"
fi
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"
# Initialize submodules if needed
cd "$WX_SOURCE"
git submodule update --init src/jpeg 2>/dev/null || true
git submodule update --init 3rdparty/catch 2>/dev/null || true
cd "$BUILD_DIR"
# Configure and build
emconfigure "$WX_SOURCE/configure" $CONFIGURE_ARGS
emmake make -j$(sysctl -n hw.ncpu 2>/dev/null || nproc)
echo ""
echo "=== Build complete ==="
ls -lh "$BUILD_DIR"/lib/*.a 2>/dev/null || echo "Libraries in $BUILD_DIR/lib/"
Step 5: Verify wxUniversal Build
Expected output libraries:
lib/
├── libwx_baseu-3.2.a # Base utilities (strings, files, etc.)
├── libwx_coreu-3.2.a # wxUniversal core (widgets, events)
├── libwx_glu-3.2.a # OpenGL/WebGL support
└── libwxregexu-3.2.a # Regex support
Verify setup.h contains:
#define wxUSE_UNIVERSAL 1
#define __WXUNIVERSAL__ 1
#define wxUSE_GLCANVAS 1
Phase 3.1: Minimal wxApp Test
Test Application
Create test/wx_minimal/main.cpp:
#include <wx/wx.h>
class MinimalApp : public wxApp {
public:
bool OnInit() override {
wxFrame* frame = new wxFrame(nullptr, wxID_ANY,
"KiCad WASM Test", wxDefaultPosition, wxSize(800, 600));
// Add a simple panel with text
wxPanel* panel = new wxPanel(frame);
new wxStaticText(panel, wxID_ANY, "wxWidgets in WebAssembly!",
wxPoint(10, 10));
frame->Show(true);
return true;
}
};
wxIMPLEMENT_APP(MinimalApp);
Build Command
emcc main.cpp \
-I$WX_BUILD/lib/wx/include/emscripten-unicode-static-3.2 \
-I$WX_SOURCE/include \
-L$WX_BUILD/lib \
-lwx_baseu-3.2 \
-lwx_coreu-3.2 \
-o minimal.html \
-s ASYNCIFY=1 \
-s ALLOW_MEMORY_GROWTH=1
Verification
- HTML file loads in browser
- wxFrame window appears (rendered to canvas)
- Text displays
- No JavaScript errors in console
Phase 3.2: wxGLCanvas + WebGL
Test Application
Create test/wx_glcanvas/main.cpp:
#include <wx/wx.h>
#include <wx/glcanvas.h>
#ifdef __EMSCRIPTEN__
#include <GLES2/gl2.h>
#else
#include <GL/gl.h>
#endif
class GLFrame : public wxFrame {
wxGLCanvas* m_canvas;
wxGLContext* m_context;
public:
GLFrame() : wxFrame(nullptr, wxID_ANY, "WebGL Test",
wxDefaultPosition, wxSize(800, 600)) {
wxGLAttributes attrs;
attrs.RGBA().DoubleBuffer().Depth(16).EndList();
m_canvas = new wxGLCanvas(this, attrs);
m_context = new wxGLContext(m_canvas);
m_canvas->Bind(wxEVT_PAINT, &GLFrame::OnPaint, this);
m_canvas->Bind(wxEVT_SIZE, &GLFrame::OnSize, this);
}
void OnPaint(wxPaintEvent& evt) {
wxPaintDC dc(m_canvas);
m_context->SetCurrent(*m_canvas);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw a simple triangle
// (shader code omitted for brevity)
m_canvas->SwapBuffers();
}
void OnSize(wxSizeEvent& evt) {
m_canvas->Refresh();
}
};
class GLApp : public wxApp {
public:
bool OnInit() override {
GLFrame* frame = new GLFrame();
frame->Show(true);
return true;
}
};
wxIMPLEMENT_APP(GLApp);
Build Command (with WebGL)
emcc main.cpp \
-I$WX_BUILD/lib/wx/include/emscripten-unicode-static-3.2 \
-I$WX_SOURCE/include \
-L$WX_BUILD/lib \
-lwx_baseu-3.2 \
-lwx_coreu-3.2 \
-lwx_glu-3.2 \
-o glcanvas.html \
-s ASYNCIFY=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s USE_WEBGL2=1 \
-s FULL_ES3=1
Verification
- WebGL context created successfully
- Canvas clears to specified color
- No WebGL errors in console
- Window resizing works
Phase 3.3: KiCad GAL Integration
Shader Conversion (GLSL 1.2 → GLSL ES 3.0)
KiCad shaders need conversion for WebGL 2.0:
Vertex Shader:
// Before (GLSL 1.2)
#version 120
attribute vec4 a_position;
varying vec4 v_color;
// After (GLSL ES 3.0)
#version 300 es
precision highp float;
in vec4 a_position;
out vec4 v_color;
Fragment Shader:
// Before (GLSL 1.2)
#version 120
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
// After (GLSL ES 3.0)
#version 300 es
precision highp float;
in vec4 v_color;
out vec4 fragColor;
void main() {
fragColor = v_color;
}
Files to Convert
kicad/common/gal/shaders/kicad_frag.glslkicad/common/gal/shaders/kicad_vert.glslkicad/common/gal/shaders/smaa_*.glsl(anti-aliasing, optional)
WebGL GAL Adapter
Create core/gal-wasm/webgl_gal.h:
// Adapter for OPENGL_GAL to work with Emscripten WebGL
class WEBGL_GAL : public OPENGL_GAL {
public:
WEBGL_GAL(...);
// Override shader loading to use GLSL ES
void loadShaders() override;
// Skip GLEW initialization (not needed in Emscripten)
void initGLEW() override { /* no-op */ }
};
Phase 3.4: PCB Viewer
Application Structure
viewer/
├── main.cpp # wxApp entry point
├── pcb_view_frame.cpp # Main frame with canvas
├── pcb_view_canvas.cpp # wxGLCanvas + GAL + VIEW
├── index.html # HTML shell
└── CMakeLists.txt
PCB View Canvas
class PCB_VIEW_CANVAS : public wxGLCanvas {
std::unique_ptr<WEBGL_GAL> m_gal;
std::unique_ptr<KIGFX::VIEW> m_view;
std::unique_ptr<KIGFX::PCB_PAINTER> m_painter;
BOARD* m_board = nullptr;
public:
PCB_VIEW_CANVAS(wxWindow* parent);
void LoadBoard(const std::string& sexpr);
protected:
void OnPaint(wxPaintEvent& evt);
void OnMouseWheel(wxMouseEvent& evt); // Zoom
void OnMouseMove(wxMouseEvent& evt); // Pan
};
JavaScript Integration
// Load PCB file via fetch and pass to WASM
async function loadPCB(url) {
const response = await fetch(url);
const content = await response.text();
// Call WASM function
Module.ccall('loadPCBContent', 'void', ['string'], [content]);
}
// File input handler
document.getElementById('fileInput').addEventListener('change', async (e) => {
const file = e.target.files[0];
const content = await file.text();
Module.ccall('loadPCBContent', 'void', ['string'], [content]);
});
Directory Structure
kicad-wasm/
├── wxwidgets/ # Your wxWidgets fork (with WASM patches)
├── kicad/ # KiCad source (submodule)
├── core/
│ ├── CMakeLists.txt # Existing: kimath, sexpr, etc.
│ ├── wasm-config/ # WASM config.h
│ └── gal-wasm/ # NEW: WebGL GAL
│ ├── webgl_gal.h
│ ├── webgl_gal.cpp
│ └── shaders/
│ ├── kicad_frag_es.glsl
│ └── kicad_vert_es.glsl
├── viewer/ # NEW: PCB Viewer app
│ ├── CMakeLists.txt
│ ├── main.cpp
│ ├── pcb_view_frame.cpp
│ ├── pcb_view_canvas.cpp
│ └── index.html
├── scripts/
│ ├── build-wxbase-wasm.sh # Existing
│ ├── build-wxuniversal-wasm.sh # NEW
│ ├── build-core-wasm.sh # Existing
│ └── build-viewer.sh # NEW
├── test/
│ ├── wx_minimal/ # NEW: Test wxApp
│ └── wx_glcanvas/ # NEW: Test WebGL
└── docs/
├── 00-OVERVIEW.md
├── 01-KNOWLEDGE-BASE.md
├── 02-PHASE2-CORE-EXTRACTION.md
└── 03-PHASE3-WXWIDGETS-WASM.md # This file
Success Criteria
Phase 3.1: wxUniversal Build
- wxWidgets fork created with WASM patches
./configurecompletes with--enable-universallibwx_baseu-3.2.a,libwx_coreu-3.2.a,libwx_glu-3.2.abuilt
Phase 3.2: Minimal wxApp
- wxFrame renders in browser
- Text/widgets display correctly
- Events (click, resize) work
Phase 3.3: wxGLCanvas
- WebGL context created
- Basic OpenGL rendering works
- Canvas clears and draws
Phase 3.4: KiCad GAL
- Shaders converted to GLSL ES
- WEBGL_GAL draws basic shapes
- Lines, circles, polygons render
Phase 3.5: PCB Viewer (First Milestone!)
- .kicad_pcb file loads
- Board outline visible
- Tracks visible
- Pads visible
- Pan/zoom works
Risk Mitigation
| Risk | Impact | Mitigation |
|---|---|---|
| wxWidgets 3.2 vs old wx patches | High | Patches are small (~10 commits), review carefully |
| GLSL shader incompatibility | Medium | Use WebGL 2.0 (GLSL ES 3.0), test incrementally |
| Performance with large boards | Medium | Accept slow initially, optimize later |
| Event handling differences | Medium | Test thoroughly, refer to wxWidgets-wasm solutions |