Index of /es40-macos/
| Name | Last Modified | Size | Type |
| ../ | | - | Directory |
| es40-macos/ | 2026-Jul-08 14:24:20 | - | Directory |
| README.txt | 2026-Jul-08 14:39:27 | 8.2K | text/plain; charset=utf-8 |
| es40-macos.tgz | 2026-Jul-08 14:37:34 | 833.3K | application/x-gtar-compressed |
# MacOS SDL3 Fix to es40-emu (https://github.com/ES40-emu) - Code Changes Summary
This document summarizes the actual code changes made to fix SDL3 issues on macOS.
As reported here https://github.com/ES40-Emu/es40/issues/97
## Problem
On macOS, SDL3 requires video operations (SDL_Init, SDL_CreateWindow, SDL_PollEvent) to run on the main thread.
The ES40 emulator runs the system in a background thread, causing crashes and a black window.
## Solution Overview
- Initialize SDL on main thread before spawning worker threads
- Run system in background thread while keeping main thread for SDL event loop
- Use shared framebuffer for thread-safe rendering (S3 thread writes, main thread renders)
- Mirror OCP console output to VGA for visibility (SRM uses OCP on macOS instead of VGA)
---
## Core Changes
### 1. **New Files Created**
#### `src/gui/sdl_main_thread.cpp`
- Implements main thread SDL event loop and rendering
- Functions:
- `sdl_init_on_main_thread_if_needed()` - Initialize SDL on main thread
- `sdl_create_window_on_main_thread_if_needed()` - Create window on main thread
- `sdl_aware_run()` - Run system in background thread, keep main thread for SDL
#### `src/gui/sdl_main_thread.h`
- Header declarations for main thread functions
### 2. **Modified Files**
#### `CMakeLists.txt`
Added new source file:
```cmake
list(APPEND ES40_SOURCES
src/gui/sdl.cpp
+ src/gui/sdl_main_thread.cpp # NEW
src/gui/gui.cpp
)
```
#### `src/AlphaSim.cpp`
Initialize SDL on main thread before spawning workers:
```cpp
#if defined(HAVE_SDL)
// On macOS, SDL_Init and SDL_CreateWindow must be on main thread
sdl_init_on_main_thread_if_needed(c);
sdl_create_window_on_main_thread_if_needed();
#endif
// ... later ...
#if defined(HAVE_SDL)
sdl_aware_run(theSystem); // Runs system in background, SDL on main
#else
theSystem->Run();
#endif
```
#### `src/gui/sdl.cpp`
Major changes for thread-safe rendering:
1. **Global SDL variables** (for cross-file access):
```cpp
-static SDL_Window* sdl_window = NULL;
-static SDL_Renderer* sdl_renderer = NULL;
-static SDL_Texture* sdl_texture = NULL;
+SDL_Window* sdl_window = NULL; // Not static
+SDL_Renderer* sdl_renderer = NULL;
+SDL_Texture* sdl_texture = NULL;
```
2. **Shared framebuffer** (macOS only):
```cpp
#ifdef __APPLE__
u32* shared_framebuffer = NULL;
unsigned shared_fb_width = 0;
unsigned shared_fb_height = 0;
std::atomic<bool> shared_fb_dirty{false};
#endif
```
3. **specific_init()** - Check if SDL already initialized:
```cpp
if (!SDL_WasInit(SDL_INIT_VIDEO)) {
if (!SDL_Init(SDL_INIT_VIDEO)) {
FAILURE_1(SDL, "Unable to initialize SDL3 video subsystem: %s", SDL_GetError());
}
}
```
4. **text_update()** - Implement VGA text mode rendering:
```cpp
void bx_sdl_gui_c::text_update(u8* old_text, u8* new_text, ...) {
// Render 80x25 text mode using 8x16 font
// Each character: 2 bytes (character + attribute)
// Render to 640x400 framebuffer
// Call graphics_frame_update() for final rendering
}
```
5. **graphics_frame_update()** - Use shared buffer on macOS:
```cpp
#ifdef __APPLE__
// S3 thread: Copy to shared buffer
memcpy(shared_framebuffer, pixels, width * height * sizeof(u32));
shared_fb_dirty.store(true, std::memory_order_release);
#else
// Other platforms: render directly
SDL_UpdateTexture(sdl_texture, NULL, pixels, width * sizeof(u32));
SDL_RenderPresent(sdl_renderer);
#endif
```
6. **handle_events()** - No-op on macOS (main thread handles events):
```cpp
#ifdef __APPLE__
// Events handled by main thread, not S3 thread
return;
#else
// Original event handling code
#endif
```
7. **dimension_update()** - Defer texture operations on macOS:
```cpp
#ifdef __APPLE__
// Just update dimensions, main thread will recreate texture
res_x = x;
res_y = y;
return;
#endif
```
#### `src/S3Trio64.cpp`
1. **Video mode tracking** (debug logging):
```cpp
static uint8_t last_reported_mode = 0xFF;
if (cur_mode != last_reported_mode) {
printf("%%S3-I-MODE: Video mode changed to %s\n", mode_name);
}
```
2. **Exception handling** in S3 thread:
```cpp
catch (std::exception& e) {
printf("Standard exception in S3 thread: %s.\n", e.what());
}
catch (...) {
printf("Unknown exception in S3 thread (possibly signal/crash).\n");
}
```
3. **VGA initialization** - Initialize text mode early:
```cpp
// Enable VGA subsystem
m_vga_subsys_enable = true;
// Initialize CRTC for 80x25 text mode
vga.crtc.horz_disp_end = 79;
vga.crtc.vert_disp_end = 399;
vga.crtc.offset = 40; // **CRITICAL** 40 words = 80 bytes per row
// Load VGA fonts into VRAM manually
for (int ch = 0; ch < 256; ch++) {
// Copy font from ROM to VRAM plane 2 at 0x20000
}
// Initialize VGA palette
for (int i = 0; i < 16; i++) {
vga.dac.color[i*3+0] = vga_palette[i][0] >> 2;
// ... set RGB values
}
```
4. **Force text mode rendering** when text data detected:
```cpp
// Check for text data in VRAM
int non_zero_count = 0;
for (int i = 0; i < 160; i++) {
if (vga.memory[i] != 0) non_zero_count++;
}
if (non_zero_count > 10) {
force_text_mode = true;
}
```
#### `src/VGA.cpp`
1. **Text mode row offset fix**:
```cpp
// Fix for macOS OCP mirroring: if offset is 0, use default
int row_offset = offset() >> 1;
if (row_offset == 0) {
row_offset = 80; // Standard text mode
}
```
2. **Force text mode for SCREEN_OFF with data**:
```cpp
if (cur_mode == SCREEN_OFF) {
// Check if text buffer has data
if (has_text_data) {
vga_vh_text(bitmap, cliprect); // Force text rendering
return 0;
}
}
```
3. **Helper methods for OCP->VGA mirroring**:
```cpp
void CVGA::write_text_char(unsigned row, unsigned col, char ch, uint8_t attr);
void CVGA::init_default_font();
void CVGA::init_default_palette();
void CVGA::scroll_text_up();
```
4. **Global helper functions**:
```cpp
void vga_start_new_message(); // Advance to new line
void vga_write_console_char(char ch); // Write char to VGA buffer
```
#### `src/VGA.h`
Added public method declarations:
```cpp
void write_text_char(unsigned row, unsigned col, char ch, uint8_t attr);
void init_default_font();
void init_default_palette();
void scroll_text_up();
```
#### `src/DPR.cpp`
OCP-to-VGA mirroring for macOS console visibility:
```cpp
#ifdef __APPLE__
extern void vga_write_console_char(char ch);
extern void vga_start_new_message();
// Signal start of new OCP message
vga_start_new_message();
// Write each character from OCP buffer to VGA
for (int i = 0; i < 16 && buf[i] != 0; i++) {
if (buf[i] != '[' && buf[i] != ']') {
vga_write_console_char(buf[i]);
}
}
#endif
```
#### `src/Serial.cpp`
Global serial port array for keyboard input forwarding:
```cpp
CSerial* srl[2] = {nullptr, nullptr};
CSerial::CSerial(...) {
// Register in global array
if (number < 2) {
srl[number] = this;
}
}
```
#### `src/es40.cfg`
Updated VGA BIOS ROM path:
```
pci0.2 = s3 {
- rom = "rom\VGABIOSFILE.bin";
+ rom = "rom/86c764x1.bin";
}
```
---
## Key Technical Details
### Thread Architecture
- **Main thread**: SDL initialization, window creation, event handling, rendering
- **S3 worker thread**: VGA emulation, pixel generation, writes to shared framebuffer
- **Synchronization**: Atomic flag (`shared_fb_dirty`) signals when rendering needed
### Rendering Pipeline (macOS)
1. S3 thread generates pixels -> writes to `shared_framebuffer`
2. Sets `shared_fb_dirty = true`
3. Main thread detects dirty flag -> renders from `shared_framebuffer`
4. Main thread calls `SDL_UpdateTexture()` + `SDL_RenderPresent()`
### VGA Text Mode
- 80 columns x 25 rows
- 2 bytes per character (character byte + attribute byte)
- Rendered using 8x16 font to 640x400 framebuffer
- Standard 16-color VGA palette
### OCP-to-VGA Mirroring (macOS only)
- SRM firmware uses OCP console on macOS (not VGA)
- DPR.cpp intercepts OCP writes
- Mirrors characters to VGA text buffer
- Makes console visible in SDL window
---
## Build Changes
Added `src/gui/sdl_main_thread.cpp` to `CMakeLists.txt`
## Platform-Specific Behavior
- **macOS (`#ifdef __APPLE__`)**: Shared framebuffer, main thread rendering, OCP mirroring
- **Other platforms**: Direct rendering from S3 thread (original behavior)
---
## Testing
- Boot SRM firmware
- Console output should be visible in SDL window
- Text mode should render correctly (80x25)
- No crashes from SDL thread violations