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 "FrontendBridge.h"
9 #include "LibretroEnvironment.h"
10 #include "LibretroTranslator.h"
11 #include "input/ButtonMapper.h"
12 #include "input/InputManager.h"
13 #include "client.h"
14
15 #include <algorithm>
16 #include <assert.h>
17 #include <kodi/General.h>
18
19 using namespace LIBRETRO;
20
21 #define S16NE_FRAMESIZE 4 // int16 L + int16 R
22
23 #define MAX_RUMBLE_STRENGTH 0xffff
24
25 #ifndef CONSTRAIN
26 // Credit: https://stackoverflow.com/questions/8941262/constrain-function-port-from-arduino
27 #define CONSTRAIN(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)))
28 #endif
29
LogFrontend(retro_log_level level,const char * fmt,...)30 void CFrontendBridge::LogFrontend(retro_log_level level, const char *fmt, ...)
31 {
32 AddonLog xbmcLevel;
33 switch (level)
34 {
35 case RETRO_LOG_DEBUG: xbmcLevel = ADDON_LOG_DEBUG; break;
36 case RETRO_LOG_INFO: xbmcLevel = ADDON_LOG_INFO; break;
37 case RETRO_LOG_WARN: xbmcLevel = ADDON_LOG_ERROR; break;
38 case RETRO_LOG_ERROR: xbmcLevel = ADDON_LOG_ERROR; break;
39 default: xbmcLevel = ADDON_LOG_ERROR; break;
40 }
41
42 char buffer[16384];
43 va_list args;
44 va_start(args, fmt);
45 vsprintf(buffer, fmt, args);
46 va_end(args);
47
48 kodi::Log(xbmcLevel, buffer);
49 }
50
VideoRefresh(const void * data,unsigned int width,unsigned int height,size_t pitch)51 void CFrontendBridge::VideoRefresh(const void* data, unsigned int width, unsigned int height, size_t pitch)
52 {
53 if (data == RETRO_HW_FRAME_BUFFER_VALID)
54 {
55 CLibretroEnvironment::Get().Video().RenderHwFrame();
56 }
57 else if (data == nullptr)
58 {
59 // Libretro is sending a frame dupe command
60 CLibretroEnvironment::Get().Video().DupeFrame();
61 }
62 else
63 {
64 CLibretroEnvironment::Get().Video().AddFrame(static_cast<const uint8_t*>(data),
65 static_cast<unsigned int>(pitch * height),
66 width,
67 height,
68 CLibretroEnvironment::Get().GetVideoFormat(),
69 CLibretroEnvironment::Get().GetVideoRotation());
70 }
71 }
72
AudioFrame(int16_t left,int16_t right)73 void CFrontendBridge::AudioFrame(int16_t left, int16_t right)
74 {
75 CLibretroEnvironment::Get().Audio().AddFrame_S16NE(left, right);
76 }
77
AudioFrames(const int16_t * data,size_t frames)78 size_t CFrontendBridge::AudioFrames(const int16_t* data, size_t frames)
79 {
80 CLibretroEnvironment::Get().Audio().AddFrames_S16NE(reinterpret_cast<const uint8_t*>(data),
81 static_cast<unsigned int>(frames * S16NE_FRAMESIZE));
82
83 return frames;
84 }
85
InputPoll(void)86 void CFrontendBridge::InputPoll(void)
87 {
88 // Not needed
89 }
90
InputState(unsigned int port,unsigned int device,unsigned int index,unsigned int id)91 int16_t CFrontendBridge::InputState(unsigned int port, unsigned int device, unsigned int index, unsigned int id)
92 {
93 int16_t inputState = 0;
94
95 // According to libretro.h, device should already be masked, but just in case
96 device &= RETRO_DEVICE_MASK;
97
98 switch (device)
99 {
100 case RETRO_DEVICE_JOYPAD:
101 case RETRO_DEVICE_KEYBOARD:
102 inputState = CInputManager::Get().ButtonState(device, port, id) ? 1 : 0;
103 break;
104
105 case RETRO_DEVICE_MOUSE:
106 case RETRO_DEVICE_LIGHTGUN:
107 static_assert(RETRO_DEVICE_ID_MOUSE_X == RETRO_DEVICE_ID_LIGHTGUN_X, "RETRO_DEVICE_ID_MOUSE_X != RETRO_DEVICE_ID_LIGHTGUN_X");
108 static_assert(RETRO_DEVICE_ID_MOUSE_Y == RETRO_DEVICE_ID_LIGHTGUN_Y, "RETRO_DEVICE_ID_MOUSE_Y != RETRO_DEVICE_ID_LIGHTGUN_Y");
109
110 switch (id)
111 {
112 case RETRO_DEVICE_ID_MOUSE_X:
113 inputState = CInputManager::Get().DeltaX(device, port);
114 break;
115 case RETRO_DEVICE_ID_MOUSE_Y:
116 inputState = CInputManager::Get().DeltaY(device, port);
117 break;
118 default:
119 {
120 inputState = CInputManager::Get().ButtonState(device, port, id) ? 1 : 0;
121 break;
122 }
123 }
124 break;
125
126 case RETRO_DEVICE_ANALOG:
127 {
128 float value = 0.0f; // Axis value between -1 and 1
129
130 if (index == RETRO_DEVICE_INDEX_ANALOG_BUTTON)
131 {
132 value = CInputManager::Get().AnalogButtonState(port, id);
133 }
134 else
135 {
136 float x, y;
137 if (CInputManager::Get().AnalogStickState(port, index, x, y))
138 {
139 if (id == RETRO_DEVICE_ID_ANALOG_X)
140 {
141 value = x;
142 }
143 else if (id == RETRO_DEVICE_ID_ANALOG_Y)
144 {
145 value = -y; // y axis is inverted
146 }
147 }
148 }
149
150 const float normalized = (value + 1.0f) / 2.0f;
151 const int clamped = std::max(0, std::min(0xffff, static_cast<int>(normalized * 0xffff)));
152 inputState = clamped - 0x8000;
153 break;
154 }
155
156 case RETRO_DEVICE_POINTER:
157 {
158 float x, y;
159 if (CInputManager::Get().AbsolutePointerState(port, index, x, y))
160 {
161 if (id == RETRO_DEVICE_ID_POINTER_X)
162 {
163 inputState = (int)(x * 0x7fff);
164 }
165 else if (id == RETRO_DEVICE_ID_POINTER_Y)
166 {
167 inputState = (int)(y * 0x7fff);
168 }
169 else if (id == RETRO_DEVICE_ID_POINTER_PRESSED)
170 {
171 inputState = 1;
172 }
173 }
174 break;
175 }
176
177 default:
178 break;
179 }
180
181 return inputState;
182 }
183
HwGetCurrentFramebuffer(void)184 uintptr_t CFrontendBridge::HwGetCurrentFramebuffer(void)
185 {
186 if (!CLibretroEnvironment::Get().GetAddon())
187 return 0;
188
189 return CLibretroEnvironment::Get().Video().GetHwFramebuffer();
190 }
191
HwGetProcAddress(const char * sym)192 retro_proc_address_t CFrontendBridge::HwGetProcAddress(const char *sym)
193 {
194 if (!CLibretroEnvironment::Get().GetAddon())
195 return nullptr;
196
197 return CLibretroEnvironment::Get().GetAddon()->HwGetProcAddress(sym);
198 }
199
RumbleSetState(unsigned int port,retro_rumble_effect effect,uint16_t strength)200 bool CFrontendBridge::RumbleSetState(unsigned int port, retro_rumble_effect effect, uint16_t strength)
201 {
202 if (!CLibretroEnvironment::Get().GetAddon())
203 return false;
204
205 std::string controllerId = CInputManager::Get().ControllerID(port);
206 std::string address = CInputManager::Get().GetAddress(port);
207 std::string libretroMotor = LibretroTranslator::GetMotorName(effect);
208 std::string featureName = CButtonMapper::Get().GetControllerFeature(controllerId, libretroMotor);
209 float magnitude = static_cast<float>(strength) / MAX_RUMBLE_STRENGTH;
210
211 if (controllerId.empty() || address.empty() || featureName.empty())
212 return false;
213
214 game_input_event eventStruct;
215 eventStruct.type = GAME_INPUT_EVENT_MOTOR;
216 eventStruct.controller_id = controllerId.c_str();
217 eventStruct.port_address = address.c_str();
218 eventStruct.port_type = GAME_PORT_CONTROLLER;
219 eventStruct.feature_name = featureName.c_str();
220 eventStruct.motor.magnitude = CONSTRAIN(magnitude, 0.0f, 1.0f);
221
222 CLibretroEnvironment::Get().GetAddon()->KodiInputEvent(eventStruct);
223 return true;
224 }
225
SensorSetState(unsigned port,retro_sensor_action action,unsigned rate)226 bool CFrontendBridge::SensorSetState(unsigned port, retro_sensor_action action, unsigned rate)
227 {
228 const bool bEnabled = (action == RETRO_SENSOR_ACCELEROMETER_ENABLE);
229
230 CInputManager::Get().EnableAnalogSensors(port, bEnabled);
231
232 return true;
233 }
234
SensorGetInput(unsigned port,unsigned id)235 float CFrontendBridge::SensorGetInput(unsigned port, unsigned id)
236 {
237 float axisState = 0.0f;
238
239 float x, y, z;
240 if (CInputManager::Get().AccelerometerState(port, x, y, z))
241 {
242 switch (id)
243 {
244 case RETRO_SENSOR_ACCELEROMETER_X:
245 axisState = x;
246 break;
247 case RETRO_SENSOR_ACCELEROMETER_Y:
248 axisState = y;
249 break;
250 case RETRO_SENSOR_ACCELEROMETER_Z:
251 axisState = z;
252 break;
253 default:
254 break;
255 }
256 }
257
258 return axisState;
259 }
260
StartCamera(void)261 bool CFrontendBridge::StartCamera(void)
262 {
263 return false; // Not implemented
264 }
265
StopCamera(void)266 void CFrontendBridge::StopCamera(void)
267 {
268 // Not implemented
269 }
270
PerfGetTimeUsec(void)271 retro_time_t CFrontendBridge::PerfGetTimeUsec(void)
272 {
273 return 0; // Not implemented
274 }
275
PerfGetCounter(void)276 retro_perf_tick_t CFrontendBridge::PerfGetCounter(void)
277 {
278 return 0; // Not implemented
279 }
280
PerfGetCpuFeatures(void)281 uint64_t CFrontendBridge::PerfGetCpuFeatures(void)
282 {
283 return 0; // Not implemented
284 }
285
PerfLog(void)286 void CFrontendBridge::PerfLog(void)
287 {
288 // Not implemented
289 }
290
PerfRegister(retro_perf_counter * counter)291 void CFrontendBridge::PerfRegister(retro_perf_counter *counter)
292 {
293 // Not implemented
294 }
295
PerfStart(retro_perf_counter * counter)296 void CFrontendBridge::PerfStart(retro_perf_counter *counter)
297 {
298 // Not implemented
299 }
300
PerfStop(retro_perf_counter * counter)301 void CFrontendBridge::PerfStop(retro_perf_counter *counter)
302 {
303 // Not implemented
304 }
305
StartLocation(void)306 bool CFrontendBridge::StartLocation(void)
307 {
308 return false; // Not implemented
309 }
310
StopLocation(void)311 void CFrontendBridge::StopLocation(void)
312 {
313 // Not implemented
314 }
315
GetLocation(double * lat,double * lon,double * horiz_accuracy,double * vert_accuracy)316 bool CFrontendBridge::GetLocation(double *lat, double *lon, double *horiz_accuracy, double *vert_accuracy)
317 {
318 return false; // Not implemented
319 }
320
SetLocationInterval(unsigned interval_ms,unsigned interval_distance)321 void CFrontendBridge::SetLocationInterval(unsigned interval_ms, unsigned interval_distance)
322 {
323 // Not implemented
324 }
325
LocationInitialized(void)326 void CFrontendBridge::LocationInitialized(void)
327 {
328 // Not implemented
329 }
330
LocationDeinitialized(void)331 void CFrontendBridge::LocationDeinitialized(void)
332 {
333 // Not implemented
334 }
335