#include "libretro_host_interface.h" #include "common/assert.h" #include "common/byte_stream.h" #include "common/file_system.h" #include "common/log.h" #include "common/string_util.h" #include "core/analog_controller.h" #include "core/digital_controller.h" #include "core/game_list.h" #include "core/gpu.h" #include "core/system.h" #include "libretro_audio_stream.h" #include "libretro_host_display.h" #include "libretro_opengl_host_display.h" #include "libretro_settings_interface.h" #include "libretro_vulkan_host_display.h" #include #include #include #include #include Log_SetChannel(LibretroHostInterface); #ifdef WIN32 #include "libretro_d3d11_host_display.h" #endif LibretroHostInterface g_libretro_host_interface; retro_environment_t g_retro_environment_callback; retro_video_refresh_t g_retro_video_refresh_callback; retro_audio_sample_t g_retro_audio_sample_callback; retro_audio_sample_batch_t g_retro_audio_sample_batch_callback; retro_input_poll_t g_retro_input_poll_callback; retro_input_state_t g_retro_input_state_callback; static retro_log_callback s_libretro_log_callback = {}; static bool s_libretro_log_callback_valid = false; static const char* GetSaveDirectory() { const char* save_directory = nullptr; if (!g_retro_environment_callback(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &save_directory) || !save_directory) save_directory = "saves"; return save_directory; } static void LibretroLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level, const char* message) { static constexpr std::array levels = { {RETRO_LOG_ERROR, RETRO_LOG_ERROR, RETRO_LOG_WARN, RETRO_LOG_INFO, RETRO_LOG_INFO, RETRO_LOG_INFO, RETRO_LOG_DEBUG, RETRO_LOG_DEBUG, RETRO_LOG_DEBUG, RETRO_LOG_DEBUG}}; s_libretro_log_callback.log(levels[level], "[%s] %s\n", (level <= LOGLEVEL_PERF) ? functionName : channelName, message); } LibretroHostInterface::LibretroHostInterface() = default; LibretroHostInterface::~LibretroHostInterface() = default; void LibretroHostInterface::InitLogging() { s_libretro_log_callback_valid = g_retro_environment_callback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &s_libretro_log_callback); if (s_libretro_log_callback_valid) Log::RegisterCallback(LibretroLogCallback, nullptr); } bool LibretroHostInterface::Initialize() { if (!HostInterface::Initialize()) return false; LoadSettings(); UpdateLogging(); return true; } void LibretroHostInterface::Shutdown() { HostInterface::Shutdown(); } void LibretroHostInterface::ReportError(const char* message) { AddFormattedOSDMessage(60.0f, "ERROR: %s", message); Log_ErrorPrint(message); } void LibretroHostInterface::ReportMessage(const char* message) { AddOSDMessage(message, 10.0f); Log_InfoPrint(message); } bool LibretroHostInterface::ConfirmMessage(const char* message) { Log_InfoPrintf("Confirm: %s", message); return false; } void LibretroHostInterface::GetGameInfo(const char* path, CDImage* image, std::string* code, std::string* title) { // Just use the filename for now... we don't have the game list. Unless we can pull this from the frontend somehow? *title = GameList::GetTitleForPath(path); code->clear(); } std::string LibretroHostInterface::GetSharedMemoryCardPath(u32 slot) const { return GetUserDirectoryRelativePath("%s/shared_card_%d.mcd", GetSaveDirectory(), slot + 1); } std::string LibretroHostInterface::GetGameMemoryCardPath(const char* game_code, u32 slot) const { return GetUserDirectoryRelativePath("%s/%s_%d.mcd", GetSaveDirectory(), game_code, slot + 1); } std::string LibretroHostInterface::GetSettingValue(const char* section, const char* key, const char* default_value /*= ""*/) { TinyString name; name.Format("%s.%s", section, key); retro_variable var{name, default_value}; if (g_retro_environment_callback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) return var.value; else return default_value; } void LibretroHostInterface::AddOSDMessage(std::string message, float duration /*= 2.0f*/) { retro_message msg = {}; msg.msg = message.c_str(); msg.frames = static_cast(duration * (m_system ? m_system->GetThrottleFrequency() : 60.0f)); g_retro_environment_callback(RETRO_ENVIRONMENT_SET_MESSAGE, &msg); } void LibretroHostInterface::retro_get_system_av_info(struct retro_system_av_info* info) { const bool use_resolution_scale = (m_settings.gpu_renderer != GPURenderer::Software); GetSystemAVInfo(info, use_resolution_scale); Log_InfoPrintf("base = %ux%u, max = %ux%u, aspect ratio = %.2f, fps = %.2f", info->geometry.base_width, info->geometry.base_height, info->geometry.max_width, info->geometry.max_height, info->geometry.aspect_ratio, info->timing.fps); } void LibretroHostInterface::GetSystemAVInfo(struct retro_system_av_info* info, bool use_resolution_scale) { const u32 resolution_scale = use_resolution_scale ? m_settings.gpu_resolution_scale : 1u; Assert(m_system); std::memset(info, 0, sizeof(*info)); info->geometry.aspect_ratio = Settings::GetDisplayAspectRatioValue(m_settings.display_aspect_ratio); if (!m_system->IsPALRegion()) { info->geometry.base_width = 640; info->geometry.base_height = 480; } else { info->geometry.base_width = 720; info->geometry.base_height = 576; } info->geometry.max_width = 1024 * resolution_scale; info->geometry.max_height = 512 * resolution_scale; info->timing.fps = m_system->GetThrottleFrequency(); info->timing.sample_rate = static_cast(AUDIO_SAMPLE_RATE); } void LibretroHostInterface::UpdateSystemAVInfo(bool use_resolution_scale) { struct retro_system_av_info avi; GetSystemAVInfo(&avi, use_resolution_scale); Log_InfoPrintf("base = %ux%u, max = %ux%u, aspect ratio = %.2f, fps = %.2f", avi.geometry.base_width, avi.geometry.base_height, avi.geometry.max_width, avi.geometry.max_height, avi.geometry.aspect_ratio, avi.timing.fps); if (!g_retro_environment_callback(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avi)) Log_ErrorPrintf("Failed to update system AV info on resolution change"); } void LibretroHostInterface::UpdateGeometry() { struct retro_system_av_info avi; const bool use_resolution_scale = (m_settings.gpu_renderer != GPURenderer::Software); GetSystemAVInfo(&avi, use_resolution_scale); Log_InfoPrintf("base = %ux%u, max = %ux%u, aspect ratio = %.2f", avi.geometry.base_width, avi.geometry.base_height, avi.geometry.max_width, avi.geometry.max_height, avi.geometry.aspect_ratio); if (!g_retro_environment_callback(RETRO_ENVIRONMENT_SET_GEOMETRY, &avi.geometry)) Log_WarningPrint("RETRO_ENVIRONMENT_SET_GEOMETRY failed"); } void LibretroHostInterface::UpdateLogging() { Log::SetFilterLevel(m_settings.log_level); if (s_libretro_log_callback_valid) Log::SetConsoleOutputParams(false); else Log::SetConsoleOutputParams(true, nullptr, m_settings.log_level); } bool LibretroHostInterface::retro_load_game(const struct retro_game_info* game) { SystemBootParameters bp; bp.filename = game->path; bp.force_software_renderer = !m_hw_render_callback_valid; if (!BootSystem(bp)) return false; if (m_settings.gpu_renderer != GPURenderer::Software) { if (!m_hw_render_callback_valid) RequestHardwareRendererContext(); else SwitchToHardwareRenderer(); } return true; } void LibretroHostInterface::retro_run_frame() { Assert(m_system); if (HasCoreVariablesChanged()) UpdateSettings(); UpdateControllers(); m_system->GetGPU()->RestoreGraphicsAPIState(); m_system->RunFrame(); m_system->GetGPU()->ResetGraphicsAPIState(); m_display->Render(); } unsigned LibretroHostInterface::retro_get_region() { return m_system->IsPALRegion() ? RETRO_REGION_PAL : RETRO_REGION_NTSC; } size_t LibretroHostInterface::retro_serialize_size() { return System::MAX_SAVE_STATE_SIZE; } bool LibretroHostInterface::retro_serialize(void* data, size_t size) { std::unique_ptr stream = ByteStream_CreateMemoryStream(data, static_cast(size)); if (!m_system->SaveState(stream.get(), 0)) { Log_ErrorPrintf("Failed to save state to memory stream"); return false; } return true; } bool LibretroHostInterface::retro_unserialize(const void* data, size_t size) { std::unique_ptr stream = ByteStream_CreateReadOnlyMemoryStream(data, static_cast(size)); if (!m_system->LoadState(stream.get())) { Log_ErrorPrintf("Failed to load save state from memory stream"); return false; } return true; } bool LibretroHostInterface::AcquireHostDisplay() { // start in software mode, switch to hardware later m_display = std::make_unique(); return true; } void LibretroHostInterface::ReleaseHostDisplay() { m_display->DestroyRenderDevice(); m_display.reset(); } std::unique_ptr LibretroHostInterface::CreateAudioStream(AudioBackend backend) { return std::make_unique(); } void LibretroHostInterface::OnSystemDestroyed() { HostInterface::OnSystemDestroyed(); m_using_hardware_renderer = false; } static std::array s_option_definitions = {{ {"Console.Region", "Console Region", "Determines which region/hardware to emulate. Auto-Detect will use the region of the disc inserted.", {{"Auto", "Auto-Detect"}, {"NTSC-J", "NTSC-J (Japan)"}, {"NTSC-U", "NTSC-U (US)"}, {"PAL", "PAL (Europe, Australia)"}}, "Auto"}, {"BIOS.FastBoot", "Fast Boot", "Skips the BIOS shell/intro, booting directly into the game. Usually safe to enable, but some games break.", {{"true", "Enabled"}, {"false", "Disabled"}}, "false"}, {"CDROM.RegionCheck", "CD-ROM Region Check", "Prevents discs from incorrect regions being read by the emulator. Usually safe to disable.", {{"true", "Enabled"}, {"false", "Disabled"}}, "false"}, {"CDROM.ReadThread", "CD-ROM Read Thread", "Reads CD-ROM sectors ahead asynchronously, reducing the risk of frame time spikes.", {{"true", "Enabled"}, {"false", "Disabled"}}, "true"}, {"CPU.ExecutionMode", "CPU Execution Mode", "Which mode to use for CPU emulation. Recompiler provides the best performance.", {{"Interpreter", "Interpreter"}, {"CachedIntepreter", "Cached Interpreter"}, {"Recompiler", "Recompiler"}}, "Recompiler"}, {"GPU.Renderer", "GPU Renderer", "Which renderer to use to emulate the GPU", { #ifdef WIN32 {"D3D11", "Hardware (D3D11)"}, #endif {"OpenGL", "Hardware (OpenGL)"}, {"Vulkan", "Hardware (Vulkan)"}, {"Software", "Software"}}, #ifdef WIN32 "D3D11" #else "OpenGL" #endif }, {"GPU.ResolutionScale", "Rendering Resolution Scale", "Scales internal rendering resolution by the specified multiplier. Larger values are slower. Some games require " "1x rendering resolution or they will have rendering issues.", {{"1", "1x (1024x512)"}, {"2", "2x (2048x1024)"}, {"3", "3x (3072x1536)"}, {"4", "4x (4096x2048)"}, {"5", "5x (5120x2160)"}, {"6", "6x (6144x3072)"}, {"7", "7x (7168x3584)"}, {"8", "8x (8192x4096)"}}, "1"}, {"GPU.TrueColor", "True Color Rendering", "Disables dithering and uses the full 8 bits per channel of color information. May break rendering in some games.", {{"true", "Enabled"}, {"false", "Disabled"}}, "false"}, {"GPU.ScaledDithering", "Scaled Dithering", "Scales the dithering pattern with the internal rendering resolution, making it less noticeable. Usually safe to " "enable.", {{"true", "Enabled"}, {"false", "Disabled"}}, "true"}, {"GPU.DisableInterlacing", "Disable Interlacing", "Disables interlaced rendering and display in the GPU. Some games can render in 480p this way, but others will " "break.", {{"true", "Enabled"}, {"false", "Disabled"}}, "false"}, {"GPU.ForceNTSCTimings", "Force NTSC Timings", "Forces PAL games to run at NTSC timings, i.e. 60hz. Some PAL games will run at their \"normal\" speeds, while " "others will break.", {{"true", "Enabled"}, {"false", "Disabled"}}, "false"}, {"Display.CropMode", "Crop Mode", "Changes how much of the image is cropped. Some games display garbage in the overscan area which is typically " "hidden.", {{"None", "None"}, {"Overscan", "Only Overscan Area"}, {"Borders", "All Borders"}}, "Overscan"}, {"Display.AspectRatio", "Aspect Ratio", "Sets the core-provided aspect ratio.", {{"4:3", "4:3"}, {"16:9", "16:9"}, {"2:1", "2:1 (VRAM 1:1)"}, {"1:1", "1:1"}}, "4:3"}, {"MemoryCards.LoadFromSaveStates", "Load Memory Cards From Save States", "Sets whether the contents of memory cards will be loaded when a save state is loaded.", {{"true", "Enabled"}, {"false", "Disabled"}}, "false"}, {"MemoryCards.Card1Type", "Memory Card 1 Type", "Sets the type of memory card for Slot 1.", {{"None", "No Memory Card"}, {"Shared", "Shared Between All Games"}, {"PerGame", "Separate Card Per Game (Game Code)"}, {"PerGameTitle", "Separate Card Per Game (Game Title)"}}, "PerGameTitle"}, {"MemoryCards.Card2Type", "Memory Card 2 Type", "Sets the type of memory card for Slot 2.", {{"None", "No Memory Card"}, {"Shared", "Shared Between All Games"}, {"PerGame", "Separate Card Per Game (Game Code)"}, {"PerGameTitle", "Separate Card Per Game (Game Title)"}}, "None"}, {"Controller1.Type", "Controller 1 Type", "Sets the type of controller for Slot 1.", {{"None", "None"}, {"DigitalController", "Digital Controller"}, {"AnalogController", "Analog Controller (DualShock)"}, {"NamcoGunCon", "Namco GunCon"}, {"PlayStationMouse", "PlayStation Mouse"}, {"NeGcon", "NeGcon"}}, "DigitalController"}, {"Controller1.Type", "Controller 1 Auto Analog Mode", "Automatically enables analog mode in supported controllers at start/reset.", {{"true", "Enabled"}, {"false", "Disabled"}}, "false"}, {"Controller2.Type", "Controller 2 Type", "Sets the type of controller for Slot 2.", {{"None", "None"}, {"DigitalController", "Digital Controller"}, {"AnalogController", "Analog Controller (DualShock)"}, {"NamcoGunCon", "Namco GunCon"}, {"PlayStationMouse", "PlayStation Mouse"}, {"NeGcon", "NeGcon"}}, "None"}, {"Controller2.Type", "Controller 2 Auto Analog Mode", "Automatically enables analog mode in supported controllers at start/reset.", {{"true", "Enabled"}, {"false", "Disabled"}}, "false"}, {"Logging.LogLevel", "Log Level", "Sets the level of information logged by the core.", {{"None", "None"}, {"Error", "Error"}, {"Warning", "Warning"}, {"Perf", "Performance"}, {"Success", "Success"}, {"Info", "Information"}, {"Dev", "Developer"}, {"Profile", "Profile"}, {"Debug", "Debug"}, {"Trace", "Trace"}}, "Info"}, {}, }}; bool LibretroHostInterface::SetCoreOptions() { unsigned options_version = 0; if (g_retro_environment_callback(RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION, &options_version) && options_version >= 1) { return g_retro_environment_callback(RETRO_ENVIRONMENT_SET_CORE_OPTIONS, &s_option_definitions); } // use legacy options struct, which sucks. do we need to? return false; } bool LibretroHostInterface::HasCoreVariablesChanged() { bool changed = false; return (g_retro_environment_callback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &changed) && changed); } void LibretroHostInterface::LoadSettings() { LibretroSettingsInterface si; m_settings.Load(si); // Assume BIOS files are located in system directory. const char* system_directory = nullptr; if (!g_retro_environment_callback(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &system_directory) || !system_directory) system_directory = "bios"; m_settings.bios_path = StringUtil::StdStringFromFormat("%s%cscph1001.bin", system_directory, FS_OSPATH_SEPERATOR_CHARACTER); } void LibretroHostInterface::UpdateSettings() { Settings old_settings(std::move(m_settings)); LoadSettings(); if (m_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale && m_settings.gpu_renderer != GPURenderer::Software) { ReportMessage("Resolution changed, updating system AV info..."); // this will probably recreate the device... so save the state first by switching to software if (m_using_hardware_renderer) SwitchToSoftwareRenderer(); UpdateSystemAVInfo(true); if (!m_hw_render_callback_valid) RequestHardwareRendererContext(); else if (!m_using_hardware_renderer) SwitchToHardwareRenderer(); // Don't let the base class mess with the GPU. old_settings.gpu_resolution_scale = m_settings.gpu_resolution_scale; } if (m_settings.gpu_renderer != old_settings.gpu_renderer) { ReportFormattedMessage("Switching to %s renderer...", Settings::GetRendererDisplayName(m_settings.gpu_renderer)); if (m_using_hardware_renderer) SwitchToSoftwareRenderer(); if (m_settings.gpu_renderer != GPURenderer::Software) RequestHardwareRendererContext(); // Don't let the base class recreate the GPU or system. old_settings.gpu_renderer = m_settings.gpu_renderer; } CheckForSettingsChanges(old_settings); } void LibretroHostInterface::CheckForSettingsChanges(const Settings& old_settings) { HostInterface::CheckForSettingsChanges(old_settings); if (m_settings.display_aspect_ratio != old_settings.display_aspect_ratio) UpdateGeometry(); if (m_settings.log_level != old_settings.log_level) UpdateLogging(); } void LibretroHostInterface::UpdateControllers() { g_retro_input_poll_callback(); for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { switch (m_settings.controller_types[i]) { case ControllerType::None: break; case ControllerType::DigitalController: UpdateControllersDigitalController(i); break; case ControllerType::AnalogController: UpdateControllersAnalogController(i); break; default: ReportFormattedError("Unhandled controller type '%s'.", Settings::GetControllerTypeDisplayName(m_settings.controller_types[i])); break; } } } void LibretroHostInterface::UpdateControllersDigitalController(u32 index) { DigitalController* controller = static_cast(m_system->GetController(index)); DebugAssert(controller); static constexpr std::array, 14> mapping = { {{DigitalController::Button::Left, RETRO_DEVICE_ID_JOYPAD_LEFT}, {DigitalController::Button::Right, RETRO_DEVICE_ID_JOYPAD_RIGHT}, {DigitalController::Button::Up, RETRO_DEVICE_ID_JOYPAD_UP}, {DigitalController::Button::Down, RETRO_DEVICE_ID_JOYPAD_DOWN}, {DigitalController::Button::Circle, RETRO_DEVICE_ID_JOYPAD_A}, {DigitalController::Button::Cross, RETRO_DEVICE_ID_JOYPAD_B}, {DigitalController::Button::Triangle, RETRO_DEVICE_ID_JOYPAD_X}, {DigitalController::Button::Square, RETRO_DEVICE_ID_JOYPAD_Y}, {DigitalController::Button::Start, RETRO_DEVICE_ID_JOYPAD_START}, {DigitalController::Button::Select, RETRO_DEVICE_ID_JOYPAD_SELECT}, {DigitalController::Button::L1, RETRO_DEVICE_ID_JOYPAD_L}, {DigitalController::Button::L2, RETRO_DEVICE_ID_JOYPAD_L2}, {DigitalController::Button::R1, RETRO_DEVICE_ID_JOYPAD_R}, {DigitalController::Button::R2, RETRO_DEVICE_ID_JOYPAD_R2}}}; for (const auto& it : mapping) { const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, it.second); controller->SetButtonState(it.first, state != 0); } } void LibretroHostInterface::UpdateControllersAnalogController(u32 index) { AnalogController* controller = static_cast(m_system->GetController(index)); DebugAssert(controller); static constexpr std::array, 16> button_mapping = { {{AnalogController::Button::Left, RETRO_DEVICE_ID_JOYPAD_LEFT}, {AnalogController::Button::Right, RETRO_DEVICE_ID_JOYPAD_RIGHT}, {AnalogController::Button::Up, RETRO_DEVICE_ID_JOYPAD_UP}, {AnalogController::Button::Down, RETRO_DEVICE_ID_JOYPAD_DOWN}, {AnalogController::Button::Circle, RETRO_DEVICE_ID_JOYPAD_A}, {AnalogController::Button::Cross, RETRO_DEVICE_ID_JOYPAD_B}, {AnalogController::Button::Triangle, RETRO_DEVICE_ID_JOYPAD_X}, {AnalogController::Button::Square, RETRO_DEVICE_ID_JOYPAD_Y}, {AnalogController::Button::Start, RETRO_DEVICE_ID_JOYPAD_START}, {AnalogController::Button::Select, RETRO_DEVICE_ID_JOYPAD_SELECT}, {AnalogController::Button::L1, RETRO_DEVICE_ID_JOYPAD_L}, {AnalogController::Button::L2, RETRO_DEVICE_ID_JOYPAD_L2}, {AnalogController::Button::L3, RETRO_DEVICE_ID_JOYPAD_L3}, {AnalogController::Button::R1, RETRO_DEVICE_ID_JOYPAD_R}, {AnalogController::Button::R2, RETRO_DEVICE_ID_JOYPAD_R2}, {AnalogController::Button::R3, RETRO_DEVICE_ID_JOYPAD_R3}}}; static constexpr std::array>, 4> axis_mapping = { {{AnalogController::Axis::LeftX, {RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X}}, {AnalogController::Axis::LeftY, {RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y}}, {AnalogController::Axis::RightX, {RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X}}, {AnalogController::Axis::RightY, {RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y}}}}; for (const auto& it : button_mapping) { const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, it.second); controller->SetButtonState(it.first, state != 0); } for (const auto& it : axis_mapping) { const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_ANALOG, it.second.first, it.second.second); controller->SetAxisState(static_cast(it.first), std::clamp(static_cast(state) / 32767.0f, -1.0f, 1.0f)); } } static std::optional RetroHwContextToRenderer(retro_hw_context_type type) { switch (type) { case RETRO_HW_CONTEXT_OPENGL: case RETRO_HW_CONTEXT_OPENGL_CORE: case RETRO_HW_CONTEXT_OPENGLES3: case RETRO_HW_CONTEXT_OPENGLES_VERSION: return GPURenderer::HardwareOpenGL; case RETRO_HW_CONTEXT_VULKAN: return GPURenderer::HardwareVulkan; #ifdef WIN32 case RETRO_HW_CONTEXT_DIRECT3D: return GPURenderer::HardwareD3D11; #endif default: return std::nullopt; } } bool LibretroHostInterface::RequestHardwareRendererContext() { GPURenderer renderer = Settings::DEFAULT_GPU_RENDERER; retro_variable renderer_variable{"GPU.Renderer", Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER)}; if (g_retro_environment_callback(RETRO_ENVIRONMENT_GET_VARIABLE, &renderer_variable) && renderer_variable.value) renderer = Settings::ParseRendererName(renderer_variable.value).value_or(Settings::DEFAULT_GPU_RENDERER); Log_InfoPrintf("Renderer = %s", Settings::GetRendererName(renderer)); unsigned preferred_renderer = 0; if (g_retro_environment_callback(RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER, &preferred_renderer)) { std::optional preferred_gpu_renderer = RetroHwContextToRenderer(static_cast(preferred_renderer)); if (!preferred_gpu_renderer.has_value() || preferred_gpu_renderer.value() != renderer) { const char* preferred_name = preferred_gpu_renderer.has_value() ? Settings::GetRendererName(preferred_gpu_renderer.value()) : "Unknown"; const char* config_name = Settings::GetRendererName(renderer); renderer = preferred_gpu_renderer.value_or(GPURenderer::Software); ReportFormattedError( "Mismatch between frontend's preferred GPU renderer '%s' and configured renderer '%s'. Please " "change your video driver to '%s'. Using '%s' renderer for now.", preferred_name, config_name, config_name, Settings::GetRendererName(renderer)); } } if (renderer == GPURenderer::Software) { m_hw_render_callback_valid = false; return false; } Log_InfoPrintf("Requesting hardware renderer context for %s", Settings::GetRendererName(renderer)); m_hw_render_callback = {}; m_hw_render_callback.context_reset = HardwareRendererContextReset; m_hw_render_callback.context_destroy = HardwareRendererContextDestroy; switch (renderer) { #ifdef WIN32 case GPURenderer::HardwareD3D11: m_hw_render_callback_valid = LibretroD3D11HostDisplay::RequestHardwareRendererContext(&m_hw_render_callback); break; #endif case GPURenderer::HardwareVulkan: m_hw_render_callback_valid = LibretroVulkanHostDisplay::RequestHardwareRendererContext(&m_hw_render_callback); break; case GPURenderer::HardwareOpenGL: m_hw_render_callback_valid = LibretroOpenGLHostDisplay::RequestHardwareRendererContext(&m_hw_render_callback); break; default: Log_ErrorPrintf("Unhandled renderer %s", Settings::GetRendererName(renderer)); m_hw_render_callback_valid = false; break; } return m_hw_render_callback_valid; } void LibretroHostInterface::HardwareRendererContextReset() { Log_InfoPrintf("Hardware context reset, type = %u", static_cast(g_libretro_host_interface.m_hw_render_callback.context_type)); g_libretro_host_interface.m_hw_render_callback_valid = true; g_libretro_host_interface.SwitchToHardwareRenderer(); } void LibretroHostInterface::SwitchToHardwareRenderer() { std::optional renderer = RetroHwContextToRenderer(m_hw_render_callback.context_type); if (!renderer.has_value()) { Log_ErrorPrintf("Unknown context type %u", static_cast(m_hw_render_callback.context_type)); return; } std::unique_ptr display = nullptr; switch (renderer.value()) { case GPURenderer::HardwareOpenGL: display = std::make_unique(); break; case GPURenderer::HardwareVulkan: display = std::make_unique(); break; #ifdef WIN32 case GPURenderer::HardwareD3D11: display = std::make_unique(); break; #endif default: Log_ErrorPrintf("Unhandled renderer '%s'", Settings::GetRendererName(renderer.value())); return; } struct retro_system_av_info avi; g_libretro_host_interface.GetSystemAVInfo(&avi, true); WindowInfo wi; wi.type = WindowInfo::Type::Libretro; wi.display_connection = &g_libretro_host_interface.m_hw_render_callback; wi.surface_width = avi.geometry.base_width; wi.surface_height = avi.geometry.base_height; wi.surface_scale = 1.0f; if (!display || !display->CreateRenderDevice(wi, {}, g_libretro_host_interface.m_settings.gpu_use_debug_device) || !display->InitializeRenderDevice({}, m_settings.gpu_use_debug_device)) { Log_ErrorPrintf("Failed to create hardware host display"); return; } std::swap(display, g_libretro_host_interface.m_display); g_libretro_host_interface.m_system->RecreateGPU(renderer.value()); display->DestroyRenderDevice(); m_using_hardware_renderer = true; } void LibretroHostInterface::HardwareRendererContextDestroy() { g_libretro_host_interface.m_hw_render_callback_valid = false; // switch back to software if (g_libretro_host_interface.m_using_hardware_renderer) { Log_InfoPrintf("Lost hardware renderer context, switching to software renderer"); g_libretro_host_interface.SwitchToSoftwareRenderer(); } } void LibretroHostInterface::SwitchToSoftwareRenderer() { std::unique_ptr display = std::make_unique(); std::swap(display, g_libretro_host_interface.m_display); g_libretro_host_interface.m_system->RecreateGPU(GPURenderer::Software); display->DestroyRenderDevice(); m_using_hardware_renderer = false; }