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,676 @@
|
|
|
1
|
+
# Headless Hardware Rendering - Technical Requirements Document
|
|
2
|
+
|
|
3
|
+
This document describes the requirements and implementation approach for supporting libretro cores that require OpenGL/Vulkan hardware rendering in emoemu, using headless GPU contexts with pixel readback.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
### Goals
|
|
8
|
+
|
|
9
|
+
1. **Enable GPU-Accelerated Cores**: Support libretro cores that require OpenGL or Vulkan hardware rendering
|
|
10
|
+
2. **Headless Operation**: Create GPU contexts without windows, suitable for terminal-based rendering
|
|
11
|
+
3. **Seamless Integration**: After GPU rendering, read pixels back and pipe them through existing Kitty/Unicode/ASCII renderers
|
|
12
|
+
4. **Cross-Platform Support**: Work on macOS, Linux, and Windows
|
|
13
|
+
|
|
14
|
+
### Non-Goals
|
|
15
|
+
|
|
16
|
+
1. Direct GPU output to terminal (not possible)
|
|
17
|
+
2. Real-time GPU-to-terminal streaming (always requires pixel readback)
|
|
18
|
+
3. Vulkan support in initial implementation (focus on OpenGL first)
|
|
19
|
+
4. WebGL compatibility (need full OpenGL, not WebGL subset)
|
|
20
|
+
|
|
21
|
+
### Background: Why Hardware Rendering?
|
|
22
|
+
|
|
23
|
+
Some libretro cores require hardware-accelerated rendering:
|
|
24
|
+
|
|
25
|
+
| Core | System | Why GPU Required |
|
|
26
|
+
|------|--------|------------------|
|
|
27
|
+
| Beetle PSX HW | PlayStation | Hardware-accelerated rendering with enhancements |
|
|
28
|
+
| ParaLLEl N64 | Nintendo 64 | Vulkan-based RDP emulation |
|
|
29
|
+
| PPSSPP | PSP | OpenGL ES for PSP GPU emulation |
|
|
30
|
+
| Dolphin | GameCube/Wii | Modern GPU required for performance |
|
|
31
|
+
| Flycast | Dreamcast | PowerVR GPU emulation |
|
|
32
|
+
|
|
33
|
+
Currently, emoemu returns `false` for `SET_HW_RENDER` environment callbacks, causing these cores to fail initialization or fall back to slower software rendering (when available).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Architecture
|
|
38
|
+
|
|
39
|
+
### Current Flow (Software Rendering)
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
┌─────────────────┐ video callback ┌──────────────────┐
|
|
43
|
+
│ Libretro Core │ ──────────────────────► │ CallbackManager │
|
|
44
|
+
│ (CPU renders) │ (pixel buffer) │ (framebuffer) │
|
|
45
|
+
└─────────────────┘ └────────┬─────────┘
|
|
46
|
+
│
|
|
47
|
+
▼
|
|
48
|
+
┌──────────────────┐
|
|
49
|
+
│ Kitty Renderer │
|
|
50
|
+
└──────────────────┘
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Proposed Flow (Hardware Rendering)
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
┌─────────────────┐ ┌──────────────────────────┐
|
|
57
|
+
│ Libretro Core │ ──── OpenGL calls ────► │ HardwareRenderContext │
|
|
58
|
+
│ (GPU renders) │ │ - Headless EGL/CGL ctx │
|
|
59
|
+
└─────────────────┘ │ - FBO management │
|
|
60
|
+
│ - glReadPixels() │
|
|
61
|
+
└────────────┬─────────────┘
|
|
62
|
+
│
|
|
63
|
+
pixel readback
|
|
64
|
+
│
|
|
65
|
+
▼
|
|
66
|
+
┌──────────────────┐
|
|
67
|
+
│ CallbackManager │
|
|
68
|
+
│ (framebuffer) │
|
|
69
|
+
└────────┬─────────┘
|
|
70
|
+
│
|
|
71
|
+
▼
|
|
72
|
+
┌──────────────────┐
|
|
73
|
+
│ Kitty Renderer │
|
|
74
|
+
└──────────────────┘
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Libretro Hardware Rendering API
|
|
80
|
+
|
|
81
|
+
### Environment Commands
|
|
82
|
+
|
|
83
|
+
| Command | Direction | Purpose |
|
|
84
|
+
|---------|-----------|---------|
|
|
85
|
+
| `SET_HW_RENDER` | Core → Frontend | Core requests hardware context |
|
|
86
|
+
| `GET_HW_RENDER_INTERFACE` | Core → Frontend | Get extended HW interface |
|
|
87
|
+
| `GET_PREFERRED_HW_RENDER` | Core → Frontend | Ask frontend's preferred context type |
|
|
88
|
+
| `SET_HW_SHARED_CONTEXT` | Core → Frontend | Request shared context |
|
|
89
|
+
|
|
90
|
+
### retro_hw_render_callback Structure
|
|
91
|
+
|
|
92
|
+
The core passes this struct via `SET_HW_RENDER`:
|
|
93
|
+
|
|
94
|
+
| Field | Type | Description |
|
|
95
|
+
|-------|------|-------------|
|
|
96
|
+
| `context_type` | `enum` | OpenGL, OpenGL Core, GLES2, GLES3, or Vulkan |
|
|
97
|
+
| `version_major` | `unsigned` | Requested OpenGL major version |
|
|
98
|
+
| `version_minor` | `unsigned` | Requested OpenGL minor version |
|
|
99
|
+
| `depth` | `bool` | Core needs depth buffer |
|
|
100
|
+
| `stencil` | `bool` | Core needs stencil buffer |
|
|
101
|
+
| `bottom_left_origin` | `bool` | Coordinate system origin |
|
|
102
|
+
| `cache_context` | `bool` | Frontend should cache context |
|
|
103
|
+
| `debug_context` | `bool` | Request debug context |
|
|
104
|
+
| `context_reset` | `callback` | Called when context is created/reset |
|
|
105
|
+
| `context_destroy` | `callback` | Called before context destruction |
|
|
106
|
+
| `get_current_framebuffer` | `callback` | **Frontend provides**: Returns FBO id |
|
|
107
|
+
| `get_proc_address` | `callback` | **Frontend provides**: Returns GL function pointer |
|
|
108
|
+
|
|
109
|
+
### API Flow
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
1. Core calls retro_load_game()
|
|
113
|
+
└── Core calls SET_HW_RENDER with requirements
|
|
114
|
+
└── Frontend creates headless GL context
|
|
115
|
+
└── Frontend writes get_current_framebuffer and get_proc_address to struct
|
|
116
|
+
└── Returns true if successful
|
|
117
|
+
|
|
118
|
+
2. Frontend calls context_reset callback
|
|
119
|
+
└── Core initializes its GL resources
|
|
120
|
+
|
|
121
|
+
3. Per frame in retro_run():
|
|
122
|
+
└── Core calls get_current_framebuffer() to get FBO
|
|
123
|
+
└── Core renders to FBO using OpenGL
|
|
124
|
+
└── Core does NOT call video_refresh callback (or calls with NULL)
|
|
125
|
+
└── Frontend calls glReadPixels() to get frame
|
|
126
|
+
|
|
127
|
+
4. On shutdown:
|
|
128
|
+
└── Frontend calls context_destroy callback
|
|
129
|
+
└── Core cleans up GL resources
|
|
130
|
+
└── Frontend destroys GL context
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Platform-Specific Context Creation
|
|
136
|
+
|
|
137
|
+
### macOS: CGL (Core Graphics OpenGL)
|
|
138
|
+
|
|
139
|
+
macOS does not use EGL. Instead, use the CGL API from the OpenGL framework:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// Load OpenGL framework
|
|
143
|
+
const opengl = koffi.load('/System/Library/Frameworks/OpenGL.framework/OpenGL');
|
|
144
|
+
|
|
145
|
+
// CGL types
|
|
146
|
+
const CGLContextObj = koffi.pointer('CGLContextObj', koffi.opaque());
|
|
147
|
+
const CGLPixelFormatObj = koffi.pointer('CGLPixelFormatObj', koffi.opaque());
|
|
148
|
+
|
|
149
|
+
// Key functions
|
|
150
|
+
const CGLChoosePixelFormat = opengl.func('int CGLChoosePixelFormat(int*, CGLPixelFormatObj*, int*)');
|
|
151
|
+
const CGLCreateContext = opengl.func('int CGLCreateContext(CGLPixelFormatObj, CGLContextObj, CGLContextObj*)');
|
|
152
|
+
const CGLSetCurrentContext = opengl.func('int CGLSetCurrentContext(CGLContextObj)');
|
|
153
|
+
const CGLDestroyContext = opengl.func('int CGLDestroyContext(CGLContextObj)');
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Pixel format attributes for offscreen rendering:**
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
const attributes = [
|
|
160
|
+
kCGLPFAOpenGLProfile, kCGLOGLPVersion_3_2_Core, // OpenGL 3.2 Core
|
|
161
|
+
kCGLPFAColorSize, 24,
|
|
162
|
+
kCGLPFADepthSize, 24,
|
|
163
|
+
kCGLPFAStencilSize, 8,
|
|
164
|
+
kCGLPFAOffScreen, // Critical: offscreen rendering
|
|
165
|
+
0 // Terminator
|
|
166
|
+
];
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Linux: EGL
|
|
170
|
+
|
|
171
|
+
EGL provides platform-agnostic OpenGL context creation:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// Load EGL and OpenGL ES libraries
|
|
175
|
+
const egl = koffi.load('libEGL.so.1');
|
|
176
|
+
const gles = koffi.load('libGLESv2.so.2');
|
|
177
|
+
|
|
178
|
+
// Or for full OpenGL:
|
|
179
|
+
const gl = koffi.load('libGL.so.1');
|
|
180
|
+
|
|
181
|
+
// Key EGL functions
|
|
182
|
+
const eglGetDisplay = egl.func('void* eglGetDisplay(void*)');
|
|
183
|
+
const eglInitialize = egl.func('int eglInitialize(void*, int*, int*)');
|
|
184
|
+
const eglChooseConfig = egl.func('int eglChooseConfig(void*, int*, void*, int, int*)');
|
|
185
|
+
const eglCreateContext = egl.func('void* eglCreateContext(void*, void*, void*, int*)');
|
|
186
|
+
const eglMakeCurrent = egl.func('int eglMakeCurrent(void*, void*, void*, void*)');
|
|
187
|
+
const eglGetProcAddress = egl.func('void* eglGetProcAddress(const char*)');
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Surfaceless context (no window/pbuffer needed):**
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// Use EGL_NO_SURFACE for surfaceless context
|
|
194
|
+
const EGL_NO_SURFACE = null;
|
|
195
|
+
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Alternative: Pbuffer surface (more compatible):**
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const pbufferAttribs = [
|
|
202
|
+
EGL_WIDTH, 1920,
|
|
203
|
+
EGL_HEIGHT, 1080,
|
|
204
|
+
EGL_NONE
|
|
205
|
+
];
|
|
206
|
+
const surface = eglCreatePbufferSurface(display, config, pbufferAttribs);
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Windows: EGL via ANGLE or WGL
|
|
210
|
+
|
|
211
|
+
**Option 1: ANGLE (Recommended)**
|
|
212
|
+
|
|
213
|
+
ANGLE provides EGL on Windows, translating OpenGL ES to DirectX:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
const egl = koffi.load('libEGL.dll'); // From ANGLE
|
|
217
|
+
const gles = koffi.load('libGLESv2.dll');
|
|
218
|
+
// Same EGL API as Linux
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Option 2: WGL (Native)**
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const opengl32 = koffi.load('opengl32.dll');
|
|
225
|
+
const wglCreateContext = opengl32.func('void* wglCreateContext(void*)');
|
|
226
|
+
const wglMakeCurrent = opengl32.func('int wglMakeCurrent(void*, void*)');
|
|
227
|
+
const wglGetProcAddress = opengl32.func('void* wglGetProcAddress(const char*)');
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
WGL requires a device context (DC), which typically requires a window. Workaround: create a hidden window.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Implementation Components
|
|
235
|
+
|
|
236
|
+
### 1. HardwareRenderContext Interface
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// src/cores/libretro/hardware-render/types.ts
|
|
240
|
+
|
|
241
|
+
export enum HWContextType {
|
|
242
|
+
NONE = 0,
|
|
243
|
+
OPENGL = 1,
|
|
244
|
+
OPENGLES2 = 2,
|
|
245
|
+
OPENGL_CORE = 3,
|
|
246
|
+
OPENGLES3 = 4,
|
|
247
|
+
OPENGLES_VERSION = 5,
|
|
248
|
+
VULKAN = 6,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface HWRenderRequest {
|
|
252
|
+
contextType: HWContextType;
|
|
253
|
+
versionMajor: number;
|
|
254
|
+
versionMinor: number;
|
|
255
|
+
depth: boolean;
|
|
256
|
+
stencil: boolean;
|
|
257
|
+
bottomLeftOrigin: boolean;
|
|
258
|
+
cacheContext: boolean;
|
|
259
|
+
debugContext: boolean;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface HardwareRenderContext {
|
|
263
|
+
/** Initialize the headless GL context */
|
|
264
|
+
init(request: HWRenderRequest): boolean;
|
|
265
|
+
|
|
266
|
+
/** Get the FBO id for the core to render to */
|
|
267
|
+
getCurrentFramebuffer(): number;
|
|
268
|
+
|
|
269
|
+
/** Get a GL function pointer by name */
|
|
270
|
+
getProcAddress(symbol: string): bigint;
|
|
271
|
+
|
|
272
|
+
/** Called after core initializes its GL resources */
|
|
273
|
+
contextReset(): void;
|
|
274
|
+
|
|
275
|
+
/** Called before destroying the context */
|
|
276
|
+
contextDestroy(): void;
|
|
277
|
+
|
|
278
|
+
/** Read rendered pixels from FBO */
|
|
279
|
+
readPixels(width: number, height: number): Uint8Array;
|
|
280
|
+
|
|
281
|
+
/** Resize the FBO */
|
|
282
|
+
resize(width: number, height: number): void;
|
|
283
|
+
|
|
284
|
+
/** Clean up all resources */
|
|
285
|
+
destroy(): void;
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### 2. FBO Management
|
|
290
|
+
|
|
291
|
+
The frontend must provide a framebuffer object (FBO) for the core to render into:
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// Pseudocode for FBO setup
|
|
295
|
+
const fbo = glGenFramebuffers(1);
|
|
296
|
+
const colorTexture = glGenTextures(1);
|
|
297
|
+
const depthRenderbuffer = glGenRenderbuffers(1);
|
|
298
|
+
|
|
299
|
+
glBindTexture(GL_TEXTURE_2D, colorTexture);
|
|
300
|
+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
|
|
301
|
+
|
|
302
|
+
glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
|
|
303
|
+
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
|
|
304
|
+
|
|
305
|
+
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
|
306
|
+
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
|
|
307
|
+
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### 3. Pixel Readback
|
|
311
|
+
|
|
312
|
+
After each frame, read pixels from the FBO:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
readPixels(width: number, height: number): Uint8Array {
|
|
316
|
+
const pixels = new Uint8Array(width * height * 4); // RGBA
|
|
317
|
+
|
|
318
|
+
glBindFramebuffer(GL_FRAMEBUFFER, this.fbo);
|
|
319
|
+
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
|
320
|
+
|
|
321
|
+
// OpenGL origin is bottom-left, libretro expects top-left
|
|
322
|
+
// Flip vertically if needed (based on bottom_left_origin flag)
|
|
323
|
+
if (!this.bottomLeftOrigin) {
|
|
324
|
+
this.flipVertical(pixels, width, height);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return pixels;
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 4. get_proc_address Implementation
|
|
332
|
+
|
|
333
|
+
Cores need to load OpenGL functions dynamically:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
getProcAddress(symbol: string): bigint {
|
|
337
|
+
// Platform-specific function lookup
|
|
338
|
+
if (process.platform === 'darwin') {
|
|
339
|
+
return CGLGetProcAddress(symbol);
|
|
340
|
+
} else if (process.platform === 'linux') {
|
|
341
|
+
return eglGetProcAddress(symbol) || dlsym(RTLD_DEFAULT, symbol);
|
|
342
|
+
} else {
|
|
343
|
+
return wglGetProcAddress(symbol) || GetProcAddress(opengl32, symbol);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### 5. Environment Handler Updates
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
// In environment/index.ts
|
|
352
|
+
|
|
353
|
+
case RETRO_ENVIRONMENT.SET_HW_RENDER: {
|
|
354
|
+
const request = this.parseHwRenderCallback(data);
|
|
355
|
+
|
|
356
|
+
// Check if we support this context type
|
|
357
|
+
if (request.contextType === HWContextType.VULKAN) {
|
|
358
|
+
logger.warn('Vulkan not supported, rejecting SET_HW_RENDER', 'HWRender');
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Create hardware render context
|
|
363
|
+
this.hwContext = createHardwareContext();
|
|
364
|
+
if (!this.hwContext.init(request)) {
|
|
365
|
+
logger.error('Failed to create hardware render context', 'HWRender');
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Write our callbacks back to the struct
|
|
370
|
+
this.writeHwRenderCallbacks(data, {
|
|
371
|
+
get_current_framebuffer: this.hwContext.getCurrentFramebuffer.bind(this.hwContext),
|
|
372
|
+
get_proc_address: this.hwContext.getProcAddress.bind(this.hwContext),
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
case RETRO_ENVIRONMENT.GET_PREFERRED_HW_RENDER: {
|
|
379
|
+
// Tell core we prefer OpenGL Core profile
|
|
380
|
+
writeUInt32LE(data, HWContextType.OPENGL_CORE);
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Performance Considerations
|
|
388
|
+
|
|
389
|
+
### Pixel Readback Latency
|
|
390
|
+
|
|
391
|
+
`glReadPixels()` is synchronous and stalls the GPU pipeline. Mitigation strategies:
|
|
392
|
+
|
|
393
|
+
| Strategy | Description | Complexity |
|
|
394
|
+
|----------|-------------|------------|
|
|
395
|
+
| **PBO (Pixel Buffer Object)** | Async readback using buffer objects | Medium |
|
|
396
|
+
| **Double/Triple buffering** | Read from previous frame's PBO | Medium |
|
|
397
|
+
| **Compute shader copy** | Copy to mapped buffer via compute | High |
|
|
398
|
+
|
|
399
|
+
**PBO async readback example:**
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
// Setup: create two PBOs for double buffering
|
|
403
|
+
const pbos = [glGenBuffer(), glGenBuffer()];
|
|
404
|
+
let currentPbo = 0;
|
|
405
|
+
|
|
406
|
+
// Each frame:
|
|
407
|
+
// 1. Start async read into current PBO
|
|
408
|
+
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbos[currentPbo]);
|
|
409
|
+
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
|
410
|
+
|
|
411
|
+
// 2. Map previous PBO (already finished) and get pixels
|
|
412
|
+
const prevPbo = 1 - currentPbo;
|
|
413
|
+
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbos[prevPbo]);
|
|
414
|
+
const pixels = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
|
415
|
+
// Copy pixels...
|
|
416
|
+
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
|
417
|
+
|
|
418
|
+
// 3. Swap
|
|
419
|
+
currentPbo = prevPbo;
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Memory Usage
|
|
423
|
+
|
|
424
|
+
| Component | Memory |
|
|
425
|
+
|-----------|--------|
|
|
426
|
+
| FBO color buffer (1080p RGBA) | ~8 MB |
|
|
427
|
+
| FBO depth/stencil buffer | ~8 MB |
|
|
428
|
+
| PBO for readback (x2) | ~16 MB |
|
|
429
|
+
| Readback buffer (CPU) | ~8 MB |
|
|
430
|
+
| **Total** | ~40 MB |
|
|
431
|
+
|
|
432
|
+
### Frame Timing
|
|
433
|
+
|
|
434
|
+
Hardware rendering adds latency:
|
|
435
|
+
|
|
436
|
+
```
|
|
437
|
+
Core renders to FBO ──┬── GPU time (varies)
|
|
438
|
+
│
|
|
439
|
+
glReadPixels() ──┼── GPU→CPU transfer + stall
|
|
440
|
+
│
|
|
441
|
+
Terminal render ──┴── Already exists
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
With async PBO readback, effective latency is 1 frame behind but throughput is maintained.
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## File Structure
|
|
449
|
+
|
|
450
|
+
```
|
|
451
|
+
src/cores/libretro/hardware-render/
|
|
452
|
+
├── index.ts # Factory and common logic
|
|
453
|
+
├── types.ts # Interfaces and enums
|
|
454
|
+
├── context-cgl.ts # macOS CGL implementation
|
|
455
|
+
├── context-egl.ts # Linux/Windows EGL implementation
|
|
456
|
+
├── context-wgl.ts # Windows WGL fallback (optional)
|
|
457
|
+
├── fbo.ts # Framebuffer object management
|
|
458
|
+
├── gl-loader.ts # OpenGL function pointer loading
|
|
459
|
+
└── consts.ts # GL constants
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Implementation Phases
|
|
465
|
+
|
|
466
|
+
### Phase 1: macOS CGL Context
|
|
467
|
+
|
|
468
|
+
1. Implement CGL context creation via koffi
|
|
469
|
+
2. Create offscreen pixel format
|
|
470
|
+
3. Basic FBO setup
|
|
471
|
+
4. Implement `get_proc_address` for macOS
|
|
472
|
+
5. Simple `glReadPixels` (synchronous)
|
|
473
|
+
6. Test with a simple OpenGL core (e.g., Beetle PSX HW in software fallback mode)
|
|
474
|
+
|
|
475
|
+
### Phase 2: Core Integration
|
|
476
|
+
|
|
477
|
+
1. Update environment handler for `SET_HW_RENDER`
|
|
478
|
+
2. Implement callback struct parsing/writing
|
|
479
|
+
3. Wire up `context_reset`/`context_destroy` callbacks
|
|
480
|
+
4. Integrate pixel readback into frame loop
|
|
481
|
+
5. Handle resolution changes via `SET_GEOMETRY`
|
|
482
|
+
|
|
483
|
+
### Phase 3: Linux EGL Support
|
|
484
|
+
|
|
485
|
+
1. Implement EGL context creation
|
|
486
|
+
2. Handle surfaceless vs pbuffer contexts
|
|
487
|
+
3. Test on Linux systems with and without GPU
|
|
488
|
+
|
|
489
|
+
### Phase 4: Performance Optimization
|
|
490
|
+
|
|
491
|
+
1. Implement PBO double-buffering
|
|
492
|
+
2. Profile and optimize readback latency
|
|
493
|
+
3. Consider compute shader approach for high-res content
|
|
494
|
+
|
|
495
|
+
### Phase 5: Windows Support
|
|
496
|
+
|
|
497
|
+
1. Add ANGLE/EGL support
|
|
498
|
+
2. Fallback to WGL with hidden window if needed
|
|
499
|
+
3. Test on various Windows configurations
|
|
500
|
+
|
|
501
|
+
### Phase 6: Vulkan (Future)
|
|
502
|
+
|
|
503
|
+
1. Create headless Vulkan instance
|
|
504
|
+
2. Render to offscreen image
|
|
505
|
+
3. Copy to host-visible memory
|
|
506
|
+
4. Significantly more complex than OpenGL
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## Testing Strategy
|
|
511
|
+
|
|
512
|
+
### Test Cores
|
|
513
|
+
|
|
514
|
+
| Core | Context Type | Notes |
|
|
515
|
+
|------|--------------|-------|
|
|
516
|
+
| Beetle PSX HW | OpenGL Core | Falls back to software if HW fails |
|
|
517
|
+
| mGBA | OpenGL (optional) | Has software fallback |
|
|
518
|
+
| Sameboy | OpenGL (optional) | Has software fallback |
|
|
519
|
+
|
|
520
|
+
### Validation Criteria
|
|
521
|
+
|
|
522
|
+
1. **Context creation**: GL context initializes without errors
|
|
523
|
+
2. **FBO completeness**: `glCheckFramebufferStatus` returns `GL_FRAMEBUFFER_COMPLETE`
|
|
524
|
+
3. **Rendering**: Core produces visible output
|
|
525
|
+
4. **Pixel accuracy**: Readback matches expected output
|
|
526
|
+
5. **Performance**: Maintains target framerate
|
|
527
|
+
|
|
528
|
+
### Debug Tools
|
|
529
|
+
|
|
530
|
+
- Set `debug_context = true` to get GL debug callbacks
|
|
531
|
+
- Use `GL_KHR_debug` extension for detailed error messages
|
|
532
|
+
- Log all GL calls in development mode
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## Limitations and Risks
|
|
537
|
+
|
|
538
|
+
### Known Limitations
|
|
539
|
+
|
|
540
|
+
1. **Vulkan not supported initially**: Focus on OpenGL first
|
|
541
|
+
2. **Performance overhead**: Pixel readback adds latency
|
|
542
|
+
3. **GPU required**: No software fallback for HW-only cores
|
|
543
|
+
4. **Platform dependencies**: Each platform needs specific implementation
|
|
544
|
+
|
|
545
|
+
### Risks
|
|
546
|
+
|
|
547
|
+
| Risk | Mitigation |
|
|
548
|
+
|------|------------|
|
|
549
|
+
| Some cores may not work with offscreen context | Test widely, document unsupported cores |
|
|
550
|
+
| Performance may be insufficient for demanding cores | Implement PBO async readback |
|
|
551
|
+
| koffi FFI complexity with GL callbacks | Careful memory management, extensive testing |
|
|
552
|
+
| macOS deprecating OpenGL | Still works, consider Metal bridge long-term |
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
## Alternatives Considered
|
|
557
|
+
|
|
558
|
+
### Alternative 1: headless-gl npm Package
|
|
559
|
+
|
|
560
|
+
The [headless-gl](https://github.com/stackgl/headless-gl) package provides WebGL 1.0 in Node.js.
|
|
561
|
+
|
|
562
|
+
**Pros:**
|
|
563
|
+
- Simple npm install
|
|
564
|
+
- Cross-platform
|
|
565
|
+
- Well-tested
|
|
566
|
+
|
|
567
|
+
**Cons:**
|
|
568
|
+
- WebGL 1.0 only (subset of OpenGL ES 2.0)
|
|
569
|
+
- Many cores require OpenGL 3.x+ features
|
|
570
|
+
- No OpenGL Core profile support
|
|
571
|
+
|
|
572
|
+
**Verdict:** Insufficient for most hardware-accelerated cores.
|
|
573
|
+
|
|
574
|
+
### Alternative 2: Separate Renderer Process
|
|
575
|
+
|
|
576
|
+
Run a helper process with full GPU access, communicate via IPC.
|
|
577
|
+
|
|
578
|
+
**Pros:**
|
|
579
|
+
- Isolation from main process
|
|
580
|
+
- Could use native GPU frameworks
|
|
581
|
+
- Easier error recovery
|
|
582
|
+
|
|
583
|
+
**Cons:**
|
|
584
|
+
- IPC overhead for frame data (~8MB/frame at 1080p)
|
|
585
|
+
- Complex synchronization
|
|
586
|
+
- Additional process management
|
|
587
|
+
|
|
588
|
+
**Verdict:** Too complex and slow for real-time rendering.
|
|
589
|
+
|
|
590
|
+
### Alternative 3: Virtual Display (Xvfb)
|
|
591
|
+
|
|
592
|
+
Use X virtual framebuffer on Linux.
|
|
593
|
+
|
|
594
|
+
**Pros:**
|
|
595
|
+
- Works with any X11 application
|
|
596
|
+
- Well-established technique
|
|
597
|
+
|
|
598
|
+
**Cons:**
|
|
599
|
+
- Linux only
|
|
600
|
+
- Requires X11 (not Wayland native)
|
|
601
|
+
- Additional dependency
|
|
602
|
+
|
|
603
|
+
**Verdict:** Linux-only, not cross-platform solution.
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
## Resources
|
|
608
|
+
|
|
609
|
+
- [Libretro OpenGL Accelerated Cores](https://docs.libretro.com/development/cores/opengl-cores/)
|
|
610
|
+
- [retro_hw_render_callback Struct Reference](https://buildbot.libretro.com/doxygen/a23611.html)
|
|
611
|
+
- [libretro.h Source](https://github.com/libretro/RetroArch/blob/master/libretro-common/include/libretro.h)
|
|
612
|
+
- [headless-gl GitHub](https://github.com/stackgl/headless-gl)
|
|
613
|
+
- [EGL Reference](https://registry.khronos.org/EGL/sdk/docs/man/html/)
|
|
614
|
+
- [CGL Reference (Apple)](https://developer.apple.com/documentation/opengl/cgl)
|
|
615
|
+
- [ANGLE Project](https://chromium.googlesource.com/angle/angle)
|
|
616
|
+
|
|
617
|
+
---
|
|
618
|
+
|
|
619
|
+
## Appendix: GL Constants
|
|
620
|
+
|
|
621
|
+
Common OpenGL constants needed for implementation:
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
// Framebuffer targets
|
|
625
|
+
const GL_FRAMEBUFFER = 0x8D40;
|
|
626
|
+
const GL_READ_FRAMEBUFFER = 0x8CA8;
|
|
627
|
+
const GL_DRAW_FRAMEBUFFER = 0x8CA9;
|
|
628
|
+
|
|
629
|
+
// Framebuffer attachments
|
|
630
|
+
const GL_COLOR_ATTACHMENT0 = 0x8CE0;
|
|
631
|
+
const GL_DEPTH_ATTACHMENT = 0x8D00;
|
|
632
|
+
const GL_STENCIL_ATTACHMENT = 0x8D20;
|
|
633
|
+
const GL_DEPTH_STENCIL_ATTACHMENT = 0x821A;
|
|
634
|
+
|
|
635
|
+
// Framebuffer status
|
|
636
|
+
const GL_FRAMEBUFFER_COMPLETE = 0x8CD5;
|
|
637
|
+
|
|
638
|
+
// Pixel formats
|
|
639
|
+
const GL_RGBA = 0x1908;
|
|
640
|
+
const GL_UNSIGNED_BYTE = 0x1401;
|
|
641
|
+
|
|
642
|
+
// Buffer targets (for PBO)
|
|
643
|
+
const GL_PIXEL_PACK_BUFFER = 0x88EB;
|
|
644
|
+
|
|
645
|
+
// Texture targets
|
|
646
|
+
const GL_TEXTURE_2D = 0x0DE1;
|
|
647
|
+
|
|
648
|
+
// Renderbuffer formats
|
|
649
|
+
const GL_DEPTH24_STENCIL8 = 0x88F0;
|
|
650
|
+
const GL_RGBA8 = 0x8058;
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## Appendix: retro_hw_render_callback Struct Layout
|
|
656
|
+
|
|
657
|
+
For parsing/writing the callback struct via koffi:
|
|
658
|
+
|
|
659
|
+
```typescript
|
|
660
|
+
// 64-bit layout (pointers are 8 bytes)
|
|
661
|
+
const RETRO_HW_RENDER_CALLBACK_LAYOUT = {
|
|
662
|
+
context_type: { offset: 0, size: 4 }, // enum (uint32)
|
|
663
|
+
context_reset: { offset: 8, size: 8 }, // function pointer
|
|
664
|
+
get_current_framebuffer: { offset: 16, size: 8 }, // function pointer
|
|
665
|
+
get_proc_address: { offset: 24, size: 8 }, // function pointer
|
|
666
|
+
depth: { offset: 32, size: 1 }, // bool
|
|
667
|
+
stencil: { offset: 33, size: 1 }, // bool
|
|
668
|
+
bottom_left_origin: { offset: 34, size: 1 }, // bool
|
|
669
|
+
version_major: { offset: 36, size: 4 }, // unsigned int
|
|
670
|
+
version_minor: { offset: 40, size: 4 }, // unsigned int
|
|
671
|
+
cache_context: { offset: 44, size: 1 }, // bool
|
|
672
|
+
context_destroy: { offset: 48, size: 8 }, // function pointer
|
|
673
|
+
debug_context: { offset: 56, size: 1 }, // bool
|
|
674
|
+
// Total size: ~64 bytes (with padding)
|
|
675
|
+
};
|
|
676
|
+
```
|