SDL_Libretro
SDL3-power libretro frontend.
Loading...
Searching...
No Matches
SDL_libretro_env.h
Go to the documentation of this file.
1
7 #if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_ENV_IMPL_ONCE)
8#define SDL_LIBRETRO_ENV_IMPL_ONCE
9
10#include <stdarg.h>
11
15static void SDL_Libretro_Logger(enum retro_log_level level, const char* fmt, ...) {
16 if (SDL_Libretro_active && level < SDL_Libretro_active->logLevel) {
17 return;
18 }
19
20 va_list args;
21 va_start(args, fmt);
22 char buf[2048];
23 SDL_vsnprintf(buf, sizeof(buf), fmt, args);
24 va_end(args);
25
26 /* Strip trailing newline */
27 size_t len = SDL_strlen(buf);
28 if (len > 0 && buf[len - 1] == '\n') buf[len - 1] = '\0';
29
30 switch (level) {
31 case RETRO_LOG_INFO: SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] %s", buf); break;
32 case RETRO_LOG_WARN: SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] %s", buf); break;
33 case RETRO_LOG_ERROR: SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] %s", buf); break;
34 case RETRO_LOG_DEBUG:
35 default: SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] %s", buf); break;
36 }
37}
38
39static retro_time_t SDL_Libretro_GetTimeUSEC(void) {
40 return (SDL_GetPerformanceCounter() * (Uint64)1000000) / SDL_GetPerformanceFrequency();
41}
42
43static uint64_t SDL_Libretro_GetCPUFeatures(void) {
44 uint64_t features = 0;
45 if (SDL_HasSSE()) features |= RETRO_SIMD_SSE;
46 if (SDL_HasSSE2()) features |= RETRO_SIMD_SSE2;
47 if (SDL_HasSSE3()) features |= RETRO_SIMD_SSE3;
48 if (SDL_HasSSE41()) features |= RETRO_SIMD_SSE4;
49 if (SDL_HasSSE42()) features |= RETRO_SIMD_SSE42;
50 if (SDL_HasAVX()) features |= RETRO_SIMD_AVX;
51 if (SDL_HasAVX2()) features |= RETRO_SIMD_AVX2;
52 return features;
53}
54
55static retro_perf_tick_t SDL_Libretro_GetPerfCounter(void) {
56 return (retro_perf_tick_t)SDL_GetPerformanceCounter();
57}
58
59static void SDL_Libretro_PerfRegister(struct retro_perf_counter* counter) {
60 SDL_Libretro* lr = SDL_Libretro_active;
61 if (!lr || !counter) return;
62
63 // Avoid registering the same counter twice.
64 for (unsigned i = 0; i < lr->core.perfCounterCount; i++) {
65 if (lr->core.perfCounters[i] == counter) {
66 counter->registered = true;
67 return;
68 }
69 }
70
71 if (lr->core.perfCounterCount >= SDL_arraysize(lr->core.perfCounters)) {
72 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
73 "PERF: counter registry full (%u), dropping '%s'",
74 lr->core.perfCounterCount, counter->ident ? counter->ident : "?");
75 return;
76 }
77
78 lr->core.perfCounters[lr->core.perfCounterCount++] = counter;
79 counter->registered = true;
80}
81
82static void SDL_Libretro_PerfStart(struct retro_perf_counter* counter) {
83 if (counter) counter->start = (retro_perf_tick_t)SDL_GetPerformanceCounter();
84}
85
86static void SDL_Libretro_PerfStop(struct retro_perf_counter* counter) {
87 if (counter) counter->total += (retro_perf_tick_t)SDL_GetPerformanceCounter() - counter->start;
88}
89
93static void SDL_Libretro_PerfLog(void) {
94 SDL_Libretro* lr = SDL_Libretro_active;
95 if (!lr) return;
96
97 // SDL_GetPerformanceCounter() is the tick source used by the perf counters, so its frequency converts accumulated ticks to seconds. */
98 Uint64 freq = SDL_GetPerformanceFrequency();
99
100 for (unsigned i = 0; i < lr->core.perfCounterCount; i++) {
101 const struct retro_perf_counter* counter = lr->core.perfCounters[i];
102 if (!counter) continue;
103
104 double ms = freq ? ((double)counter->total / (double)freq) * 1000.0 : 0.0;
105 SDL_Log("[PERF] %s: %llu calls, %llu ticks (%.4f ms)",
106 counter->ident ? counter->ident : "?",
107 (unsigned long long)counter->call_cnt,
108 (unsigned long long)counter->total,
109 ms);
110 }
111}
112
113static bool SDL_Libretro_SetRumbleState(unsigned port, enum retro_rumble_effect effect, uint16_t strength) {
114 SDL_Libretro* lr = SDL_Libretro_active;
115 if (!lr || port >= SDL_LIBRETRO_RUMBLE_PORTS) return false;
116
117 float normalized = (float)strength / 65535.0f;
118 if (effect == RETRO_RUMBLE_STRONG) {
119 lr->core.rumbleStrong[port] = normalized;
120 } else {
121 lr->core.rumbleWeak[port] = normalized;
122 }
123
124 if (port < 16 && lr->gamepads[port]) {
125 Uint16 lo = (Uint16)(lr->core.rumbleWeak[port] * 65535.0f);
126 Uint16 hi = (Uint16)(lr->core.rumbleStrong[port] * 65535.0f);
127 SDL_RumbleGamepad(lr->gamepads[port], lo, hi, 100);
128 }
129
130 return true;
131}
132
133static const char* SDL_Libretro_GetDirectory(SDL_Libretro* lr, unsigned cmd) {
134 switch (cmd) {
135 case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
136 return lr->systemDirectory[0] ? lr->systemDirectory : NULL;
137 case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
138 return lr->saveDirectory[0] ? lr->saveDirectory : NULL;
139 case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
140 return lr->coreAssetsDirectory[0] ? lr->coreAssetsDirectory : NULL;
141 case RETRO_ENVIRONMENT_GET_PLAYLIST_DIRECTORY:
142 return lr->playlistDirectory[0] ? lr->playlistDirectory : NULL;
143 case RETRO_ENVIRONMENT_GET_FILE_BROWSER_START_DIRECTORY:
144 return lr->fileBrowserStartDirectory[0] ? lr->fileBrowserStartDirectory : NULL;
145 default:
146 return NULL;
147 }
148}
149
154static float SDL_Libretro_GetTargetRefreshRate(SDL_Libretro* lr) {
155 float rate = 60.0f;
156 if (lr->window) {
157 SDL_DisplayID display = SDL_GetDisplayForWindow(lr->window);
158 if (display) {
159 const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display);
160 if (mode && mode->refresh_rate > 0.0f) {
161 rate = mode->refresh_rate;
162 }
163 }
164 }
165 return rate;
166}
167
173static const char* SDL_Libretro_PickCoreOptionText(const char* base, const char* categorized, const char* categoryKey) {
174 if (categoryKey && categoryKey[0] && categorized && categorized[0]) {
175 return categorized;
176 }
177 return base;
178}
179
180static bool SDL_Libretro_EnvironmentCallback(unsigned cmd, void* data) {
181 SDL_Libretro* lr = SDL_Libretro_active;
182 if (!lr) return false;
183
184 switch (cmd) {
185 case RETRO_ENVIRONMENT_SET_ROTATION: {
186 if (!data) return false;
187 lr->core.rotation = (int)*(const unsigned*)data;
188 SDL_Log("[SDL_Libretro] SET_ROTATION: %d (%d deg)", lr->core.rotation, lr->core.rotation * 90);
189 return true;
190 }
191
192 case RETRO_ENVIRONMENT_GET_OVERSCAN: {
193 return false;
194 }
195
196 case RETRO_ENVIRONMENT_GET_CAN_DUPE: {
197 if (!data) return false;
198 *(bool*)data = true;
199 return true;
200 }
201
202 case RETRO_ENVIRONMENT_SET_MESSAGE: {
203 const struct retro_message* msg = (const struct retro_message*)data;
204 if (!msg || !msg->msg || msg->msg[0] == '\0') return msg != NULL;
205 double seconds = msg->frames / (lr->core.fps > 0 ? lr->core.fps : 60.0);
206 SDL_Libretro_SetMessage(lr, msg->msg, seconds);
207 return true;
208 }
209
210 case RETRO_ENVIRONMENT_SHUTDOWN: {
211 SDL_Log("[SDL_Libretro] Shutdown requested");
212 lr->core.shutdown = true;
213 return true;
214 }
215
216 case RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL: {
217 if (!data) return false;
218 lr->core.performanceLevel = *(const unsigned*)data;
219 return true;
220 }
221
222 case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
223 case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
224 case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
225 case RETRO_ENVIRONMENT_GET_PLAYLIST_DIRECTORY:
226 case RETRO_ENVIRONMENT_GET_FILE_BROWSER_START_DIRECTORY: {
227 // If data is NULL, it's checking if the environment callback is available.
228 if (!data) return true;
229 *(const char**)data = SDL_Libretro_GetDirectory(lr, cmd);
230 return true;
231 }
232
233 case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT: {
234 if (!data) return false;
235 enum retro_pixel_format fmt = *(const enum retro_pixel_format*)data;
236 switch (fmt) {
237 case RETRO_PIXEL_FORMAT_0RGB1555:
238 case RETRO_PIXEL_FORMAT_XRGB8888:
239 case RETRO_PIXEL_FORMAT_RGB565: {
240 bool changed = (fmt != lr->core.pixelFormat);
241 lr->core.pixelFormat = fmt;
242 // The texture's format is fixed at creation. The load path builds it from this field, a runtime change (texture already exists) defers a rebuild to the next RunFrame.
243 if (changed && lr->core.texture) {
244 lr->core.videoReinitPending = true;
245 }
246 return true;
247 }
248 default:
249 // Unsupported. Signal false without clobbering the current format.
250 return false;
251 }
252 }
253
254 case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS: {
255 const struct retro_input_descriptor* desc = (const struct retro_input_descriptor*)data;
256 if (!desc) return true;
257 unsigned count = 0;
258 for (const struct retro_input_descriptor* d = desc; d->description; d++) count++;
259 SDL_free(lr->core.inputDescriptors);
260 lr->core.inputDescriptors = NULL;
261 lr->core.inputDescriptorCount = 0;
262 if (count > 0) {
263 lr->core.inputDescriptors = (struct retro_input_descriptor*)SDL_malloc(count * sizeof(*desc));
264 if (lr->core.inputDescriptors) {
265 SDL_memcpy(lr->core.inputDescriptors, desc, count * sizeof(*desc));
266 lr->core.inputDescriptorCount = count;
267 }
268 }
269 return true;
270 }
271
272 case RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK: {
273 if (!data) return true;
274 const struct retro_keyboard_callback* cb = (const struct retro_keyboard_callback*)data;
275 lr->core.keyboard_event = cb->callback;
276 return true;
277 }
278
279 case RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE: {
280 const struct retro_disk_control_callback* cb = (const struct retro_disk_control_callback*)data;
281 if (!cb) return true;
282 SDL_memset(&lr->core.disk_control, 0, sizeof(lr->core.disk_control));
283 lr->core.disk_control.set_eject_state = cb->set_eject_state;
284 lr->core.disk_control.get_eject_state = cb->get_eject_state;
285 lr->core.disk_control.get_image_index = cb->get_image_index;
286 lr->core.disk_control.set_image_index = cb->set_image_index;
287 lr->core.disk_control.get_num_images = cb->get_num_images;
288 lr->core.disk_control.replace_image_index = cb->replace_image_index;
289 lr->core.disk_control.add_image_index = cb->add_image_index;
290 lr->core.diskControlActive = true;
291 return true;
292 }
293
294 case RETRO_ENVIRONMENT_SET_HW_RENDER: {
295 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] SET_HW_RENDER not supported");
296 return false;
297 }
298
299 case RETRO_ENVIRONMENT_GET_VARIABLE: {
300 // When NULL data is passed, the core is checking if the environment callback is available.
301 if (!data) return true;
302 struct retro_variable* var = (struct retro_variable*)data;
303 if (!var->key) return false;
304 const SDL_LibretroOption* o = SDL_Libretro_GetOption(lr, var->key);
305 if (o) {
306 var->value = o->value;
307 return true;
308 }
309 return false;
310 }
311
312 case RETRO_ENVIRONMENT_SET_VARIABLE: {
313 // When NULL data is passed, it's just checking if the callback is available.
314 if (!data) return true;
315 const struct retro_variable* var = (const struct retro_variable*)data;
316 if (!var->key || !var->value) return false;
317 return SDL_Libretro_SetOptionValue(lr, var->key, var->value);
318 }
319
320 case RETRO_ENVIRONMENT_SET_VARIABLES: {
321 // When NULL data is passed, it's just checking if the callback is available.
322 if (!data) return true;
323 const struct retro_variable* var = (const struct retro_variable*)data;
324 for (; var->key; var++) {
325 const char* label = "";
326 const char* defaultVal = "";
327 // Copy the value so that we can manipulate it as needed.
328 // Only needed until the end of this call.
329 char* valueBuffer = var->value ? SDL_strdup(var->value) : NULL;
330 struct retro_core_option_value* values = NULL;
331 unsigned vcount = 0;
332 if (valueBuffer) {
333 char* semicolon = SDL_strchr(valueBuffer, ';');
334 if (semicolon) {
335 // Split off the label, trimming trailing spaces.
336 *semicolon = '\0';
337 char* labelEnd = semicolon;
338 while (labelEnd > valueBuffer && labelEnd[-1] == ' ') {
339 *(--labelEnd) = '\0';
340 }
341 label = valueBuffer;
342
343 char* opts = semicolon + 1;
344 while (*opts == ' ') {
345 opts++;
346 }
347
348 // Count the pipe-separated values so the array is sized exactly.
349 unsigned count = *opts ? 1 : 0;
350 for (const char* p = opts; *p; p++) {
351 if (*p == '|') count++;
352 }
353 if (count > 0) {
354 // Extra trailing entry keeps a NULL sentinel for InitCoreOption.
355 values = (struct retro_core_option_value*)SDL_calloc(count + 1, sizeof(*values));
356 }
357 if (values) {
358 // Version 1 and 2 of the variables API carry no per-value labels, only value strings.
359 char* tok = opts;
360 while (tok && *tok) {
361 char* nextPipe = SDL_strchr(tok, '|');
362 if (nextPipe) *nextPipe = '\0';
363 values[vcount].value = tok;
364 values[vcount].label = NULL;
365 vcount++;
366 tok = nextPipe ? nextPipe + 1 : NULL;
367 }
368 }
369 // The first value is the default (terminate it in place).
370 char* firstPipe = SDL_strchr(opts, '|');
371 if (firstPipe) *firstPipe = '\0';
372 defaultVal = opts;
373 }
374 }
375 SDL_Libretro_InitCoreOption(lr, var->key,
376 defaultVal,
377 label,
378 values,
379 NULL, NULL);
380 SDL_free(values);
381 SDL_free(valueBuffer);
382 }
383 return true;
384 }
385
386 case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE: {
387 if (!data) return true;
388 *(bool*)data = lr->core.optionsDirty;
389 lr->core.optionsDirty = false;
390 return true;
391 }
392
393 case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME: {
394 if (!data) return true;
395 lr->core.supportNoGame = *(const bool*)data;
396 return true;
397 }
398
399 case RETRO_ENVIRONMENT_GET_LIBRETRO_PATH: {
400 if (!data) return true;
401 *(const char**)data = lr->core.corePath[0] ? lr->core.corePath : NULL;
402 return true;
403 }
404
405 case RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK: {
406 if (!data) return true;
407 lr->core.runloop_frame_time = *(const struct retro_frame_time_callback*)data;
408 return true;
409 }
410
411 case RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK: {
412 // When data is NULL, the core is checking if the environment callback is available.
413 if (!data) return true;
414 lr->core.audio_callback = *(const struct retro_audio_callback*)data;
415 return true;
416 }
417
418 case RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE: {
419 if (!data) return true;
420 struct retro_rumble_interface* rumble = (struct retro_rumble_interface*)data;
421 rumble->set_rumble_state = SDL_Libretro_SetRumbleState;
422 return true;
423 }
424
425 case RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES: {
426 if (!data) return true;
427 *(uint64_t*)data =
428 (1 << RETRO_DEVICE_JOYPAD) |
429 (1 << RETRO_DEVICE_MOUSE) |
430 (1 << RETRO_DEVICE_KEYBOARD) |
431 (1 << RETRO_DEVICE_LIGHTGUN) |
432 (1 << RETRO_DEVICE_ANALOG) |
433 (1 << RETRO_DEVICE_POINTER);
434 return true;
435 }
436
437 case RETRO_ENVIRONMENT_GET_LOG_INTERFACE: {
438 if (!data) return true;
439 struct retro_log_callback* cb = (struct retro_log_callback*)data;
440 cb->log = &SDL_Libretro_Logger;
441 return true;
442 }
443
444 case RETRO_ENVIRONMENT_GET_PERF_INTERFACE: {
445 if (!data) return true;
446 struct retro_perf_callback* perf = (struct retro_perf_callback*)data;
447 perf->get_time_usec = &SDL_Libretro_GetTimeUSEC;
448 perf->get_cpu_features = &SDL_Libretro_GetCPUFeatures;
449 perf->get_perf_counter = &SDL_Libretro_GetPerfCounter;
450 perf->perf_register = &SDL_Libretro_PerfRegister;
451 perf->perf_start = &SDL_Libretro_PerfStart;
452 perf->perf_stop = &SDL_Libretro_PerfStop;
453 perf->perf_log = &SDL_Libretro_PerfLog;
454 return true;
455 }
456
457 case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO: {
458 if (!data) return false;
459 const struct retro_system_av_info* av = (const struct retro_system_av_info*)data;
460 lr->core.fps = av->timing.fps;
461 lr->core.aspectRatio = av->geometry.aspect_ratio;
462
463 // A base-resolution change is picked up by VideoRefresh on the next frame; aspect is applied at render time. Only the audio stream (frequency fixed at open) needs an explicit reopen, deferred to RunFrame.
464 if (av->timing.sample_rate != lr->core.sampleRate) {
465 lr->core.sampleRate = av->timing.sample_rate;
466 if (lr->core.audioStream) {
467 lr->core.audioReinitPending = true;
468 }
469 }
470 return true;
471 }
472
473 case RETRO_ENVIRONMENT_SET_GEOMETRY: {
474 if (!data) return true;
475 const struct retro_game_geometry* geom = (const struct retro_game_geometry*)data;
476 // Geometry updates during runtime are applied in SDL_Libretro_VideoRefresh().
477 lr->core.aspectRatio = geom->aspect_ratio;
478 return true;
479 }
480
481 case RETRO_ENVIRONMENT_GET_USERNAME: {
482 if (!data) return true;
483 *(const char**)data = lr->username;
484 return true;
485 }
486
487 case RETRO_ENVIRONMENT_GET_LANGUAGE: {
488 if (!data) return true;
489 *(unsigned*)data = RETRO_LANGUAGE_ENGLISH;
490 return true;
491 }
492
493 case RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS: {
494 if (!data) return true;
495 lr->core.serializationQuirks = *(const uint64_t*)data;
496 return true;
497 }
498
499 case 25:
500 case RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE: {
501 if (!data) return true;
502 struct retro_sensor_interface* sensor = (struct retro_sensor_interface*)data;
503 sensor->set_sensor_state = SDL_Libretro_SetSensorState;
504 sensor->get_sensor_input = SDL_Libretro_GetSensorInput;
505 return true;
506 }
507
508 case 47:
509 case RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE: {
510 if (!data) return true;
511 *(int*)data = RETRO_AV_ENABLE_VIDEO | RETRO_AV_ENABLE_AUDIO;
512 return true;
513 }
514
515 case 49:
516 case RETRO_ENVIRONMENT_GET_FASTFORWARDING: {
517 if (!data) return true;
518 *(bool*)data = SDL_Libretro_GetSpeed(lr) > 1.0f;
519 return true;
520 }
521
522 case 50:
523 case RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE: {
524 if (!data) return true;
525 *(float*)data = SDL_Libretro_GetTargetRefreshRate(lr);
526 return true;
527 }
528
529 case 51:
530 case RETRO_ENVIRONMENT_GET_INPUT_BITMASKS: {
531 return true;
532 }
533
534 case RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION: {
535 if (!data) return false;
536 *(unsigned*)data = 1;
537 return true;
538 }
539
540 case 71:
541 case RETRO_ENVIRONMENT_GET_THROTTLE_STATE: {
542 if (!data) return false;
543 struct retro_throttle_state* throttle = (struct retro_throttle_state*)data;
544
545 double fps = lr->core.fps > 0.0 ? lr->core.fps : 60.0;
546 float speed = lr->speed;
547 if (speed == 0.0f) {
548 throttle->mode = RETRO_THROTTLE_FRAME_STEPPING;
549 throttle->rate = 0.0f;
550 }
551 else if (speed > 1.0f) {
552 throttle->mode = RETRO_THROTTLE_FAST_FORWARD;
553 throttle->rate = (float)(fps * speed);
554 }
555 else if (speed > 0.0f && speed < 1.0f) {
556 throttle->mode = RETRO_THROTTLE_SLOW_MOTION;
557 throttle->rate = (float)(fps * speed);
558 }
559 else if (speed < 0.0f) {
560 throttle->mode = RETRO_THROTTLE_REWINDING;
561 throttle->rate = (float)(fps * -speed);
562 }
563 else {
564 throttle->mode = RETRO_THROTTLE_NONE;
565 throttle->rate = (float)fps;
566
567 // VSYNC
568 int vsync = SDL_RENDERER_VSYNC_DISABLED;
569 if (lr->renderer && SDL_GetRenderVSync(lr->renderer, &vsync) && vsync != SDL_RENDERER_VSYNC_DISABLED) {
570 float interval = (vsync == SDL_RENDERER_VSYNC_ADAPTIVE) ? 1.0f : (float)vsync;
571 float vsyncRate = SDL_Libretro_GetTargetRefreshRate(lr) / interval;
572 if (vsyncRate < fps) {
573 throttle->mode = RETRO_THROTTLE_VSYNC;
574 throttle->rate = vsyncRate;
575 }
576 }
577 }
578
579 return true;
580 }
581
582 case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO: {
583 const struct retro_controller_info* info = (const struct retro_controller_info*)data;
584 if (!info) return true;
585 SDL_free(lr->core.controllerInfo);
586 lr->core.controllerInfo = NULL;
587 lr->core.controllerPortCount = 0;
588 unsigned count = 0;
589 for (unsigned i = 0; info[i].types; i++) count++;
590 if (count > 0) {
591 lr->core.controllerInfo = (struct retro_controller_info*)SDL_malloc(count * sizeof(*info));
592 if (lr->core.controllerInfo) {
593 SDL_memcpy(lr->core.controllerInfo, info, count * sizeof(*info));
594 lr->core.controllerPortCount = count;
595 }
596 }
597 return true;
598 }
599
600 case 36:
601 case RETRO_ENVIRONMENT_SET_MEMORY_MAPS: {
602 if (!data) return true;
603 const struct retro_memory_map* map = (const struct retro_memory_map*)data;
604 SDL_Libretro_FreeMemoryMap(lr);
605 if (map->num_descriptors > 0) {
606 size_t sz = map->num_descriptors * sizeof(struct retro_memory_descriptor);
607 lr->core.memoryMapDescriptors = (struct retro_memory_descriptor*)SDL_malloc(sz);
608 if (lr->core.memoryMapDescriptors) {
609 SDL_memcpy(lr->core.memoryMapDescriptors, map->descriptors, sz);
610 lr->core.memoryMapDescriptorCount = map->num_descriptors;
611 // The core only guarantees the addrspace label strings for the duration of this call, so deep-copy them.
612 for (unsigned i = 0; i < lr->core.memoryMapDescriptorCount; i++) {
613 const char* as = lr->core.memoryMapDescriptors[i].addrspace;
614 if (as) lr->core.memoryMapDescriptors[i].addrspace = SDL_strdup(as);
615 }
616 }
617 }
618 return true;
619 }
620
621 case RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK: {
622 // NULL data unregisters the callback.
623 // @see SDL_Libretro_ReportAudioBufferStatus
624 const struct retro_audio_buffer_status_callback* cb = (const struct retro_audio_buffer_status_callback*)data;
625 lr->core.audio_buffer_status.callback = cb ? cb->callback : NULL;
626 return true;
627 }
628
629 case RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY: {
630 if (!data) return true;
631 lr->core.minimumAudioLatencyMs = *(const unsigned*)data;
632 // Apply the new latency target live if audio is already running.
633 if (lr->core.audioStream) {
634 SDL_Libretro_UpdateAudioThreshold(lr);
635 }
636 return true;
637 }
638
639 case RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE: {
640 if (!data) return false;
641 const struct retro_system_content_info_override* overrides =
642 (const struct retro_system_content_info_override*)data;
643 SDL_Libretro_FreeContentInfoOverrides(lr);
644 unsigned count = 0;
645 while (overrides[count].extensions) count++;
646 if (count > 0) {
647 lr->core.contentInfoOverrides = (struct retro_system_content_info_override*)
648 SDL_calloc(count, sizeof(struct retro_system_content_info_override));
649 if (lr->core.contentInfoOverrides) {
650 for (unsigned i = 0; i < count; i++) {
651 lr->core.contentInfoOverrides[i] = overrides[i];
652 // The core only guarantees the extensions string for this call, so deep-copy it.
653 lr->core.contentInfoOverrides[i].extensions = SDL_strdup(overrides[i].extensions);
654 }
655 lr->core.contentInfoOverrideCount = count;
656 }
657 }
658 return true;
659 }
660
661 case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION: {
662 if (!data) return true;
663 *(unsigned*)data = 2;
664 return true;
665 }
666
667 case RETRO_ENVIRONMENT_SET_CORE_OPTIONS: {
668 if (!data) return true;
669 const struct retro_core_option_definition* defs = (const struct retro_core_option_definition*)data;
670 unsigned count = 0;
671 while (defs[count].key) count++;
672 struct retro_core_option_v2_definition* v2defs =
673 (struct retro_core_option_v2_definition*)SDL_calloc(count + 1, sizeof(*v2defs));
674 if (!v2defs) return false;
675 for (unsigned i = 0; i < count; i++) {
676 v2defs[i].key = defs[i].key;
677 v2defs[i].desc = defs[i].desc;
678 v2defs[i].info = defs[i].info;
679 v2defs[i].default_value = defs[i].default_value;
680 SDL_memcpy(v2defs[i].values, defs[i].values, sizeof(defs[i].values));
681 }
682 struct retro_core_options_v2 opts = { NULL, v2defs };
683 bool result = SDL_Libretro_EnvironmentCallback(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, &opts);
684 SDL_free(v2defs);
685 return result;
686 }
687
688 case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL: {
689 if (!data) return false;
690 const struct retro_core_options_intl* intl = (const struct retro_core_options_intl*)data;
691 const struct retro_core_option_definition* defs = intl->us;
692 if (!defs) return false;
693 unsigned count = 0;
694 while (defs[count].key) count++;
695 struct retro_core_option_v2_definition* v2defs =
696 (struct retro_core_option_v2_definition*)SDL_calloc(count + 1, sizeof(*v2defs));
697 if (!v2defs) return false;
698 for (unsigned i = 0; i < count; i++) {
699 v2defs[i].key = defs[i].key;
700 v2defs[i].desc = defs[i].desc;
701 v2defs[i].info = defs[i].info;
702 v2defs[i].default_value = defs[i].default_value;
703 SDL_memcpy(v2defs[i].values, defs[i].values, sizeof(defs[i].values));
704 }
705 struct retro_core_options_v2 opts = { NULL, v2defs };
706 bool result = SDL_Libretro_EnvironmentCallback(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, &opts);
707 SDL_free(v2defs);
708 return result;
709 }
710
711 case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY: {
712 if (!data) return false;
713 const struct retro_core_option_display* opt = (const struct retro_core_option_display*)data;
714 if (!opt->key) return false;
715 for (unsigned i = 0; i < lr->core.optionCount; i++) {
716 if (lr->core.options[i].key && SDL_strcmp(lr->core.options[i].key, opt->key) == 0) {
717 lr->core.options[i].visible = opt->visible;
718 lr->core.optionsDirty = true;
719 return true;
720 }
721 }
722 return false;
723 }
724
725 case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2: {
726 if (!data) return false;
727 const struct retro_core_options_v2* opts = (const struct retro_core_options_v2*)data;
728 if (!opts->definitions) {
729 return true; // There arn't any options.
730 }
731
732 // Register the option categories (if any) before the options that reference them.
733 if (opts->categories) {
734 for (unsigned c = 0; opts->categories[c].key; c++) {
735 SDL_Libretro_InitCoreOptionCategory(lr, opts->categories[c].key,
736 opts->categories[c].desc, opts->categories[c].info);
737 }
738 }
739
740 for (unsigned i = 0; opts->definitions[i].key; i++) {
741 const struct retro_core_option_v2_definition* def = &opts->definitions[i];
742 const char* defaultVal = def->default_value ? def->default_value : "";
743 // Prefer the *_categorized text when the option belongs to a category.
744 const char* desc = SDL_Libretro_PickCoreOptionText(def->desc, def->desc_categorized, def->category_key);
745 const char* info = SDL_Libretro_PickCoreOptionText(def->info, def->info_categorized, def->category_key);
746 SDL_Libretro_InitCoreOption(lr, def->key,
747 defaultVal,
748 desc,
749 def->values,
750 info,
751 def->category_key);
752 }
753 return true;
754 }
755
756 case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL: {
757 if (!data) return false;
758 const struct retro_core_options_v2_intl* intl = (const struct retro_core_options_v2_intl*)data;
759 // Frontend language is English, so the US definitions are used directly. The `us` member is already a retro_core_options_v2, so forward it as-is.
760 if (!intl->us) return false;
761 return SDL_Libretro_EnvironmentCallback(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, intl->us);
762 }
763
764 case RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION: {
765 if (!data) return true;
766 *(unsigned*)data = 1;
767 return true;
768 }
769
770 case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK: {
771 if (!data) {
772 lr->core.optionsUpdateDisplayCallback = NULL;
773 return true;
774 }
775 const struct retro_core_options_update_display_callback* cb =
776 (const struct retro_core_options_update_display_callback*)data;
777 lr->core.optionsUpdateDisplayCallback = cb->callback;
778 return true;
779 }
780
781 case RETRO_ENVIRONMENT_SET_MESSAGE_EXT: {
782 const struct retro_message_ext* msg = (const struct retro_message_ext*)data;
783 if (!msg || !msg->msg) return true;
784 double seconds = msg->duration / 1000.0;
785
786 if (msg->target == RETRO_MESSAGE_TARGET_LOG || msg->target == RETRO_MESSAGE_TARGET_ALL) {
787 switch (msg->level) {
788 case RETRO_LOG_DEBUG: SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "[Core] %s", msg->msg); break;
789 case RETRO_LOG_WARN: SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[Core] %s", msg->msg); break;
790 case RETRO_LOG_ERROR: SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Core] %s", msg->msg); break;
791 default: SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[Core] %s", msg->msg); break;
792 }
793 }
794
795 if (msg->target != RETRO_MESSAGE_TARGET_LOG) {
796 SDL_Libretro_OsdPush(lr, msg->msg, seconds, msg->priority, msg->type, msg->progress);
797 }
798 return true;
799 }
800
801 case RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE: {
802 if (!data) return true;
803 lr->core.disk_control = *(const struct retro_disk_control_ext_callback*)data;
804 lr->core.diskControlActive = true;
805 return true;
806 }
807
808 case RETRO_ENVIRONMENT_GET_GAME_INFO_EXT: {
809 // A non-NULL full_path means LoadGame populated this for content.
810 if (!data || !lr->core.gameInfoExt.full_path) return false;
811 *(const struct retro_game_info_ext**)data = &lr->core.gameInfoExt;
812 return true;
813 }
814
815 case 40:
816 case RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER: {
817 if (!data || !lr->core.texture) return false;
818 struct retro_framebuffer* fb = (struct retro_framebuffer*)data;
819 if (fb->width != lr->core.width || fb->height != lr->core.height) return false;
820 // The lock is write-only (discards prior contents), so a core asking to read back can't use it.
821 if (fb->access_flags & RETRO_MEMORY_ACCESS_READ) return false;
822
823 // Drop any lock still held from an earlier acquire this frame (or a frame that never reached video_cb) before re-locking.
824 SDL_Libretro_ReleaseSoftwareFramebuffer(lr);
825
826 void* pixels = NULL;
827 int pitch = 0;
828 if (!SDL_LockTexture(lr->core.texture, NULL, &pixels, &pitch)) return false;
829
830 fb->data = pixels;
831 fb->pitch = (size_t)pitch;
832 fb->format = lr->core.pixelFormat;
833 fb->memory_flags = RETRO_MEMORY_TYPE_CACHED;
834 lr->core.softwareFramebufferPixels = pixels;
835 return true;
836 }
837
838 case 45:
839 case RETRO_ENVIRONMENT_GET_VFS_INTERFACE: {
840 if (!data) return true;
841 struct retro_vfs_interface_info* info = (struct retro_vfs_interface_info*)data;
842 if (info->required_interface_version > 4) {
843 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
844 "[SDL_Libretro] GET_VFS_INTERFACE: unsupported required_interface_version %u",
845 info->required_interface_version);
846 return false;
847 }
848
849 // Let the core know which version of the VFS we support.
850 info->required_interface_version = 4;
851 info->iface = &lr->vfs_interface;
852 return true;
853 }
854
855 case 75:
856 case RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE: {
857 if (!data) return false;
858 struct retro_microphone_interface* mic = (struct retro_microphone_interface*)data;
859 mic->interface_version = RETRO_MICROPHONE_INTERFACE_VERSION;
860 mic->open_mic = SDL_Libretro_MicOpen;
861 mic->close_mic = SDL_Libretro_MicClose;
862 mic->get_params = SDL_Libretro_MicGetParams;
863 mic->set_mic_state = SDL_Libretro_MicSetState;
864 mic->get_mic_state = SDL_Libretro_MicGetState;
865 mic->read_mic = SDL_Libretro_MicRead;
866 return true;
867 }
868
869 case 77:
870 case RETRO_ENVIRONMENT_GET_DEVICE_POWER: {
871 if (!data) return true;
872 struct retro_device_power* power = (struct retro_device_power*)data;
873 int percent;
874 SDL_PowerState sdl_state = SDL_GetPowerInfo(&power->seconds, &percent);
875 switch (sdl_state) {
876 case SDL_POWERSTATE_ON_BATTERY:
877 power->state = RETRO_POWERSTATE_DISCHARGING;
878 break;
879 case SDL_POWERSTATE_CHARGING:
880 power->state = RETRO_POWERSTATE_CHARGING;
881 break;
882 case SDL_POWERSTATE_CHARGED:
883 power->state = RETRO_POWERSTATE_CHARGED;
884 break;
885 case SDL_POWERSTATE_NO_BATTERY:
886 power->state = RETRO_POWERSTATE_PLUGGED_IN;
887 break;
888 default:
889 return false;
890 }
891 power->percent = (int8_t)percent;
892 return true;
893 }
894
895 case 81:
896 case RETRO_ENVIRONMENT_GET_TARGET_SAMPLE_RATE: {
897 if (!data) return true;
898 SDL_AudioSpec spec;
899 if (lr->core.audioStream && SDL_GetAudioStreamFormat(lr->core.audioStream, NULL, &spec) && spec.freq > 0) {
900 // Active Device
901 *(unsigned*)data = (unsigned)spec.freq;
902 } else if (SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL) && spec.freq > 0) {
903 // Default Device
904 *(unsigned*)data = (unsigned)spec.freq;
905 } else {
906 // Desired Frequency
908 }
909 return true;
910 }
911
912 // Unimplemented
913 case 26:
914 case RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE:
915 case RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE:
916 case RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK:
917 case 41:
918 case RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE:
919 case 42:
920 case RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS:
921 case 43:
922 case RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE:
923 case 87:
924 case RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT:
925 case 46:
926 case RETRO_ENVIRONMENT_GET_LED_INTERFACE:
927 return false;
928
929 default: {
930 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] Unhandled environment callback: %u", cmd);
931 return false;
932 }
933 }
934}
935
936#endif /* SDL_LIBRETRO_ENV_IMPL_ONCE */
#define SDL_LIBRETRO_AUDIO_DEFAULT_SAMPLE_RATE
Fallback rate used when a core reports no audio sample rate (<= 0).
void SDL_Libretro_SetMessage(SDL_Libretro *lr, const char *msg, double duration)
Add a message to be displayed within the libretro context.
bool SDL_Libretro_SetOptionValue(SDL_Libretro *lr, const char *key, const char *value)
Set a core option's value; fails if the value isn't one the core declared.
const SDL_LibretroOption * SDL_Libretro_GetOption(const SDL_Libretro *lr, const char *key)
Retrieve details about a given core option.
A core option and its current state.
const char * value
Description / help text; may be empty.