23#include "imgui/imgui.h"
36constexpr int kNativeSampleRate = 32040;
90 auto* wasm_backend =
static_cast<audio::WasmAudioBackend*
>(
audio_backend_.get());
91 wasm_backend->HandleUserInteraction();
99 int safe_type = std::clamp(type, 0, 4);
105 return static_cast<int>(
snes_.
apu().dsp().interpolation_type);
109 const std::vector<uint8_t>& rom_data) {
115 const char* env_value = std::getenv(
"YAZE_USE_SDL_AUDIO_STREAM");
116 if (env_value && std::atoi(env_value) != 0) {
147 LOG_ERROR(
"Emulator",
"Failed to initialize audio backend");
149 LOG_INFO(
"Emulator",
"Audio backend initialized: %s",
156 snes_.
cpu().on_breakpoint_hit_ = [
this](uint32_t pc) ->
bool {
162 snes_.
cpu().on_instruction_executed_ =
163 [
this](uint32_t address, uint8_t opcode,
164 const std::vector<uint8_t>& operands,
const std::string& mnemonic,
165 const std::string& operand_str) {
167 mnemonic, operand_str);
196 LOG_ERROR(
"Emulator",
"Failed to initialize audio backend");
199 LOG_INFO(
"Emulator",
"Audio backend initialized for headless mode");
201 LOG_INFO(
"Emulator",
"Using external (shared) audio backend");
212 const double frame_rate =
snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
217 wanted_samples_ =
static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
224 LOG_INFO(
"Emulator",
"SNES initialized for headless mode");
230 const double frame_rate =
snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
232 wanted_samples_ =
static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
253 audio_backend_->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
256 audio_backend_->SetAudioStreamResampling(
false, kNativeSampleRate, 2);
263 uint64_t current_count = SDL_GetPerformanceCounter();
277 int frames_processed = 0;
278 constexpr int kMaxFramesPerUpdate = 2;
281 static int16_t native_audio_buffer[2048];
296 const uint32_t max_buffer =
static_cast<uint32_t
>(
wanted_samples_ * 6);
298 if (status.queued_frames < max_buffer) {
304 kNativeSampleRate)) {
305 static int log_counter = 0;
306 if (++log_counter % 60 == 0) {
309 "Resampling failed (Native=%d, Backend=%d) - Dropping "
310 "audio to prevent speedup/pitch shift",
311 kNativeSampleRate, backend_rate);
340 static int entry_count = 0;
341 if (entry_count < 5 || entry_count % 300 == 0) {
342 LOG_INFO(
"Emulator",
"RunAudioFrame ENTRY #%d: init=%d, running=%d, backend=%p (external=%p, owned=%p)",
344 static_cast<void*
>(backend),
351 static int skip_count = 0;
352 if (skip_count < 5) {
353 LOG_WARN(
"Emulator",
"RunAudioFrame SKIPPED: init=%d, running=%d",
363 backend->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
375 static int16_t audio_buffer[2048];
378 bool queued = backend->QueueSamplesNative(
382 static int frame_log_count = 0;
383 if (frame_log_count < 5 || frame_log_count % 300 == 0) {
384 LOG_INFO(
"Emulator",
"RunAudioFrame: wanted=%d, queued=%s, stream=%s",
390 if (!queued && backend->SupportsAudioStream()) {
392 LOG_INFO(
"Emulator",
"RunAudioFrame: First queue failed, re-enabling resampling");
393 backend->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
395 queued = backend->QueueSamplesNative(
397 LOG_INFO(
"Emulator",
"RunAudioFrame: Retry queued=%s", queued ?
"YES" :
"NO");
401 LOG_WARN(
"Emulator",
"RunAudioFrame: AUDIO DROPPED - resampling not working!");
408 const char* env_value = std::getenv(
"YAZE_USE_SDL_AUDIO_STREAM");
409 if (env_value && std::atoi(env_value) != 0) {
417 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
418 "Emulator renderer not initialized");
441 LOG_ERROR(
"Emulator",
"Failed to initialize audio backend");
443 LOG_INFO(
"Emulator",
"Audio backend initialized (lazy): %s",
454 LOG_ERROR(
"Emulator",
"Failed to initialize input manager");
457 LOG_INFO(
"Emulator",
"Input manager initialized: %s",
471 512, 480, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING);
473 printf(
"Failed to create PPU texture: %s\n", SDL_GetError());
492 const double frame_rate =
snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
496 wanted_samples_ =
static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
515 static bool was_running_before_resize =
false;
519 was_running_before_resize =
true;
522 was_running_before_resize) {
525 was_running_before_resize =
false;
537 uint64_t current_count = SDL_GetPerformanceCounter();
549 int frames_to_process = 0;
560 if (frames_to_process > max_frames) {
564 frames_to_process = max_frames;
569 constexpr int kTurboFrames = 8;
570 for (
int i = 0; i < kTurboFrames; i++) {
579 frames_to_process = 1;
585 for (
int i = 0; i < frames_to_process; i++) {
587 uint64_t frame_start = SDL_GetPerformanceCounter();
588 bool should_render = (i == frames_to_process - 1);
589 uint32_t queued_frames = 0;
590 float audio_rms_left = 0.0f;
591 float audio_rms_right = 0.0f;
606 int16_t temp_audio_buffer[2048];
611 LOG_INFO(
"Emulator",
"Enabling audio stream resampling (32040Hz -> Device Rate)");
612 audio_backend_->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
615 LOG_INFO(
"Emulator",
"Disabling audio stream resampling");
616 audio_backend_->SetAudioStreamResampling(
false, kNativeSampleRate, 2);
623 queued_frames = audio_status.queued_frames;
626 const uint32_t max_buffer = samples_per_frame * 6;
627 const uint32_t optimal_buffer = 2048;
629 if (queued_frames < max_buffer) {
636 auto compute_rms = [&](
int total_samples) {
637 if (total_samples <= 0 || frame_buffer ==
nullptr) {
638 audio_rms_left = 0.0f;
639 audio_rms_right = 0.0f;
644 const int frames = total_samples / 2;
645 for (
int s = 0; s < frames; ++s) {
646 const float l =
static_cast<float>(frame_buffer[2 * s]);
647 const float r =
static_cast<float>(frame_buffer[2 * s + 1]);
652 frames > 0 ? std::sqrt(sum_l / frames) / 32768.0f : 0.0f;
654 frames > 0 ? std::sqrt(sum_r / frames) / 32768.0f : 0.0f;
656 compute_rms(num_samples);
660 int effective_rate = kNativeSampleRate;
661 if (queued_frames > optimal_buffer + 256) {
662 effective_rate += 60;
663 }
else if (queued_frames < optimal_buffer - 256) {
664 effective_rate -= 60;
672 audio_backend_->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
680 static int error_count = 0;
681 if (++error_count % 300 == 0) {
683 "Resampling failed, dropping audio to prevent 1.5x speed "
690 static int overflow_count = 0;
691 if (++overflow_count % 60 == 0) {
693 "Audio buffer overflow (count: %d, queued: %u)",
694 overflow_count, queued_frames);
712 const uint64_t frame_end = SDL_GetPerformanceCounter();
713 const double elapsed_ms =
715 (
static_cast<double>(frame_end - frame_start) /
719 audio_rms_left, audio_rms_right);
730#ifndef __EMSCRIPTEN__
748 uint64_t dma_bytes, uint64_t vram_bytes,
749 float audio_rms_left,
float audio_rms_right) {
753 static_cast<float>(audio_frames);
755 static_cast<float>(dma_bytes);
757 static_cast<float>(vram_bytes);
769 int head,
int count) {
770 std::vector<float> out;
774 for (
int i = 0; i < count; ++i) {
776 out.push_back(data[idx]);
818 constexpr size_t kBankSize = 0x8000;
822 const size_t bank_count =
rom_data_.size() / kBankSize;
823 std::vector<float> free_bytes;
824 free_bytes.reserve(bank_count);
825 for (
size_t bank = 0; bank < bank_count; ++bank) {
826 size_t free_count = 0;
827 const size_t base = bank * kBankSize;
828 for (
size_t i = 0; i < kBankSize && (base + i) <
rom_data_.size(); ++i) {
833 free_bytes.push_back(
static_cast<float>(free_count));
865 if (cpu_visible && *cpu_visible) {
866 if (cpu_card.
Begin(cpu_visible)) {
874 if (ppu_visible && *ppu_visible) {
875 if (ppu_card.
Begin(ppu_visible)) {
882 bool* memory_visible =
884 if (memory_visible && *memory_visible) {
885 if (memory_card.
Begin(memory_visible)) {
891 bool* breakpoints_visible =
893 if (breakpoints_visible && *breakpoints_visible) {
894 if (breakpoints_card.
Begin(breakpoints_visible)) {
897 breakpoints_card.
End();
900 bool* performance_visible =
902 if (performance_visible && *performance_visible) {
903 if (performance_card.
Begin(performance_visible)) {
906 performance_card.
End();
909 bool* ai_agent_visible =
911 if (ai_agent_visible && *ai_agent_visible) {
912 if (ai_card.
Begin(ai_agent_visible)) {
918 bool* save_states_visible =
920 if (save_states_visible && *save_states_visible) {
921 if (save_states_card.
Begin(save_states_visible)) {
924 save_states_card.
End();
927 bool* keyboard_config_visible =
929 if (keyboard_config_visible && *keyboard_config_visible) {
930 if (keyboard_card.
Begin(keyboard_config_visible)) {
939 bool* virtual_controller_visible =
941 if (virtual_controller_visible && *virtual_controller_visible) {
942 if (controller_card.
Begin(virtual_controller_visible)) {
945 controller_card.
End();
948 bool* apu_debugger_visible =
950 if (apu_debugger_visible && *apu_debugger_visible) {
951 if (apu_card.
Begin(apu_debugger_visible)) {
957 bool* audio_mixer_visible =
959 if (audio_mixer_visible && *audio_mixer_visible) {
960 if (audio_card.
Begin(audio_mixer_visible)) {
966 }
catch (
const std::exception& e) {
968 ImGui::Text(
"Error loading emulator UI: %s", e.what());
969 if (ImGui::Button(
"Retry")) {
972 theme_manager.InitializeBuiltInThemes();
1005 const auto& theme = theme_manager.GetCurrentTheme();
1028 static char bp_addr[16] =
"00FFD9";
1030 ImGui::PushItemWidth(100);
1031 ImGui::InputText(
"##BPAddr", bp_addr, IM_ARRAYSIZE(bp_addr),
1032 ImGuiInputTextFlags_CharsHexadecimal |
1033 ImGuiInputTextFlags_CharsUppercase);
1034 ImGui::PopItemWidth();
1037 uint32_t addr = std::strtoul(bp_addr,
nullptr, 16);
1041 absl::StrFormat(
"BP at $%06X", addr));
1045 ImGui::BeginChild(
"##BPList", ImVec2(0, 100),
true);
1048 bool enabled = bp.enabled;
1049 if (ImGui::Checkbox(absl::StrFormat(
"##en%d", bp.id).c_str(),
1054 ImGui::Text(
"$%06X", bp.address);
1056 ImGui::TextDisabled(
"(hits: %d)", bp.hit_count);
1058 if (ImGui::SmallButton(
1068 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"CPU Status");
1069 ImGui::PushStyleColor(ImGuiCol_ChildBg,
1070 ConvertColorToImVec4(theme.child_bg));
1071 ImGui::BeginChild(
"##CpuStatus", ImVec2(0, 180),
true);
1074 if (ImGui::BeginTable(
1076 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1077 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed, 60);
1078 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 80);
1079 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed, 60);
1080 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 80);
1081 ImGui::TableHeadersRow();
1083 ImGui::TableNextRow();
1084 ImGui::TableNextColumn();
1086 ImGui::TableNextColumn();
1087 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1089 ImGui::TableNextColumn();
1091 ImGui::TableNextColumn();
1092 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1095 ImGui::TableNextRow();
1096 ImGui::TableNextColumn();
1098 ImGui::TableNextColumn();
1099 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1101 ImGui::TableNextColumn();
1103 ImGui::TableNextColumn();
1104 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1107 ImGui::TableNextRow();
1108 ImGui::TableNextColumn();
1110 ImGui::TableNextColumn();
1111 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1113 ImGui::TableNextColumn();
1115 ImGui::TableNextColumn();
1116 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1119 ImGui::TableNextRow();
1120 ImGui::TableNextColumn();
1122 ImGui::TableNextColumn();
1123 ImGui::TextColored(ConvertColorToImVec4(theme.success),
"0x%04X",
1125 ImGui::TableNextColumn();
1127 ImGui::TableNextColumn();
1128 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1131 ImGui::TableNextRow();
1132 ImGui::TableNextColumn();
1134 ImGui::TableNextColumn();
1135 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
"0x%02X",
1137 ImGui::TableNextColumn();
1138 ImGui::Text(
"Cycle");
1139 ImGui::TableNextColumn();
1140 ImGui::TextColored(ConvertColorToImVec4(theme.info),
"%llu",
1147 ImGui::PopStyleColor();
1150 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"SPC700 Status");
1151 ImGui::PushStyleColor(ImGuiCol_ChildBg,
1152 ConvertColorToImVec4(theme.child_bg));
1153 ImGui::BeginChild(
"##SpcStatus", ImVec2(0, 150),
true);
1155 if (ImGui::BeginTable(
1157 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1158 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed, 50);
1159 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 60);
1160 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed, 50);
1161 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 60);
1162 ImGui::TableHeadersRow();
1164 ImGui::TableNextRow();
1165 ImGui::TableNextColumn();
1167 ImGui::TableNextColumn();
1168 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1170 ImGui::TableNextColumn();
1172 ImGui::TableNextColumn();
1173 ImGui::TextColored(ConvertColorToImVec4(theme.success),
"0x%04X",
1176 ImGui::TableNextRow();
1177 ImGui::TableNextColumn();
1179 ImGui::TableNextColumn();
1180 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1182 ImGui::TableNextColumn();
1184 ImGui::TableNextColumn();
1185 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1188 ImGui::TableNextRow();
1189 ImGui::TableNextColumn();
1191 ImGui::TableNextColumn();
1192 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1194 ImGui::TableNextColumn();
1196 ImGui::TableNextColumn();
1198 ConvertColorToImVec4(theme.warning),
"0x%02X",
1205 ImGui::PopStyleColor();
1208 if (ImGui::CollapsingHeader(
"Disassembly Viewer",
1209 ImGuiTreeNodeFlags_DefaultOpen)) {
1210 uint32_t current_pc =
1212 auto& disasm =
snes_.
cpu().disassembly_viewer();
1213 if (disasm.IsAvailable()) {
1214 disasm.Render(current_pc,
snes_.
cpu().breakpoints_);
1216 ImGui::TextColored(ConvertColorToImVec4(theme.error),
1217 "Disassembly viewer unavailable.");
1220 }
catch (
const std::exception& e) {
1223 ImGui::PopStyleColor();
1227 ImGui::Text(
"CPU Debugger Error: %s", e.what());
1242 const std::vector<InstructionEntry>& instruction_log) {
1250 const auto& theme = theme_manager.GetCurrentTheme();
1252 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
1254 ImGui::TextWrapped(
"Save state functionality will be implemented here.");
1279 if (ImGui::SliderFloat(
"Master Volume", &volume, 0.0f, 1.0f,
"%.2f")) {
1284 ImGui::Text(
"Channel Mutes (Debug)");
1288 if (ImGui::BeginTable(
"AudioChannels", 4)) {
1289 for (
int i = 0; i < 8; ++i) {
1290 ImGui::TableNextColumn();
1291 bool mute = dsp.GetChannelMute(i);
1292 std::string label =
"Ch " + std::to_string(i + 1);
1293 if (ImGui::Checkbox(label.c_str(), &mute)) {
1294 dsp.SetChannelMute(i, mute);
1301 if (ImGui::Button(
"Mute All")) {
1302 for (
int i = 0; i < 8; ++i) dsp.SetChannelMute(i,
true);
1305 if (ImGui::Button(
"Unmute All")) {
1306 for (
int i = 0; i < 8; ++i) dsp.SetChannelMute(i,
false);
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
const auto & vector() const
bool * GetVisibilityFlag(size_t session_id, const std::string &base_card_id)
bool ShouldBreakOnExecute(uint32_t pc, CpuType cpu)
Check if execution should break at this address.
void RemoveBreakpoint(uint32_t id)
Remove a breakpoint by ID.
void SetEnabled(uint32_t id, bool enabled)
Enable or disable a breakpoint.
std::vector< Breakpoint > GetAllBreakpoints() const
Get all breakpoints.
uint32_t AddBreakpoint(uint32_t address, Type type, CpuType cpu, const std::string &condition="", const std::string &description="")
Add a new breakpoint.
std::array< float, kMetricHistorySize > audio_queue_history_
gfx::IRenderer * renderer()
void Initialize(gfx::IRenderer *renderer, const std::vector< uint8_t > &rom_data)
void RenderModernCpuDebugger()
std::unique_ptr< audio::IAudioBackend > audio_backend_
std::vector< float > FrameTimeHistory() const
void SetInputConfig(const input::InputConfig &config)
std::array< float, kMetricHistorySize > vram_bytes_history_
int metric_history_count_
bool audio_stream_env_checked_
static constexpr int kMetricHistorySize
void set_use_sdl_audio_stream(bool enabled)
std::vector< float > RomBankFreeBytes() const
std::array< float, kMetricHistorySize > audio_rms_left_history_
std::vector< float > AudioRmsRightHistory() const
void RenderMemoryViewer()
audio::IAudioBackend * external_audio_backend_
bool audio_stream_config_dirty_
std::array< float, kMetricHistorySize > frame_time_history_
void set_interpolation_type(int type)
bool use_sdl_audio_stream_
std::vector< float > VramBytesHistory() const
std::array< float, kMetricHistorySize > fps_history_
std::vector< float > AudioQueueHistory() const
void RenderKeyboardConfig()
std::vector< float > DmaBytesHistory() const
debug::DisassemblyViewer disassembly_viewer_
std::function< void(const input::InputConfig &) input_config_changed_callback_)
void PushFrameMetrics(float frame_ms, uint32_t audio_frames, uint64_t dma_bytes, uint64_t vram_bytes, float audio_rms_left, float audio_rms_right)
std::vector< uint8_t > rom_data_
bool audio_stream_active_
void RenderPerformanceMonitor()
bool EnsureInitialized(Rom *rom)
void RenderAIAgentPanel()
input::InputConfig input_config_
void RenderBreakpointList()
BreakpointManager breakpoint_manager_
editor::PanelManager * panel_manager_
std::vector< float > FpsHistory() const
input::InputManager input_manager_
std::array< float, kMetricHistorySize > audio_rms_right_history_
void RenderCpuInstructionLog(const std::vector< InstructionEntry > &instructionLog)
std::vector< float > AudioRmsLeftHistory() const
audio::IAudioBackend * audio_backend()
std::array< float, kMetricHistorySize > dma_bytes_history_
void RenderEmulatorInterface()
gfx::IRenderer * renderer_
int get_interpolation_type() const
auto mutable_cycles() -> uint64_t &
void SetSamples(int16_t *sample_data, int wanted_samples)
void Reset(bool hard=false)
uint64_t vram_bytes_frame() const
void Init(const std::vector< uint8_t > &rom_data)
uint64_t dma_bytes_frame() const
auto memory() -> MemoryImpl &
void SetPixels(uint8_t *pixel_data)
static std::unique_ptr< IAudioBackend > Create(BackendType type)
void RecordInstruction(uint32_t address, uint8_t opcode, const std::vector< uint8_t > &operands, const std::string &mnemonic, const std::string &operand_str)
Record an instruction execution.
Defines an abstract interface for all rendering operations.
virtual void UnlockTexture(TextureHandle texture)=0
virtual bool LockTexture(TextureHandle texture, SDL_Rect *rect, void **pixels, int *pitch)=0
virtual TextureHandle CreateTextureWithFormat(int width, int height, uint32_t format, int access)=0
Creates a new texture with a specific pixel format.
Draggable, dockable panel for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
static ThemeManager & Get()
#define ICON_MD_PLAY_ARROW
#define ICON_MD_VIDEOGAME_ASSET
#define ICON_MD_BUG_REPORT
#define ICON_MD_MUSIC_NOTE
#define ICON_MD_SKIP_NEXT
#define ICON_MD_SPORTS_ESPORTS
#define ICON_MD_AUDIO_FILE
#define ICON_MD_SMART_TOY
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
bool g_window_is_resizing
constexpr double kNtscFrameRate
constexpr double kSpeedCalibration
constexpr double kPalFrameRate
constexpr int kMusicEditorSampleRate
std::vector< float > CopyHistoryOrdered(const std::array< float, Emulator::kMetricHistorySize > &data, int head, int count)
void RenderKeyboardConfig(input::InputManager *manager, const std::function< void(const input::InputConfig &)> &on_config_changed)
Render keyboard configuration UI.
void RenderPerformanceMonitor(Emulator *emu)
Performance metrics (FPS, frame time, audio status)
void RenderAIAgentPanel(Emulator *emu)
AI Agent panel for automated testing/gameplay.
void RenderSnesPpu(Emulator *emu)
SNES PPU output display.
void RenderNavBar(Emulator *emu)
Navigation bar with play/pause, step, reset controls.
void RenderBreakpointList(Emulator *emu)
Breakpoint list and management.
void RenderApuDebugger(Emulator *emu)
APU/Audio debugger with handshake tracker.
void RenderMemoryViewer(Emulator *emu)
Memory viewer/editor.
void RenderCpuInstructionLog(Emulator *emu, uint32_t log_size)
CPU instruction log (legacy, prefer DisassemblyViewer)
void RenderVirtualController(Emulator *emu)
Virtual SNES controller for testing input without keyboard Useful for debugging input issues - bypass...