mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-04-27 08:25:42 -04:00

* CPU/Recompiler: Use rel32 call where possible for no-args * JitCodeBuffer: Support using preallocated buffer * CPU/Recompiler/AArch64: Use bl instead of blr for short branches * CPU/CodeCache: Allocate recompiler buffer in program space This means we don't need 64-bit moves for every call out of the recompiler. * GTE: Don't store as u16 and load as u32 * CPU/Recompiler: Add methods to emit global load/stores * GTE: Convert class to namespace * CPU/Recompiler: Call GTE functions directly * Settings: Turn into a global variable * GPU: Replace local pointers with global * InterruptController: Turn into a global pointer * System: Replace local pointers with global * Timers: Turn into a global instance * DMA: Turn into a global instance * SPU: Turn into a global instance * CDROM: Turn into a global instance * MDEC: Turn into a global instance * Pad: Turn into a global instance * SIO: Turn into a global instance * CDROM: Move audio FIFO to the heap * CPU/Recompiler: Drop ASMFunctions No longer needed since we have code in the same 4GB window. * CPUCodeCache: Turn class into namespace * Bus: Local pointer -> global pointers * CPU: Turn class into namespace * Bus: Turn into namespace * GTE: Store registers in CPU state struct Allows relative addressing on ARM. * CPU/Recompiler: Align code storage to page size * CPU/Recompiler: Fix relative branches on A64 * HostInterface: Local references to global * System: Turn into a namespace, move events out * Add guard pages * Android: Fix build
1090 lines
35 KiB
C++
1090 lines
35 KiB
C++
#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/bus.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 <array>
|
|
#include <cstring>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <vector>
|
|
Log_SetChannel(LibretroHostInterface);
|
|
|
|
#ifdef WIN32
|
|
#include "libretro_d3d11_host_display.h"
|
|
#endif
|
|
|
|
LibretroHostInterface g_libretro_host_interface;
|
|
#define P_THIS (&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 bool s_libretro_log_callback_registered = false;
|
|
|
|
static void LibretroLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level,
|
|
const char* message)
|
|
{
|
|
static constexpr std::array<retro_log_level, LOGLEVEL_COUNT> 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()
|
|
{
|
|
// should be cleaned up by the context destroy, but just in case
|
|
if (m_hw_render_display)
|
|
{
|
|
m_hw_render_display->DestroyRenderDevice();
|
|
m_hw_render_display.reset();
|
|
}
|
|
}
|
|
|
|
void LibretroHostInterface::InitLogging()
|
|
{
|
|
if (s_libretro_log_callback_registered)
|
|
return;
|
|
|
|
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);
|
|
s_libretro_log_callback_registered = true;
|
|
}
|
|
}
|
|
|
|
bool LibretroHostInterface::Initialize()
|
|
{
|
|
if (!HostInterface::Initialize())
|
|
return false;
|
|
|
|
LoadSettings();
|
|
UpdateLogging();
|
|
return true;
|
|
}
|
|
|
|
void LibretroHostInterface::Shutdown()
|
|
{
|
|
HostInterface::Shutdown();
|
|
}
|
|
|
|
void LibretroHostInterface::ReportError(const char* message)
|
|
{
|
|
AddFormattedOSDMessage(10.0f, "ERROR: %s", message);
|
|
Log_ErrorPrint(message);
|
|
}
|
|
|
|
void LibretroHostInterface::ReportMessage(const char* message)
|
|
{
|
|
AddOSDMessage(message, 5.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();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
std::string LibretroHostInterface::GetSharedMemoryCardPath(u32 slot) const
|
|
{
|
|
return StringUtil::StdStringFromFormat("%s%cduckstation_shared_card_%d.mcd", GetSaveDirectory(),
|
|
FS_OSPATH_SEPERATOR_CHARACTER, slot + 1);
|
|
}
|
|
|
|
std::string LibretroHostInterface::GetGameMemoryCardPath(const char* game_code, u32 slot) const
|
|
{
|
|
return StringUtil::StdStringFromFormat("%s%c%s_%d.mcd", GetSaveDirectory(), FS_OSPATH_SEPERATOR_CHARACTER, game_code,
|
|
slot + 1);
|
|
}
|
|
|
|
std::string LibretroHostInterface::GetShaderCacheBasePath() const
|
|
{
|
|
// TODO: Is there somewhere we can save our shaders?
|
|
Log_WarningPrint("No shader cache directory available, startup will be slower.");
|
|
return std::string();
|
|
}
|
|
|
|
std::string LibretroHostInterface::GetStringSettingValue(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<u32>(duration * (System::IsShutdown() ? 60.0f : System::GetThrottleFrequency()));
|
|
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 = (g_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 ? g_settings.gpu_resolution_scale : 1u;
|
|
Assert(System::IsValid());
|
|
|
|
std::memset(info, 0, sizeof(*info));
|
|
|
|
info->geometry.aspect_ratio = Settings::GetDisplayAspectRatioValue(g_settings.display_aspect_ratio);
|
|
info->geometry.base_width = 320;
|
|
info->geometry.base_height = 240;
|
|
info->geometry.max_width = GPU::VRAM_WIDTH * resolution_scale;
|
|
info->geometry.max_height = GPU::VRAM_HEIGHT * resolution_scale;
|
|
|
|
info->timing.fps = System::GetThrottleFrequency();
|
|
info->timing.sample_rate = static_cast<double>(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 = (g_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(g_settings.log_level);
|
|
|
|
if (s_libretro_log_callback_valid)
|
|
Log::SetConsoleOutputParams(false);
|
|
else
|
|
Log::SetConsoleOutputParams(true, nullptr, g_settings.log_level);
|
|
}
|
|
|
|
bool LibretroHostInterface::retro_load_game(const struct retro_game_info* game)
|
|
{
|
|
SystemBootParameters bp;
|
|
bp.filename = game->path;
|
|
bp.media_playlist_index = m_next_disc_index.value_or(0);
|
|
bp.force_software_renderer = !m_hw_render_callback_valid;
|
|
|
|
if (!BootSystem(bp))
|
|
return false;
|
|
|
|
if (g_settings.gpu_renderer != GPURenderer::Software)
|
|
{
|
|
if (!m_hw_render_callback_valid)
|
|
RequestHardwareRendererContext();
|
|
else
|
|
SwitchToHardwareRenderer();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void LibretroHostInterface::retro_run_frame()
|
|
{
|
|
Assert(!System::IsShutdown());
|
|
|
|
if (HasCoreVariablesChanged())
|
|
UpdateSettings();
|
|
|
|
UpdateControllers();
|
|
|
|
g_gpu->RestoreGraphicsAPIState();
|
|
|
|
System::RunFrame();
|
|
|
|
g_gpu->ResetGraphicsAPIState();
|
|
|
|
m_display->Render();
|
|
}
|
|
|
|
unsigned LibretroHostInterface::retro_get_region()
|
|
{
|
|
return 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<ByteStream> stream = ByteStream_CreateMemoryStream(data, static_cast<u32>(size));
|
|
if (!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<ByteStream> stream = ByteStream_CreateReadOnlyMemoryStream(data, static_cast<u32>(size));
|
|
if (!System::LoadState(stream.get()))
|
|
{
|
|
Log_ErrorPrintf("Failed to load save state from memory stream");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void* LibretroHostInterface::retro_get_memory_data(unsigned id)
|
|
{
|
|
switch (id)
|
|
{
|
|
case RETRO_MEMORY_SYSTEM_RAM:
|
|
return System::IsShutdown() ? nullptr : Bus::g_ram;
|
|
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
size_t LibretroHostInterface::retro_get_memory_size(unsigned id)
|
|
{
|
|
switch (id)
|
|
{
|
|
case RETRO_MEMORY_SYSTEM_RAM:
|
|
return Bus::RAM_SIZE;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool LibretroHostInterface::AcquireHostDisplay()
|
|
{
|
|
// start in software mode, switch to hardware later
|
|
m_display = std::make_unique<LibretroHostDisplay>();
|
|
return true;
|
|
}
|
|
|
|
void LibretroHostInterface::ReleaseHostDisplay()
|
|
{
|
|
if (m_hw_render_display)
|
|
{
|
|
m_hw_render_display->DestroyRenderDevice();
|
|
m_hw_render_display.reset();
|
|
}
|
|
|
|
m_display->DestroyRenderDevice();
|
|
m_display.reset();
|
|
}
|
|
|
|
std::unique_ptr<AudioStream> LibretroHostInterface::CreateAudioStream(AudioBackend backend)
|
|
{
|
|
return std::make_unique<LibretroAudioStream>();
|
|
}
|
|
|
|
void LibretroHostInterface::OnSystemDestroyed()
|
|
{
|
|
HostInterface::OnSystemDestroyed();
|
|
m_using_hardware_renderer = false;
|
|
}
|
|
|
|
static std::array<retro_core_option_definition, 23> 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.PatchFastBoot",
|
|
"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",
|
|
"Internal Resolution Scale",
|
|
"Scales internal VRAM resolution by the specified multiplier. Larger values are slower. Some games require "
|
|
"1x VRAM resolution or they will have rendering issues.",
|
|
{{"1", "1x"},
|
|
{"2", "2x"},
|
|
{"3", "3x (for 720p)"},
|
|
{"4", "4x"},
|
|
{"5", "5x (for 1080p)"},
|
|
{"6", "6x (for 1440p)"},
|
|
{"7", "7x"},
|
|
{"8", "8x"},
|
|
{"9", "9x (for 4K)"},
|
|
{"10", "10x"},
|
|
{"11", "11x"},
|
|
{"12", "12x"},
|
|
{"13", "13x"},
|
|
{"14", "14x"},
|
|
{"15", "15x"},
|
|
{"16", "16x"}},
|
|
"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"},
|
|
{"GPU.WidescreenHack",
|
|
"Widescreen Hack",
|
|
"Increases the field of view from 4:3 to 16:9 in 3D games. For 2D games, or games which use pre-rendered "
|
|
"backgrounds, this enhancement will not work as expected.",
|
|
{{"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.AutoEnableAnalog",
|
|
"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.AutoEnableAnalog",
|
|
"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;
|
|
g_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";
|
|
g_settings.bios_path =
|
|
StringUtil::StdStringFromFormat("%s%cscph1001.bin", system_directory, FS_OSPATH_SEPERATOR_CHARACTER);
|
|
|
|
// Ensure we don't use the standalone memcard directory in shared mode.
|
|
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
|
g_settings.memory_card_paths[i] = GetSharedMemoryCardPath(i);
|
|
}
|
|
|
|
void LibretroHostInterface::UpdateSettings()
|
|
{
|
|
Settings old_settings(std::move(g_settings));
|
|
LoadSettings();
|
|
|
|
if (g_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale &&
|
|
g_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 = g_settings.gpu_resolution_scale;
|
|
}
|
|
|
|
if (g_settings.gpu_renderer != old_settings.gpu_renderer)
|
|
{
|
|
ReportFormattedMessage("Switch to %s renderer pending, please restart the core to apply.",
|
|
Settings::GetRendererDisplayName(g_settings.gpu_renderer));
|
|
g_settings.gpu_renderer = old_settings.gpu_renderer;
|
|
}
|
|
|
|
CheckForSettingsChanges(old_settings);
|
|
}
|
|
|
|
void LibretroHostInterface::CheckForSettingsChanges(const Settings& old_settings)
|
|
{
|
|
HostInterface::CheckForSettingsChanges(old_settings);
|
|
|
|
if (g_settings.display_aspect_ratio != old_settings.display_aspect_ratio)
|
|
UpdateGeometry();
|
|
|
|
if (g_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 (g_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(g_settings.controller_types[i]));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LibretroHostInterface::UpdateControllersDigitalController(u32 index)
|
|
{
|
|
DigitalController* controller = static_cast<DigitalController*>(System::GetController(index));
|
|
DebugAssert(controller);
|
|
|
|
static constexpr std::array<std::pair<DigitalController::Button, u32>, 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<AnalogController*>(System::GetController(index));
|
|
DebugAssert(controller);
|
|
|
|
static constexpr std::array<std::pair<AnalogController::Button, u32>, 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<std::pair<AnalogController::Axis, std::pair<u32, u32>>, 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<s32>(it.first), std::clamp(static_cast<float>(state) / 32767.0f, -1.0f, 1.0f));
|
|
}
|
|
}
|
|
|
|
static std::optional<GPURenderer> 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;
|
|
}
|
|
}
|
|
|
|
static std::optional<GPURenderer> RenderAPIToRenderer(HostDisplay::RenderAPI api)
|
|
{
|
|
switch (api)
|
|
{
|
|
case HostDisplay::RenderAPI::OpenGL:
|
|
case HostDisplay::RenderAPI::OpenGLES:
|
|
return GPURenderer::HardwareOpenGL;
|
|
|
|
case HostDisplay::RenderAPI::Vulkan:
|
|
return GPURenderer::HardwareVulkan;
|
|
|
|
#ifdef WIN32
|
|
case HostDisplay::RenderAPI::D3D11:
|
|
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<GPURenderer> preferred_gpu_renderer =
|
|
RetroHwContextToRenderer(static_cast<retro_hw_context_type>(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<unsigned>(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()
|
|
{
|
|
// use the existing device if we just resized the window
|
|
std::optional<GPURenderer> renderer;
|
|
std::unique_ptr<HostDisplay> display = std::move(m_hw_render_display);
|
|
if (display)
|
|
{
|
|
Log_InfoPrintf("Using existing hardware display");
|
|
renderer = RenderAPIToRenderer(display->GetRenderAPI());
|
|
}
|
|
else
|
|
{
|
|
renderer = RetroHwContextToRenderer(m_hw_render_callback.context_type);
|
|
if (!renderer.has_value())
|
|
{
|
|
Log_ErrorPrintf("Unknown context type %u", static_cast<unsigned>(m_hw_render_callback.context_type));
|
|
return;
|
|
}
|
|
|
|
switch (renderer.value())
|
|
{
|
|
case GPURenderer::HardwareOpenGL:
|
|
display = std::make_unique<LibretroOpenGLHostDisplay>();
|
|
break;
|
|
|
|
case GPURenderer::HardwareVulkan:
|
|
display = std::make_unique<LibretroVulkanHostDisplay>();
|
|
break;
|
|
|
|
#ifdef WIN32
|
|
case GPURenderer::HardwareD3D11:
|
|
display = std::make_unique<LibretroD3D11HostDisplay>();
|
|
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_settings.gpu_use_debug_device) ||
|
|
!display->InitializeRenderDevice({}, g_settings.gpu_use_debug_device))
|
|
{
|
|
Log_ErrorPrintf("Failed to create hardware host display");
|
|
return;
|
|
}
|
|
}
|
|
|
|
std::swap(display, g_libretro_host_interface.m_display);
|
|
System::RecreateGPU(renderer.value());
|
|
display->DestroyRenderDevice();
|
|
m_using_hardware_renderer = true;
|
|
}
|
|
|
|
void LibretroHostInterface::HardwareRendererContextDestroy()
|
|
{
|
|
// 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();
|
|
}
|
|
|
|
if (g_libretro_host_interface.m_hw_render_display)
|
|
{
|
|
g_libretro_host_interface.m_hw_render_display->DestroyRenderDevice();
|
|
g_libretro_host_interface.m_hw_render_display.reset();
|
|
}
|
|
|
|
g_libretro_host_interface.m_hw_render_callback_valid = false;
|
|
}
|
|
|
|
void LibretroHostInterface::SwitchToSoftwareRenderer()
|
|
{
|
|
// keep the hw renderer around in case we need it later
|
|
if (m_using_hardware_renderer)
|
|
{
|
|
m_hw_render_display = std::move(m_display);
|
|
m_using_hardware_renderer = false;
|
|
}
|
|
|
|
m_display = std::make_unique<LibretroHostDisplay>();
|
|
System::RecreateGPU(GPURenderer::Software);
|
|
}
|
|
|
|
bool LibretroHostInterface::DiskControlSetEjectState(bool ejected)
|
|
{
|
|
if (System::IsShutdown())
|
|
{
|
|
Log_ErrorPrintf("DiskControlSetEjectState() - no system");
|
|
return false;
|
|
}
|
|
|
|
Log_DevPrintf("DiskControlSetEjectState(%u)", static_cast<unsigned>(ejected));
|
|
|
|
if (ejected)
|
|
{
|
|
if (!System::HasMedia())
|
|
return false;
|
|
|
|
System::RemoveMedia();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
const u32 image_to_insert = P_THIS->m_next_disc_index.value_or(0);
|
|
Log_DevPrintf("Inserting image %u", image_to_insert);
|
|
return System::SwitchMediaFromPlaylist(image_to_insert);
|
|
}
|
|
}
|
|
|
|
bool LibretroHostInterface::DiskControlGetEjectState()
|
|
{
|
|
if (System::IsShutdown())
|
|
{
|
|
Log_ErrorPrintf("DiskControlGetEjectState() - no system");
|
|
return false;
|
|
}
|
|
|
|
Log_DevPrintf("DiskControlGetEjectState() -> %u", static_cast<unsigned>(System::HasMedia()));
|
|
return System::HasMedia();
|
|
}
|
|
|
|
unsigned LibretroHostInterface::DiskControlGetImageIndex()
|
|
{
|
|
if (System::IsShutdown())
|
|
{
|
|
Log_ErrorPrintf("DiskControlGetImageIndex() - no system");
|
|
return false;
|
|
}
|
|
|
|
const u32 index = P_THIS->m_next_disc_index.value_or(System::GetMediaPlaylistIndex());
|
|
Log_DevPrintf("DiskControlGetImageIndex() -> %u", index);
|
|
return index;
|
|
}
|
|
|
|
bool LibretroHostInterface::DiskControlSetImageIndex(unsigned index)
|
|
{
|
|
if (System::IsShutdown())
|
|
{
|
|
Log_ErrorPrintf("DiskControlSetImageIndex() - no system");
|
|
return false;
|
|
}
|
|
|
|
Log_DevPrintf("DiskControlSetImageIndex(%u)", index);
|
|
|
|
if (index >= System::GetMediaPlaylistCount())
|
|
return false;
|
|
|
|
P_THIS->m_next_disc_index = index;
|
|
return true;
|
|
}
|
|
|
|
unsigned LibretroHostInterface::DiskControlGetNumImages()
|
|
{
|
|
if (System::IsShutdown())
|
|
{
|
|
Log_ErrorPrintf("DiskControlGetNumImages() - no system");
|
|
return false;
|
|
}
|
|
|
|
Log_DevPrintf("DiskControlGetNumImages() -> %u", System::GetMediaPlaylistCount());
|
|
return static_cast<unsigned>(System::GetMediaPlaylistCount());
|
|
}
|
|
|
|
bool LibretroHostInterface::DiskControlReplaceImageIndex(unsigned index, const retro_game_info* info)
|
|
{
|
|
if (System::IsShutdown())
|
|
{
|
|
Log_ErrorPrintf("DiskControlReplaceImageIndex() - no system");
|
|
return false;
|
|
}
|
|
|
|
Log_DevPrintf("DiskControlReplaceImageIndex(%u, %s)", index, info ? info->path : "null");
|
|
if (info && info->path)
|
|
return System::ReplaceMediaPathFromPlaylist(index, info->path);
|
|
else
|
|
return System::RemoveMediaPathFromPlaylist(index);
|
|
}
|
|
|
|
bool LibretroHostInterface::DiskControlAddImageIndex()
|
|
{
|
|
if (System::IsShutdown())
|
|
{
|
|
Log_ErrorPrintf("DiskControlAddImageIndex() - no system");
|
|
return false;
|
|
}
|
|
|
|
Log_DevPrintf("DiskControlAddImageIndex() -> %zu", System::GetMediaPlaylistCount());
|
|
System::AddMediaPathToPlaylist({});
|
|
return true;
|
|
}
|
|
|
|
bool LibretroHostInterface::DiskControlSetInitialImage(unsigned index, const char* path)
|
|
{
|
|
Log_DevPrintf("DiskControlSetInitialImage(%u, %s)", index, path);
|
|
P_THIS->m_next_disc_index = index;
|
|
return true;
|
|
}
|
|
|
|
bool LibretroHostInterface::DiskControlGetImagePath(unsigned index, char* path, size_t len)
|
|
{
|
|
if (System::IsShutdown() || index >= System::GetMediaPlaylistCount())
|
|
return false;
|
|
|
|
const std::string& image_path = System::GetMediaPlaylistPath(index);
|
|
Log_DevPrintf("DiskControlGetImagePath(%u) -> %s", index, image_path.c_str());
|
|
if (image_path.empty())
|
|
return false;
|
|
|
|
StringUtil::Strlcpy(path, image_path.c_str(), len);
|
|
return true;
|
|
}
|
|
|
|
bool LibretroHostInterface::DiskControlGetImageLabel(unsigned index, char* label, size_t len)
|
|
{
|
|
if (System::IsShutdown() || index >= System::GetMediaPlaylistCount())
|
|
return false;
|
|
|
|
const std::string& image_path = System::GetMediaPlaylistPath(index);
|
|
if (image_path.empty())
|
|
return false;
|
|
|
|
const std::string_view title = GameList::GetTitleForPath(label);
|
|
StringUtil::Strlcpy(label, title, len);
|
|
Log_DevPrintf("DiskControlGetImagePath(%u) -> %s", index, label);
|
|
return true;
|
|
}
|
|
|
|
void LibretroHostInterface::InitDiskControlInterface()
|
|
{
|
|
unsigned version = 0;
|
|
if (g_retro_environment_callback(RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION, &version) && version >= 1)
|
|
{
|
|
retro_disk_control_ext_callback ext_cb = {
|
|
&LibretroHostInterface::DiskControlSetEjectState, &LibretroHostInterface::DiskControlGetEjectState,
|
|
&LibretroHostInterface::DiskControlGetImageIndex, &LibretroHostInterface::DiskControlSetImageIndex,
|
|
&LibretroHostInterface::DiskControlGetNumImages, &LibretroHostInterface::DiskControlReplaceImageIndex,
|
|
&LibretroHostInterface::DiskControlAddImageIndex, &LibretroHostInterface::DiskControlSetInitialImage,
|
|
&LibretroHostInterface::DiskControlGetImagePath, &LibretroHostInterface::DiskControlGetImageLabel};
|
|
if (g_retro_environment_callback(RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE, &ext_cb))
|
|
return;
|
|
}
|
|
|
|
retro_disk_control_callback cb = {
|
|
&LibretroHostInterface::DiskControlSetEjectState, &LibretroHostInterface::DiskControlGetEjectState,
|
|
&LibretroHostInterface::DiskControlGetImageIndex, &LibretroHostInterface::DiskControlSetImageIndex,
|
|
&LibretroHostInterface::DiskControlGetNumImages, &LibretroHostInterface::DiskControlReplaceImageIndex,
|
|
&LibretroHostInterface::DiskControlAddImageIndex};
|
|
if (!g_retro_environment_callback(RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE, &cb))
|
|
Log_WarningPrint("Failed to set disk control interface");
|
|
}
|