refactor: 💡 remove orphaned files

This commit is contained in:
Istvan Matejcsok 2026-06-05 13:18:24 +02:00
commit c148443b7b
13 changed files with 54 additions and 1038 deletions

View file

@ -1,36 +0,0 @@
#!/bin/bash
# Assert all 3 repos are on the given branch.
# Exit 0 if all match. Exit 1 with details if any mismatch (including detached HEAD).
# Usage: ./assert-on-branch.sh <branch-name>
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./repos.sh
source "$SCRIPT_DIR/repos.sh"
if [ $# -ne 1 ]; then
echo "Usage: $0 <branch-name>" >&2
exit 2
fi
WANT="$1"
mismatched=()
for repo in "${REPOS[@]}"; do
p=$(repo_path "$repo")
cur=$(git -C "$p" branch --show-current)
if [ "$cur" != "$WANT" ]; then
mismatched+=("$repo: on '${cur:-DETACHED-HEAD}' (want '$WANT')")
fi
done
if [ ${#mismatched[@]} -eq 0 ]; then
echo "All 3 repos on branch: $WANT"
exit 0
fi
echo "Branch mismatch:" >&2
for line in "${mismatched[@]}"; do
echo " $line" >&2
done
exit 1

View file

@ -237,8 +237,8 @@ emar rcs "${STUBS_BUILD}/libgit2_stub.a" "${STUBS_BUILD}/libgit2_stub.o"
emcc -c "${STUBS_DIR}/curl_stub.c" -o "${STUBS_BUILD}/curl_stub.o" emcc -c "${STUBS_DIR}/curl_stub.c" -o "${STUBS_BUILD}/curl_stub.o"
emar rcs "${STUBS_BUILD}/libcurl_stub.a" "${STUBS_BUILD}/curl_stub.o" emar rcs "${STUBS_BUILD}/libcurl_stub.a" "${STUBS_BUILD}/curl_stub.o"
# Note: GLU tesselator is now implemented in wasm/stubs/glu_wasm_impl.cpp # Note: the GLU tesselator is provided by kicad/libs/kimath/glu_tess/glu_tess_impl.cpp,
# It's compiled as part of the GAL library (requires KiCad headers) # compiled as part of the GAL library (requires KiCad headers).
# Compile NNG stub (IPC API requires NNG but sockets don't work in WASM) # Compile NNG stub (IPC API requires NNG but sockets don't work in WASM)
emcc -c -I"${STUBS_DIR}" "${STUBS_DIR}/nng_stub.c" -o "${STUBS_BUILD}/nng_stub.o" emcc -c -I"${STUBS_DIR}" "${STUBS_DIR}/nng_stub.c" -o "${STUBS_BUILD}/nng_stub.o"

View file

@ -1,26 +0,0 @@
# WASM Compatibility Layer for KiCad
# This directory contains WASM-specific implementations that replace
# platform-specific code in KiCad without modifying KiCad's source.
cmake_minimum_required(VERSION 3.22)
project(kicad_wasm_compat)
# Only build for Emscripten
if(NOT EMSCRIPTEN)
message(FATAL_ERROR "This project is only for Emscripten builds")
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Include directories
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/config
${CMAKE_CURRENT_SOURCE_DIR}/shims
)
# kiplatform WASM implementation
add_subdirectory(kiplatform)
# libcontext Asyncify fiber implementation
add_subdirectory(libcontext)

View file

@ -1,74 +1,83 @@
# WASM Compatibility Layer # WASM Compatibility Layer
This directory contains WASM-specific implementations that allow KiCad to run in a web browser **without modifying KiCad's source code**. This directory contains WASM-specific implementations that allow KiCad to run in a
web browser while keeping our KiCad fork **as close to upstream as possible**.
## Principle ## Principle
Instead of patching KiCad source files, we: Instead of patching KiCad source files, we:
1. Override include paths to use our headers first 1. Provide alternative implementations for platform-specific code (kiplatform)
2. Provide alternative implementations for platform-specific code 2. Stub out libraries/features that can't work in the browser (libgit2, curl, nng, scripting, 3D viewer, ...)
3. Link our libraries instead of system libraries 3. Expose KiCad to JavaScript via Embind bindings
4. Override host package detection during cross-compilation (cmake find-modules)
The KiCad-side hooks for this are small `if(EMSCRIPTEN)` branches in KiCad's own
CMakeLists that pull sources from this directory — see "How it's wired" below.
## Directory Structure ## Directory Structure
``` ```
wasm/ wasm/
├── CMakeLists.txt # Master CMake for compatibility layer
├── README.md # This file ├── README.md # This file
├── kiplatform/ # Platform abstraction implementations ├── kiplatform/ # Platform abstraction implementations (compiled into KiCad)
│ ├── CMakeLists.txt
│ ├── app.cpp # App lifecycle (paths, startup) │ ├── app.cpp # App lifecycle (paths, startup)
│ ├── drivers.cpp # GPU detection (returns "WebGL") │ ├── drivers.cpp # GPU detection (returns "WebGL")
│ ├── environment.cpp # Environment variables (localStorage) │ ├── environment.cpp # Environment variables
│ ├── io.cpp # File I/O (WASM virtual filesystem) │ ├── io.cpp # File I/O (WASM virtual filesystem)
│ ├── policy.cpp # Security policy (always permissive) │ ├── policy.cpp # Security policy (always permissive)
│ ├── secrets.cpp # Credential storage (localStorage) │ ├── secrets.cpp # Credential storage
│ ├── sysinfo.cpp # System information │ ├── sysinfo.cpp # System information
│ └── printing.cpp # Print support (browser print()) │ ├── printing.cpp # Print support (browser print())
├── libcontext/ # Coroutine/fiber implementation │ └── ui.cpp # UI helpers
│ ├── CMakeLists.txt ├── bindings/ # Embind bindings exposing each app to JavaScript
│ └── fcontext_wasm.cpp # Emscripten Asyncify fibers │ ├── pcbnew_embind.cpp
├── shims/ # Header overrides │ ├── eeschema_embind.cpp
│ └── *.h # Headers that redirect to our impls │ ├── pl_editor_embind.cpp
└── config/ # Build configuration │ └── calculator_embind.cpp
├── kicad_wasm_config.h # Version and feature config ├── stubs/ # Stub implementations + header shims for unavailable deps
└── setup.h # Platform setup │ ├── *.c / *.cpp # libgit2, curl, nng, scripting, 3D viewer, frame stubs, ...
│ ├── char_traits_uint16_workaround.h
│ └── GL/ nng/ ngspice/ # Header stubs found via include paths
└── cmake/ # CMake find-module overrides for cross-compilation
└── Find*.cmake / Use*.cmake
``` ```
## How It Works ## How it's wired
### Include Path Override ### kiplatform — compiled into KiCad
When building KiCad for WASM, we add our directories first in the include path: The `kiplatform/*.cpp` files are added directly to KiCad's kiplatform library by an
`if(EMSCRIPTEN)` branch in `kicad/libs/kiplatform/CMakeLists.txt`, which references
them as `${PROJECT_SOURCE_DIR}/../wasm/kiplatform/*.cpp`. There is no separate
`libkiplatform_wasm.a`.
```bash ### stubs — compiled by the build script and KiCad CMakeLists
-I$PROJECT_ROOT/wasm/shims
-I$PROJECT_ROOT/wasm/kiplatform
-I$PROJECT_ROOT/stubs/include
```
This means when KiCad includes `<kiplatform/app.h>`, it finds our version first. `scripts/kicad/build-kicad-target.sh` compiles the C stubs (`libgit2_stub.c`,
`curl_stub.c`, `nng_stub.c`) and force-includes `char_traits_uint16_workaround.h`.
App-specific `*_frame_stub.cpp` / `*_scripting_stub.cpp` are picked up per app, and
the remaining `*_stub.cpp` files are pulled in by `if(EMSCRIPTEN)` branches in the
KiCad fork's own CMakeLists. Header stubs under `GL/`, `nng/`, `ngspice/` are resolved
via include paths.
### Library Override ### bindings — per app
We build `libkiplatform_wasm.a` and link it instead of the native kiplatform: `build-kicad-target.sh` compiles `wasm/bindings/<app>_embind.cpp` for the app being
built (apps without an embind file get an empty placeholder object).
```bash ### cmake — module path
-L$BUILD_ROOT/wasm -lkiplatform_wasm
```
### CMake Integration `build-kicad-target.sh` passes `-DCMAKE_MODULE_PATH="${PROJECT_ROOT}/wasm/cmake"` so
the WASM find-module stubs override host package detection.
The main KiCad build is configured to find our implementations: > Coroutine/fiber support is **not** in this directory — it comes from the KiCad fork's
> `kicad/thirdparty/libcontext/libcontext.cpp` (`LIBCONTEXT_PLATFORM_wasm32`). The GLU
```cmake > tesselator comes from `kicad/libs/kimath/glu_tess/glu_tess_impl.cpp`.
-DCMAKE_MODULE_PATH="$PROJECT_ROOT/cmake"
-DKIPLATFORM_LIBRARY="$BUILD_ROOT/wasm/libkiplatform_wasm.a"
```
## Adding New Implementations ## Adding New Implementations
1. Create the implementation file in the appropriate directory 1. Create the implementation file in the appropriate directory (`kiplatform/`, `stubs/`, `bindings/`).
2. Add it to the CMakeLists.txt 2. Wire it in: a stub C file goes in `build-kicad-target.sh`; a kiplatform/app source
3. Ensure the header interface matches KiCad's expected interface goes in the relevant `if(EMSCRIPTEN)` branch of the KiCad-side CMakeLists.
4. Test with a minimal build before full integration 3. Ensure the header interface matches KiCad's expected interface.
4. Test with a minimal build before full integration.

View file

@ -1,49 +0,0 @@
# FindKiplatformWASM.cmake
# Find module for WASM implementation of kiplatform
#
# This module is used instead of the platform-specific kiplatform
# when building KiCad for WebAssembly.
#
# This module defines:
# KIPLATFORM_FOUND - System has kiplatform for WASM
# KIPLATFORM_INCLUDE_DIRS - Include directories
# KIPLATFORM_LIBRARIES - Libraries to link
# KIPLATFORM_SOURCES - Source files to compile
include(FindPackageHandleStandardArgs)
if(NOT EMSCRIPTEN)
message(FATAL_ERROR "FindKiplatformWASM is only for Emscripten builds")
endif()
# Get the directory containing this file
get_filename_component(_FIND_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(KIPLATFORM_WASM_DIR "${_FIND_DIR}/../kiplatform" ABSOLUTE)
# Define source files
set(KIPLATFORM_SOURCES
${KIPLATFORM_WASM_DIR}/app.cpp
${KIPLATFORM_WASM_DIR}/drivers.cpp
${KIPLATFORM_WASM_DIR}/environment.cpp
${KIPLATFORM_WASM_DIR}/io.cpp
${KIPLATFORM_WASM_DIR}/policy.cpp
${KIPLATFORM_WASM_DIR}/secrets.cpp
${KIPLATFORM_WASM_DIR}/sysinfo.cpp
${KIPLATFORM_WASM_DIR}/ui.cpp
)
# Include directory for platform headers
# We use KiCad's own headers, just provide our implementation
set(KIPLATFORM_INCLUDE_DIRS ${KIPLATFORM_WASM_DIR})
# No prebuilt library - sources are compiled directly into KiCad
set(KIPLATFORM_LIBRARIES "")
# Mark as found
set(KIPLATFORM_FOUND TRUE)
find_package_handle_standard_args(KiplatformWASM
REQUIRED_VARS KIPLATFORM_SOURCES KIPLATFORM_INCLUDE_DIRS
)
mark_as_advanced(KIPLATFORM_SOURCES KIPLATFORM_INCLUDE_DIRS KIPLATFORM_LIBRARIES)

View file

@ -1,41 +0,0 @@
# FindLibcontextWASM.cmake
# Find module for WASM implementation of libcontext using Asyncify
#
# This module replaces the platform-specific libcontext assembly
# when building KiCad for WebAssembly.
#
# This module defines:
# LIBCONTEXT_FOUND - System has libcontext for WASM
# LIBCONTEXT_INCLUDE_DIRS - Include directories
# LIBCONTEXT_LIBRARIES - Libraries to link (none for WASM)
# LIBCONTEXT_SOURCES - Source files to compile
include(FindPackageHandleStandardArgs)
if(NOT EMSCRIPTEN)
message(FATAL_ERROR "FindLibcontextWASM is only for Emscripten builds")
endif()
# Get the directory containing this file
get_filename_component(_FIND_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(LIBCONTEXT_WASM_DIR "${_FIND_DIR}/../libcontext" ABSOLUTE)
# Define source files
set(LIBCONTEXT_SOURCES
${LIBCONTEXT_WASM_DIR}/libcontext_wasm.cpp
)
# Include directories
set(LIBCONTEXT_INCLUDE_DIRS ${LIBCONTEXT_WASM_DIR})
# No prebuilt library
set(LIBCONTEXT_LIBRARIES "")
# Mark as found
set(LIBCONTEXT_FOUND TRUE)
find_package_handle_standard_args(LibcontextWASM
REQUIRED_VARS LIBCONTEXT_SOURCES LIBCONTEXT_INCLUDE_DIRS
)
mark_as_advanced(LIBCONTEXT_SOURCES LIBCONTEXT_INCLUDE_DIRS LIBCONTEXT_LIBRARIES)

View file

@ -1,50 +0,0 @@
# KiCad WASM Configuration
# This file configures KiCad to use WASM-specific implementations
if(NOT EMSCRIPTEN)
message(FATAL_ERROR "KiCadWASMConfig.cmake is only for Emscripten builds")
endif()
# Set the path to WASM compatibility layer
get_filename_component(KICAD_WASM_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
message(STATUS "KiCad WASM compatibility layer: ${KICAD_WASM_DIR}")
# Add include directories for WASM implementations
# These will be searched BEFORE KiCad's own includes, allowing us to
# provide our own implementations without modifying KiCad source
set(KICAD_WASM_INCLUDE_DIRS
${KICAD_WASM_DIR}/kiplatform
${KICAD_WASM_DIR}/libcontext
${KICAD_WASM_DIR}/config
${KICAD_WASM_DIR}/shims
)
# Define macro to indicate we're using WASM platform
add_definitions(-DKICAD_PLATFORM_WASM=1)
add_definitions(-D__WXUNIVERSAL__=1)
# Emscripten-specific compile options
add_compile_options(
-pthread
-sUSE_PTHREADS=1
)
# Emscripten-specific link options
add_link_options(
-pthread
-sUSE_PTHREADS=1
-sPTHREAD_POOL_SIZE=4
-sASYNCIFY=1
-sASYNCIFY_STACK_SIZE=65536
-sALLOW_MEMORY_GROWTH=1
-sINITIAL_MEMORY=256MB
-sMAXIMUM_MEMORY=4GB
-sMODULARIZE=1
-sEXPORT_ES6=1
-sENVIRONMENT=web,worker
)
# Export variables for use by parent CMakeLists
set(KICAD_WASM_INCLUDE_DIRS ${KICAD_WASM_INCLUDE_DIRS} PARENT_SCOPE)
set(KICAD_WASM_DIR ${KICAD_WASM_DIR} PARENT_SCOPE)

View file

@ -1,25 +0,0 @@
# WASM implementation of kiplatform
# Provides WASM-specific implementations of KiCad's platform abstraction layer
set(KIPLATFORM_WASM_SRCS
app.cpp
drivers.cpp
environment.cpp
io.cpp
policy.cpp
secrets.cpp
sysinfo.cpp
ui.cpp
)
add_library(kiplatform_wasm STATIC ${KIPLATFORM_WASM_SRCS})
target_include_directories(kiplatform_wasm PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../config
${PROJECT_ROOT}/kicad/libs/kiplatform/include
)
# Link with wxWidgets
target_link_libraries(kiplatform_wasm
# wxWidgets will be added during main build
)

View file

@ -1,23 +0,0 @@
# WASM implementation of libcontext using Emscripten Asyncify
# Provides fiber/coroutine support for KiCad's router
set(LIBCONTEXT_WASM_SRCS
libcontext_wasm.cpp
)
add_library(libcontext_wasm STATIC ${LIBCONTEXT_WASM_SRCS})
target_include_directories(libcontext_wasm PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
# Asyncify is required for fiber support
# These flags must also be set on the final executable
target_compile_options(libcontext_wasm PRIVATE
-pthread
)
target_link_options(libcontext_wasm INTERFACE
-sASYNCIFY=1
-sASYNCIFY_STACK_SIZE=65536
)

View file

@ -1,180 +0,0 @@
/*
* WASM implementation of libcontext using Emscripten Asyncify
*
* Emscripten's Asyncify allows us to implement fiber/coroutine semantics
* by saving and restoring the WebAssembly call stack.
*
* Implementation strategy:
* - Each fiber context stores an Asyncify data buffer
* - jump_fcontext suspends current execution and resumes target
* - make_fcontext creates a new context with a function entry point
*
* Note: This requires the WASM module to be compiled with:
* -sASYNCIFY=1
* -sASYNCIFY_STACK_SIZE=65536 (or larger if needed)
*
* For more information on Asyncify:
* https://emscripten.org/docs/porting/asyncify.html
*/
#include <cstdlib>
#include <cstring>
#include <cstdint>
#include <cstdio>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/fiber.h>
#endif
// Match the API from libcontext.h
#define LIBCONTEXT_CALL_CONVENTION
#ifdef __cplusplus
extern "C" {
#endif
namespace libcontext
{
// Context structure that stores fiber state
struct fiber_context {
emscripten_fiber_t fiber;
void (*entry_func)(intptr_t);
intptr_t entry_arg;
bool initialized;
bool running;
// Stack for asyncify data
char asyncify_stack[65536];
// C stack
char* c_stack;
size_t c_stack_size;
};
// Current running context
static fiber_context* g_current_context = nullptr;
static fiber_context g_main_context;
static bool g_main_initialized = false;
// Fiber entry wrapper
static void fiber_entry_wrapper(void* arg)
{
fiber_context* ctx = (fiber_context*)arg;
if (ctx && ctx->entry_func) {
ctx->entry_func(ctx->entry_arg);
}
// If entry function returns, we need to handle it
// In original libcontext, this would call _exit
// For WASM, we'll just return to main context
}
typedef void* fcontext_t;
void LIBCONTEXT_CALL_CONVENTION release_fcontext( fcontext_t ctx )
{
#ifdef __EMSCRIPTEN__
if (ctx) {
fiber_context* fctx = (fiber_context*)ctx;
if (fctx->c_stack) {
free(fctx->c_stack);
}
free(fctx);
}
#endif
}
intptr_t LIBCONTEXT_CALL_CONVENTION jump_fcontext( fcontext_t* ofc, fcontext_t nfc,
intptr_t vp, bool preserve_fpu )
{
#ifdef __EMSCRIPTEN__
// Initialize main context if needed
if (!g_main_initialized) {
memset(&g_main_context, 0, sizeof(g_main_context));
emscripten_fiber_init_from_current_context(
&g_main_context.fiber,
g_main_context.asyncify_stack,
sizeof(g_main_context.asyncify_stack)
);
g_main_context.initialized = true;
g_main_context.running = true;
g_current_context = &g_main_context;
g_main_initialized = true;
}
fiber_context* old_ctx = g_current_context;
fiber_context* new_ctx = (fiber_context*)nfc;
if (!new_ctx || !new_ctx->initialized) {
fprintf(stderr, "jump_fcontext: invalid target context\n");
return 0;
}
// Store the argument in the new context
new_ctx->entry_arg = vp;
// Save the old context pointer
if (ofc) {
*ofc = (fcontext_t)old_ctx;
}
// Switch contexts
g_current_context = new_ctx;
old_ctx->running = false;
new_ctx->running = true;
// Perform the fiber switch
emscripten_fiber_swap(&old_ctx->fiber, &new_ctx->fiber);
// When we return here, we've been switched back to
// Return the value passed to us
return g_current_context->entry_arg;
#else
return 0;
#endif
}
fcontext_t LIBCONTEXT_CALL_CONVENTION make_fcontext( void* sp, size_t size,
void (* fn)( intptr_t ) )
{
#ifdef __EMSCRIPTEN__
// Allocate context structure
fiber_context* ctx = (fiber_context*)malloc(sizeof(fiber_context));
if (!ctx) {
return nullptr;
}
memset(ctx, 0, sizeof(fiber_context));
ctx->entry_func = fn;
ctx->entry_arg = 0;
ctx->c_stack = (char*)sp - size; // sp points to top of stack
ctx->c_stack_size = size;
// Initialize the fiber
// Note: sp is the TOP of the stack (highest address)
// The stack grows downward, so we need to pass the bottom
void* stack_bottom = (char*)sp - size;
emscripten_fiber_init(
&ctx->fiber,
fiber_entry_wrapper,
ctx, // User data for entry function
stack_bottom, // C stack (bottom)
size, // C stack size
ctx->asyncify_stack, // Asyncify stack
sizeof(ctx->asyncify_stack) // Asyncify stack size
);
ctx->initialized = true;
ctx->running = false;
return (fcontext_t)ctx;
#else
return nullptr;
#endif
}
}; // namespace libcontext
#ifdef __cplusplus
};
#endif

View file

@ -1,52 +0,0 @@
/*
* WASM implementation of libcontext using Emscripten Asyncify
*
* This header provides WASM-specific definitions for the libcontext API.
* The implementation uses Emscripten's Asyncify feature to implement
* fiber-like context switching in WebAssembly.
*
* Asyncify works by:
* 1. Instrumenting the WASM code to save/restore the call stack
* 2. Allowing execution to suspend at any point
* 3. Resuming execution from where it was suspended
*
* This is used by KiCad's router for coroutine-based routing.
*/
#ifndef LIBCONTEXT_WASM_H
#define LIBCONTEXT_WASM_H
#include <cstddef>
#include <cstdint>
// Define WASM platform
#define LIBCONTEXT_PLATFORM_wasm
#define LIBCONTEXT_CALL_CONVENTION
#ifdef __cplusplus
namespace libcontext {
#endif
typedef void* fcontext_t;
#ifdef __cplusplus
extern "C" {
#endif
void LIBCONTEXT_CALL_CONVENTION release_fcontext( fcontext_t ctx );
intptr_t LIBCONTEXT_CALL_CONVENTION jump_fcontext( fcontext_t* ofc, fcontext_t nfc,
intptr_t vp, bool preserve_fpu = true );
fcontext_t LIBCONTEXT_CALL_CONVENTION make_fcontext( void* sp, size_t size,
void (* fn)( intptr_t ) );
#ifdef __cplusplus
} // extern "C"
#endif
#ifdef __cplusplus
} // namespace libcontext
#endif
#endif // LIBCONTEXT_WASM_H

View file

@ -1,178 +0,0 @@
/**
* gl_immediate_shim.js
*
* Custom OpenGL immediate mode shims for KiCad WASM port.
* Fixes Emscripten LEGACY_GL_EMULATION issues:
* 1. Color-per-vertex requirement - injects color before each vertex automatically
* 2. Missing double-precision functions (glVertex2d, glVertex3d, glColor3d, glColor4d)
*
* Usage: emcc ... --js-library=lib/gl_immediate_shim.js
*/
addToLibrary({
// ==================================================================
// GLImmediateShim - State tracking and function wrapping
// ==================================================================
$GLImmediateShim__deps: ['$GLImmediate', 'glBegin', 'glEnd', 'glVertex2f', 'glVertex3f', 'glColor3f', 'glColor4f'],
$GLImmediateShim__postset: 'GLImmediateShim.init();',
$GLImmediateShim: {
// Current color state (persistent across vertices)
currentColor: null,
// Track if we're inside glBegin/glEnd block
inBeginEnd: false,
// Track if color was called since the last vertex
// This prevents double-injection when code already calls color per vertex
colorCalledSinceLastVertex: false,
// Original functions we're wrapping
origFns: {},
// Initialization flag
initialized: false,
init: function() {
if (GLImmediateShim.initialized) return;
if (typeof GLImmediate === 'undefined') {
// GLImmediate not ready yet, will be called again
console.log('[GLImmediateShim] Waiting for GLImmediate...');
return;
}
console.log('[GLImmediateShim] Initializing OpenGL immediate mode shims');
// Initialize current color to white (OpenGL default)
GLImmediateShim.currentColor = new Float32Array([1.0, 1.0, 1.0, 1.0]);
// Store original functions
GLImmediateShim.origFns = {
glBegin: _glBegin,
glEnd: _glEnd,
glVertex2f: _glVertex2f,
glVertex3f: _glVertex3f,
glColor3f: _glColor3f,
glColor4f: _glColor4f,
};
// Install shims
_glBegin = GLImmediateShim.shimBegin;
_glEnd = GLImmediateShim.shimEnd;
_glVertex2f = GLImmediateShim.shimVertex2f;
_glVertex3f = GLImmediateShim.shimVertex3f;
_glColor3f = GLImmediateShim.shimColor3f;
_glColor4f = GLImmediateShim.shimColor4f;
GLImmediateShim.initialized = true;
console.log('[GLImmediateShim] Initialized successfully');
},
// ==================================================================
// Shim implementations
// ==================================================================
shimBegin: function(mode) {
GLImmediateShim.inBeginEnd = true;
GLImmediateShim.colorCalledSinceLastVertex = false;
GLImmediateShim.origFns.glBegin(mode);
},
shimEnd: function() {
GLImmediateShim.inBeginEnd = false;
GLImmediateShim.origFns.glEnd();
},
shimColor3f: function(r, g, b) {
var c = GLImmediateShim.currentColor;
c[0] = r;
c[1] = g;
c[2] = b;
c[3] = 1.0;
if (GLImmediateShim.inBeginEnd) {
// Mark that color was explicitly called for this vertex
GLImmediateShim.colorCalledSinceLastVertex = true;
}
GLImmediateShim.origFns.glColor3f(r, g, b);
},
shimColor4f: function(r, g, b, a) {
var c = GLImmediateShim.currentColor;
c[0] = r;
c[1] = g;
c[2] = b;
c[3] = a;
if (GLImmediateShim.inBeginEnd) {
// Mark that color was explicitly called for this vertex
GLImmediateShim.colorCalledSinceLastVertex = true;
}
GLImmediateShim.origFns.glColor4f(r, g, b, a);
},
// Inject current color before each vertex (only if not already called)
injectColor: function() {
if (!GLImmediateShim.inBeginEnd) return;
// Only inject color if it wasn't already called for this vertex
if (!GLImmediateShim.colorCalledSinceLastVertex) {
var c = GLImmediateShim.currentColor;
GLImmediateShim.origFns.glColor4f(c[0], c[1], c[2], c[3]);
}
// Reset the flag for the next vertex
GLImmediateShim.colorCalledSinceLastVertex = false;
},
shimVertex2f: function(x, y) {
GLImmediateShim.injectColor();
GLImmediateShim.origFns.glVertex2f(x, y);
},
shimVertex3f: function(x, y, z) {
GLImmediateShim.injectColor();
GLImmediateShim.origFns.glVertex3f(x, y, z);
},
},
// ==================================================================
// Double-precision vertex functions (missing in Emscripten)
// ==================================================================
glVertex2d__deps: ['glVertex2f'],
glVertex2d: function(x, y) {
_glVertex2f(x, y);
},
glVertex3d__deps: ['glVertex3f'],
glVertex3d: function(x, y, z) {
_glVertex3f(x, y, z);
},
glVertex4d__deps: ['glVertex4f'],
glVertex4d: function(x, y, z, w) {
_glVertex4f(x, y, z, w);
},
// ==================================================================
// Double-precision color functions
// ==================================================================
glColor3d__deps: ['glColor3f'],
glColor3d: function(r, g, b) {
_glColor3f(r, g, b);
},
glColor4d__deps: ['glColor4f'],
glColor4d: function(r, g, b, a) {
_glColor4f(r, g, b, a);
},
});
// Ensure GLImmediateShim is included in the build
if (typeof extraLibraryFuncs !== 'undefined') {
extraLibraryFuncs.push('$GLImmediateShim');
}

View file

@ -1,333 +0,0 @@
/**
* GLU Tesselator implementation for WebAssembly builds
*
* Uses KiCad's earcut-based POLYGON_TRIANGULATION instead of GLU library.
* This enables OpenGL GAL to work in WASM with proper polygon triangulation.
*/
#include <vector>
#include <array>
#include <cstring>
#include <geometry/shape_line_chain.h>
#include <geometry/shape_poly_set.h>
#include <geometry/polygon_triangulation.h>
// GL types
typedef double GLdouble;
typedef float GLfloat;
typedef unsigned int GLenum;
typedef unsigned char GLboolean;
typedef void GLvoid;
typedef void (*_GLUfuncptr)(void);
// GL constants
#ifndef GL_TRUE
#define GL_TRUE 1
#endif
#ifndef GL_FALSE
#define GL_FALSE 0
#endif
// GLU constants
#define GLU_TESS_BEGIN 100100
#define GLU_TESS_VERTEX 100101
#define GLU_TESS_END 100102
#define GLU_TESS_ERROR 100103
#define GLU_TESS_EDGE_FLAG 100104
#define GLU_TESS_COMBINE 100105
#define GLU_TESS_BEGIN_DATA 100106
#define GLU_TESS_VERTEX_DATA 100107
#define GLU_TESS_END_DATA 100108
#define GLU_TESS_ERROR_DATA 100109
#define GLU_TESS_EDGE_FLAG_DATA 100110
#define GLU_TESS_COMBINE_DATA 100111
#define GLU_TESS_WINDING_RULE 100140
#define GLU_TESS_WINDING_ODD 100130
#define GLU_TESS_WINDING_NONZERO 100131
#define GLU_TESS_WINDING_POSITIVE 100132
#define GLU_TESS_WINDING_NEGATIVE 100133
#define GLU_TESS_WINDING_ABS_GEQ_TWO 100134
// Vertex data stored during tesselation
struct TessVertex
{
std::array<GLdouble, 3> coords;
void* userData; // The data pointer passed to gluTessVertex
};
struct GLUtesselator
{
// Callbacks
void (*vertexCallback)(void* vertex) = nullptr;
void (*vertexDataCallback)(void* vertex, void* userData) = nullptr;
void (*combineCallback)(GLdouble coords[3], void* vertex_data[4],
GLfloat weight[4], void** dataOut) = nullptr;
void (*combineDataCallback)(GLdouble coords[3], void* vertex_data[4],
GLfloat weight[4], void** dataOut, void* userData) = nullptr;
void (*edgeFlagCallback)(GLboolean flag) = nullptr;
void (*edgeFlagDataCallback)(GLboolean flag, void* userData) = nullptr;
void (*errorCallback)(GLenum error) = nullptr;
void (*errorDataCallback)(GLenum error, void* userData) = nullptr;
void (*beginCallback)(GLenum type) = nullptr;
void (*beginDataCallback)(GLenum type, void* userData) = nullptr;
void (*endCallback)() = nullptr;
void (*endDataCallback)(void* userData) = nullptr;
// Contour data - each contour is a list of vertices
std::vector<std::vector<TessVertex>> contours;
std::vector<TessVertex>* currentContour = nullptr;
// User data passed to gluTessBeginPolygon
void* polygonUserData = nullptr;
// Properties
GLenum windingRule = GLU_TESS_WINDING_POSITIVE;
};
extern "C" {
GLUtesselator* gluNewTess()
{
return new GLUtesselator();
}
void gluDeleteTess(GLUtesselator* tess)
{
delete tess;
}
void gluTessCallback(GLUtesselator* tess, GLenum which, _GLUfuncptr fn)
{
if (!tess) return;
switch (which) {
case GLU_TESS_VERTEX:
tess->vertexCallback = (void(*)(void*))fn;
break;
case GLU_TESS_VERTEX_DATA:
tess->vertexDataCallback = (void(*)(void*, void*))fn;
break;
case GLU_TESS_COMBINE:
tess->combineCallback = (void(*)(GLdouble[3], void*[4], GLfloat[4], void**))fn;
break;
case GLU_TESS_COMBINE_DATA:
tess->combineDataCallback = (void(*)(GLdouble[3], void*[4], GLfloat[4], void**, void*))fn;
break;
case GLU_TESS_EDGE_FLAG:
tess->edgeFlagCallback = (void(*)(GLboolean))fn;
break;
case GLU_TESS_EDGE_FLAG_DATA:
tess->edgeFlagDataCallback = (void(*)(GLboolean, void*))fn;
break;
case GLU_TESS_ERROR:
tess->errorCallback = (void(*)(GLenum))fn;
break;
case GLU_TESS_ERROR_DATA:
tess->errorDataCallback = (void(*)(GLenum, void*))fn;
break;
case GLU_TESS_BEGIN:
tess->beginCallback = (void(*)(GLenum))fn;
break;
case GLU_TESS_BEGIN_DATA:
tess->beginDataCallback = (void(*)(GLenum, void*))fn;
break;
case GLU_TESS_END:
tess->endCallback = (void(*)())fn;
break;
case GLU_TESS_END_DATA:
tess->endDataCallback = (void(*)(void*))fn;
break;
}
}
void gluTessProperty(GLUtesselator* tess, GLenum which, GLdouble value)
{
if (!tess) return;
if (which == GLU_TESS_WINDING_RULE)
tess->windingRule = static_cast<GLenum>(value);
}
void gluGetTessProperty(GLUtesselator* tess, GLenum which, GLdouble* value)
{
if (!tess || !value) return;
if (which == GLU_TESS_WINDING_RULE)
*value = static_cast<GLdouble>(tess->windingRule);
}
void gluTessNormal(GLUtesselator* tess, GLdouble x, GLdouble y, GLdouble z)
{
// Ignored - earcut works in 2D (XY plane)
(void)tess; (void)x; (void)y; (void)z;
}
void gluTessBeginPolygon(GLUtesselator* tess, void* userData)
{
if (!tess) return;
tess->contours.clear();
tess->currentContour = nullptr;
tess->polygonUserData = userData;
}
void gluTessBeginContour(GLUtesselator* tess)
{
if (!tess) return;
tess->contours.emplace_back();
tess->currentContour = &tess->contours.back();
}
void gluTessVertex(GLUtesselator* tess, GLdouble coords[3], void* data)
{
if (!tess || !tess->currentContour) return;
TessVertex v;
v.coords = {coords[0], coords[1], coords[2]};
v.userData = data;
tess->currentContour->push_back(v);
}
void gluTessEndContour(GLUtesselator* tess)
{
if (!tess) return;
tess->currentContour = nullptr;
}
void gluTessEndPolygon(GLUtesselator* tess)
{
if (!tess) return;
// Need at least one contour with 3+ vertices
if (tess->contours.empty())
return;
const auto& mainContour = tess->contours[0];
if (mainContour.size() < 3)
return;
// Convert to SHAPE_LINE_CHAIN for earcut
// Note: earcut works with integer coordinates, so we scale appropriately
// KiCad uses nanometers internally, so coordinates are already integers
SHAPE_LINE_CHAIN chain;
for (const auto& v : mainContour) {
chain.Append(VECTOR2I(static_cast<int>(v.coords[0]),
static_cast<int>(v.coords[1])));
}
chain.SetClosed(true);
// Triangulate using KiCad's earcut implementation
SHAPE_POLY_SET::TRIANGULATED_POLYGON result(-1);
POLYGON_TRIANGULATION triangulator(result);
if (!triangulator.TesselatePolygon(chain, nullptr)) {
// Tesselation failed - call error callback if registered
if (tess->errorDataCallback)
tess->errorDataCallback(100151, tess->polygonUserData); // GLU_TESS_ERROR1
else if (tess->errorCallback)
tess->errorCallback(100151);
return;
}
// Call edge flag callback to indicate we're producing triangles
// (edge flag callback forces GLU to output only triangles, which earcut always does)
if (tess->edgeFlagDataCallback)
tess->edgeFlagDataCallback(GL_TRUE, tess->polygonUserData);
else if (tess->edgeFlagCallback)
tess->edgeFlagCallback(GL_TRUE);
// Output triangles via vertex callback
// The result contains triangle indices that map back to input vertices
size_t triCount = result.GetTriangleCount();
for (size_t i = 0; i < triCount; i++) {
VECTOR2I a, b, c;
result.GetTriangle(i, a, b, c);
// Find the original vertex indices by matching coordinates
// (POLYGON_TRIANGULATION preserves vertex order for the first N vertices)
auto findVertex = [&](const VECTOR2I& pt) -> size_t {
for (size_t j = 0; j < mainContour.size(); j++) {
if (static_cast<int>(mainContour[j].coords[0]) == pt.x &&
static_cast<int>(mainContour[j].coords[1]) == pt.y) {
return j;
}
}
return 0; // fallback
};
size_t idxA = findVertex(a);
size_t idxB = findVertex(b);
size_t idxC = findVertex(c);
// Call vertex callback for each triangle vertex
// The callback receives the user data pointer that was passed to gluTessVertex
if (tess->vertexDataCallback) {
tess->vertexDataCallback(mainContour[idxA].userData, tess->polygonUserData);
tess->vertexDataCallback(mainContour[idxB].userData, tess->polygonUserData);
tess->vertexDataCallback(mainContour[idxC].userData, tess->polygonUserData);
} else if (tess->vertexCallback) {
tess->vertexCallback(mainContour[idxA].userData);
tess->vertexCallback(mainContour[idxB].userData);
tess->vertexCallback(mainContour[idxC].userData);
}
}
}
const unsigned char* gluErrorString(GLenum error)
{
static const unsigned char errStr[] = "GLU tesselator error";
(void)error;
return errStr;
}
//=============================================================================
// Quadric stubs - not implemented (used for 3D primitives, not critical)
//=============================================================================
typedef struct GLUquadric GLUquadric;
GLUquadric* gluNewQuadric()
{
return nullptr;
}
void gluDeleteQuadric(GLUquadric* q)
{
(void)q;
}
void gluQuadricDrawStyle(GLUquadric* q, int s)
{
(void)q; (void)s;
}
void gluQuadricNormals(GLUquadric* q, int n)
{
(void)q; (void)n;
}
void gluCylinder(GLUquadric* q, double b, double t, double h, int sl, int st)
{
(void)q; (void)b; (void)t; (void)h; (void)sl; (void)st;
}
void gluDisk(GLUquadric* q, double i, double o, int sl, int lp)
{
(void)q; (void)i; (void)o; (void)sl; (void)lp;
}
void gluSphere(GLUquadric* q, double r, int sl, int st)
{
(void)q; (void)r; (void)sl; (void)st;
}
void gluPerspective(double fovy, double aspect, double zNear, double zFar)
{
(void)fovy; (void)aspect; (void)zNear; (void)zFar;
}
} // extern "C"