1 /*
2 * Copyright (C) 2014-2020 Team Kodi (https://kodi.tv)
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 * See LICENSE.md for more information.
6 */
7
8 #include "LibretroEnvironment.h"
9 #include "ClientBridge.h"
10 #include "FrontendBridge.h"
11 #include "libretro.h"
12 #include "LibretroDLL.h"
13 #include "LibretroTranslator.h"
14 #include "input/InputManager.h"
15 #include "log/Log.h"
16 #include "settings/Settings.h"
17 #include "video/VideoGeometry.h"
18 #include "client.h"
19
20 #include <kodi/General.h>
21
22 using namespace LIBRETRO;
23
24 namespace LIBRETRO
25 {
EnvCallback(unsigned cmd,void * data)26 bool EnvCallback(unsigned cmd, void* data)
27 {
28 return CLibretroEnvironment::Get().EnvironmentCallback(cmd, data);
29 }
30 }
31
CLibretroEnvironment(void)32 CLibretroEnvironment::CLibretroEnvironment(void) :
33 m_addon(nullptr),
34 m_client(nullptr),
35 m_clientBridge(nullptr),
36 m_videoFormat(GAME_PIXEL_FORMAT_0RGB1555), // Default libretro format
37 m_videoRotation(GAME_VIDEO_ROTATION_0)
38 {
39 }
40
Get(void)41 CLibretroEnvironment& CLibretroEnvironment::Get(void)
42 {
43 static CLibretroEnvironment _instance;
44 return _instance;
45 }
46
Initialize(CGameLibRetro * addon,CLibretroDLL * client,CClientBridge * clientBridge)47 void CLibretroEnvironment::Initialize(CGameLibRetro* addon,
48 CLibretroDLL* client,
49 CClientBridge* clientBridge)
50 {
51 m_addon = addon;
52 m_client = client;
53 m_clientBridge = clientBridge;
54
55 m_videoStream.Initialize(m_addon);
56 m_audioStream.Initialize(m_addon);
57
58 m_settings.Initialize(m_addon);
59 m_resources.Initialize(m_addon);
60
61 // Install environment callback
62 m_client->retro_set_environment(EnvCallback);
63
64 // Install remaining callbacks
65 m_client->retro_set_video_refresh(CFrontendBridge::VideoRefresh);
66 m_client->retro_set_audio_sample(CFrontendBridge::AudioFrame);
67 m_client->retro_set_audio_sample_batch(CFrontendBridge::AudioFrames);
68 m_client->retro_set_input_poll(CFrontendBridge::InputPoll);
69 m_client->retro_set_input_state(CFrontendBridge::InputState);
70 }
71
Deinitialize()72 void CLibretroEnvironment::Deinitialize()
73 {
74 CloseStreams();
75
76 m_resources.Deinitialize();
77 m_settings.Deinitialize();
78 }
79
CloseStreams()80 void CLibretroEnvironment::CloseStreams()
81 {
82 m_videoStream.Deinitialize();
83 m_audioStream.Deinitialize();
84 }
85
UpdateVideoGeometry(const retro_game_geometry & geometry)86 void CLibretroEnvironment::UpdateVideoGeometry(const retro_game_geometry &geometry)
87 {
88 CVideoGeometry videoGeometry(geometry);
89 m_videoStream.SetGeometry(videoGeometry);
90 }
91
SetSetting(const std::string & name,const std::string & value)92 void CLibretroEnvironment::SetSetting(const std::string& name, const std::string& value)
93 {
94 m_settings.SetCurrentValue(name, value);
95 }
96
GetResourcePath(const char * relPath)97 std::string CLibretroEnvironment::GetResourcePath(const char* relPath)
98 {
99 return m_resources.GetFullPath(relPath);
100 }
101
OnFrameEnd()102 void CLibretroEnvironment::OnFrameEnd()
103 {
104 m_videoStream.OnFrameEnd();
105 }
106
EnvironmentCallback(unsigned int cmd,void * data)107 bool CLibretroEnvironment::EnvironmentCallback(unsigned int cmd, void *data)
108 {
109 if (!m_addon || !m_clientBridge)
110 return false;
111
112 switch (cmd)
113 {
114 case RETRO_ENVIRONMENT_SET_ROTATION:
115 {
116 unsigned* typedData = reinterpret_cast<unsigned*>(data);
117 if (typedData)
118 m_videoRotation = LibretroTranslator::GetVideoRotation(*typedData);
119 break;
120 }
121 case RETRO_ENVIRONMENT_GET_OVERSCAN:
122 {
123 bool* typedData = reinterpret_cast<bool*>(data);
124 if (typedData)
125 *typedData = !CSettings::Get().CropOverscan();
126 break;
127 }
128 case RETRO_ENVIRONMENT_GET_CAN_DUPE:
129 {
130 bool* typedData = reinterpret_cast<bool*>(data);
131 if (typedData)
132 *typedData = true;
133 break;
134 }
135 case RETRO_ENVIRONMENT_SET_MESSAGE:
136 {
137 // Sets a message to be displayed. Generally not for trivial messages.
138 const retro_message* typedData = reinterpret_cast<const retro_message*>(data);
139 if (typedData)
140 {
141 const char* msg = typedData->msg;
142 kodi::QueueFormattedNotification(QUEUE_INFO, msg);
143 }
144 break;
145 }
146 case RETRO_ENVIRONMENT_SHUTDOWN:
147 {
148 m_addon->CloseGame();
149 break;
150 }
151 case RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL:
152 {
153 const unsigned* typedData = reinterpret_cast<const unsigned*>(data);
154 // Removed from Game API
155 (void)typedData;
156 break;
157 }
158 case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
159 {
160 const char** typedData = reinterpret_cast<const char**>(data);
161 if (typedData)
162 {
163 *typedData = m_resources.GetSystemDir();
164 }
165 break;
166 }
167 case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
168 {
169 const retro_pixel_format* typedData = reinterpret_cast<const retro_pixel_format*>(data);
170 if (!typedData)
171 return false;
172
173 dsyslog("Setting libretro pixel format \"%s\"", LibretroTranslator::VideoFormatToString(*typedData));
174
175 m_videoFormat = LibretroTranslator::GetVideoFormat(*typedData);
176
177 break;
178 }
179 case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS:
180 {
181 const retro_input_descriptor* typedData = reinterpret_cast<const retro_input_descriptor*>(data);
182 if (typedData)
183 CInputManager::Get().LogInputDescriptors(typedData);
184 break;
185 }
186 case RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK:
187 {
188 const retro_keyboard_callback* typedData = reinterpret_cast<const retro_keyboard_callback*>(data);
189 if (typedData)
190 {
191 // Store callback from libretro client
192 m_clientBridge->SetKeyboardEvent(typedData->callback);
193 }
194 break;
195 }
196 case RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE:
197 {
198 const retro_disk_control_callback *typedData = reinterpret_cast<const retro_disk_control_callback*>(data);
199 if (typedData)
200 {
201 // Disk control interface not implemented
202 return false;
203 }
204 break;
205 }
206 case RETRO_ENVIRONMENT_SET_HW_RENDER:
207 {
208 retro_hw_render_callback* typedData = reinterpret_cast<retro_hw_render_callback*>(data);
209 if (typedData)
210 {
211 // Translate struct and report hw info to frontend
212 game_stream_hw_framebuffer_properties hw_info;
213 hw_info.context_type = LibretroTranslator::GetHWContextType(typedData->context_type);
214 hw_info.depth = typedData->depth;
215 hw_info.stencil = typedData->stencil;
216 hw_info.bottom_left_origin = typedData->bottom_left_origin;
217 hw_info.version_major = typedData->version_major;
218 hw_info.version_minor = typedData->version_minor;
219 hw_info.cache_context = typedData->cache_context;
220 hw_info.debug_context = typedData->debug_context;
221 m_videoStream.EnableHardwareRendering(hw_info);
222
223 // Store callbacks from libretro client
224 m_clientBridge->SetHwContextReset(typedData->context_reset);
225 m_clientBridge->SetHwContextDestroy(typedData->context_destroy);
226
227 // Expose frontend callbacks to libretro client
228 typedData->get_current_framebuffer = CFrontendBridge::HwGetCurrentFramebuffer;
229 typedData->get_proc_address = CFrontendBridge::HwGetProcAddress;
230 }
231 break;
232 }
233 case RETRO_ENVIRONMENT_GET_VARIABLE:
234 {
235 retro_variable* typedData = reinterpret_cast<retro_variable*>(data);
236 if (typedData)
237 {
238 const char* strKey = typedData->key;
239 if (strKey == nullptr)
240 return false;
241
242 typedData->value = m_settings.GetCurrentValue(strKey);
243
244 // Assume libretro core is retrieving all variables at a time
245 m_settings.SetUnchanged();
246 }
247 break;
248 }
249 case RETRO_ENVIRONMENT_SET_VARIABLES:
250 {
251 const retro_variable* typedData = reinterpret_cast<const retro_variable*>(data);
252 if (typedData)
253 {
254 m_settings.SetAllSettings(typedData);
255 }
256 break;
257 }
258 case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
259 {
260 bool* typedData = reinterpret_cast<bool*>(data);
261 if (typedData)
262 {
263 *typedData = m_settings.Changed();
264 }
265 break;
266 }
267 case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
268 {
269 const bool* typedData = reinterpret_cast<const bool*>(data);
270 if (typedData)
271 {
272 const bool bSupportsNoGame = *typedData;
273 if (bSupportsNoGame)
274 kodi::Log(ADDON_LOG_DEBUG, "Libretro client supports loading with no game");
275 }
276 break;
277 }
278 case RETRO_ENVIRONMENT_GET_LIBRETRO_PATH:
279 {
280 const char** typedData = reinterpret_cast<const char**>(data);
281 if (typedData)
282 {
283 *typedData = m_resources.GetContentDirectory();
284 }
285 break;
286 }
287 case RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK:
288 {
289 const retro_audio_callback *typedData = reinterpret_cast<const retro_audio_callback*>(data);
290 if (typedData)
291 {
292 // Store callbacks from libretro client
293 m_clientBridge->SetAudioAvailable(typedData->callback);
294 m_clientBridge->SetAudioEnable(typedData->set_state);
295 }
296 break;
297 }
298 case RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK:
299 {
300 const retro_frame_time_callback *typedData = reinterpret_cast<const retro_frame_time_callback*>(data);
301 if (typedData)
302 {
303 // Store callbacks from libretro client.
304 m_clientBridge->SetFrameTime(typedData->callback);
305 return false;
306 }
307 break;
308 }
309 case RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE:
310 {
311 retro_rumble_interface* typedData = reinterpret_cast<retro_rumble_interface*>(data);
312 if (typedData)
313 {
314 // Expose callback to libretro core
315 typedData->set_rumble_state = CFrontendBridge::RumbleSetState;
316 }
317 break;
318 }
319 case RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES:
320 {
321 uint64_t* typedData = reinterpret_cast<uint64_t*>(data);
322 if (typedData)
323 *typedData = CInputManager::Get().GetDeviceCaps();
324 break;
325 }
326 case RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE:
327 {
328 retro_sensor_interface* typedData = reinterpret_cast<retro_sensor_interface*>(data);
329 if (typedData)
330 {
331 // Expose callbacks to libretro core
332 typedData->set_sensor_state = CFrontendBridge::SensorSetState;
333 typedData->get_sensor_input = CFrontendBridge::SensorGetInput;
334 }
335 break;
336 }
337 case RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE:
338 {
339 retro_camera_callback* typedData = reinterpret_cast<retro_camera_callback*>(data);
340 if (typedData)
341 {
342 // Camera interface not implemented
343 return false;
344 }
345 break;
346 }
347 case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
348 {
349 retro_log_callback* typedData = reinterpret_cast<retro_log_callback*>(data);
350 if (typedData)
351 {
352 // Expose callback to libretro core
353 typedData->log = CFrontendBridge::LogFrontend; // libretro logging forwards to Kodi add-on log function
354 }
355 break;
356 }
357 case RETRO_ENVIRONMENT_GET_PERF_INTERFACE:
358 {
359 retro_perf_callback* typedData = reinterpret_cast<retro_perf_callback*>(data);
360 if (typedData)
361 {
362 // Expose callbacks to libretro core
363 typedData->get_time_usec = CFrontendBridge::PerfGetTimeUsec;
364 typedData->get_cpu_features = CFrontendBridge::PerfGetCpuFeatures;
365 typedData->get_perf_counter = CFrontendBridge::PerfGetCounter;
366 typedData->perf_register = CFrontendBridge::PerfRegister;
367 typedData->perf_start = CFrontendBridge::PerfStart;
368 typedData->perf_stop = CFrontendBridge::PerfStop;
369 typedData->perf_log = CFrontendBridge::PerfLog;
370 }
371 break;
372 }
373 case RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE:
374 {
375 retro_location_callback* typedData = reinterpret_cast<retro_location_callback*>(data);
376 if (typedData)
377 {
378 // Expose callbacks to libretro core
379 typedData->start = CFrontendBridge::StartLocation;
380 typedData->stop = CFrontendBridge::StopLocation;
381 typedData->get_position = CFrontendBridge::GetLocation;
382 typedData->set_interval = CFrontendBridge::SetLocationInterval;
383 typedData->initialized = CFrontendBridge::LocationInitialized;
384 typedData->deinitialized = CFrontendBridge::LocationDeinitialized;
385 }
386 break;
387 }
388 case RETRO_ENVIRONMENT_GET_CONTENT_DIRECTORY:
389 {
390 const char** typedData = reinterpret_cast<const char**>(data);
391 if (typedData)
392 {
393 *typedData = m_resources.GetContentDirectory();
394 }
395 break;
396 }
397 case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
398 {
399 const char** typedData = reinterpret_cast<const char**>(data);
400 if (typedData)
401 {
402 *typedData = m_resources.GetSaveDirectory();
403 }
404 break;
405 }
406 case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO:
407 {
408 const retro_system_av_info* typedData = reinterpret_cast<const retro_system_av_info*>(data);
409 if (!typedData)
410 return false;
411
412 CVideoGeometry videoGeometry(typedData->geometry);
413 m_videoStream.SetGeometry(videoGeometry);
414
415 //! @todo Reopen streams if geometry changes
416
417 //! @todo Report updating timing info to frontend
418 const double fps = typedData->timing.fps;
419 const double sampleRate = typedData->timing.sample_rate;
420
421 break;
422 }
423 case RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK:
424 {
425 const retro_get_proc_address_interface* typedData = reinterpret_cast<const retro_get_proc_address_interface*>(data);
426 if (typedData)
427 {
428 // get_proc_address() interface not implemented
429 return false;
430 }
431 break;
432 }
433 case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO:
434 {
435 const retro_subsystem_info* typedData = reinterpret_cast<const retro_subsystem_info*>(data);
436 if (typedData)
437 {
438 // Not implemented
439 return false;
440 }
441 break;
442 }
443 case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
444 {
445 const retro_controller_info* typedData = reinterpret_cast<const retro_controller_info*>(data);
446 if (typedData)
447 {
448 CInputManager::Get().SetControllerInfo(typedData);
449 }
450 break;
451 }
452 case RETRO_ENVIRONMENT_SET_MEMORY_MAPS:
453 {
454 const retro_memory_map* typedData = reinterpret_cast<const retro_memory_map*>(data);
455 if (typedData)
456 {
457 // Not implemented
458 return false;
459 }
460 break;
461 }
462 case RETRO_ENVIRONMENT_SET_GEOMETRY:
463 {
464 const retro_game_geometry* typedData = reinterpret_cast<const retro_game_geometry*>(data);
465 if (typedData)
466 {
467 // Not implemented
468 return false;
469 }
470 break;
471 }
472 case RETRO_ENVIRONMENT_GET_USERNAME:
473 {
474 const char** typedData = reinterpret_cast<const char**>(data);
475 if (typedData)
476 {
477 // Not implemented
478 return false;
479 }
480 break;
481 }
482 case RETRO_ENVIRONMENT_GET_LANGUAGE:
483 {
484 unsigned int* typedData = reinterpret_cast<unsigned int*>(data);
485 if (typedData)
486 {
487 // Not implemented
488 return false;
489 }
490 break;
491 }
492 case RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER:
493 {
494 retro_framebuffer* typedData = reinterpret_cast<retro_framebuffer*>(data);
495 if (typedData)
496 {
497 // Get framebuffer params from core
498 const unsigned int accessFlags = typedData->access_flags;
499 const unsigned int width = typedData->width;
500 const unsigned int height = typedData->height;
501
502 // Reading framebuffers not supported
503 if (accessFlags & RETRO_MEMORY_ACCESS_READ)
504 return false;
505
506 game_stream_sw_framebuffer_buffer framebuffer{};
507 if (!m_videoStream.GetSwFramebuffer(width, height, m_videoFormat, framebuffer))
508 return false;
509
510 // Report framebuffer info to frontend
511 typedData->data = framebuffer.data;
512 typedData->pitch = framebuffer.size / height;
513 typedData->format = LibretroTranslator::GetLibretroVideoFormat(framebuffer.format);
514 typedData->memory_flags = 0;
515 }
516 break;
517 }
518 case RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT:
519 {
520 // Not implemented
521 return false;
522 }
523 case RETRO_ENVIRONMENT_GET_VFS_INTERFACE:
524 {
525 const uint32_t supported_vfs_version = 1;
526
527 retro_vfs_interface_info* typedData = reinterpret_cast<retro_vfs_interface_info*>(data);
528 if (typedData)
529 {
530 // Not implemented
531 return false;
532 }
533 break;
534 }
535 case RETRO_ENVIRONMENT_GET_LED_INTERFACE:
536 {
537 retro_led_interface* typedData = reinterpret_cast<retro_led_interface*>(data);
538 if (typedData)
539 {
540 // Not implemented
541 return false;
542 }
543 break;
544 }
545 case RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE:
546 {
547 const retro_hw_render_interface* typedData = reinterpret_cast<const retro_hw_render_interface*>(data);
548 if (typedData)
549 {
550 // Not implemented
551 return false;
552 }
553 break;
554 }
555 case RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS:
556 {
557 const bool* typedData = reinterpret_cast<const bool*>(data);
558 if (typedData)
559 {
560 // Not implemented
561 return false;
562 }
563 break;
564 }
565 case RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE:
566 {
567 const retro_hw_render_context_negotiation_interface* typedData = reinterpret_cast<const retro_hw_render_context_negotiation_interface*>(data);
568 if (typedData)
569 {
570 // Not implemented
571 return false;
572 }
573 break;
574 }
575 case RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS:
576 {
577 const retro_hw_render_context_negotiation_interface* typedData = reinterpret_cast<const retro_hw_render_context_negotiation_interface*>(data);
578 if (typedData)
579 {
580 // Not implemented
581 return false;
582 }
583 break;
584 }
585 default:
586 return false;
587 }
588
589 return true;
590 }
591