Skip to content

MillerTechnologyPeru/swift-embedded-nds

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

143 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

swift-nds

Nintendo DS homebrew written in Embedded Swift, ported from the official libnds examples. Each example is a faithful translation of its C/C++ original that compiles with an Embedded Swift toolchain and runs on real hardware / emulators (melonDS, DeSmuME).

Examples

Directory Ported from Demonstrates
hello_world hello_world Console output, VBlank IRQ handler, touch read
ansi_console Graphics/Printing/ansi_console ANSI cursor escape sequences
print_both_screens Graphics/Printing/print_both_screens A text console on each screen (main + sub)
console_windows Graphics/Printing/console_windows Multiple windowed consoles over one map
stopwatch time/stopwatch Hardware timer elapsed/pause, formatted output
timercallback time/timercallback Timer IRQ callback (C function pointer), PSG sound
realtimeclock time/RealTimeClock RTC via time()/gmtime, digital + 3D analog watch face, day-of-week calc
pxi pxi Dual-CPU: Swift ARM9 ↔ custom C ARM7 over a PXI channel (firmware JEDEC ID, touch pressure)
keyboard_stdin input/keyboard/keyboard_stdin On-screen keyboard, callback field, iscanf stdin
keyboard_async input/keyboard/keyboard_async Polled keyboard via keyboardUpdate
sprites_simple Graphics/Sprites/simple OAM sprites on both engines, VRAM banks, palettes
touch_test input/Touch_Pad/touch_test grit-converted sprite, OAM, touch min/max tracking
custom_font Graphics/Printing/custom_font grit bitmap font loaded into a console
bg_256color Graphics/Backgrounds/256_color_bmp grit 8bpp bitmap background
bg_16bit Graphics/Backgrounds/16bit_color_bmp grit 16bpp bitmap, LZ77 decompress into VRAM
double_buffer Graphics/Backgrounds/Double_Buffer Double-buffered bitmap BG via map-base flip
tilemap Graphics/grit/256colorTilemap grit tiles+map+palette, BGCTRL/char-base
effects_windows Graphics/Effects/windows Hardware display window over a BG
sprite_rotate Graphics/Sprites/sprite_rotate oamRotateScale, size-doubling
bitmap_sprites Graphics/Sprites/bitmap_sprites Sprites in bitmap/256/16-colour formats
sprite_ext_palettes Graphics/Sprites/sprite_extended_palettes Extended sprite palettes
animate_simple Graphics/Sprites/animate_simple Two sprite-animation strategies (copy vs. preload)
allocation_test Graphics/Sprites/allocation_test Sprite-gfx allocator stress test, oamAllocateGfx/Free
ext_palettes_bg Graphics/Ext_Palettes/backgrounds Extended BG palettes, 8bpp tiled BGs
rotscale_text Graphics/Printing/rotscale_text Rotating/scaling text on an ExRotation BG
ortho Graphics/3D/Ortho Orthographic vs. perspective projection toggle
both_screens_3d Graphics/3D/3D_Both_Screens 3D on both screens via display capture (cube + pyramid)
nehe1 Graphics/3D/nehe/lesson01 Minimal NeHe 3D setup
nehe2 Graphics/3D/nehe/lesson02 First polygons (triangle + quad)
nehe3 Graphics/3D/nehe/lesson03 Per-vertex colour
nehe4 Graphics/3D/nehe/lesson04 Rotation (glRotatef)
nehe5 Graphics/3D/nehe/lesson05 Solid 3D pyramid + colour cube
nehe6 Graphics/3D/nehe/lesson06 PCX texture (bin2s blob + runtime loadPCX) on a cube
nehe7 Graphics/3D/nehe/lesson07 Lit textured cube, normals, toggleable lighting
nehe8 Graphics/3D/nehe/lesson08 Alpha-blended translucent cube
nehe9 Graphics/3D/nehe/lesson09 Blended colour-cycling star field
nehe10 Graphics/3D/nehe/lesson10 Textured world walkthrough (runtime-parsed World.txt), hardware fog + shadow-polygon cube
nehe10b Graphics/3D/nehe/lesson10b All-fixed-point variant: v16/t16 geometry, integer LUT camera (sinLerp/glRotatef32i)
nehe11 Graphics/3D/nehe/lesson11 Waving-flag texture grid mesh
mixed_text_3d Graphics/3D/Mixed_Text_3D 3D + text console sharing one screen, %f printf
display_list Graphics/3D/Display_List Hand-built display list (packed FIFO commands)
simple_tri Graphics/3D/Simple_Tri 3D engine, GL pipeline, fixed-point + float GL calls
simple_quad Graphics/3D/Simple_Quad 3D GL_QUAD, D-pad rotation
display_list_2 Graphics/3D/Display_List_2 bin2o display list (teapot), hardware lighting
toon_shading Graphics/3D/Toon_Shading Display-list blob, toon table, stylus rotate
textured_quad Graphics/3D/Textured_Quad Texture blob, glTexImage2D, texcoords/normals
textured_cube Graphics/3D/Textured_Cube Texture blob, vertex/uv tables, display-capture motion blur
env_mapping Graphics/3D/Env_Mapping TEXGEN_NORMAL reflection mapping, texture matrix
picking Graphics/3D/Picking 3D picking via gluPickMatrix + position test
boxtest Graphics/3D/BoxTest Hardware frustum BoxTest/BoxTestf, cull modes, vertex/poly RAM + cpuTiming readout
capture Graphics/capture/ScreenShot Rotating bitmap/256/16-colour sprites, display capture → 24-bit BMP saved via libfat
rotation Graphics/Backgrounds/rotation Raw bitmap+palette blobs, rotation/scale background
all_in_one Graphics/Backgrounds/all_in_one ~40-demo menu: every BG type/size, HW+SW scrolling, mosaic/rotate/scale, ext palettes, multilayer — pre-assembled grit .s assets
paletted_cube Graphics/3D/Paletted_Cube All DS texture formats (grit .tga + compressed blobs), palette swap
gl2d_primitives Graphics/gl2d/primitives Easy GL2D: boxes/triangles/lines/pixels, sinLerp
gl2d_dual_screen Graphics/gl2d/dual_screen GL2D mirrored to both screens via display capture
gl2d_fonts Graphics/gl2d/fonts GL2D sprite-set bitmap fonts (grit atlas + uvcoord tables)
gl2d_scrolling Graphics/gl2d/scrolling GL2D tile-set scrolling engine + animated sprite, camera
gl2d_sprites Graphics/gl2d/sprites GL2D sprite rotate/scale/stretch/flip, palette-swap tint
gl2d_2dplus3d Graphics/gl2d/2Dplus3D 3D textured mesh (ported vertex buffer) + GL2D 2D sprites, fixed-point atan2
wifi_autoconnect dswifi/autoconnect dswifi: connect via firmware WFC settings, print DHCP IP/gateway/DNS
wifi_httpget dswifi/httpget dswifi: DNS resolve + TCP socket + live HTTP/1.1 GET, prints response
wifi_ap_search dswifi/ap_search dswifi: AP scan/select UI, signal-strength icon (VBlank IRQ), WEP/WPA key entry via on-screen keyboard, connect + DNS
exception_test debugging/exceptionTest Default exception handler, raw memory access

Building

Requirements:

  • devkitPro with devkitARM, libnds, calico, and ndstool (dkp-pacman -S nds-dev).
  • An Embedded-Swift-capable Swift toolchain (a recent DEVELOPMENT-SNAPSHOT).
export DEVKITPRO=/opt/devkitpro
export DEVKITARM=$DEVKITPRO/devkitARM

cd hello_world && make          # or any other example dir
# -> hello_world.nds

make SWIFTC=/path/to/swiftc      # if swiftc isn't the Embedded toolchain on PATH

The NDS Swift package

The repo is a Swift package (Package.swift) that vends an idiomatic Swift overlay over libnds, so an example can import NDS and write

import NDS

IRQ.vblank.set { frame += 1 }
Console.demoInit()
Console.print("Hello DS dev'rs\n")

while System.mainLoop {
    System.waitForVBlank()
    Keys.scan()
    if Keys.down.contains(.start) { break }
}

instead of the raw C (videoSetMode(MODE_0_2D.rawValue), irqSet, nds_puts, …).

  • Sources/CNDS is a systemLibrary target: the C interop (module map, umbrella header, and the shim) that exposes <nds.h> + gl2d + dswifi as import CNDS. This is the single source of truth the Makefiles also use.
  • Sources/NDS is the overlay — one file per libnds subsystem (Video, Background, Sprite, Console, VideoGL, GL2D, Sound, Input, Timer, Interrupt, DMA, and the rest). Each C prefix becomes a caseless enum namespace, C flag masks become OptionSets (Key, IRQ), and the macro-only APIs the importer drops (RGB15, inttov16, the pointer/register macros) get real Swift types. Every wrapper is @inline(__always), so it compiles down to the exact underlying libnds call — zero overhead.

Sources/NDS/Exports.swift does @_exported import CNDS, so import NDS also re-exports the full raw libnds API: nothing is hidden, and the two styles mix freely. Every example is ported to the overlay — each source/main.swift does import NDS and uses the idiomatic wrappers (Video, Background, Sprite/OAM, GL, GL2D, Keys, Console, Wifi, …), dropping to raw CNDS/_Volatile only for the handful of genuinely low-level bits (custom hardware registers in capture / all_in_one, the glColorTableEXT palette calls, raw sockets in wifi_httpget).

Note: SwiftPM cannot cross-compile Embedded Swift to the DS's bare-metal armv5te, so swift build does not produce a .nds. The package is a source library; the Makefiles below compile the NDS target into a standalone Embedded Swift module and link it into each ROM.

How it works

Everything shared lives in common/ and Sources/; each example is just a source/main.swift plus a three-line Makefile that sets TARGET and includes common/common.mk. Examples with assets set GRAPHICS := gfx (a directory of .png/.bmp/.tga + .grit pairs) and/or DATA := data (a directory of .bin blobs); both can be set at once. EXTRA_HEADERS := … lists hand-written headers (e.g. texture-packer uvcoord tables) to expose to Swift with the same nds_asset_*() accessor treatment. ASM_ASSETS := asmdata points at a directory of ready-made grit/Cearn .s data files (assembled and linked as-is); pair it with EXTRA_HEADERS for the matching extern const …[] declarations so the symbols reach Swift via the same nds_asset_*() accessors (used by all_in_one). ARM7_SRC := arm7 points at a directory of ARM7 C sources: instead of packaging calico's prebuilt ARM7, common.mk compiles them with ds7.specs + -lcalico_ds7 into a custom ARM7 binary and feeds that to ndstool -7. Only the ARM9 is Embedded Swift; the ARM7 stays C (low-level system code). Used by the dual-CPU pxi example.

Assets (grit + bin2o)

For examples that set GRAPHICS or DATA, common.mk runs grit on each image and bin2s on each .bin blob, compiles the generated data, and gathers the generated headers into a single bridging header handed to Swift — so the symbols (ballTiles, fontPal, teapot_bin, …) are visible alongside import CNDS. grit images become tile/palette tables; blobs become raw byte arrays for display lists, textures, bitmaps, etc.

A C global array imports into Swift as a tuple (a copy), not a pointer. Two ways to use the data:

  • Copying it yourself (e.g. into sprite VRAM): withUnsafeBytes(of: ballTiles) { … } and loop — the temporary is valid for the closure body, which is all a synchronous copy needs.
  • Handing a pointer to libnds (e.g. ConsoleFont.gfx for consoleSetFont): use the generated nds_asset_<symbol>() accessor, which returns the address of the real linked symbol. Passing a withUnsafeBytes temporary here is a bug — the pointer dangles once the closure returns and libnds renders garbage (a black screen, in the custom_font case).

The build pipeline (common.mk)

  1. NDS overlay module → object + .swiftmodule. The Sources/NDS files are compiled once as a standalone Embedded Swift module (-module-name NDS -parse-as-library -emit-module), producing NDS.o and NDS.swiftmodule in the example's build/.
  2. Embedded Swift main.swift → object. Targets armv5te-none-none-eabi, matching the DS's ARM946E-S, and imports the NDS module built in step 1. Release toolchains only ship an armv4t Embedded stdlib slice, so this needs a patched toolchain with an armv5te slice (set SWIFTC in common.mk or on the command line). Embedded clang is pointed at newlib's headers, libnds, calico, and the Sources/CNDS module map.
  3. C shim → object with devkitARM (Sources/CNDS/shim.c).
  4. Link main.swift.o + NDS.o + shim.o against the modern calico-based libnds (-specs=…/ds9.specs, -lnds9 -lcalico_ds9).
  5. Package the .nds with ndstool (calico's prebuilt ARM7 + default icon).

Bridges the gaps between Embedded Swift and libnds:

  • Variadic stdio. Swift imports C variadic functions but cannot call them, so nds_puts / nds_printf_1i / nds_printf_2i forward into iprintf, nds_printf_1f / nds_printf_2f into printf (for %f), and nds_scanf_str into iscanf — all formatting stays inside libnds.
  • Macro-only APIs. Preprocessor macros the Swift importer can't surface are re-exported as real functions: nds_timer_freq_1024 (TIMER_FREQ_1024), nds_bus_clock (BUS_CLOCK), the SPRITE_PALETTE* / SPRITE_GFX* / BG_PALETTE pointer macros, the GFX_TEX_COORD / GFX_BUSY / GFX_POLYGON_RAM_USAGE 3D registers, and the display-capture motion-blur registers (nds_motion_blur_*).
  • Runtime support devkitARM doesn't provide for this target:
    • posix_memalign (Swift's allocator wants it; newlib only has memalign).
    • __atomic_* outline helpers — armv5te has no atomic instructions, so LLVM emits libcalls; implemented with a short interrupt lock (safe on the single ARM9 core).
    • arc4random_buf — referenced by Swift's runtime; the NDS has no entropy source, so a small non-cryptographic xorshift fills the buffer.

Swift ↔ libnds interop notes

  • import CNDS (Sources/CNDS/module.modulemap) exposes <nds.h> + the shim; import NDS adds the idiomatic overlay on top (and re-exports CNDS).
  • A non-capturing top-level Swift function bridges automatically to the C function-pointer types used by irqSet / timerStart, and can be assigned to a callback field on a struct too (e.g. kbd.pointee.OnKeyPressed = …).
  • libnds C enums (VideoMode, …) import as Swift types — pass .rawValue where a function wants the underlying u32 (e.g. videoSetMode(MODE_0_2D.rawValue)).
  • touchRead returns Bool; some libnds calls return distinct enum types, so match Swift's stricter typing rather than the C "everything is an int" style.
  • A few constants use the function-like BIT(n) macro (e.g. KEY_TOUCH), which the importer drops — spell the bit out in Swift (1 << 14).
  • 3D / GL: the float GL entry points (glRotateX, gluPerspective, gluLookAt, …) are real inline functions and import fine — Embedded Swift's soft-float calls them directly. POLY_ALPHA(n) is also an inline function, but inttov16 / floattof32 are function-like macros, so they're reimplemented as tiny Swift helpers. Functions typed to take a GL enum (glMatrixMode, glBegin) accept the enum value directly; where one wants a raw int/u32 (glEnable, an OR with POLY_CULL_NONE), pass .rawValue.
  • In a file named main.swift, top-level code is the entry point — no @main.
  • swiWaitForVBlank is a macro alias; call the underlying threadWaitForVBlank().
  • KEY_* are #defined as plain integers, so keysDown() & KEY_START works directly (no .rawValue). DutyCycle_50 is a macro alias for the importable enum SoundDuty_50.

Remaining

The ported set above covers every major interop pattern: console, timers, IRQ callbacks, PSG sound, input/keyboard, sprites/OAM, 3D/GL, grit graphics, and bin2o binary blobs (display lists, textures, all DS texture formats). What's left in the upstream example tree still needs its own toolchain or runtime work, not just more of the same translation:

  • Audio (audio/maxmod/*) — needs the mmutil soundbank pipeline.
  • Filesystem (filesystem/*) — libfat + DLDI / NitroFS. (The libfat write path is already exercised by capture; the dedicated filesystem/* demos additionally need NitroFS / directory traversal.)
  • Wi-Fi (dswifi/*) — done: all three examples (wifi_autoconnect, wifi_httpget, wifi_ap_search) are ported on the dswifi stack.
  • Dual-CPU — the pxi example is done (Swift ARM9 + a custom C ARM7, via ARM7_SRC); the combined templates would reuse the same path.
  • Large GL2D demos (Graphics/gl2d/*) — big, asset-heavy (but buildable on the existing grit + bin2o + GL foundations).

The common/ scaffolding (grit + bin2o pipelines) applies to the asset-heavy graphics ones unchanged; the others are each a distinct piece of plumbing.

About

Embedded Swift for Nintendo DS

Resources

Stars

26 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors