Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve genesis emulation #88

Merged
merged 8 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ ESP32-S3-BOX-3 which provides:
- NES Emulator (nofrendo)
- Gameboy / Gameboy Color emulator (gnuboy)
- Sega Master System / Game Gear emulator (smsplus)
- Genesis emulator (gwenesis); NOTE: this is a WIP and does not support full-speed / sound / savestates yet.
- Genesis emulator (gwenesis) - full speed / buttery smooth when muted; unmuted it runs a little slower but has nice sound
- LVGL main menu with rom select (including boxart display) and settings page
(all generated from Squareline Studio)
- LVGL emulation paused menu with save slot select, save slot image display,
Expand Down
4 changes: 2 additions & 2 deletions components/genesis/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ idf_component_register(
REQUIRES box-emu statistics
)
# target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-char-subscripts -Wno-attributes -Wno-implicit-fallthrough -Wno-unused-function -Wno-unused-variable -Wno-discarded-qualifiers)
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-const-variable -Wno-unused-value -O3)
# target_compile_definitions(${COMPONENT_LIB} PRIVATE GWENESIS_AUDIO_ACCURATE=0)
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-const-variable -Wno-unused-value -Ofast)
target_compile_definitions(${COMPONENT_LIB} PRIVATE GWENESIS_AUDIO_ACCURATE=0)
2 changes: 0 additions & 2 deletions components/genesis/gwenesis/src/bus/gwenesis_bus.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ __license__ = "GPLv3"
#define GWENESIS_REFRESH_RATE_PAL 50
#define GWENESIS_AUDIO_FREQ_PAL 52781

#define GWENESIS_AUDIO_ACCURATE 0

#define Z80_FREQ_DIVISOR 14 // Frequency divisor to Z80 clock
#define VDP_CYCLES_PER_LINE 3420// VDP Cycles per Line
#define SCREEN_WIDTH 320
Expand Down
39 changes: 27 additions & 12 deletions components/genesis/src/genesis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ static uint8_t *frame_buffer = nullptr;

extern unsigned char* VRAM;
extern int zclk;
int system_clock;
static int system_clock;
int scan_line;

int16_t *gwenesis_sn76489_buffer = nullptr;
Expand All @@ -46,7 +46,9 @@ int16_t *gwenesis_ym2612_buffer = nullptr;
int ym2612_index;
int ym2612_clock;

static int frameskip = 3;
static constexpr int full_frameskip = 3;
static constexpr int muted_frameskip = 2;
static int frameskip = full_frameskip;

static FILE *savestate_fp = NULL;
static int savestate_errors = 0;
Expand Down Expand Up @@ -185,8 +187,9 @@ void IRAM_ATTR run_genesis_rom() {
static GamepadState previous_state = {};
auto state = BoxEmu::get().gamepad_state();

// set frameskip to be 3 if muted, 60 otherwise
frameskip = 3; // hal::is_muted() ? 3 : 60;
bool sound_enabled = !espp::EspBox::get().is_muted();

frameskip = sound_enabled ? full_frameskip : muted_frameskip;

if (previous_state != state) {
// button mapping:
Expand Down Expand Up @@ -224,8 +227,6 @@ void IRAM_ATTR run_genesis_rom() {

gwenesis_vdp_render_config();

bool sound_enabled = !espp::EspBox::get().is_muted();

/* Reset the difference clocks and audio index */
system_clock = 0;
zclk = sound_enabled ? 0 : 0x1000000;
Expand All @@ -238,7 +239,7 @@ void IRAM_ATTR run_genesis_rom() {

scan_line = 0;

int _vdp_cycles_per_line = VDP_CYCLES_PER_LINE / 2;
static constexpr int _vdp_cycles_per_line = VDP_CYCLES_PER_LINE;

while (scan_line < lines_per_frame) {
system_clock += _vdp_cycles_per_line;
Expand All @@ -251,7 +252,7 @@ void IRAM_ATTR run_genesis_rom() {
* =1 : cycle accurate mode. audio is refreshed when CPUs are performing a R/W access
* =0 : line accurate mode. audio is refreshed every lines.
*/
if (GWENESIS_AUDIO_ACCURATE == 0) {
if (GWENESIS_AUDIO_ACCURATE == 0 && sound_enabled) {
gwenesis_SN76489_run(system_clock);
ym2612_run(system_clock);
}
Expand Down Expand Up @@ -292,14 +293,13 @@ void IRAM_ATTR run_genesis_rom() {
if (scan_line == (screen_height + 1)) {
z80_irq_line(0);
}

} // end of scanline loop

/* Audio
* synchronize YM2612 and SN76489 to system_clock
* it completes the missing audio sample for accurate audio mode
*/
if (GWENESIS_AUDIO_ACCURATE == 1) {
if (GWENESIS_AUDIO_ACCURATE == 1 && sound_enabled) {
gwenesis_SN76489_run(system_clock);
ym2612_run(system_clock);
}
Expand All @@ -324,8 +324,21 @@ void IRAM_ATTR run_genesis_rom() {

if (sound_enabled) {
// push the audio buffer to the audio task
int audio_len = REG1_PAL ? GWENESIS_AUDIO_BUFFER_LENGTH_PAL : GWENESIS_AUDIO_BUFFER_LENGTH_NTSC;
espp::EspBox::get().play_audio((uint8_t*)gwenesis_ym2612_buffer, audio_len);
int audio_len = std::max(sn76489_index, ym2612_index);
// Mix gwenesis_sn76489_buffer and gwenesis_ym2612_buffer together
const int16_t* sn76489_buffer = gwenesis_sn76489_buffer;
const int16_t* ym2612_buffer = gwenesis_ym2612_buffer;
for (int i = 0; i < audio_len; i++) {
int16_t sample = 0;
if (sn76489_index < audio_len) {
sample += sn76489_buffer[sn76489_index];
}
if (ym2612_index < audio_len) {
sample += ym2612_buffer[ym2612_index];
}
gwenesis_sn76489_buffer[i] = sample;
}
espp::EspBox::get().play_audio((uint8_t*)gwenesis_ym2612_buffer, audio_len * sizeof(int16_t));
}

// manage statistics
Expand All @@ -336,6 +349,8 @@ void IRAM_ATTR run_genesis_rom() {
if (elapsed < max_frame_time) {
auto sleep_time = (max_frame_time - elapsed) / 1e3;
std::this_thread::sleep_for(sleep_time * std::chrono::milliseconds(1));
} else {
vTaskDelay(1);
}
}

Expand Down
6 changes: 5 additions & 1 deletion main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ extern "C" void app_main(void) {
logger.info("Bootup");

// initialize the hardware abstraction layer
BoxEmu &emu = BoxEmu::get();
espp::EspBox &box = espp::EspBox::get();
logger.info("Running on {}", box.box_type());
BoxEmu &emu = BoxEmu::get();
logger.info("Box Emu version: {}", emu.version());

// initialize
Expand Down Expand Up @@ -78,6 +78,10 @@ extern "C" void app_main(void) {

print_heap_state();

// set the task priority (for main) to high
vTaskPrioritySet(nullptr, 20);

// main loop
while (true) {
// reset gui ready to play and user_quit
gui.ready_to_play(false);
Expand Down
6 changes: 3 additions & 3 deletions patches.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ else
echo "Applying patches to esp-idf in '${IDF_PATH}'"
fi

lodestone_dir=$(pwd)
cur_dir=$(pwd)
patches=($(find patches -type f))
cd "${IDF_PATH}"
for patch in "${patches[@]}"; do
echo "Applying patch: ${patch}"
git apply ${lodestone_dir}/${patch}
git apply ${cur_dir}/${patch}
done

cd ${lodestone_dir}
cd ${cur_dir}
11 changes: 11 additions & 0 deletions sdkconfig.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,14 @@ CONFIG_LV_USE_THEME_DEFAULT=y
CONFIG_LV_THEME_DEFAULT_DARK=y
CONFIG_LV_THEME_DEFAULT_GROW=y
CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=30

CONFIG_I2S_ISR_IRAM_SAFE=y
CONFIG_I2C_ISR_IRAM_SAFE=y
CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y
CONFIG_GDMA_ISR_IRAM_SAFE=y
CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=n

CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y

CONFIG_SPI_FLASH_ROM_IMPL=y