Index of /es40-macos/

NameLast ModifiedSizeType
../ -  Directory
es40-macos/2026-Jul-08 14:24:20-  Directory
README.txt2026-Jul-08 14:39:278.2Ktext/plain; charset=utf-8
es40-macos.tgz2026-Jul-08 14:37:34833.3Kapplication/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
Webserver 1.0