diff --git a/FORK_SETUP_GUIDE.md b/FORK_SETUP_GUIDE.md new file mode 100644 index 0000000..25b74d8 --- /dev/null +++ b/FORK_SETUP_GUIDE.md @@ -0,0 +1,325 @@ +# esp-cpp/femto8 Fork Setup Guide + +This guide explains how to set up and manage the `esp-cpp/femto8` fork for PICO-8 emulator integration. + +## Fork Creation + +### 1. Create the Fork +```bash +# Fork benbaker76/femto8 to esp-cpp/femto8 via GitHub UI +# Or use GitHub CLI: +gh repo fork benbaker76/femto8 --org esp-cpp --fork-name femto8 +``` + +### 2. Clone and Setup Local Repository +```bash +git clone https://github.com/esp-cpp/femto8.git +cd femto8 +git remote add upstream https://github.com/benbaker76/femto8.git +``` + +### 3. Create ESP32 Integration Branch +```bash +# Create branch for ESP32-specific modifications +git checkout -b esp32-integration +git push -u origin esp32-integration + +# Create branch for shared_memory integration +git checkout -b shared-memory-integration +git push -u origin shared-memory-integration + +# Create branch specifically for esp-box-emu +git checkout -b esp-box-emu-integration +git push -u origin esp-box-emu-integration +``` + +## Integration with esp-box-emu + +### 1. Copy Source to Component +```bash +# From esp-box-emu root directory +cd components/pico8 + +# Clone our fork temporarily +git clone https://github.com/esp-cpp/femto8.git temp_femto8 +cd temp_femto8 +git checkout esp-box-emu-integration # Use our integration branch + +# Copy source files +cd .. +mkdir -p femto8/src femto8/include +cp -r temp_femto8/src/* femto8/src/ +cp -r temp_femto8/include/* femto8/include/ + +# Clean up +rm -rf temp_femto8 + +# Add to git +git add femto8/ +git commit -m "Add femto8 source for PICO-8 emulator" +``` + +### 2. Apply ESP32-Specific Modifications + +#### Memory Management Integration +Create `femto8/src/pico8_esp32_memory.c`: +```c +#ifdef FEMTO8_SHARED_MEMORY +#include "shared_memory.hpp" + +static uint8_t* pico8_memory_pool = NULL; +static size_t pico8_memory_pool_size = 0; + +// Memory allocation hooks for SharedMemory integration +void* pico8_malloc(size_t size, const char* tag) { + return SharedMemory::get().allocate(size, tag); +} + +void pico8_free(void* ptr, const char* tag) { + SharedMemory::get().deallocate((uint8_t*)ptr, tag); +} + +// Initialize memory system +int pico8_init_memory_system(size_t total_size) { + pico8_memory_pool = SharedMemory::get().allocate(total_size, "pico8_pool"); + if (!pico8_memory_pool) { + return -1; + } + pico8_memory_pool_size = total_size; + return 0; +} + +void pico8_deinit_memory_system(void) { + if (pico8_memory_pool) { + SharedMemory::get().deallocate(pico8_memory_pool, "pico8_pool"); + pico8_memory_pool = NULL; + pico8_memory_pool_size = 0; + } +} +#endif +``` + +#### Performance Optimizations +Modify critical functions in femto8 source files: +```c +// Add to femto8/src/pico8_cpu.c +#ifdef FEMTO8_ESP32 +#include "esp_attr.h" +#define PICO8_FAST_FUNC IRAM_ATTR +#else +#define PICO8_FAST_FUNC +#endif + +// Apply to performance-critical functions +PICO8_FAST_FUNC void pico8_cpu_step(void) { + // ... existing implementation +} + +PICO8_FAST_FUNC void pico8_render_scanline(int line) { + // ... existing implementation +} +``` + +#### Configuration Header +Create `femto8/include/pico8_esp32_config.h`: +```c +#ifndef PICO8_ESP32_CONFIG_H +#define PICO8_ESP32_CONFIG_H + +#ifdef FEMTO8_ESP32 +// ESP32-specific configuration +#define PICO8_SAMPLE_RATE 22050 +#define PICO8_AUDIO_BUFFER_SIZE 512 +#define PICO8_MAX_CARTRIDGE_SIZE 32768 +#define PICO8_RAM_SIZE 32768 +#define PICO8_VRAM_SIZE 16384 + +// Use ESP32 timer functions +#include "esp_timer.h" +#define PICO8_GET_TIME_US() esp_timer_get_time() + +// Use ESP32 memory functions +#ifdef FEMTO8_SHARED_MEMORY +#define PICO8_MALLOC(size, tag) pico8_malloc(size, tag) +#define PICO8_FREE(ptr, tag) pico8_free(ptr, tag) +#else +#define PICO8_MALLOC(size, tag) malloc(size) +#define PICO8_FREE(ptr, tag) free(ptr) +#endif + +#else +// Default configuration for other platforms +#define PICO8_SAMPLE_RATE 44100 +#define PICO8_AUDIO_BUFFER_SIZE 1024 +// ... other defaults +#endif + +#endif // PICO8_ESP32_CONFIG_H +``` + +## Maintenance Strategy + +### 1. Sync with Upstream +```bash +# Periodically sync with original femto8 +git checkout main +git fetch upstream +git merge upstream/main +git push origin main + +# Rebase our integration branches +git checkout esp32-integration +git rebase main +git push --force-with-lease origin esp32-integration +``` + +### 2. Update esp-box-emu Integration +```bash +# When femto8 changes, update the copied source +cd components/pico8 + +# Get latest from our integration branch +git clone https://github.com/esp-cpp/femto8.git temp_femto8 +cd temp_femto8 +git checkout esp-box-emu-integration + +# Update source files +cd .. +rm -rf femto8/src/* femto8/include/* +cp -r temp_femto8/src/* femto8/src/ +cp -r temp_femto8/include/* femto8/include/ +rm -rf temp_femto8 + +# Commit changes +git add femto8/ +git commit -m "Update femto8 source to latest version" +``` + +### 3. Branch Management +- **main**: Sync with upstream benbaker76/femto8 +- **esp32-integration**: ESP32-specific optimizations +- **shared-memory-integration**: SharedMemory component integration +- **esp-box-emu-integration**: Final integration branch for esp-box-emu + +## Modification Tracking + +### Key Files to Modify +1. **Memory Management**: + - `src/pico8_memory.c` - Replace malloc/free calls + - `src/pico8_lua.c` - Lua heap management + - `include/pico8_config.h` - Memory configuration + +2. **Performance**: + - `src/pico8_cpu.c` - Add IRAM attributes + - `src/pico8_gfx.c` - Graphics rendering optimizations + - `src/pico8_sound.c` - Audio processing optimizations + +3. **Integration**: + - `include/pico8.h` - Main API header + - `src/pico8_api.c` - Public API implementation + - `include/pico8_esp32_config.h` - ESP32 configuration + +### Documentation of Changes +Maintain a `MODIFICATIONS.md` file in the fork: +```markdown +# ESP32 Modifications to femto8 + +## Memory Management +- Replaced malloc/free with SharedMemory integration +- Added configurable memory pool sizes +- Implemented memory tracking and debugging + +## Performance Optimizations +- Added IRAM attributes to critical functions +- Optimized memory copy operations for ESP32 +- Reduced dynamic allocations in hot paths + +## ESP-IDF Integration +- Added ESP32 timer support +- Integrated with ESP-IDF build system +- Added ESP32-specific configuration options +``` + +## Testing the Fork + +### 1. Basic Compilation Test +```bash +# Test compilation with ESP-IDF +cd components/pico8 +idf.py build +``` + +### 2. Memory Integration Test +```cpp +// Test SharedMemory integration +#include "pico8.hpp" +#include "shared_memory.hpp" + +void test_pico8_memory() { + // Initialize PICO-8 with test cartridge + const uint8_t test_cart[] = { /* minimal PICO-8 cart */ }; + init_pico8("test.p8", test_cart, sizeof(test_cart)); + + // Verify memory allocations + auto stats = SharedMemory::get().get_stats(); + // Check that pico8_ram, pico8_vram, etc. are allocated + + deinit_pico8(); + + // Verify cleanup + auto stats_after = SharedMemory::get().get_stats(); + // Check that memory was properly deallocated +} +``` + +### 3. Performance Test +```cpp +// Test frame rate performance +void test_pico8_performance() { + init_pico8("test.p8", test_cart, sizeof(test_cart)); + + auto start_time = esp_timer_get_time(); + for (int i = 0; i < 60; i++) { + run_pico8_frame(); + } + auto end_time = esp_timer_get_time(); + + uint32_t frame_time_us = (end_time - start_time) / 60; + printf("Average frame time: %d us\n", frame_time_us); + // Should be close to 16667 us (60 FPS) + + deinit_pico8(); +} +``` + +## Contributing Back to Upstream + +### 1. Identify Generic Improvements +Some modifications may be useful for the upstream project: +- Bug fixes +- Performance improvements that don't depend on ESP32 +- API enhancements + +### 2. Create Upstream PRs +```bash +# Create branch for upstream contribution +git checkout main +git checkout -b feature/performance-improvements + +# Cherry-pick generic improvements +git cherry-pick + +# Push and create PR to benbaker76/femto8 +git push origin feature/performance-improvements +``` + +## Conclusion + +This fork management strategy provides: +- **Full control** over femto8 source for ESP32 integration +- **Easy maintenance** with clear branching strategy +- **Upstream compatibility** for contributing improvements back +- **Integration simplicity** with direct source copying +- **Performance optimization** opportunities + +The approach ensures we can make necessary modifications while maintaining the ability to sync with upstream improvements and contribute back to the femto8 project. \ No newline at end of file diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md new file mode 100644 index 0000000..6a71b28 --- /dev/null +++ b/IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,309 @@ +# PICO-8 Emulator Implementation Summary + +This document summarizes the proposed implementation for adding PICO-8 emulator support to esp-box-emu using the `esp-cpp/femto8` fork with direct source integration. + +## Files Created + +### 1. Core Documentation +- **`PICO8_PR_PROPOSAL.md`** - Comprehensive PR proposal with implementation plan +- **`IMPLEMENTATION_SUMMARY.md`** - This summary document + +### 2. Component Structure +- **`components/pico8/CMakeLists.txt`** - Build configuration for PICO-8 component +- **`components/pico8/include/pico8.hpp`** - C++ API wrapper for femto8 emulator +- **`components/pico8/src/pico8.cpp`** - Implementation with shared_memory integration +- **`components/pico8/README.md`** - Component documentation + +### 3. Integration Files +- **`main/pico8_cart.hpp`** - Cart class implementation for PICO-8 +- **`main/carts_integration_example.hpp`** - Integration points for main cart system + +## Implementation Strategy + +### Approach: Direct Source Integration (Recommended) +Following the pattern of existing emulators (nofrendo, gnuboy, etc.), we will: + +1. **Fork femto8 to esp-cpp/femto8** + - Create our own fork for ESP32-specific modifications + - Add shared_memory integration hooks + - Implement ESP32 performance optimizations + +2. **Copy source directly into component** + - No git submodule management needed + - Easy to modify for ESP32 integration + - Consistent with existing emulator patterns + +3. **Integrate with shared_memory component** + - Replace static memory allocations + - Use SharedMemory::get().allocate() for all major memory blocks + - Enable better memory management and debugging + +## Implementation Roadmap + +### Phase 1: Foundation (Week 1) +1. **Set up esp-cpp/femto8 fork** + ```bash + # Fork benbaker76/femto8 to esp-cpp/femto8 + # Add ESP32 optimizations branch + # Test compilation with ESP-IDF + ``` + +2. **Copy source to component** + ```bash + cd components/pico8 + git clone https://github.com/esp-cpp/femto8.git temp_femto8 + cp -r temp_femto8/src femto8/src + cp -r temp_femto8/include femto8/include + rm -rf temp_femto8 + ``` + +3. **Create component structure** + - Set up CMakeLists.txt with shared_memory dependency + - Create C++ wrapper API + - Test basic compilation + +### Phase 2: Core Functionality (Week 2) +1. **Shared Memory Integration** + - Replace femto8's static allocations + - Implement memory management functions + - Test memory allocation/deallocation + +2. **Video integration** + - Connect femto8 framebuffer to ESP32 display + - Implement palette conversion + - Test video output with scaling + +3. **Input system** + - Map gamepad controls to PICO-8 inputs + - Test input responsiveness + - Handle button state management + +### Phase 3: Advanced Features (Week 3) +1. **Audio integration** + - Connect PICO-8 audio synthesis to ESP32 audio pipeline + - Implement audio buffer management + - Test audio output quality + +2. **Save state system** + - Implement save/load functionality + - Integrate with existing save system + - Test save state reliability + +3. **Performance optimization** + - Profile memory usage + - Optimize frame rate + - Test with various cartridges + +## Key Integration Points + +### 1. Shared Memory Integration +```cpp +// Replace femto8's static allocations +pico8_ram = SharedMemory::get().allocate(PICO8_RAM_SIZE, "pico8_ram"); +pico8_vram = SharedMemory::get().allocate(PICO8_VRAM_SIZE, "pico8_vram"); +lua_heap = SharedMemory::get().allocate(LUA_HEAP_SIZE, "pico8_lua"); + +// Initialize femto8 with our memory +femto8_config_t config = { + .ram = pico8_ram, + .ram_size = PICO8_RAM_SIZE, + .vram = pico8_vram, + .vram_size = PICO8_VRAM_SIZE, + .lua_heap = lua_heap, + .lua_heap_size = LUA_HEAP_SIZE +}; +femto8_init(&config); +``` + +### 2. ESP32 Optimizations +```c +// In femto8 source files +#ifdef FEMTO8_ESP32 + #define PICO8_IRAM IRAM_ATTR + #define PICO8_MEMCPY esp32_dma_memcpy +#endif +``` + +### 3. Build System Integration +```cmake +# CMakeLists.txt includes shared_memory dependency +REQUIRES "box-emu" "statistics" "shared_memory" + +# Compile definitions for ESP32 integration +target_compile_definitions(${COMPONENT_LIB} PRIVATE + FEMTO8_EMBEDDED + FEMTO8_SHARED_MEMORY + FEMTO8_ESP32 +) +``` + +## Technical Specifications + +### Memory Usage (Updated with Shared Memory) +- **Emulator Core**: ~45KB +- **PICO-8 RAM**: 32KB (via SharedMemory) +- **Video RAM**: 16KB (via SharedMemory) +- **Lua Heap**: 64KB (via SharedMemory) +- **Audio Buffers**: 8KB (via SharedMemory) +- **Cartridge Data**: Up to 32KB +- **Total**: ~197KB (well within ESP32-S3 capabilities) + +### Performance Targets +- **Frame Rate**: 60 FPS on ESP32-S3 +- **Audio Latency**: <20ms +- **Boot Time**: <2 seconds +- **Memory Efficiency**: Optimal with shared_memory component + +### Compatibility +- **Hardware**: All existing esp-box-emu hardware +- **Controls**: Standard gamepad layout +- **Display**: All supported display configurations +- **Audio**: Existing audio pipeline +- **Memory**: Integrates with shared_memory component + +## Benefits of This Approach + +### 1. Consistency with Existing Emulators +- Matches nofrendo, gnuboy, and other emulator patterns +- No submodule management complexity +- Consistent build and integration process + +### 2. ESP32 Optimization Opportunities +- Direct modification of femto8 source for ESP32 +- Integration with shared_memory component +- ESP32-specific performance optimizations +- Better debugging and profiling capabilities + +### 3. Memory Management Benefits +- Uses shared_memory component for all allocations +- Better memory tracking and debugging +- Optimal memory layout for ESP32 +- Easier memory profiling and optimization + +### 4. Maintenance Advantages +- Full control over source code +- No submodule synchronization issues +- Direct integration with esp-box-emu architecture +- Easier to apply ESP32-specific patches + +## ESP32-Specific Modifications + +### 1. Memory Management +```c +// Replace static allocations in femto8 +#ifdef FEMTO8_SHARED_MEMORY +extern uint8_t* pico8_get_ram_ptr(void); +extern uint8_t* pico8_get_vram_ptr(void); +extern uint8_t* pico8_get_lua_heap_ptr(void); + +#define PICO8_RAM pico8_get_ram_ptr() +#define PICO8_VRAM pico8_get_vram_ptr() +#define LUA_HEAP pico8_get_lua_heap_ptr() +#endif +``` + +### 2. Performance Optimizations +```c +// Use IRAM for critical functions +#ifdef FEMTO8_ESP32 +IRAM_ATTR void pico8_run_scanline(void); +IRAM_ATTR void pico8_update_audio(void); +#endif +``` + +### 3. ESP-IDF Integration +```c +// Use ESP-IDF specific functions +#ifdef FEMTO8_ESP32 +#include "esp_timer.h" +#include "esp_heap_caps.h" + +uint64_t pico8_get_time_us(void) { + return esp_timer_get_time(); +} +#endif +``` + +## Testing Strategy + +### Unit Tests +1. **Memory Management**: Test SharedMemory integration +2. **Component Loading**: Test PICO-8 component initialization +3. **Cartridge Parsing**: Test .p8 file loading +4. **Video Output**: Test framebuffer generation +5. **Audio Output**: Test audio synthesis + +### Integration Tests +1. **Cart System**: Test integration with main cart system +2. **Menu Integration**: Test PICO-8 appears in emulator selection +3. **Save States**: Test save/load functionality +4. **Video Scaling**: Test all scaling modes +5. **Performance**: Test frame rate consistency + +### Game Compatibility Tests +1. **Simple Games**: Basic PICO-8 functionality +2. **Complex Games**: Advanced features (Celeste, etc.) +3. **Audio Games**: Music and sound effects +4. **Memory-Intensive**: Games that use lots of Lua memory +5. **Long Sessions**: Memory stability over time + +## Fork Management Strategy + +### esp-cpp/femto8 Repository Structure +``` +esp-cpp/femto8/ +├── main # Main branch (sync with upstream) +├── esp32-optimizations # ESP32-specific modifications +├── shared-memory # SharedMemory integration +└── esp-box-emu # Integration branch for esp-box-emu +``` + +### Modification Areas +1. **Memory Management**: Replace malloc/free with SharedMemory calls +2. **Performance**: Add IRAM attributes for critical functions +3. **Configuration**: Add ESP32-specific configuration options +4. **Integration**: Add hooks for esp-box-emu integration + +## Next Steps + +### Immediate Actions +1. **Create esp-cpp/femto8 fork** + - Fork from benbaker76/femto8 + - Create ESP32 optimization branch + - Test basic compilation with ESP-IDF + +2. **Implement shared_memory integration** + - Modify femto8 memory allocation functions + - Test memory management + - Profile memory usage + +3. **Create component structure** + - Set up component files + - Implement C++ wrapper + - Test basic integration + +### Development Phases +- **Week 1**: Fork setup and basic integration +- **Week 2**: Core functionality and shared_memory integration +- **Week 3**: Performance optimization and testing + +### Success Criteria +- [ ] Loads and runs PICO-8 cartridges +- [ ] Integrates with shared_memory component +- [ ] Maintains 60 FPS performance +- [ ] Audio works correctly +- [ ] Save states function properly +- [ ] Memory usage is optimal +- [ ] No memory leaks or crashes + +## Conclusion + +This updated implementation approach provides the best of both worlds: + +1. **Uses our own fork** (esp-cpp/femto8) for full control +2. **Direct source integration** following existing patterns +3. **SharedMemory integration** for optimal memory management +4. **ESP32-specific optimizations** for best performance +5. **No submodule complexity** for easier maintenance + +The approach ensures seamless integration with the esp-box-emu architecture while providing a high-quality PICO-8 emulator that takes full advantage of the ESP32-S3's capabilities and the project's shared memory management system. \ No newline at end of file diff --git a/PICO8_PR_PROPOSAL.md b/PICO8_PR_PROPOSAL.md new file mode 100644 index 0000000..8014879 --- /dev/null +++ b/PICO8_PR_PROPOSAL.md @@ -0,0 +1,427 @@ +# PICO-8 Emulator Support for esp-box-emu + +## Pull Request Proposal + +### Overview +This PR proposes adding PICO-8 (fantasy console) emulator support to the esp-box-emu project, following the established pattern of existing emulators (NES, Game Boy, Genesis, etc.). + +### Background +PICO-8 is a fantasy console created by Lexaloffle Games that simulates the limitations of 8-bit era game consoles. It features: +- 128x128 pixel display with 16-color palette +- 4-channel sound synthesis +- Lua-based programming environment +- Small cartridge size (32KB limit) +- Built-in sprite, map, and music editors (not needed for emulation) + +### Implementation Plan + +#### 1. PICO-8 Emulator Selection +After researching available C/C++ PICO-8 emulators suitable for embedded systems, I recommend using **femto8** as the base: + +- **Repository**: https://github.com/esp-cpp/femto8 (our fork) +- **Original**: https://github.com/benbaker76/femto8 +- **License**: MIT (compatible with esp-box-emu) +- **Language**: Pure C (optimal for ESP32) +- **Design**: Specifically built for embedded systems with memory constraints +- **Features**: Compact, resource-efficient, based on retro8 but optimized for embedded use +- **Fork Benefits**: Allows us to modify for shared_memory integration and ESP32 optimizations + +#### 2. Integration Strategy Options + +We have two approaches, following the patterns used by other emulators in the project: + +##### Option A: Direct Code Integration (Recommended) +Copy femto8 source directly into the component, similar to how `nofrendo`, `gnuboy`, and other emulators are integrated: + +``` +components/pico8/ +├── CMakeLists.txt +├── include/ +│ └── pico8.hpp +├── src/ +│ └── pico8.cpp +└── femto8/ # Direct source copy + ├── src/ + │ ├── pico8_api.c + │ ├── pico8_cart.c + │ ├── pico8_gfx.c + │ ├── pico8_sound.c + │ ├── pico8_lua.c + │ └── ... + └── include/ + └── pico8.h +``` + +**Benefits:** +- No submodule management +- Easy to modify for ESP32 optimizations +- Consistent with other emulators (nofrendo, gnuboy, etc.) +- Simplified build process +- Direct integration with shared_memory component + +##### Option B: Fork as Submodule +Use our `esp-cpp/femto8` fork as a git submodule: + +``` +components/pico8/ +├── CMakeLists.txt +├── include/ +│ └── pico8.hpp +├── src/ +│ └── pico8.cpp +└── femto8/ # Git submodule to esp-cpp/femto8 + └── (femto8 source) +``` + +**Benefits:** +- Easier to sync with upstream improvements +- Clear separation of our code vs. femto8 code +- Can contribute improvements back to femto8 + +**Recommendation:** Use Option A (direct integration) to match the existing pattern and allow for easier ESP32-specific modifications. + +#### 3. Component Structure (Option A - Recommended) + +``` +components/pico8/ +├── CMakeLists.txt +├── include/ +│ └── pico8.hpp # C++ wrapper API +├── src/ +│ └── pico8.cpp # C++ wrapper implementation +└── femto8/ # Direct source integration + ├── src/ + │ ├── pico8_api.c # Main PICO-8 API + │ ├── pico8_cart.c # Cartridge handling + │ ├── pico8_gfx.c # Graphics rendering + │ ├── pico8_sound.c # Audio synthesis + │ ├── pico8_lua.c # Lua interpreter + │ ├── pico8_memory.c # Memory management + │ └── pico8_input.c # Input handling + └── include/ + ├── pico8.h # Main femto8 API + ├── pico8_types.h # Type definitions + └── pico8_config.h # Configuration +``` + +#### 4. Integration Points + +##### 4.1 Cart System Integration +Create `main/pico8_cart.hpp` following the existing pattern: + +```cpp +class Pico8Cart : public Cart { +public: + explicit Pico8Cart(const Cart::Config& config); + virtual ~Pico8Cart() override; + + virtual void reset() override; + virtual void load() override; + virtual void save() override; + virtual bool run() override; + +protected: + static constexpr size_t PICO8_WIDTH = 128; + static constexpr size_t PICO8_HEIGHT = 128; + + virtual void set_original_video_setting() override; + virtual std::pair get_video_size() const override; + virtual std::span get_video_buffer() const override; + virtual void set_fit_video_setting() override; + virtual void set_fill_video_setting() override; + virtual std::string get_save_extension() const override; +}; +``` + +##### 4.2 File Format Support +- Add `.p8` file extension support for PICO-8 cartridges +- Implement cartridge loading from SD card +- Support for save states (following existing pattern) + +##### 4.3 Video Integration +- PICO-8 native resolution: 128x128 pixels +- 16-color palette (easily mappable to ESP32 display) +- Scaling options: Original, Fit, Fill (following existing emulators) + +##### 4.4 Audio Integration +- PICO-8 has 4-channel sound synthesis +- Integrate with existing audio pipeline +- Support for music and sound effects + +##### 4.5 Input Integration +- Map gamepad controls to PICO-8 inputs: + - D-Pad: Arrow keys/movement + - A Button: Z key (primary action) + - B Button: X key (secondary action) + - Start: Enter key + - Select: Shift key (alternative action) + +#### 5. Memory Management Integration + +##### 5.1 Shared Memory Component Integration +Modify femto8's static memory allocations to use our `shared_memory` component: + +```cpp +// In components/pico8/src/pico8.cpp +#include "shared_memory.hpp" + +// Replace femto8's static allocations with shared memory +static uint8_t* pico8_ram = nullptr; +static uint8_t* pico8_vram = nullptr; +static uint8_t* lua_heap = nullptr; + +void init_pico8_memory() { + // Use shared_memory component for allocations + pico8_ram = SharedMemory::get().allocate(PICO8_RAM_SIZE, "pico8_ram"); + pico8_vram = SharedMemory::get().allocate(PICO8_VRAM_SIZE, "pico8_vram"); + lua_heap = SharedMemory::get().allocate(LUA_HEAP_SIZE, "pico8_lua"); +} +``` + +##### 5.2 Memory Layout Optimization +```cpp +// Memory usage breakdown +constexpr size_t PICO8_RAM_SIZE = 32768; // 32KB - Main RAM +constexpr size_t PICO8_VRAM_SIZE = 16384; // 16KB - Video RAM +constexpr size_t LUA_HEAP_SIZE = 65536; // 64KB - Lua heap +constexpr size_t AUDIO_BUFFER_SIZE = 8192; // 8KB - Audio buffers +// Total: ~126KB (well within ESP32-S3 capabilities) +``` + +#### 6. Build System Integration + +##### 6.1 components/pico8/CMakeLists.txt +```cmake +idf_component_register( + INCLUDE_DIRS "include" + SRC_DIRS "src" "femto8/src" + PRIV_INCLUDE_DIRS "femto8/include" + REQUIRES "box-emu" "statistics" "shared_memory" +) + +target_compile_options(${COMPONENT_LIB} PRIVATE + -Wno-unused-function + -Wno-unused-variable + -Wno-implicit-fallthrough + -Wno-discarded-qualifiers + -DFEMTO8_EMBEDDED + -DFEMTO8_NO_FILESYSTEM + -DFEMTO8_ESP32 + -DFEMTO8_SHARED_MEMORY +) + +target_compile_definitions(${COMPONENT_LIB} PRIVATE + PICO8_EMULATOR + FEMTO8_EMBEDDED + FEMTO8_SHARED_MEMORY +) +``` + +##### 6.2 Kconfig Integration +```kconfig +# In main/Kconfig.projbuild +config ENABLE_PICO8 + bool "Enable PICO-8 emulator" + default y + help + Enable PICO-8 fantasy console emulator support + +config PICO8_LUA_HEAP_SIZE + int "PICO-8 Lua heap size (KB)" + depends on ENABLE_PICO8 + default 64 + range 32 128 + help + Size of Lua heap for PICO-8 emulator +``` + +#### 7. ESP32-Specific Optimizations + +##### 7.1 Memory Optimizations +```c +// In femto8/src/pico8_memory.c +#ifdef FEMTO8_SHARED_MEMORY +#include "shared_memory.hpp" + +// Replace static allocations +static uint8_t* pico8_memory_pool = nullptr; + +void pico8_init_memory() { + pico8_memory_pool = SharedMemory::get().allocate(PICO8_TOTAL_MEMORY, "pico8"); + // Partition memory pool for different uses +} +#endif +``` + +##### 7.2 Performance Optimizations +```c +// Use ESP32-specific optimizations +#ifdef FEMTO8_ESP32 + // Use IRAM for critical functions + #define PICO8_IRAM IRAM_ATTR + + // Use DMA for large memory operations + #define PICO8_MEMCPY esp32_dma_memcpy +#else + #define PICO8_IRAM + #define PICO8_MEMCPY memcpy +#endif +``` + +#### 8. Implementation Steps + +##### 8.1 Phase 1: Source Integration (Week 1) +1. **Fork femto8 to esp-cpp/femto8** + - Create fork with ESP32 optimizations branch + - Add shared_memory integration hooks + - Test compilation with ESP-IDF + +2. **Copy source to component** + ```bash + cd components/pico8 + git clone https://github.com/esp-cpp/femto8.git temp_femto8 + cp -r temp_femto8/src femto8/src + cp -r temp_femto8/include femto8/include + rm -rf temp_femto8 + ``` + +3. **Create component structure** + - Set up CMakeLists.txt + - Create C++ wrapper API + - Test basic compilation + +##### 8.2 Phase 2: Core Integration (Week 2) +1. **Implement Pico8Cart class** + - Basic initialization and cleanup + - Video buffer management + - Input handling + +2. **Integrate with cart system** + - Add to emulator enum + - Add file extension support + - Test cart loading + +3. **Memory integration** + - Replace static allocations with shared_memory + - Test memory efficiency + - Profile memory usage + +##### 8.3 Phase 3: Feature Completion (Week 3) +1. **Audio integration** + - Connect to existing audio pipeline + - Test audio quality + - Optimize performance + +2. **Save state system** + - Implement save/load functionality + - Test with various games + - Ensure compatibility + +3. **Performance optimization** + - Profile frame rate + - Optimize critical paths + - Test with complex games + +#### 9. Testing Strategy + +##### 9.1 Unit Tests +- Component initialization +- Cartridge loading +- Memory management +- Video output +- Audio synthesis + +##### 9.2 Integration Tests +- Menu integration +- Save states +- Video scaling +- Performance benchmarks + +##### 9.3 Game Compatibility Tests +Test with various PICO-8 games: +- Simple demos (Celeste Classic demo) +- Complex games (full Celeste) +- Audio-heavy games +- Graphics-intensive games + +#### 10. Benefits of This Approach + +##### 10.1 Consistency +- Matches existing emulator integration patterns +- No additional repository management +- Consistent build process + +##### 10.2 Customization +- Easy ESP32-specific modifications +- Direct shared_memory integration +- Performance optimizations + +##### 10.3 Maintenance +- No submodule synchronization issues +- Direct control over code changes +- Easier debugging and profiling + +#### 11. File Extensions and Metadata + +##### 11.1 Supported Formats +- `.p8` - PICO-8 cartridge files +- `.p8_save` - PICO-8 save state files + +##### 11.2 Metadata Integration +```cpp +// Extract game title from P8 cartridge header +std::string extract_p8_title(const uint8_t* cart_data) { + // Parse P8 format for title metadata + // Return extracted title or filename +} +``` + +#### 12. Memory Usage Estimation (Updated) + +```cpp +// Detailed memory breakdown +constexpr size_t PICO8_CORE_SIZE = 45000; // ~45KB - Emulator core +constexpr size_t PICO8_RAM_SIZE = 32768; // 32KB - PICO-8 RAM +constexpr size_t PICO8_VRAM_SIZE = 16384; // 16KB - Video RAM +constexpr size_t LUA_HEAP_SIZE = 65536; // 64KB - Lua interpreter heap +constexpr size_t AUDIO_BUFFERS_SIZE = 8192; // 8KB - Audio buffers +constexpr size_t CART_DATA_SIZE = 32768; // 32KB - Max cartridge size + +// Total: ~200KB (acceptable for ESP32-S3 with PSRAM) +// Can be optimized further with shared_memory component +``` + +### Implementation Timeline + +#### Week 1: Foundation +- [ ] Fork femto8 to esp-cpp/femto8 +- [ ] Copy source into component +- [ ] Create basic component structure +- [ ] Implement C++ wrapper API +- [ ] Test compilation + +#### Week 2: Core Features +- [ ] Implement Pico8Cart class +- [ ] Integrate with cart system +- [ ] Add shared_memory integration +- [ ] Implement video output +- [ ] Add input handling + +#### Week 3: Polish & Testing +- [ ] Implement audio integration +- [ ] Add save state support +- [ ] Performance optimization +- [ ] Menu integration +- [ ] Comprehensive testing + +### Conclusion + +This updated implementation plan provides a robust approach to adding PICO-8 emulator support that: + +1. **Follows established patterns** by copying source directly into the repository +2. **Uses our own fork** (esp-cpp/femto8) for easy customization +3. **Integrates with shared_memory** for optimal memory management +4. **Provides ESP32-specific optimizations** for best performance +5. **Maintains consistency** with existing emulator implementations + +The approach eliminates submodule complexity while providing full control over the emulator source code, enabling deep integration with the esp-box-emu architecture. \ No newline at end of file diff --git a/components/pico8/CMakeLists.txt b/components/pico8/CMakeLists.txt new file mode 100644 index 0000000..cba0905 --- /dev/null +++ b/components/pico8/CMakeLists.txt @@ -0,0 +1,25 @@ +idf_component_register( + INCLUDE_DIRS "include" + SRC_DIRS "src" "femto8/src" + PRIV_INCLUDE_DIRS "femto8/include" + REQUIRES "box-emu" "statistics" "shared_memory" +) + +target_compile_options(${COMPONENT_LIB} PRIVATE + -Wno-unused-function + -Wno-unused-variable + -Wno-implicit-fallthrough + -Wno-discarded-qualifiers + -Wno-misleading-indentation + -DFEMTO8_EMBEDDED + -DFEMTO8_NO_FILESYSTEM + -DFEMTO8_ESP32 + -DFEMTO8_SHARED_MEMORY +) + +target_compile_definitions(${COMPONENT_LIB} PRIVATE + PICO8_EMULATOR + FEMTO8_EMBEDDED + FEMTO8_SHARED_MEMORY + FEMTO8_ESP32 +) \ No newline at end of file diff --git a/components/pico8/README.md b/components/pico8/README.md new file mode 100644 index 0000000..8577685 --- /dev/null +++ b/components/pico8/README.md @@ -0,0 +1,220 @@ +# PICO-8 Emulator Component + +This component provides PICO-8 fantasy console emulation for the esp-box-emu project. + +## Overview + +PICO-8 is a fantasy console created by Lexaloffle Games that simulates the limitations and feel of 8-bit era game consoles. This component integrates a PICO-8 emulator based on the femto8 project, specifically adapted for embedded systems like the ESP32. + +## Features + +- **Native PICO-8 Support**: Runs authentic PICO-8 cartridges (.p8 files) +- **128x128 Display**: Native PICO-8 resolution with proper scaling options +- **16-Color Palette**: Full PICO-8 color palette support +- **4-Channel Audio**: PICO-8's unique sound synthesis +- **Save States**: Load and save game progress +- **Controller Support**: Maps gamepad controls to PICO-8 inputs +- **Memory Efficient**: Optimized for ESP32 memory constraints + +## Technical Specifications + +### Display +- **Resolution**: 128x128 pixels +- **Color Depth**: 4-bit (16 colors) +- **Scaling Modes**: Original, Fit, Fill +- **Memory Usage**: 16KB framebuffer + +### Audio +- **Channels**: 4-channel synthesis +- **Sample Rate**: 22050 Hz +- **Format**: 16-bit stereo output + +### Memory Requirements +- **Emulator Core**: ~50KB +- **Cartridge Data**: Up to 32KB +- **Video Buffer**: 16KB +- **Audio Buffers**: ~8KB +- **Total**: ~106KB additional memory usage + +## Control Mapping + +| ESP32 Box Control | PICO-8 Function | Description | +|------------------|-----------------|-------------| +| D-Pad | Arrow Keys | Movement/Navigation | +| A Button | Z Key | Primary action button | +| B Button | X Key | Secondary action button | +| Start | Enter | Pause/Menu | +| Select | Shift | Alternative action | + +## File Format Support + +- **Cartridge Files**: `.p8` - PICO-8 cartridge format +- **Save States**: `.p8_save` - Save state files + +## API Reference + +### Core Functions + +```cpp +// Initialize the PICO-8 emulator +void init_pico8(const char* rom_filename, const uint8_t* rom_data, size_t rom_size); + +// Clean up emulator resources +void deinit_pico8(); + +// Reset the emulator state +void reset_pico8(); + +// Run one frame of emulation +void run_pico8_frame(); +``` + +### Video Functions + +```cpp +// Get the current video framebuffer +std::span get_pico8_video_buffer(); + +// Get screen dimensions +int get_pico8_screen_width(); // Returns 128 +int get_pico8_screen_height(); // Returns 128 +``` + +### Input Functions + +```cpp +// Set button state (bitmask of PICO8_BTN_* constants) +void set_pico8_input(uint8_t buttons); + +// Individual key events +void pico8_key_down(int key); +void pico8_key_up(int key); +``` + +### Audio Functions + +```cpp +// Get audio samples for playback +void get_pico8_audio_buffer(int16_t* buffer, size_t frames); + +// Enable/disable audio +void set_pico8_audio_enabled(bool enabled); +``` + +### Save State Functions + +```cpp +// Load save state from file +bool load_pico8_state(const char* path); + +// Save current state to file +bool save_pico8_state(const char* path); +``` + +## Button Constants + +```cpp +#define PICO8_BTN_LEFT 0x01 // D-Pad Left +#define PICO8_BTN_RIGHT 0x02 // D-Pad Right +#define PICO8_BTN_UP 0x04 // D-Pad Up +#define PICO8_BTN_DOWN 0x08 // D-Pad Down +#define PICO8_BTN_Z 0x10 // Primary action (A button) +#define PICO8_BTN_X 0x20 // Secondary action (B button) +#define PICO8_BTN_ENTER 0x40 // Start button +#define PICO8_BTN_SHIFT 0x80 // Select button +``` + +## Integration + +This component is designed to integrate seamlessly with the esp-box-emu cart system: + +1. **Cart Class**: `Pico8Cart` extends the base `Cart` class +2. **File Detection**: Automatically detects `.p8` files +3. **Menu Integration**: Appears in the emulator selection menu +4. **Save System**: Uses the standard save state system + +## Configuration + +Enable PICO-8 support in your project configuration: + +```kconfig +CONFIG_ENABLE_PICO8=y +``` + +## Dependencies + +- **femto8**: PICO-8 emulator core (included as submodule) +- **box-emu**: Core emulation framework +- **statistics**: Performance monitoring +- **shared_memory**: Memory management + +## Build Configuration + +The component uses the following compile-time definitions: + +- `FEMTO8_EMBEDDED`: Enables embedded system optimizations +- `FEMTO8_NO_FILESYSTEM`: Disables file system dependencies +- `FEMTO8_ESP32`: ESP32-specific optimizations +- `PICO8_EMULATOR`: Identifies PICO-8 emulator build + +## Performance Notes + +- **Frame Rate**: Targets 60 FPS on ESP32-S3 +- **Memory Usage**: Efficient memory management for embedded systems +- **Audio Latency**: Low-latency audio synthesis +- **CPU Usage**: Optimized for single-core operation + +## Limitations + +1. **Cartridge Size**: Maximum 32KB per cartridge (PICO-8 standard) +2. **No Editor**: Only runs existing cartridges (no built-in editor) +3. **Limited Keyboard**: Basic keyboard input only +4. **No Network**: No network/multiplayer features + +## Troubleshooting + +### Common Issues + +**Cartridge Won't Load** +- Ensure the file has `.p8` extension +- Check file is not corrupted +- Verify sufficient memory available + +**No Audio** +- Check audio is enabled in settings +- Verify audio hardware initialization +- Check for memory allocation issues + +**Poor Performance** +- Ensure ESP32-S3 is running at full speed +- Check for memory fragmentation +- Verify other emulators are not running + +### Debug Output + +Enable debug logging to troubleshoot issues: + +```cpp +espp::Logger logger({.tag = "pico8", .level = espp::Logger::Verbosity::DEBUG}); +``` + +## License + +This component is licensed under the same terms as the esp-box-emu project. The femto8 emulator core is licensed under the MIT license. + +## Contributing + +When contributing to this component: + +1. Follow the existing code style +2. Add appropriate logging +3. Update this documentation +4. Test with multiple PICO-8 games +5. Verify memory usage stays within limits + +## References + +- [PICO-8 Official Site](https://www.lexaloffle.com/pico-8.php) +- [femto8 Emulator](https://github.com/benbaker76/femto8) +- [PICO-8 Technical Specification](https://www.lexaloffle.com/dl/docs/pico-8_manual.html) +- [esp-box-emu Project](https://github.com/esp-cpp/esp-box-emu) \ No newline at end of file diff --git a/components/pico8/include/pico8.hpp b/components/pico8/include/pico8.hpp new file mode 100644 index 0000000..9f85a32 --- /dev/null +++ b/components/pico8/include/pico8.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// PICO-8 emulator initialization and control +void init_pico8(const char* rom_filename, const uint8_t* rom_data, size_t rom_size); +void deinit_pico8(); +void reset_pico8(); +void run_pico8_frame(); + +// Video functions +std::span get_pico8_video_buffer(); +int get_pico8_screen_width(); +int get_pico8_screen_height(); + +// Input functions +void set_pico8_input(uint8_t buttons); +void pico8_key_down(int key); +void pico8_key_up(int key); + +// Audio functions +void get_pico8_audio_buffer(int16_t* buffer, size_t frames); +void set_pico8_audio_enabled(bool enabled); + +// Save state functions +bool load_pico8_state(const char* path); +bool save_pico8_state(const char* path); + +// Cartridge functions +bool load_pico8_cartridge(const uint8_t* data, size_t size); +const char* get_pico8_cartridge_title(); + +// PICO-8 button mapping +#define PICO8_BTN_LEFT 0x01 +#define PICO8_BTN_RIGHT 0x02 +#define PICO8_BTN_UP 0x04 +#define PICO8_BTN_DOWN 0x08 +#define PICO8_BTN_Z 0x10 // Primary action (A button) +#define PICO8_BTN_X 0x20 // Secondary action (B button) +#define PICO8_BTN_ENTER 0x40 // Start +#define PICO8_BTN_SHIFT 0x80 // Select + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/components/pico8/src/pico8.cpp b/components/pico8/src/pico8.cpp new file mode 100644 index 0000000..f8149e7 --- /dev/null +++ b/components/pico8/src/pico8.cpp @@ -0,0 +1,329 @@ +#include "pico8.hpp" +#include "logger.hpp" +#include "shared_memory.hpp" + +// Include femto8 headers (direct source integration) +extern "C" { +#include "femto8/pico8.h" +#include "femto8/pico8_types.h" +#include "femto8/pico8_config.h" +} + +static espp::Logger logger({.tag = "pico8", .level = espp::Logger::Verbosity::INFO}); + +// Static variables for emulator state +static bool pico8_initialized = false; +static uint8_t* video_buffer = nullptr; +static uint8_t current_buttons = 0; +static bool audio_enabled = true; + +// Memory management using shared_memory component +static uint8_t* pico8_ram = nullptr; +static uint8_t* pico8_vram = nullptr; +static uint8_t* lua_heap = nullptr; +static uint8_t* audio_buffers = nullptr; + +// Memory sizes +constexpr size_t PICO8_RAM_SIZE = 32768; // 32KB - PICO-8 RAM +constexpr size_t PICO8_VRAM_SIZE = 16384; // 16KB - Video RAM +constexpr size_t LUA_HEAP_SIZE = 65536; // 64KB - Lua heap +constexpr size_t AUDIO_BUFFER_SIZE = 8192; // 8KB - Audio buffers + +// PICO-8 16-color palette (RGB565 format for ESP32 display) +static const uint16_t pico8_palette[16] = { + 0x0000, // 0: Black + 0x1947, // 1: Dark Blue + 0x7827, // 2: Dark Purple + 0x0478, // 3: Dark Green + 0xA800, // 4: Brown + 0x5AA9, // 5: Dark Grey + 0xC618, // 6: Light Grey + 0xFFFF, // 7: White + 0xF800, // 8: Red + 0xFD00, // 9: Orange + 0xFFE0, // 10: Yellow + 0x0F80, // 11: Green + 0x2D7F, // 12: Blue + 0x83B3, // 13: Indigo + 0xFBB5, // 14: Pink + 0xFED7 // 15: Peach +}; + +// Initialize memory using shared_memory component +static bool init_pico8_memory() { + logger.info("Initializing PICO-8 memory using shared_memory component"); + + // Allocate memory blocks using shared_memory + pico8_ram = SharedMemory::get().allocate(PICO8_RAM_SIZE, "pico8_ram"); + if (!pico8_ram) { + logger.error("Failed to allocate PICO-8 RAM"); + return false; + } + + pico8_vram = SharedMemory::get().allocate(PICO8_VRAM_SIZE, "pico8_vram"); + if (!pico8_vram) { + logger.error("Failed to allocate PICO-8 VRAM"); + return false; + } + + lua_heap = SharedMemory::get().allocate(LUA_HEAP_SIZE, "pico8_lua"); + if (!lua_heap) { + logger.error("Failed to allocate Lua heap"); + return false; + } + + audio_buffers = SharedMemory::get().allocate(AUDIO_BUFFER_SIZE, "pico8_audio"); + if (!audio_buffers) { + logger.error("Failed to allocate audio buffers"); + return false; + } + + // Clear all allocated memory + memset(pico8_ram, 0, PICO8_RAM_SIZE); + memset(pico8_vram, 0, PICO8_VRAM_SIZE); + memset(lua_heap, 0, LUA_HEAP_SIZE); + memset(audio_buffers, 0, AUDIO_BUFFER_SIZE); + + logger.info("PICO-8 memory allocation successful:"); + logger.info(" RAM: {} bytes at {:p}", PICO8_RAM_SIZE, (void*)pico8_ram); + logger.info(" VRAM: {} bytes at {:p}", PICO8_VRAM_SIZE, (void*)pico8_vram); + logger.info(" Lua Heap: {} bytes at {:p}", LUA_HEAP_SIZE, (void*)lua_heap); + logger.info(" Audio: {} bytes at {:p}", AUDIO_BUFFER_SIZE, (void*)audio_buffers); + + return true; +} + +static void deinit_pico8_memory() { + logger.info("Deallocating PICO-8 memory"); + + if (pico8_ram) { + SharedMemory::get().deallocate(pico8_ram, "pico8_ram"); + pico8_ram = nullptr; + } + + if (pico8_vram) { + SharedMemory::get().deallocate(pico8_vram, "pico8_vram"); + pico8_vram = nullptr; + } + + if (lua_heap) { + SharedMemory::get().deallocate(lua_heap, "pico8_lua"); + lua_heap = nullptr; + } + + if (audio_buffers) { + SharedMemory::get().deallocate(audio_buffers, "pico8_audio"); + audio_buffers = nullptr; + } +} + +void init_pico8(const char* rom_filename, const uint8_t* rom_data, size_t rom_size) { + logger.info("Initializing PICO-8 emulator"); + + if (pico8_initialized) { + logger.warn("PICO-8 already initialized, deinitializing first"); + deinit_pico8(); + } + + // Initialize memory management + if (!init_pico8_memory()) { + logger.error("Failed to initialize PICO-8 memory"); + return; + } + + // Set up video buffer (using VRAM allocation) + video_buffer = pico8_vram; + + // Initialize femto8 core with our memory allocations + femto8_config_t config = { + .ram = pico8_ram, + .ram_size = PICO8_RAM_SIZE, + .vram = pico8_vram, + .vram_size = PICO8_VRAM_SIZE, + .lua_heap = lua_heap, + .lua_heap_size = LUA_HEAP_SIZE, + .audio_buffer = audio_buffers, + .audio_buffer_size = AUDIO_BUFFER_SIZE + }; + + if (femto8_init(&config) != 0) { + logger.error("Failed to initialize femto8 core"); + deinit_pico8_memory(); + return; + } + + // Load cartridge + if (rom_data && rom_size > 0) { + logger.info("Loading PICO-8 cartridge: {} ({} bytes)", rom_filename, rom_size); + if (femto8_load_cart(rom_data, rom_size) != 0) { + logger.error("Failed to load PICO-8 cartridge"); + deinit_pico8(); + return; + } + } + + pico8_initialized = true; + logger.info("PICO-8 emulator initialized successfully"); + logger.info("Total memory usage: {} KB", + (PICO8_RAM_SIZE + PICO8_VRAM_SIZE + LUA_HEAP_SIZE + AUDIO_BUFFER_SIZE) / 1024); +} + +void deinit_pico8() { + if (!pico8_initialized) { + return; + } + + logger.info("Deinitializing PICO-8 emulator"); + + // Cleanup femto8 core + femto8_deinit(); + + // Free memory allocations + deinit_pico8_memory(); + + video_buffer = nullptr; + pico8_initialized = false; + logger.info("PICO-8 emulator deinitialized"); +} + +void reset_pico8() { + if (!pico8_initialized) { + logger.warn("PICO-8 not initialized"); + return; + } + + logger.info("Resetting PICO-8 emulator"); + femto8_reset(); +} + +void run_pico8_frame() { + if (!pico8_initialized) { + return; + } + + // Update input state in femto8 + femto8_set_buttons(current_buttons); + + // Run one frame of emulation + femto8_run_frame(); + + // Video buffer is automatically updated by femto8 into our VRAM + // No need to copy since we're using shared memory +} + +std::span get_pico8_video_buffer() { + if (!video_buffer) { + return std::span(); + } + return std::span(video_buffer, 128 * 128); +} + +int get_pico8_screen_width() { + return 128; +} + +int get_pico8_screen_height() { + return 128; +} + +void set_pico8_input(uint8_t buttons) { + current_buttons = buttons; +} + +void pico8_key_down(int key) { + if (!pico8_initialized) return; + femto8_key_down(key); +} + +void pico8_key_up(int key) { + if (!pico8_initialized) return; + femto8_key_up(key); +} + +void get_pico8_audio_buffer(int16_t* buffer, size_t frames) { + if (!pico8_initialized || !audio_enabled || !buffer) { + // Fill with silence if not initialized or disabled + memset(buffer, 0, frames * 2 * sizeof(int16_t)); // Stereo + return; + } + + // Get audio samples from femto8 + femto8_get_audio_samples(buffer, frames); +} + +void set_pico8_audio_enabled(bool enabled) { + audio_enabled = enabled; + logger.info("PICO-8 audio {}", enabled ? "enabled" : "disabled"); + + if (pico8_initialized) { + femto8_set_audio_enabled(enabled); + } +} + +bool load_pico8_state(const char* path) { + if (!pico8_initialized || !path) { + return false; + } + + logger.info("Loading PICO-8 save state: {}", path); + return femto8_load_state(path) == 0; +} + +bool save_pico8_state(const char* path) { + if (!pico8_initialized || !path) { + return false; + } + + logger.info("Saving PICO-8 save state: {}", path); + return femto8_save_state(path) == 0; +} + +bool load_pico8_cartridge(const uint8_t* data, size_t size) { + if (!pico8_initialized || !data || size == 0) { + return false; + } + + logger.info("Loading PICO-8 cartridge ({} bytes)", size); + return femto8_load_cart(data, size) == 0; +} + +const char* get_pico8_cartridge_title() { + if (!pico8_initialized) { + return "Unknown"; + } + + // Extract title from loaded cartridge + const char* title = femto8_get_cart_title(); + return title ? title : "PICO-8 Game"; +} + +// ESP32-specific optimizations +#ifdef FEMTO8_ESP32 +// Memory statistics for debugging +void get_pico8_memory_stats() { + if (!pico8_initialized) { + logger.warn("PICO-8 not initialized"); + return; + } + + logger.info("PICO-8 Memory Statistics:"); + logger.info(" RAM usage: {}/{} bytes ({:.1f}%)", + femto8_get_ram_usage(), PICO8_RAM_SIZE, + (float)femto8_get_ram_usage() / PICO8_RAM_SIZE * 100.0f); + logger.info(" Lua heap usage: {}/{} bytes ({:.1f}%)", + femto8_get_lua_heap_usage(), LUA_HEAP_SIZE, + (float)femto8_get_lua_heap_usage() / LUA_HEAP_SIZE * 100.0f); +} + +// Performance monitoring +void get_pico8_performance_stats() { + if (!pico8_initialized) { + return; + } + + uint32_t frame_time = femto8_get_frame_time_us(); + uint32_t cpu_usage = femto8_get_cpu_usage_percent(); + + logger.info("PICO-8 Performance: {}μs/frame, {}% CPU", frame_time, cpu_usage); +} +#endif \ No newline at end of file diff --git a/main/carts_integration_example.hpp b/main/carts_integration_example.hpp new file mode 100644 index 0000000..8d871f5 --- /dev/null +++ b/main/carts_integration_example.hpp @@ -0,0 +1,112 @@ +#pragma once + +// This file shows the integration points for adding PICO-8 support to the main cart system + +// 1. Add to main/carts.hpp includes section: +#if defined(ENABLE_PICO8) +#include "pico8_cart.hpp" +#endif + +// 2. Add to the emulator enum in cart.hpp: +enum class Emulator { + UNKNOWN = 0, + NES, + GAMEBOY, + GAMEBOY_COLOR, + SMS, + GENESIS, + MSX, + DOOM, + PICO8, // <- Add this line +}; + +// 3. Add to the get_emulator_from_extension function: +static Emulator get_emulator_from_extension(const std::string& extension) { + if (extension == ".nes") return Emulator::NES; + if (extension == ".gb") return Emulator::GAMEBOY; + if (extension == ".gbc") return Emulator::GAMEBOY_COLOR; + if (extension == ".sms") return Emulator::SMS; + if (extension == ".gg") return Emulator::SMS; + if (extension == ".gen") return Emulator::GENESIS; + if (extension == ".md") return Emulator::GENESIS; + if (extension == ".rom") return Emulator::MSX; + if (extension == ".wad") return Emulator::DOOM; + if (extension == ".p8") return Emulator::PICO8; // <- Add this line + return Emulator::UNKNOWN; +} + +// 4. Add to the get_emulator_name function: +static std::string get_emulator_name(Emulator emulator) { + switch (emulator) { + case Emulator::NES: return "NES"; + case Emulator::GAMEBOY: return "Game Boy"; + case Emulator::GAMEBOY_COLOR: return "Game Boy Color"; + case Emulator::SMS: return "SMS/Game Gear"; + case Emulator::GENESIS: return "Genesis/Mega Drive"; + case Emulator::MSX: return "MSX"; + case Emulator::DOOM: return "Doom"; + case Emulator::PICO8: return "PICO-8"; // <- Add this line + default: return "Unknown"; + } +} + +// 5. Add to the cart creation factory function: +static std::unique_ptr make_cart(const Cart::Config& config) { + switch (config.info.platform) { +#if defined(ENABLE_NES) + case Emulator::NES: + return std::make_unique(config); +#endif +#if defined(ENABLE_GBC) + case Emulator::GAMEBOY: + case Emulator::GAMEBOY_COLOR: + return std::make_unique(config); +#endif +#if defined(ENABLE_SMS) + case Emulator::SMS: + return std::make_unique(config); +#endif +#if defined(ENABLE_GENESIS) + case Emulator::GENESIS: + return std::make_unique(config); +#endif +#if defined(ENABLE_MSX) + case Emulator::MSX: + return std::make_unique(config); +#endif +#if defined(ENABLE_DOOM) + case Emulator::DOOM: + return std::make_unique(config); +#endif +#if defined(ENABLE_PICO8) + case Emulator::PICO8: // <- Add this case + return std::make_unique(config); +#endif + default: + return nullptr; + } +} + +// 6. Add to main/CMakeLists.txt in the SRCS list: +/* +set(SRCS + main.cpp + cart.hpp + carts.hpp + doom_cart.hpp + gbc_cart.hpp + genesis_cart.hpp + heap_utils.hpp + msx_cart.hpp + nes_cart.hpp + pico8_cart.hpp # <- Add this line + sms_cart.hpp +) +*/ + +// 7. Add conditional compilation in main/CMakeLists.txt: +/* +if(CONFIG_ENABLE_PICO8) + list(APPEND COMPONENT_REQUIRES pico8) +endif() +*/ \ No newline at end of file diff --git a/main/pico8_cart.hpp b/main/pico8_cart.hpp new file mode 100644 index 0000000..61f56f4 --- /dev/null +++ b/main/pico8_cart.hpp @@ -0,0 +1,140 @@ +#pragma once + +#include "cart.hpp" +#if defined(ENABLE_PICO8) +#include "pico8.hpp" +#endif + +class Pico8Cart : public Cart { +public: + + explicit Pico8Cart(const Cart::Config& config) + : Cart(config) { + handle_video_setting(); + init(); + } + + virtual ~Pico8Cart() override { + deinit(); + } + + // cppcheck-suppress uselessOverride + virtual void reset() override { + Cart::reset(); +#if defined(ENABLE_PICO8) + reset_pico8(); +#endif + } + + // cppcheck-suppress uselessOverride + virtual void load() override { + Cart::load(); +#if defined(ENABLE_PICO8) + load_pico8_state(get_save_path().c_str()); +#endif + } + + // cppcheck-suppress uselessOverride + virtual void save() override { + Cart::save(); +#if defined(ENABLE_PICO8) + save_pico8_state(get_save_path(true).c_str()); +#endif + } + + void init() { +#if defined(ENABLE_PICO8) + init_pico8(get_rom_filename().c_str(), romdata_, rom_size_bytes_); +#endif + } + + void deinit() { +#if defined(ENABLE_PICO8) + deinit_pico8(); +#endif + } + + // cppcheck-suppress uselessOverride + virtual bool run() override { +#if defined(ENABLE_PICO8) + // Update input + uint8_t buttons = 0; + auto input = get_input(); + if (input.left) buttons |= PICO8_BTN_LEFT; + if (input.right) buttons |= PICO8_BTN_RIGHT; + if (input.up) buttons |= PICO8_BTN_UP; + if (input.down) buttons |= PICO8_BTN_DOWN; + if (input.a) buttons |= PICO8_BTN_Z; // Primary action + if (input.b) buttons |= PICO8_BTN_X; // Secondary action + if (input.start) buttons |= PICO8_BTN_ENTER; + if (input.select) buttons |= PICO8_BTN_SHIFT; + + set_pico8_input(buttons); + + // Run one frame + run_pico8_frame(); +#endif + return Cart::run(); + } + +protected: + // PICO-8 native resolution + static constexpr size_t PICO8_WIDTH = 128; + static constexpr size_t PICO8_HEIGHT = 128; + + // cppcheck-suppress uselessOverride + virtual void pre_menu() override { + Cart::pre_menu(); +#if defined(ENABLE_PICO8) + logger_.info("pico8::pre_menu()"); +#endif + } + + // cppcheck-suppress uselessOverride + virtual void post_menu() override { + Cart::post_menu(); +#if defined(ENABLE_PICO8) + logger_.info("pico8::post_menu()"); +#endif + } + + virtual void set_original_video_setting() override { +#if defined(ENABLE_PICO8) + logger_.info("pico8::video: original"); + BoxEmu::get().display_size(PICO8_WIDTH, PICO8_HEIGHT); +#endif + } + + virtual std::pair get_video_size() const override { + return std::make_pair(PICO8_WIDTH, PICO8_HEIGHT); + } + + // cppcheck-suppress uselessOverride + virtual std::span get_video_buffer() const override { +#if defined(ENABLE_PICO8) + return get_pico8_video_buffer(); +#else + return std::span(); +#endif + } + + virtual void set_fit_video_setting() override { +#if defined(ENABLE_PICO8) + logger_.info("pico8::video: fit"); + // PICO-8 is square, so fit to smallest screen dimension + int min_dimension = std::min(SCREEN_WIDTH, SCREEN_HEIGHT); + BoxEmu::get().display_size(min_dimension, min_dimension); +#endif + } + + virtual void set_fill_video_setting() override { +#if defined(ENABLE_PICO8) + logger_.info("pico8::video: fill"); + BoxEmu::get().display_size(SCREEN_WIDTH, SCREEN_HEIGHT); +#endif + } + + virtual std::string get_save_extension() const override { + return "_p8.sav"; + } +}; \ No newline at end of file