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"
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
# It's compiled as part of the GAL library (requires KiCad headers)
# Note: the GLU tesselator is provided by kicad/libs/kimath/glu_tess/glu_tess_impl.cpp,
# compiled as part of the GAL library (requires KiCad headers).
# 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"

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
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
Instead of patching KiCad source files, we:
1. Override include paths to use our headers first
2. Provide alternative implementations for platform-specific code
3. Link our libraries instead of system libraries
1. Provide alternative implementations for platform-specific code (kiplatform)
2. Stub out libraries/features that can't work in the browser (libgit2, curl, nng, scripting, 3D viewer, ...)
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
```
wasm/
├── CMakeLists.txt # Master CMake for compatibility layer
├── README.md # This file
├── kiplatform/ # Platform abstraction implementations
│ ├── CMakeLists.txt
├── kiplatform/ # Platform abstraction implementations (compiled into KiCad)
│ ├── app.cpp # App lifecycle (paths, startup)
│ ├── drivers.cpp # GPU detection (returns "WebGL")
│ ├── environment.cpp # Environment variables (localStorage)
│ ├── environment.cpp # Environment variables
│ ├── io.cpp # File I/O (WASM virtual filesystem)
│ ├── policy.cpp # Security policy (always permissive)
│ ├── secrets.cpp # Credential storage (localStorage)
│ ├── secrets.cpp # Credential storage
│ ├── sysinfo.cpp # System information
│ └── printing.cpp # Print support (browser print())
├── libcontext/ # Coroutine/fiber implementation
│ ├── CMakeLists.txt
│ └── fcontext_wasm.cpp # Emscripten Asyncify fibers
├── shims/ # Header overrides
│ └── *.h # Headers that redirect to our impls
└── config/ # Build configuration
├── kicad_wasm_config.h # Version and feature config
└── setup.h # Platform setup
│ ├── printing.cpp # Print support (browser print())
│ └── ui.cpp # UI helpers
├── bindings/ # Embind bindings exposing each app to JavaScript
│ ├── pcbnew_embind.cpp
│ ├── eeschema_embind.cpp
│ ├── pl_editor_embind.cpp
│ └── calculator_embind.cpp
├── stubs/ # Stub implementations + header shims for unavailable deps
│ ├── *.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
-I$PROJECT_ROOT/wasm/shims
-I$PROJECT_ROOT/wasm/kiplatform
-I$PROJECT_ROOT/stubs/include
```
### stubs — compiled by the build script and KiCad CMakeLists
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
-L$BUILD_ROOT/wasm -lkiplatform_wasm
```
### cmake — module path
### 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:
```cmake
-DCMAKE_MODULE_PATH="$PROJECT_ROOT/cmake"
-DKIPLATFORM_LIBRARY="$BUILD_ROOT/wasm/libkiplatform_wasm.a"
```
> 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
> tesselator comes from `kicad/libs/kimath/glu_tess/glu_tess_impl.cpp`.
## Adding New Implementations
1. Create the implementation file in the appropriate directory
2. Add it to the CMakeLists.txt
3. Ensure the header interface matches KiCad's expected interface
4. Test with a minimal build before full integration
1. Create the implementation file in the appropriate directory (`kiplatform/`, `stubs/`, `bindings/`).
2. Wire it in: a stub C file goes in `build-kicad-target.sh`; a kiplatform/app source
goes in the relevant `if(EMSCRIPTEN)` branch of the KiCad-side CMakeLists.
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"