8#include "absl/strings/str_format.h"
25#include "imgui/imgui.h"
38constexpr int kNativeSampleRate = 32040;
94 wasm_backend->HandleUserInteraction();
103 int safe_type = std::clamp(type, 0, 4);
104 snes_.
apu().dsp().interpolation_type =
111 return static_cast<int>(
snes_.
apu().dsp().interpolation_type);
115 const std::vector<uint8_t>& rom_data) {
121 const char* env_value = std::getenv(
"YAZE_USE_SDL_AUDIO_STREAM");
122 if (env_value && std::atoi(env_value) != 0) {
153 LOG_ERROR(
"Emulator",
"Failed to initialize audio backend");
155 LOG_INFO(
"Emulator",
"Audio backend initialized: %s",
162 snes_.
cpu().on_breakpoint_hit_ = [
this](uint32_t pc) ->
bool {
168 snes_.
cpu().on_instruction_executed_ =
169 [
this](uint32_t address, uint8_t opcode,
170 const std::vector<uint8_t>& operands,
const std::string& mnemonic,
171 const std::string& operand_str) {
173 mnemonic, operand_str);
187 LOG_INFO(
"Emulator",
"Using external (shared) audio backend");
203 if (!backend->Initialize(config)) {
205 "Failed to initialize audio backend; falling back to Null");
208 if (!backend->Initialize(config)) {
209 LOG_ERROR(
"Emulator",
"Failed to initialize Null audio backend");
216 LOG_INFO(
"Emulator",
"Audio backend initialized for headless mode: %s",
228 const double frame_rate =
229 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
235 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
242 LOG_INFO(
"Emulator",
"SNES initialized for headless mode");
248 const double frame_rate =
249 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
252 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
259 if (rom_data.empty()) {
260 return absl::InvalidArgumentError(
"runtime ROM data is empty");
267 const double frame_rate =
268 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
271 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
286 return absl::OkStatus();
304 audio_backend_->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
307 audio_backend_->SetAudioStreamResampling(
false, kNativeSampleRate, 2);
314 uint64_t current_count = SDL_GetPerformanceCounter();
328 int frames_processed = 0;
329 constexpr int kMaxFramesPerUpdate = 2;
332 static int16_t native_audio_buffer[2048];
335 frames_processed < kMaxFramesPerUpdate) {
348 const uint32_t max_buffer =
static_cast<uint32_t
>(
wanted_samples_ * 6);
350 if (status.queued_frames < max_buffer) {
356 static int log_counter = 0;
357 if (++log_counter % 60 == 0) {
360 "Resampling failed (Native=%d, Backend=%d) - Dropping "
361 "audio to prevent speedup/pitch shift",
362 kNativeSampleRate, backend_rate);
391 static int entry_count = 0;
392 if (entry_count < 5 || entry_count % 300 == 0) {
394 "RunAudioFrame ENTRY #%d: init=%d, running=%d, backend=%p "
395 "(external=%p, owned=%p)",
397 static_cast<void*
>(backend),
404 static int skip_count = 0;
405 if (skip_count < 5) {
406 LOG_WARN(
"Emulator",
"RunAudioFrame SKIPPED: init=%d, running=%d",
416 backend->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
428 static int16_t audio_buffer[2048];
431 bool queued = backend->QueueSamplesNative(audio_buffer,
wanted_samples_, 2,
435 static int frame_log_count = 0;
436 if (frame_log_count < 5 || frame_log_count % 300 == 0) {
437 LOG_INFO(
"Emulator",
"RunAudioFrame: wanted=%d, queued=%s, stream=%s",
443 if (!queued && backend->SupportsAudioStream()) {
446 "RunAudioFrame: First queue failed, re-enabling resampling");
447 backend->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
451 LOG_INFO(
"Emulator",
"RunAudioFrame: Retry queued=%s",
452 queued ?
"YES" :
"NO");
457 "RunAudioFrame: AUDIO DROPPED - resampling not working!");
464 const char* env_value = std::getenv(
"YAZE_USE_SDL_AUDIO_STREAM");
465 if (env_value && std::atoi(env_value) != 0) {
473 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
474 "Emulator renderer not initialized");
497 LOG_ERROR(
"Emulator",
"Failed to initialize audio backend");
499 LOG_INFO(
"Emulator",
"Audio backend initialized (lazy): %s",
510 LOG_ERROR(
"Emulator",
"Failed to initialize input manager");
513 LOG_INFO(
"Emulator",
"Input manager initialized: %s",
527 512, 480, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING);
529 printf(
"Failed to create PPU texture: %s\n", SDL_GetError());
548 const double frame_rate =
549 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
554 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
573 static bool was_running_before_resize =
false;
577 was_running_before_resize =
true;
580 was_running_before_resize) {
583 was_running_before_resize =
false;
595 uint64_t current_count = SDL_GetPerformanceCounter();
607 int frames_to_process = 0;
618 if (frames_to_process > max_frames) {
622 frames_to_process = max_frames;
627 constexpr int kTurboFrames = 8;
628 for (
int i = 0; i < kTurboFrames; i++) {
637 frames_to_process = 1;
643 for (
int i = 0; i < frames_to_process; i++) {
645 uint64_t frame_start = SDL_GetPerformanceCounter();
646 bool should_render = (i == frames_to_process - 1);
647 uint32_t queued_frames = 0;
648 float audio_rms_left = 0.0f;
649 float audio_rms_right = 0.0f;
664 int16_t temp_audio_buffer[2048];
665 int16_t* frame_buffer =
673 "Enabling audio stream resampling (32040Hz -> Device Rate)");
678 LOG_INFO(
"Emulator",
"Disabling audio stream resampling");
679 audio_backend_->SetAudioStreamResampling(
false, kNativeSampleRate,
687 queued_frames = audio_status.queued_frames;
690 const uint32_t max_buffer = samples_per_frame * 6;
691 const uint32_t optimal_buffer = 2048;
693 if (queued_frames < max_buffer) {
700 auto compute_rms = [&](
int total_samples) {
701 if (total_samples <= 0 || frame_buffer ==
nullptr) {
702 audio_rms_left = 0.0f;
703 audio_rms_right = 0.0f;
708 const int frames = total_samples / 2;
709 for (
int s = 0; s < frames; ++s) {
710 const float l =
static_cast<float>(frame_buffer[2 * s]);
711 const float r =
static_cast<float>(frame_buffer[2 * s + 1]);
716 frames > 0 ? std::sqrt(sum_l / frames) / 32768.0f : 0.0f;
718 frames > 0 ? std::sqrt(sum_r / frames) / 32768.0f : 0.0f;
720 compute_rms(num_samples);
724 int effective_rate = kNativeSampleRate;
725 if (queued_frames > optimal_buffer + 256) {
726 effective_rate += 60;
727 }
else if (queued_frames < optimal_buffer - 256) {
728 effective_rate -= 60;
745 static int error_count = 0;
746 if (++error_count % 300 == 0) {
749 "Resampling failed, dropping audio to prevent 1.5x speed "
756 static int overflow_count = 0;
757 if (++overflow_count % 60 == 0) {
759 "Audio buffer overflow (count: %d, queued: %u)",
760 overflow_count, queued_frames);
778 const uint64_t frame_end = SDL_GetPerformanceCounter();
779 const double elapsed_ms =
780 1000.0 * (
static_cast<double>(frame_end - frame_start) /
784 audio_rms_left, audio_rms_right);
795#ifndef __EMSCRIPTEN__
813 uint64_t dma_bytes, uint64_t vram_bytes,
814 float audio_rms_left,
float audio_rms_right) {
830 const std::array<float, Emulator::kMetricHistorySize>& data,
int head,
832 std::vector<float> out;
836 for (
int i = 0; i < count; ++i) {
838 out.push_back(data[idx]);
880 constexpr size_t kBankSize = 0x8000;
884 const size_t bank_count =
rom_data_.size() / kBankSize;
885 std::vector<float> free_bytes;
886 free_bytes.reserve(bank_count);
887 for (
size_t bank = 0; bank < bank_count; ++bank) {
888 size_t free_count = 0;
889 const size_t base = bank * kBankSize;
890 for (
size_t i = 0; i < kBankSize && (base + i) <
rom_data_.size(); ++i) {
895 free_bytes.push_back(
static_cast<float>(free_count));
927 if (cpu_visible && *cpu_visible) {
928 if (cpu_card.
Begin(cpu_visible)) {
936 if (ppu_visible && *ppu_visible) {
937 if (ppu_card.
Begin(ppu_visible)) {
944 bool* memory_visible =
946 if (memory_visible && *memory_visible) {
947 if (memory_card.
Begin(memory_visible)) {
953 bool* breakpoints_visible =
955 if (breakpoints_visible && *breakpoints_visible) {
956 if (breakpoints_card.
Begin(breakpoints_visible)) {
959 breakpoints_card.
End();
962 bool* performance_visible =
964 if (performance_visible && *performance_visible) {
965 if (performance_card.
Begin(performance_visible)) {
968 performance_card.
End();
971 bool* ai_agent_visible =
973 if (ai_agent_visible && *ai_agent_visible) {
974 if (ai_card.
Begin(ai_agent_visible)) {
980 bool* save_states_visible =
982 if (save_states_visible && *save_states_visible) {
983 if (save_states_card.
Begin(save_states_visible)) {
986 save_states_card.
End();
989 bool* keyboard_config_visible =
991 if (keyboard_config_visible && *keyboard_config_visible) {
992 if (keyboard_card.
Begin(keyboard_config_visible)) {
1001 bool* virtual_controller_visible =
1003 if (virtual_controller_visible && *virtual_controller_visible) {
1004 if (controller_card.
Begin(virtual_controller_visible)) {
1007 controller_card.
End();
1010 bool* apu_debugger_visible =
1012 if (apu_debugger_visible && *apu_debugger_visible) {
1013 if (apu_card.
Begin(apu_debugger_visible)) {
1019 bool* audio_mixer_visible =
1021 if (audio_mixer_visible && *audio_mixer_visible) {
1022 if (audio_card.
Begin(audio_mixer_visible)) {
1028 }
catch (
const std::exception& e) {
1030 ImGui::Text(
"Error loading emulator UI: %s", e.what());
1031 if (ImGui::Button(
"Retry")) {
1034 theme_manager.InitializeBuiltInThemes();
1067 const auto& theme = theme_manager.GetCurrentTheme();
1090 static char bp_addr[16] =
"00FFD9";
1092 ImGui::PushItemWidth(100);
1093 ImGui::InputText(
"##BPAddr", bp_addr, IM_ARRAYSIZE(bp_addr),
1094 ImGuiInputTextFlags_CharsHexadecimal |
1095 ImGuiInputTextFlags_CharsUppercase);
1096 ImGui::PopItemWidth();
1099 uint32_t addr = std::strtoul(bp_addr,
nullptr, 16);
1103 absl::StrFormat(
"BP at $%06X", addr));
1107 ImGui::BeginChild(
"##BPList", ImVec2(0, 100),
true);
1110 bool enabled = bp.enabled;
1111 if (ImGui::Checkbox(absl::StrFormat(
"##en%d", bp.id).c_str(),
1116 ImGui::Text(
"$%06X", bp.address);
1118 ImGui::TextDisabled(
"(hits: %d)", bp.hit_count);
1120 if (ImGui::SmallButton(
1130 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"CPU Status");
1133 "##CpuStatus", ImVec2(0, 180),
1134 {.bg = ConvertColorToImVec4(theme.child_bg)},
true);
1137 if (ImGui::BeginTable(
1139 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1140 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed,
1142 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 80);
1143 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed,
1145 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 80);
1146 ImGui::TableHeadersRow();
1148 ImGui::TableNextRow();
1149 ImGui::TableNextColumn();
1151 ImGui::TableNextColumn();
1152 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1154 ImGui::TableNextColumn();
1156 ImGui::TableNextColumn();
1157 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1160 ImGui::TableNextRow();
1161 ImGui::TableNextColumn();
1163 ImGui::TableNextColumn();
1164 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1166 ImGui::TableNextColumn();
1168 ImGui::TableNextColumn();
1169 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1172 ImGui::TableNextRow();
1173 ImGui::TableNextColumn();
1175 ImGui::TableNextColumn();
1176 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1178 ImGui::TableNextColumn();
1180 ImGui::TableNextColumn();
1181 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1184 ImGui::TableNextRow();
1185 ImGui::TableNextColumn();
1187 ImGui::TableNextColumn();
1188 ImGui::TextColored(ConvertColorToImVec4(theme.success),
"0x%04X",
1190 ImGui::TableNextColumn();
1192 ImGui::TableNextColumn();
1193 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1196 ImGui::TableNextRow();
1197 ImGui::TableNextColumn();
1199 ImGui::TableNextColumn();
1200 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
"0x%02X",
1202 ImGui::TableNextColumn();
1203 ImGui::Text(
"Cycle");
1204 ImGui::TableNextColumn();
1205 ImGui::TextColored(ConvertColorToImVec4(theme.info),
"%llu",
1213 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"SPC700 Status");
1216 "##SpcStatus", ImVec2(0, 150),
1217 {.bg = ConvertColorToImVec4(theme.child_bg)},
true);
1219 if (ImGui::BeginTable(
1221 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1222 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed,
1224 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 60);
1225 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed,
1227 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 60);
1228 ImGui::TableHeadersRow();
1230 ImGui::TableNextRow();
1231 ImGui::TableNextColumn();
1233 ImGui::TableNextColumn();
1234 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1236 ImGui::TableNextColumn();
1238 ImGui::TableNextColumn();
1239 ImGui::TextColored(ConvertColorToImVec4(theme.success),
"0x%04X",
1242 ImGui::TableNextRow();
1243 ImGui::TableNextColumn();
1245 ImGui::TableNextColumn();
1246 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1248 ImGui::TableNextColumn();
1250 ImGui::TableNextColumn();
1251 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1254 ImGui::TableNextRow();
1255 ImGui::TableNextColumn();
1257 ImGui::TableNextColumn();
1258 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1260 ImGui::TableNextColumn();
1262 ImGui::TableNextColumn();
1264 ConvertColorToImVec4(theme.warning),
"0x%02X",
1272 if (ImGui::CollapsingHeader(
"Disassembly Viewer",
1273 ImGuiTreeNodeFlags_DefaultOpen)) {
1274 uint32_t current_pc =
1276 auto& disasm =
snes_.
cpu().disassembly_viewer();
1277 if (disasm.IsAvailable()) {
1278 disasm.Render(current_pc,
snes_.
cpu().breakpoints_);
1280 ImGui::TextColored(ConvertColorToImVec4(theme.error),
1281 "Disassembly viewer unavailable.");
1284 }
catch (
const std::exception& e) {
1286 ImGui::Text(
"CPU Debugger Error: %s", e.what());
1301 const std::vector<InstructionEntry>& instruction_log) {
1309 const auto& theme = theme_manager.GetCurrentTheme();
1311 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
1313 ImGui::TextWrapped(
"Save state functionality will be implemented here.");
1338 if (ImGui::SliderFloat(
"Master Volume", &volume, 0.0f, 1.0f,
"%.2f")) {
1343 ImGui::Text(
"Channel Mutes (Debug)");
1347 if (ImGui::BeginTable(
"AudioChannels", 4)) {
1348 for (
int i = 0; i < 8; ++i) {
1349 ImGui::TableNextColumn();
1350 bool mute = dsp.GetChannelMute(i);
1351 std::string label =
"Ch " + std::to_string(i + 1);
1352 if (ImGui::Checkbox(label.c_str(), &mute)) {
1353 dsp.SetChannelMute(i, mute);
1360 if (ImGui::Button(
"Mute All")) {
1361 for (
int i = 0; i < 8; ++i)
1362 dsp.SetChannelMute(i,
true);
1365 if (ImGui::Button(
"Unmute All")) {
1366 for (
int i = 0; i < 8; ++i)
1367 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 * GetWindowVisibilityFlag(size_t session_id, const std::string &base_window_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 ClearAll()
Clear all breakpoints.
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::WorkspaceWindowManager * window_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()
absl::Status ReloadRuntimeRom(const std::vector< uint8_t > &rom_data)
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 Clear()
Clear all recorded instructions.
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)
RAII guard for ImGui child windows with optional styling.
static ThemeManager & Get()
#define ICON_MD_PLAY_ARROW
#define ICON_MD_VIDEOGAME_ASSET
#define ICON_MD_BUG_REPORT
#define ICON_MD_AUDIOTRACK
#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...