emoemu 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +77 -0
- package/.node-version +1 -0
- package/CLAUDE.md +435 -0
- package/README.md +404 -0
- package/TODO.md +655 -0
- package/dist/index.cjs +25108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +25085 -0
- package/docs/building-libretro-cores-arm-mac.md +237 -0
- package/docs/config-file-format.md +488 -0
- package/docs/cores-trd.md +425 -0
- package/docs/headless-hardware-rendering-trd.md +676 -0
- package/docs/libretro-cores-trd.md +997 -0
- package/docs/mupen64-software-rendering-trd.md +751 -0
- package/docs/n64-support-trd.md +306 -0
- package/docs/native-rendering-trd.md +540 -0
- package/docs/native-ui-rendering-trd.md +1195 -0
- package/docs/netplay-trd.md +665 -0
- package/docs/retroarch-netplay-docs.md +277 -0
- package/docs/save-state-format.md +666 -0
- package/eslint.config.js +111 -0
- package/icon/icon.png +0 -0
- package/icon/icon.pxd +0 -0
- package/package.json +63 -0
- package/pnpm-workspace.yaml +10 -0
- package/src/Emulator/consts.ts +14 -0
- package/src/Emulator/index.ts +2496 -0
- package/src/Emulator/saveState/index.ts +155 -0
- package/src/Emulator/screenshot/index.ts +160 -0
- package/src/Emulator/terminalDimensions/index.ts +79 -0
- package/src/Emulator/types.ts +83 -0
- package/src/cli/commands/consts.ts +10 -0
- package/src/cli/commands/index.ts +462 -0
- package/src/cli/parseArgs/consts.ts +17 -0
- package/src/cli/parseArgs/index.ts +457 -0
- package/src/cli/parseArgs/types.ts +61 -0
- package/src/cli/runEmulator/index.ts +406 -0
- package/src/cli/runEmulator/types.ts +7 -0
- package/src/consts.ts +19 -0
- package/src/core/button/consts.ts +35 -0
- package/src/core/button/index.ts +123 -0
- package/src/core/core.ts +300 -0
- package/src/core/index.ts +19 -0
- package/src/cores/libretro/api/index.ts +334 -0
- package/src/cores/libretro/api/types.ts +148 -0
- package/src/cores/libretro/callbacks/consts.ts +41 -0
- package/src/cores/libretro/callbacks/index.ts +456 -0
- package/src/cores/libretro/consts.ts +45 -0
- package/src/cores/libretro/coreOptions/consts.ts +36 -0
- package/src/cores/libretro/coreOptions/index.ts +222 -0
- package/src/cores/libretro/environment/consts.ts +118 -0
- package/src/cores/libretro/environment/index.ts +1095 -0
- package/src/cores/libretro/index.ts +937 -0
- package/src/cores/libretro/loader/index.ts +496 -0
- package/src/cores/libretro/pixelFormat/consts.ts +43 -0
- package/src/cores/libretro/pixelFormat/index.ts +397 -0
- package/src/cores/libretro/types.ts +339 -0
- package/src/frontend/AudioManager/index.ts +420 -0
- package/src/frontend/SettingsManager/index.ts +250 -0
- package/src/frontend/config/index.ts +608 -0
- package/src/frontend/config/tests.ts +354 -0
- package/src/frontend/config/types.ts +36 -0
- package/src/frontend/consts.ts +114 -0
- package/src/frontend/coreBuilder/index.ts +644 -0
- package/src/frontend/coreBuilder/types.ts +15 -0
- package/src/frontend/coreDownloader/index.ts +620 -0
- package/src/frontend/coreDownloader/types.ts +17 -0
- package/src/frontend/corePreferences/index.ts +69 -0
- package/src/frontend/coreRegistry/index.ts +276 -0
- package/src/frontend/directoryCache/index.ts +75 -0
- package/src/frontend/index.ts +79 -0
- package/src/frontend/notifications/consts.ts +14 -0
- package/src/frontend/notifications/index.ts +250 -0
- package/src/frontend/playlist/consts.ts +168 -0
- package/src/frontend/playlist/index.ts +899 -0
- package/src/frontend/playlist/labelFormatter/consts.ts +15 -0
- package/src/frontend/playlist/labelFormatter/index.ts +155 -0
- package/src/frontend/playlist/labelFormatter/tests.ts +153 -0
- package/src/frontend/playlist/reader/index.ts +559 -0
- package/src/frontend/playlist/sync/index.ts +511 -0
- package/src/frontend/playlist/systemLookup/index.ts +233 -0
- package/src/frontend/playlist/utils/index.ts +50 -0
- package/src/frontend/romScanner/consts.ts +348 -0
- package/src/frontend/romScanner/index.ts +1957 -0
- package/src/frontend/saveServices/consts.ts +2 -0
- package/src/frontend/saveServices/index.ts +313 -0
- package/src/frontend/serviceProvider/index.ts +108 -0
- package/src/frontend/serviceProvider/types.ts +13 -0
- package/src/index.ts +428 -0
- package/src/input/Controller/consts.ts +50 -0
- package/src/input/Controller/index.ts +81 -0
- package/src/input/GamepadManager/consts.ts +22 -0
- package/src/input/GamepadManager/index.ts +418 -0
- package/src/input/InputManager/consts.ts +86 -0
- package/src/input/InputManager/index.ts +593 -0
- package/src/input/InputMapper/consts.ts +33 -0
- package/src/input/InputMapper/index.ts +436 -0
- package/src/input/consts.ts +410 -0
- package/src/input/gamepadProfiles/index.ts +1100 -0
- package/src/input/index.ts +38 -0
- package/src/input/inputUtils/index.ts +31 -0
- package/src/netplay/FrameBuffer/consts.ts +2 -0
- package/src/netplay/FrameBuffer/index.ts +364 -0
- package/src/netplay/FrameBuffer/tests.ts +286 -0
- package/src/netplay/InputBuffer/consts.ts +7 -0
- package/src/netplay/InputBuffer/index.ts +347 -0
- package/src/netplay/InputBuffer/tests.ts +166 -0
- package/src/netplay/NetplayClient/index.ts +976 -0
- package/src/netplay/NetplayConnection/index.ts +536 -0
- package/src/netplay/NetplayDiscovery/consts.ts +41 -0
- package/src/netplay/NetplayDiscovery/index.ts +525 -0
- package/src/netplay/NetplayServer/index.ts +1407 -0
- package/src/netplay/SyncManager/index.ts +984 -0
- package/src/netplay/SyncManager/tests.ts +419 -0
- package/src/netplay/consts.ts +371 -0
- package/src/netplay/crc32/consts.ts +14 -0
- package/src/netplay/crc32/index.ts +97 -0
- package/src/netplay/crc32/tests.ts +40 -0
- package/src/netplay/index.ts +41 -0
- package/src/netplay/netplayLogger/consts.ts +30 -0
- package/src/netplay/netplayLogger/index.ts +345 -0
- package/src/netplay/protocol/consts.ts +86 -0
- package/src/netplay/protocol/index.ts +1280 -0
- package/src/netplay/protocol/tests.ts +606 -0
- package/src/netplay/protocol/types.ts +20 -0
- package/src/netplay/types.ts +395 -0
- package/src/rendering/KittyRenderer/index.ts +616 -0
- package/src/rendering/NativeRenderer/index.ts +279 -0
- package/src/rendering/NativeRenderer/tests.ts +133 -0
- package/src/rendering/TerminalRenderer/index.ts +770 -0
- package/src/rendering/consts.ts +401 -0
- package/src/rendering/fonts/CozetteVector.ttf +0 -0
- package/src/rendering/index.ts +26 -0
- package/src/rendering/nativeUi/NativeWindowManager/index.ts +158 -0
- package/src/rendering/nativeUi/NativeWindowManager/tests.ts +81 -0
- package/src/rendering/nativeUi/consts.ts +6 -0
- package/src/rendering/nativeUi/index.ts +20 -0
- package/src/rendering/postProcessing/consts.ts +38 -0
- package/src/rendering/postProcessing/index.ts +923 -0
- package/src/rendering/shared/ansi/consts.ts +12 -0
- package/src/rendering/shared/ansi/index.ts +104 -0
- package/src/rendering/shared/consts.ts +2 -0
- package/src/rendering/shared/fitToTerminal/index.ts +67 -0
- package/src/ui/AddRomsPrompt/consts.ts +13 -0
- package/src/ui/AddRomsPrompt/index.tsx +781 -0
- package/src/ui/App/consts.ts +2 -0
- package/src/ui/App/index.tsx +456 -0
- package/src/ui/AppCapabilities/index.tsx +67 -0
- package/src/ui/ConfigContext/index.tsx +56 -0
- package/src/ui/CoreManager/consts.ts +11 -0
- package/src/ui/CoreManager/index.tsx +779 -0
- package/src/ui/CoreSelector/consts.ts +2 -0
- package/src/ui/CoreSelector/index.tsx +251 -0
- package/src/ui/DialogContainer/index.tsx +42 -0
- package/src/ui/DialogOptionsList/index.tsx +61 -0
- package/src/ui/DuplicateCrcPrompt/consts.ts +5 -0
- package/src/ui/DuplicateCrcPrompt/index.tsx +146 -0
- package/src/ui/GamepadContext/consts.ts +15 -0
- package/src/ui/GamepadContext/index.tsx +295 -0
- package/src/ui/NativeDialog/index.tsx +120 -0
- package/src/ui/NetplayDisconnectedDialog/index.tsx +93 -0
- package/src/ui/NetplayPauseMenu/consts.ts +2 -0
- package/src/ui/NetplayPauseMenu/index.tsx +97 -0
- package/src/ui/RomBrowser/NetplayPanel/consts.ts +24 -0
- package/src/ui/RomBrowser/NetplayPanel/index.tsx +520 -0
- package/src/ui/RomBrowser/SettingsPanel/index.tsx +478 -0
- package/src/ui/RomBrowser/consts.ts +61 -0
- package/src/ui/RomBrowser/index.tsx +1164 -0
- package/src/ui/RomBrowser/settingsConfig/index.ts +320 -0
- package/src/ui/RomBrowser/types.ts +67 -0
- package/src/ui/SaveStateDialog/consts.ts +2 -0
- package/src/ui/SaveStateDialog/index.tsx +225 -0
- package/src/ui/WarningDialog/index.tsx +113 -0
- package/src/ui/consts.ts +27 -0
- package/src/ui/hooks/useClearTerminal/consts.ts +2 -0
- package/src/ui/hooks/useClearTerminal/index.ts +37 -0
- package/src/ui/hooks/useDialogNavigation/index.ts +99 -0
- package/src/ui/hooks/useGamepad/consts.ts +21 -0
- package/src/ui/hooks/useGamepad/index.ts +194 -0
- package/src/ui/index.ts +27 -0
- package/src/utils/buffer/consts.ts +17 -0
- package/src/utils/buffer/index.ts +129 -0
- package/src/utils/color/consts.ts +58 -0
- package/src/utils/color/index.ts +183 -0
- package/src/utils/compression/consts.ts +50 -0
- package/src/utils/compression/index.ts +101 -0
- package/src/utils/consts.ts +2 -0
- package/src/utils/crc32/consts.ts +22 -0
- package/src/utils/crc32/index.ts +83 -0
- package/src/utils/ensureDirectory/index.ts +10 -0
- package/src/utils/format/consts.ts +8 -0
- package/src/utils/format/index.ts +53 -0
- package/src/utils/getErrorMessage/index.ts +10 -0
- package/src/utils/index.ts +113 -0
- package/src/utils/ini/index.ts +200 -0
- package/src/utils/kitty/consts.ts +13 -0
- package/src/utils/kitty/index.ts +181 -0
- package/src/utils/logger/consts.ts +35 -0
- package/src/utils/logger/index.ts +217 -0
- package/src/utils/paths/consts.ts +18 -0
- package/src/utils/paths/index.ts +151 -0
- package/src/utils/png/consts.ts +34 -0
- package/src/utils/png/index.ts +131 -0
- package/src/utils/readJsonFile/index.ts +16 -0
- package/src/utils/rotateLogFile/index.ts +44 -0
- package/src/utils/safeClose/index.ts +10 -0
- package/src/utils/terminal/consts.ts +8 -0
- package/src/utils/terminal/index.ts +102 -0
- package/src/utils/thumbnailRenderer/consts.ts +2 -0
- package/src/utils/thumbnailRenderer/index.ts +147 -0
- package/src/utils/typedError/index.ts +26 -0
- package/tsconfig.json +31 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
# Native Rendering Support - Technical Requirements Document
|
|
2
|
+
|
|
3
|
+
This document describes the requirements and implementation approach for adding a native-window rendering backend in emoemu, providing a native window alternative to terminal-based rendering.
|
|
4
|
+
|
|
5
|
+
> **Implementation note:** This design shipped via the [`ink-native`](https://www.npmjs.com/package/ink-native) package (bundled `fenster` window backend + embedded Cozette bitmap font) instead of the custom SDL2/koffi bindings originally proposed below - **zero system dependencies** are required (no SDL2 install). The CLI flag is `--native` and the config value is `video_driver = "native"`. `ink-native` owns the single native window (created via `createStreams()`); the game renderer writes each post-processed frame directly into ink-native's shared `Uint32Array` framebuffer (`0xAARRGGBB`, via `packColor`) and calls `renderer.present()`, while the Ink UI renders into the same window. The app hands the window off between UI and game with `window.pause()` / `window.resume()`. See [native-ui-rendering-trd.md](./native-ui-rendering-trd.md) for the UI side of this design.
|
|
6
|
+
>
|
|
7
|
+
> **Known limitations of the shipped implementation:** no runtime `setTitle` (ink-native exposes none - see "Status Bar / OSD" below, which is superseded), no runtime scale-factor change (`menu_scale_factor` maps to ink-native's `scaleFactor`, applied only at window creation), and no programmatic window resize (the game software-scales/letterboxes into the fixed window instead). The low-level `SDL_*` pseudocode throughout this document (SDL2 API surface, koffi bindings, `SdlRenderer` class) describes the pre-`ink-native` exploration and was not implemented as written - `ink-native` handles window/framebuffer management internally.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
### Goals
|
|
12
|
+
|
|
13
|
+
1. **Native Window Rendering**: Render emulator output to a native window instead of the terminal
|
|
14
|
+
2. **Performance**: Eliminate terminal I/O bottleneck that limits frame rates
|
|
15
|
+
3. **Cross-Platform**: Support macOS, Linux, and Windows via the bundled `fenster` backend (via `ink-native`), with zero system dependencies
|
|
16
|
+
4. **Feature Parity**: Support all post-processing effects available in terminal renderers
|
|
17
|
+
5. **Seamless Integration**: Add "native" as a new video driver alongside kitty, terminal, ascii, emoji
|
|
18
|
+
|
|
19
|
+
### Non-Goals
|
|
20
|
+
|
|
21
|
+
1. Hardware-accelerated rendering (GPU shaders) - native rendering uses CPU/software rendering
|
|
22
|
+
2. Multiple window support
|
|
23
|
+
3. Fullscreen exclusive mode (windowed fullscreen is acceptable)
|
|
24
|
+
4. Custom window chrome or UI elements beyond the game display
|
|
25
|
+
|
|
26
|
+
### Background: Terminal I/O Bottleneck
|
|
27
|
+
|
|
28
|
+
Terminal rendering has inherent limitations:
|
|
29
|
+
|
|
30
|
+
| Issue | Impact |
|
|
31
|
+
|-------|--------|
|
|
32
|
+
| **I/O bandwidth** | Writing pixels as ANSI escape codes or PNG data is slower than direct framebuffer access |
|
|
33
|
+
| **Terminal parsing** | Terminal emulators must parse escape sequences, adding latency |
|
|
34
|
+
| **Redraw overhead** | Full-screen updates require significant data transfer |
|
|
35
|
+
| **Protocol limitations** | Even Kitty graphics protocol has per-frame overhead |
|
|
36
|
+
|
|
37
|
+
A native window bypasses all of these by rendering directly to a window's framebuffer.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Architecture
|
|
42
|
+
|
|
43
|
+
### Renderer Interface
|
|
44
|
+
|
|
45
|
+
All renderers implement the `Renderer` interface defined in `src/Emulator/types.ts`:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
interface Renderer {
|
|
49
|
+
render(frameBuffer: Uint8Array): string;
|
|
50
|
+
renderRgb15?(frameBuffer: Uint16Array): string;
|
|
51
|
+
renderRgb24?(frameBuffer: Uint8Array): string;
|
|
52
|
+
clearScreen(): string;
|
|
53
|
+
hideCursor(): string;
|
|
54
|
+
showCursor(): string;
|
|
55
|
+
getStatusRow(): number;
|
|
56
|
+
moveCursorToRow(row: number): string;
|
|
57
|
+
setDimensions?(width: number, height: number): void;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Challenge**: The interface returns strings (ANSI escape sequences) written to stdout. SDL rendering doesn't output strings.
|
|
62
|
+
|
|
63
|
+
### Proposed Solution: Dual-Mode Architecture
|
|
64
|
+
|
|
65
|
+
Modify the rendering pipeline to support both terminal-based and window-based renderers:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
interface Renderer {
|
|
69
|
+
// Existing string-based methods (terminal renderers)
|
|
70
|
+
render(frameBuffer: Uint8Array): string;
|
|
71
|
+
renderRgb15?(frameBuffer: Uint16Array): string;
|
|
72
|
+
renderRgb24?(frameBuffer: Uint8Array): string;
|
|
73
|
+
|
|
74
|
+
// New: Direct rendering (window-based renderers)
|
|
75
|
+
renderDirect?(frameBuffer: Uint8Array): void;
|
|
76
|
+
renderRgb15Direct?(frameBuffer: Uint16Array): void;
|
|
77
|
+
renderRgb24Direct?(frameBuffer: Uint8Array): void;
|
|
78
|
+
|
|
79
|
+
// Renderer type indicator
|
|
80
|
+
readonly isWindowBased?: boolean;
|
|
81
|
+
|
|
82
|
+
// Terminal-specific (no-op for window renderers)
|
|
83
|
+
clearScreen(): string;
|
|
84
|
+
hideCursor(): string;
|
|
85
|
+
showCursor(): string;
|
|
86
|
+
getStatusRow(): number;
|
|
87
|
+
moveCursorToRow(row: number): string;
|
|
88
|
+
|
|
89
|
+
// Window-specific (optional)
|
|
90
|
+
setDimensions?(width: number, height: number): void;
|
|
91
|
+
destroy?(): void; // Cleanup SDL resources
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Emulator Integration
|
|
96
|
+
|
|
97
|
+
In `emulator.ts`, modify the render dispatch:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// In the render method
|
|
101
|
+
if (this.renderer.isWindowBased) {
|
|
102
|
+
// Direct rendering - no stdout write
|
|
103
|
+
if (colorFormat === 'rgb24' && this.renderer.renderRgb24Direct) {
|
|
104
|
+
this.renderer.renderRgb24Direct(framebuffer);
|
|
105
|
+
} else if (colorFormat === 'rgb15' && this.renderer.renderRgb15Direct) {
|
|
106
|
+
this.renderer.renderRgb15Direct(framebuffer);
|
|
107
|
+
} else if (this.renderer.renderDirect) {
|
|
108
|
+
this.renderer.renderDirect(framebuffer);
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
// Existing terminal-based rendering
|
|
112
|
+
const output = this.renderer.render(framebuffer);
|
|
113
|
+
process.stdout.write(output);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## SDL Integration
|
|
120
|
+
|
|
121
|
+
> **Historical design exploration:** This section (Library Selection, SDL2 API Surface, `SdlRenderer` class) predates adopting `ink-native` and was not implemented as written - see the Implementation note at the top of this document. `ink-native` bundles its own `fenster` window backend; emoemu does not write any SDL2/koffi bindings.
|
|
122
|
+
|
|
123
|
+
### Library Selection
|
|
124
|
+
|
|
125
|
+
**Recommended**: `@aspect-energy/node-sdl2` or `@aspect-energy/sdl-bindings`
|
|
126
|
+
|
|
127
|
+
| Library | Pros | Cons |
|
|
128
|
+
|---------|------|------|
|
|
129
|
+
| `node-sdl2` | Active maintenance, TypeScript support | May require native compilation |
|
|
130
|
+
| `sdl2-ffi` | Pure FFI via ffi-napi | Older, less maintained |
|
|
131
|
+
| Custom koffi bindings | Full control, consistent with libretro approach | More implementation work |
|
|
132
|
+
|
|
133
|
+
**Recommendation**: Use koffi (already used for libretro) to create SDL2 bindings. This maintains consistency and avoids adding new native dependencies.
|
|
134
|
+
|
|
135
|
+
### SDL2 API Surface
|
|
136
|
+
|
|
137
|
+
Minimal SDL2 functions needed:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// Initialization
|
|
141
|
+
SDL_Init(SDL_INIT_VIDEO): number;
|
|
142
|
+
SDL_Quit(): void;
|
|
143
|
+
|
|
144
|
+
// Window
|
|
145
|
+
SDL_CreateWindow(title: string, x: number, y: number, w: number, h: number, flags: number): Pointer;
|
|
146
|
+
SDL_DestroyWindow(window: Pointer): void;
|
|
147
|
+
SDL_SetWindowTitle(window: Pointer, title: string): void;
|
|
148
|
+
|
|
149
|
+
// Renderer (SDL's 2D renderer, not to be confused with our Renderer interface)
|
|
150
|
+
SDL_CreateRenderer(window: Pointer, index: number, flags: number): Pointer;
|
|
151
|
+
SDL_DestroyRenderer(renderer: Pointer): void;
|
|
152
|
+
SDL_RenderClear(renderer: Pointer): number;
|
|
153
|
+
SDL_RenderPresent(renderer: Pointer): void;
|
|
154
|
+
|
|
155
|
+
// Texture
|
|
156
|
+
SDL_CreateTexture(renderer: Pointer, format: number, access: number, w: number, h: number): Pointer;
|
|
157
|
+
SDL_DestroyTexture(texture: Pointer): void;
|
|
158
|
+
SDL_UpdateTexture(texture: Pointer, rect: Pointer | null, pixels: Buffer, pitch: number): number;
|
|
159
|
+
SDL_RenderCopy(renderer: Pointer, texture: Pointer, src: Pointer | null, dst: Pointer | null): number;
|
|
160
|
+
|
|
161
|
+
// Events (for window close, resize)
|
|
162
|
+
SDL_PollEvent(event: Pointer): number;
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### SdlRenderer Class
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// src/rendering/sdl-renderer.ts
|
|
169
|
+
|
|
170
|
+
export interface SdlRendererOptions {
|
|
171
|
+
width: number;
|
|
172
|
+
height: number;
|
|
173
|
+
scale?: number;
|
|
174
|
+
title?: string;
|
|
175
|
+
// Post-processing options
|
|
176
|
+
gamma?: number;
|
|
177
|
+
scanlines?: number;
|
|
178
|
+
saturation?: number;
|
|
179
|
+
brightness?: number;
|
|
180
|
+
contrast?: number;
|
|
181
|
+
vignette?: number;
|
|
182
|
+
bloom?: number;
|
|
183
|
+
ntsc?: number;
|
|
184
|
+
curvature?: number;
|
|
185
|
+
chromaticAberration?: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export class SdlRenderer implements Renderer {
|
|
189
|
+
readonly isWindowBased = true;
|
|
190
|
+
|
|
191
|
+
private window: Pointer;
|
|
192
|
+
private sdlRenderer: Pointer;
|
|
193
|
+
private texture: Pointer;
|
|
194
|
+
private frameBuffer: Uint8Array;
|
|
195
|
+
|
|
196
|
+
constructor(options: SdlRendererOptions) {
|
|
197
|
+
// Initialize SDL
|
|
198
|
+
// Create window at scaled dimensions
|
|
199
|
+
// Create renderer and texture
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
renderDirect(frameBuffer: Uint8Array): void {
|
|
203
|
+
// Apply post-processing effects to frameBuffer
|
|
204
|
+
// Convert indexed color to RGB24
|
|
205
|
+
// Update texture with pixel data
|
|
206
|
+
// Render texture to window
|
|
207
|
+
// Present
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
renderRgb15Direct(frameBuffer: Uint16Array): void {
|
|
211
|
+
// Convert RGB15 to RGB24
|
|
212
|
+
// Apply effects
|
|
213
|
+
// Update and present
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
renderRgb24Direct(frameBuffer: Uint8Array): void {
|
|
217
|
+
// Apply effects directly
|
|
218
|
+
// Update and present
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Terminal methods return empty strings (no-op)
|
|
222
|
+
render(_frameBuffer: Uint8Array): string { return ''; }
|
|
223
|
+
clearScreen(): string { return ''; }
|
|
224
|
+
hideCursor(): string { return ''; }
|
|
225
|
+
showCursor(): string { return ''; }
|
|
226
|
+
getStatusRow(): number { return 0; }
|
|
227
|
+
moveCursorToRow(_row: number): string { return ''; }
|
|
228
|
+
|
|
229
|
+
destroy(): void {
|
|
230
|
+
// Clean up SDL resources
|
|
231
|
+
SDL_DestroyTexture(this.texture);
|
|
232
|
+
SDL_DestroyRenderer(this.sdlRenderer);
|
|
233
|
+
SDL_DestroyWindow(this.window);
|
|
234
|
+
SDL_Quit();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Type System Changes (Shipped)
|
|
242
|
+
|
|
243
|
+
### VideoDriver Type
|
|
244
|
+
|
|
245
|
+
**File**: `src/frontend/config/types.ts`
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// Before
|
|
249
|
+
export type VideoDriver = "kitty" | "terminal" | "ascii" | "emoji";
|
|
250
|
+
|
|
251
|
+
// After (shipped)
|
|
252
|
+
export type VideoDriver = "native" | "kitty" | "terminal" | "ascii" | "emoji";
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### RenderMode Type
|
|
256
|
+
|
|
257
|
+
**File**: `src/frontend/SettingsManager/index.ts`
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
// Before
|
|
261
|
+
export type RenderMode = 'kitty' | 'terminal' | 'ascii' | 'emoji';
|
|
262
|
+
|
|
263
|
+
// After (shipped)
|
|
264
|
+
export type RenderMode = 'native' | 'kitty' | 'terminal' | 'ascii' | 'emoji';
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Type Guards
|
|
268
|
+
|
|
269
|
+
**File**: `src/frontend/config/types.ts`
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
export const VIDEO_DRIVERS: readonly VideoDriver[] = ['native', 'kitty', 'terminal', 'ascii', 'emoji'];
|
|
273
|
+
|
|
274
|
+
export const isVideoDriver = (value: unknown): value is VideoDriver => {
|
|
275
|
+
return isString(value) && VIDEO_DRIVERS.includes(value as VideoDriver);
|
|
276
|
+
};
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## CLI Integration
|
|
282
|
+
|
|
283
|
+
### New Flag (Shipped as `--native`)
|
|
284
|
+
|
|
285
|
+
**File**: `src/index.ts`
|
|
286
|
+
|
|
287
|
+
Added `--native` flag alongside existing video driver flags:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
.option('--native', 'Use native window rendering (best performance)')
|
|
291
|
+
.option('--kitty', 'Use Kitty graphics protocol')
|
|
292
|
+
.option('--terminal', 'Use terminal character rendering')
|
|
293
|
+
.option('--ascii', 'Use colored ASCII character rendering')
|
|
294
|
+
.option('--emoji', 'Use emoji character rendering')
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Driver Selection Function
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
const videoDriverToRenderMode = (driver: VideoDriver | null): RenderMode | undefined => {
|
|
301
|
+
switch (driver) {
|
|
302
|
+
case null: return undefined;
|
|
303
|
+
case "native": return "native";
|
|
304
|
+
case "kitty": return "kitty";
|
|
305
|
+
case "terminal": return "terminal";
|
|
306
|
+
case "ascii": return "ascii";
|
|
307
|
+
case "emoji": return "emoji";
|
|
308
|
+
default: return "kitty";
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Configuration
|
|
316
|
+
|
|
317
|
+
### Config File
|
|
318
|
+
|
|
319
|
+
**File**: `~/.config/emoemu/emoemu.cfg`
|
|
320
|
+
|
|
321
|
+
```ini
|
|
322
|
+
video_driver = "native"
|
|
323
|
+
menu_scale_factor = 3
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Config Keys (Shipped)
|
|
327
|
+
|
|
328
|
+
Unlike the `sdl_scale` / `sdl_vsync` / `sdl_window_width` / `sdl_window_height` keys originally proposed below, no new config keys shipped. Window scale reuses the existing `menu_scale_factor` key (passed to `ink-native` as `scaleFactor`, applied only at window creation); `ink-native` has no VSync toggle or window-size override:
|
|
329
|
+
|
|
330
|
+
| Key | Type | Default | Description |
|
|
331
|
+
|-----|------|---------|-------------|
|
|
332
|
+
| `menu_scale_factor` | number | auto | Window scale, passed to `ink-native` as `scaleFactor`. Applies only at window creation. |
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Window Management
|
|
337
|
+
|
|
338
|
+
> **Historical design (SDL):** This section describes the original SDL-based window management design. The shipped implementation uses `ink-native` (fenster), which owns the window and has no VSync toggle (fixed `frameRate`, default 60), no runtime `setTitle`, and no programmatic window resize.
|
|
339
|
+
|
|
340
|
+
### Initialization
|
|
341
|
+
|
|
342
|
+
1. Calculate window size: `sourceWidth * scale` x `sourceHeight * scale`
|
|
343
|
+
2. Create centered window with title "emoemu - {game_name}"
|
|
344
|
+
3. Create SDL renderer with VSync if enabled
|
|
345
|
+
4. Create streaming texture matching source dimensions
|
|
346
|
+
|
|
347
|
+
### Event Handling
|
|
348
|
+
|
|
349
|
+
Poll SDL events in the main loop to handle:
|
|
350
|
+
|
|
351
|
+
| Event | Action |
|
|
352
|
+
|-------|--------|
|
|
353
|
+
| `SDL_QUIT` | Clean shutdown |
|
|
354
|
+
| `SDL_WINDOWEVENT_RESIZED` | Update scale/viewport |
|
|
355
|
+
| `SDL_WINDOWEVENT_FOCUS_LOST` | Optional: pause emulation |
|
|
356
|
+
|
|
357
|
+
### Input Considerations
|
|
358
|
+
|
|
359
|
+
SDL can capture keyboard events. Two approaches:
|
|
360
|
+
|
|
361
|
+
1. **Hybrid mode**: Continue using terminal keyboard input (Kitty protocol), SDL only for display
|
|
362
|
+
2. **Full SDL mode**: Use SDL for both display and keyboard input
|
|
363
|
+
|
|
364
|
+
**Recommendation**: Start with hybrid mode (SDL display + terminal input) to minimize changes. Full SDL input can be added later.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Post-Processing Effects
|
|
369
|
+
|
|
370
|
+
Reuse existing post-processing pipeline from `src/rendering/post-processing/`:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
import { applyEffects, EffectOptions } from './post-processing';
|
|
374
|
+
|
|
375
|
+
// In the native renderer's RGB conversion path (NativeRenderer)
|
|
376
|
+
const processed = applyEffects(frameBuffer, width, height, {
|
|
377
|
+
gamma: this.gamma,
|
|
378
|
+
scanlines: this.scanlines,
|
|
379
|
+
saturation: this.saturation,
|
|
380
|
+
// ... other effects
|
|
381
|
+
});
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
The effects operate on raw pixel data, so they work identically for native and terminal renderers.
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Status Bar / OSD
|
|
389
|
+
|
|
390
|
+
Terminal renderers display status information (FPS, core info) in terminal rows below the game display. For a native window:
|
|
391
|
+
|
|
392
|
+
> **Shipped reality:** `ink-native` exposes no runtime `setTitle`, so **Option C below is not available** - it was the original recommendation but could not be implemented. Options A and B remain the viable approaches for status/OSD in native mode.
|
|
393
|
+
|
|
394
|
+
### Option A: Render to Texture
|
|
395
|
+
|
|
396
|
+
Draw status text directly on the native framebuffer using a bitmap font. Simple but limited styling.
|
|
397
|
+
|
|
398
|
+
### Option B: Separate Terminal Output
|
|
399
|
+
|
|
400
|
+
Keep status/OSD in terminal while game renders to the native window. Users see:
|
|
401
|
+
- Native window with game display
|
|
402
|
+
- Terminal with status info, notifications
|
|
403
|
+
|
|
404
|
+
### Option C: Window Title (not available - see note above)
|
|
405
|
+
|
|
406
|
+
Put key status info in window title: "emoemu - Super Mario Bros [60 FPS]". Not possible with `ink-native`, which has no runtime title-setting API.
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## File Structure
|
|
411
|
+
|
|
412
|
+
```
|
|
413
|
+
src/rendering/
|
|
414
|
+
├── index.ts # Add SdlRenderer export
|
|
415
|
+
├── sdl-renderer.ts # New: SDL renderer implementation
|
|
416
|
+
├── sdl-bindings.ts # New: koffi SDL2 bindings
|
|
417
|
+
├── renderer.ts # Existing: TerminalRenderer
|
|
418
|
+
├── kitty-renderer.ts # Existing: KittyRenderer
|
|
419
|
+
└── ...
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Implementation Phases
|
|
425
|
+
|
|
426
|
+
### Phase 1: SDL Bindings
|
|
427
|
+
|
|
428
|
+
1. Create `sdl-bindings.ts` with koffi FFI definitions
|
|
429
|
+
2. Implement basic window creation and destruction
|
|
430
|
+
3. Test standalone (outside emulator)
|
|
431
|
+
|
|
432
|
+
### Phase 2: SdlRenderer Class
|
|
433
|
+
|
|
434
|
+
1. Implement `SdlRenderer` with basic RGB24 rendering
|
|
435
|
+
2. Add to renderer exports
|
|
436
|
+
3. Integrate with emulator's renderer selection
|
|
437
|
+
|
|
438
|
+
### Phase 3: Type System & CLI
|
|
439
|
+
|
|
440
|
+
1. Update `VideoDriver` and `RenderMode` types
|
|
441
|
+
2. Add `--native` CLI flag
|
|
442
|
+
3. Update type guards
|
|
443
|
+
4. Add config keys
|
|
444
|
+
|
|
445
|
+
### Phase 4: Color Format Support
|
|
446
|
+
|
|
447
|
+
1. Implement indexed color rendering (NES palette conversion)
|
|
448
|
+
2. Implement RGB15 rendering (GBC, SNES)
|
|
449
|
+
3. Test with various cores
|
|
450
|
+
|
|
451
|
+
### Phase 5: Post-Processing
|
|
452
|
+
|
|
453
|
+
1. Integrate existing effects pipeline
|
|
454
|
+
2. Add SDL-specific effect options if needed
|
|
455
|
+
3. Test all effects
|
|
456
|
+
|
|
457
|
+
### Phase 6: Polish
|
|
458
|
+
|
|
459
|
+
1. Window management (resize, close handling)
|
|
460
|
+
2. Status display (window title or terminal)
|
|
461
|
+
3. Documentation updates
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## Testing Strategy
|
|
466
|
+
|
|
467
|
+
### Manual Testing
|
|
468
|
+
|
|
469
|
+
| Test Case | Expected Result |
|
|
470
|
+
|-----------|-----------------|
|
|
471
|
+
| Launch with `--native` | Native window opens, game renders |
|
|
472
|
+
| NES ROM (indexed color) | Correct colors from palette |
|
|
473
|
+
| SNES ROM (RGB15) | Correct color conversion |
|
|
474
|
+
| N64 ROM (RGB24) | Direct rendering, good performance |
|
|
475
|
+
| Window close | Clean shutdown, no crash |
|
|
476
|
+
| Effects (CRT preset) | Visual effects applied |
|
|
477
|
+
|
|
478
|
+
### Performance Testing
|
|
479
|
+
|
|
480
|
+
Compare frame rates between SDL and terminal modes:
|
|
481
|
+
|
|
482
|
+
| System | Terminal (Kitty) | SDL | Expected Improvement |
|
|
483
|
+
|--------|-----------------|-----|---------------------|
|
|
484
|
+
| NES | 60 FPS | 60 FPS | Equivalent (not bottlenecked) |
|
|
485
|
+
| N64 | 30-40 FPS | 50-60 FPS | ~50% improvement |
|
|
486
|
+
|
|
487
|
+
### Compatibility Testing
|
|
488
|
+
|
|
489
|
+
Test on:
|
|
490
|
+
- macOS (Apple Silicon and Intel)
|
|
491
|
+
- Linux (X11 and Wayland)
|
|
492
|
+
- Windows (if supported)
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## Dependencies
|
|
497
|
+
|
|
498
|
+
### Runtime (Shipped)
|
|
499
|
+
|
|
500
|
+
`ink-native` bundles its `fenster` native window backend and the Cozette bitmap font directly in the npm package - **zero system dependencies**. No SDL2 install of any kind is required on any platform:
|
|
501
|
+
|
|
502
|
+
| Platform | Installation |
|
|
503
|
+
|----------|-------------|
|
|
504
|
+
| macOS | None - bundled with `ink-native` |
|
|
505
|
+
| Linux | None - bundled with `ink-native` |
|
|
506
|
+
| Windows | None - bundled with `ink-native` |
|
|
507
|
+
|
|
508
|
+
### Build Dependencies
|
|
509
|
+
|
|
510
|
+
None - `ink-native` ships prebuilt/bundled, so there is no native compilation step (this matches the original goal of avoiding one, though the mechanism changed from koffi FFI to `ink-native`'s bundled backend).
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## Limitations
|
|
515
|
+
|
|
516
|
+
1. **No hardware acceleration**: software rendering is used; GPU shaders not available
|
|
517
|
+
2. **Shared window with UI**: Game and Ink UI share a single native window (see [native-ui-rendering-trd.md](./native-ui-rendering-trd.md)), not integrated into the terminal
|
|
518
|
+
3. **Input latency**: May have slightly different input characteristics than terminal mode
|
|
519
|
+
4. **Status display**: Limited compared to terminal's flexible text output
|
|
520
|
+
5. **No runtime `setTitle`**: `ink-native` exposes no API to change the window title after creation
|
|
521
|
+
6. **No runtime scale-factor change**: `menu_scale_factor` (→ `scaleFactor`) only applies at window creation
|
|
522
|
+
7. **No programmatic window resize**: the game letterboxes into the fixed window instead of resizing it
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## Future Enhancements
|
|
527
|
+
|
|
528
|
+
1. **SDL GPU renderer**: Use SDL's GPU-accelerated renderer for better scaling
|
|
529
|
+
2. **Shader support**: Add shader passes for advanced effects
|
|
530
|
+
3. **Full SDL input**: Use SDL for keyboard/gamepad input
|
|
531
|
+
4. **Fullscreen mode**: Toggle between windowed and fullscreen
|
|
532
|
+
5. **Multiple windows**: Support for debug views (VRAM, nametables)
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## Resources
|
|
537
|
+
|
|
538
|
+
- [SDL2 Documentation](https://wiki.libsdl.org/SDL2/FrontPage)
|
|
539
|
+
- [SDL2 Rendering API](https://wiki.libretro.com/index.php/SDL2_Rendering)
|
|
540
|
+
- [koffi FFI Library](https://koffi.dev/)
|