SDL_Libretro 0.1.0
SDL3-power libretro frontend.
Loading...
Searching...
No Matches
SDL_libretro_video.h
Go to the documentation of this file.
1
7#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_VIDEO_IMPL_ONCE)
8#define SDL_LIBRETRO_VIDEO_IMPL_ONCE
9
14static SDL_PixelFormat SDL_Libretro_GetTextureFormat(enum retro_pixel_format fmt) {
15 switch (fmt) {
16 case RETRO_PIXEL_FORMAT_XRGB8888: return SDL_PIXELFORMAT_XRGB8888;
17 case RETRO_PIXEL_FORMAT_RGB565: return SDL_PIXELFORMAT_RGB565;
18 case RETRO_PIXEL_FORMAT_0RGB1555: return SDL_PIXELFORMAT_XRGB1555;
19 default: return SDL_PIXELFORMAT_RGB565;
20 }
21}
22
29static void SDL_Libretro_ReleaseSoftwareFramebuffer(SDL_Libretro* lr) {
30 if (!lr->core.softwareFramebufferPixels) {
31 return;
32 }
33 if (lr->core.texture) {
34 SDL_UnlockTexture(lr->core.texture);
35 }
36 lr->core.softwareFramebufferPixels = NULL;
37}
38
43static bool SDL_Libretro_InitVideo(SDL_Libretro* lr) {
44 if (!lr || !lr->renderer) return false;
45
46 // Make sure we're starting a clean video context.
47 SDL_Libretro_CloseVideo(lr);
48
49 // If there is no desired width/height, select an arbitrary one.
50 if (lr->core.width == 0 || lr->core.height == 0) {
51 lr->core.width = 320;
52 lr->core.height = 240;
53 }
54
55 // Build the Texture.
56 lr->core.texture = SDL_CreateTexture(lr->renderer, SDL_Libretro_GetTextureFormat(lr->core.pixelFormat), SDL_TEXTUREACCESS_STREAMING, lr->core.width, lr->core.height);
57
58 if (!lr->core.texture) {
59 SDL_SetError("[SDL_Libretro] Failed to create texture: %s", SDL_GetError());
60 return false;
61 }
62 lr->core.videoReinitPending = false;
63
64 SDL_SetTextureScaleMode(lr->core.texture, lr->scaleMode);
65 return true;
66}
67
72static void SDL_Libretro_CloseVideo(SDL_Libretro* lr) {
73 if (!lr) return;
74
75 SDL_Libretro_ReleaseSoftwareFramebuffer(lr);
76 if (lr->core.texture) {
77 SDL_DestroyTexture(lr->core.texture);
78 lr->core.texture = NULL;
79 }
80}
81
82static void SDL_Libretro_VideoRefresh(const void* data, unsigned width, unsigned height, size_t pitch) {
83 SDL_Libretro* lr = SDL_Libretro_active;
84 if (!lr) return;
85
86 // Software framebuffer (RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER)
87 if (lr->core.softwareFramebufferPixels) {
88 void* swfb = lr->core.softwareFramebufferPixels;
89 SDL_Libretro_ReleaseSoftwareFramebuffer(lr);
90 if (data == swfb) return;
91 }
92
93 if (!data) return;
94
95 // A hardware-rendered core signals its frame via this sentinel rather than a real pointer; we're software-only, so there's nothing to copy.
96 if (data == RETRO_HW_FRAME_BUFFER_VALID) return;
97
98 // Rebuild the texture when the core's frame dimensions change.
99 if (width != lr->core.width || height != lr->core.height) {
100 lr->core.width = width;
101 lr->core.height = height;
102 if (!SDL_Libretro_InitVideo(lr)) return;
103 }
104
105 // Make sure the texture is workable.
106 if (!lr->core.texture) return;
107
108 // Copy straight into the texture's backing memory to avoid the extra staging copy SDL_UpdateTexture() makes.
109 void* pixels = NULL;
110 int lockPitch = 0;
111 if (!SDL_LockTexture(lr->core.texture, NULL, &pixels, &lockPitch)) {
112 return;
113 }
114
115 // The core's `pitch` and the texture's locked pitch can differ, so copy row by row.
116 const Uint8* src = (const Uint8*)data;
117 Uint8* dst = (Uint8*)pixels;
118 size_t rowBytes = pitch < (size_t)lockPitch ? pitch : (size_t)lockPitch;
119 if (pitch == (size_t)lockPitch) {
120 // The strides match, so we can do a straight copy.
121 SDL_memcpy(dst, src, rowBytes * height);
122 }
123 else {
124 for (size_t y = 0; y < height; y++) {
125 SDL_memcpy(dst + y * lockPitch, src + y * pitch, rowBytes);
126 }
127 }
128
129 SDL_UnlockTexture(lr->core.texture);
130}
131
141bool SDL_Libretro_SetRenderer(SDL_Libretro* lr, SDL_Renderer* renderer) {
142 if (!lr || !renderer) {
143 SDL_SetError("[SDL_Libretro] Invalid context");
144 return false;
145 }
146
147 bool changed = (lr->renderer != renderer);
148 lr->renderer = renderer;
149 lr->window = SDL_GetRenderWindow(renderer);
150
151 // Build the texture if a game is loaded but has none yet (deferred init after
152 // a renderer-less LoadGame), or rebuild it against the new renderer on a swap
153 // (a texture is bound to the renderer that created it). With no game loaded,
154 // LoadGame's InitVideo handles it once the geometry is known.
155 if (lr->core.gameLoaded && (!lr->core.texture || changed)) {
156 return SDL_Libretro_InitVideo(lr);
157 }
158
159 return true;
160}
161
168SDL_Renderer* SDL_Libretro_GetRenderer(const SDL_Libretro* lr) {
169 return lr ? lr->renderer : NULL;
170}
171
185SDL_Texture* SDL_Libretro_GetTexture(const SDL_Libretro* lr) {
186 return (lr && lr->core.texture) ? lr->core.texture : NULL;
187}
188
201SDL_Surface* SDL_Libretro_CreateSurface(const SDL_Libretro* lr) {
202 if (!lr || !lr->core.texture || !lr->renderer) return NULL;
203
204 // Blit the current frame 1:1 into an offscreen target texture
205 // and read it back.
206 SDL_Renderer* renderer = lr->renderer;
207 int w = (int)lr->core.width;
208 int h = (int)lr->core.height;
209
210 SDL_Texture* target = SDL_CreateTexture(renderer, SDL_Libretro_GetTextureFormat(lr->core.pixelFormat), SDL_TEXTUREACCESS_TARGET, w, h);
211 if (!target) return NULL;
212
213 SDL_Texture* prevTarget = SDL_GetRenderTarget(renderer);
214 SDL_Surface* out = NULL;
215 if (SDL_SetRenderTarget(renderer, target)) {
216 // Draw the frame unscaled and unrotated into the target, then read it.
217 if (SDL_RenderTexture(renderer, lr->core.texture, NULL, NULL)) {
218 out = SDL_RenderReadPixels(renderer, NULL);
219 }
220 SDL_SetRenderTarget(renderer, prevTarget);
221 }
222
223 SDL_DestroyTexture(target);
224 return out;
225}
226
236bool SDL_Libretro_SaveScreenshot(const SDL_Libretro* lr, const char* path) {
237 SDL_Surface* screenshot = SDL_Libretro_CreateSurface(lr);
238 if (screenshot == NULL) {
239 return false;
240 }
241 bool saved = SDL_SavePNG(screenshot, path != NULL ? path : "screenshot.png");
242 SDL_DestroySurface(screenshot);
243 return saved;
244}
245
254static void SDL_Libretro_FitRect(const SDL_Libretro* lr, SDL_FRect* rect, bool rotated) {
255 SDL_FRect avail = *rect;
256
257 // Nothing to fit into a zero-size area, or when we're stretching it.
258 if (avail.w <= 0.0f || avail.h <= 0.0f || lr->fitMode == SDL_LIBRETRO_FIT_STRETCH) return;
259
260 // Fix the aspect ratio.
261 float srcAspect = lr->core.aspectRatio;
262 if (srcAspect <= 0.0f && lr->core.width > 0 && lr->core.height > 0) {
263 srcAspect = (float)lr->core.width / (float)lr->core.height;
264 }
265
266 // Letterbox to the effective aspect ratio within the available area.
267 float aspect = (rotated && srcAspect > 0.0f) ? 1.0f / srcAspect : srcAspect;
268 if (aspect > 0.0f) {
269 if (aspect > avail.w / avail.h) {
270 rect->h = avail.w / aspect;
271 rect->y = avail.y + (avail.h - rect->h) * 0.5f;
272 }
273 else {
274 rect->w = avail.h * aspect;
275 rect->x = avail.x + (avail.w - rect->w) * 0.5f;
276 }
277 }
278
279 // Integer scaling: a quarter turn maps the core's width to the vertical
280 // extent and its height to the horizontal.
281 if (lr->fitMode == SDL_LIBRETRO_FIT_INTEGER && lr->core.width > 0 && lr->core.height > 0) {
282 float coreW = rotated ? (float)lr->core.height : (float)lr->core.width;
283 float coreH = rotated ? (float)lr->core.width : (float)lr->core.height;
284 int scale = SDL_max(1, SDL_min((int)(rect->w / coreW), (int)(rect->h / coreH)));
285 float w = coreW * (float)scale;
286 float h = coreH * (float)scale;
287 rect->x += (rect->w - w) * 0.5f;
288 rect->y += (rect->h - h) * 0.5f;
289 rect->w = w;
290 rect->h = h;
291 }
292}
293
304bool SDL_Libretro_Render(SDL_Libretro* lr, SDL_Renderer* renderer, const SDL_FRect* dstRect) {
305 if (!lr || !renderer) return false;
306
307 // Adopt the renderer if it changed or isn't initialized.
308 if (renderer != lr->renderer && !SDL_Libretro_SetRenderer(lr, renderer)) return false;
309
310 // Requires a texture to render.
311 if (!lr->core.texture) return false;
312
313 // Determine the destination area: the given rect, or the full render output.
314 if (dstRect) {
315 lr->core.renderDstRect = *dstRect;
316 }
317 else {
318 // With logical presentation, draw in logical units; otherwise fill
319 // the current output (the render target if one is set).
320 int w, h;
321 SDL_RendererLogicalPresentation mode = SDL_LOGICAL_PRESENTATION_DISABLED;
322 if (!SDL_GetRenderLogicalPresentation(renderer, &w, &h, &mode) ||
323 mode == SDL_LOGICAL_PRESENTATION_DISABLED) {
324 if (!SDL_GetCurrentRenderOutputSize(renderer, &w, &h)) return false;
325 }
326 lr->core.renderDstRect = (SDL_FRect){0.0f, 0.0f, (float)w, (float)h};
327 }
328
329 // A 90/270 turn swaps the image's on-screen extents.
330 bool rotated = (lr->core.rotation & 1) != 0;
331
332 // Fit the destination to optimally fit within the renderer.
333 SDL_Libretro_FitRect(lr, &lr->core.renderDstRect, rotated);
334
335 // libretro SET_ROTATION is counter-clockwise, but SDL_RenderTextureRotated's
336 // angle is clockwise, so negate it.
337 double angle = -(lr->core.rotation * 90.0);
338
339 if (rotated) {
340 SDL_FRect dst = lr->core.renderDstRect;
341 dst.w = lr->core.renderDstRect.h;
342 dst.h = lr->core.renderDstRect.w;
343 dst.x = lr->core.renderDstRect.x + (lr->core.renderDstRect.w - dst.w) * 0.5f;
344 dst.y = lr->core.renderDstRect.y + (lr->core.renderDstRect.h - dst.h) * 0.5f;
345 return SDL_RenderTextureRotated(renderer, lr->core.texture, NULL, &dst, angle, NULL, SDL_FLIP_NONE);
346 }
347
348 return SDL_RenderTextureRotated(renderer, lr->core.texture, NULL, &lr->core.renderDstRect, angle, NULL, SDL_FLIP_NONE);
349}
350
358void SDL_Libretro_GetSize(const SDL_Libretro* lr, int* w, int* h) {
359 if (w) *w = lr ? (int)lr->core.width : 0;
360 if (h) *h = lr ? (int)lr->core.height : 0;
361}
362
371float SDL_Libretro_GetAspectRatio(const SDL_Libretro* lr) {
372 return lr ? lr->core.aspectRatio : 0.0f;
373}
374
381double SDL_Libretro_GetFPS(const SDL_Libretro* lr) {
382 return lr ? lr->core.fps : 0.0;
383}
384
393int SDL_Libretro_GetRotation(const SDL_Libretro* lr) {
394 return lr ? lr->core.rotation * 90 : 0;
395}
396
407bool SDL_Libretro_SetFitMode(SDL_Libretro* lr, SDL_LibretroFitMode mode) {
408 if (!lr) return false;
409 if ((int)mode < (int)SDL_LIBRETRO_FIT_FIRST || (int)mode >= (int)SDL_LIBRETRO_FIT_COUNT) {
410 return SDL_InvalidParamError("mode");
411 }
412 lr->fitMode = mode;
413 return true;
414}
415
425 return lr ? lr->fitMode : SDL_LIBRETRO_FIT_ASPECT;
426}
427
438bool SDL_Libretro_SetScaleMode(SDL_Libretro* lr, SDL_ScaleMode mode) {
439 if (!lr) return false;
440 bool valid = (mode == SDL_SCALEMODE_NEAREST || mode == SDL_SCALEMODE_LINEAR);
441#if SDL_VERSION_ATLEAST(3, 4, 0)
442 if (mode == SDL_SCALEMODE_PIXELART) valid = true;
443#endif
444 if (!valid) {
445 return SDL_InvalidParamError("mode");
446 }
447 lr->scaleMode = mode;
448 if (lr->core.texture) {
449 SDL_SetTextureScaleMode(lr->core.texture, mode);
450 }
451 return true;
452}
453
462SDL_ScaleMode SDL_Libretro_GetScaleMode(const SDL_Libretro* lr) {
463 return lr ? lr->scaleMode : SDL_SCALEMODE_NEAREST;
464}
465
466#endif /* SDL_LIBRETRO_VIDEO_IMPL_ONCE */
bool SDL_Libretro_Render(SDL_Libretro *lr, SDL_Renderer *renderer, const SDL_FRect *dstRect)
Render the libretro context in the given renderer.
SDL_ScaleMode SDL_Libretro_GetScaleMode(const SDL_Libretro *lr)
Gets the texture scale mode used when the libretro frame is scaled.
SDL_LibretroFitMode SDL_Libretro_GetFitMode(const SDL_Libretro *lr)
Gets how the frame is fit into the destination rectangle when rendered.
bool SDL_Libretro_SaveScreenshot(const SDL_Libretro *lr, const char *path)
Saves a PNG screenshot of the current frame.
SDL_Surface * SDL_Libretro_CreateSurface(const SDL_Libretro *lr)
Creates a new surface from the current libretro context.
int SDL_Libretro_GetRotation(const SDL_Libretro *lr)
Gets the screen rotation requested by the core.
SDL_LibretroFitMode
When rendering the libretro context, determine how to display within the destination.
double SDL_Libretro_GetFPS(const SDL_Libretro *lr)
Gets the frame rate the core expects to run at.
bool SDL_Libretro_SetFitMode(SDL_Libretro *lr, SDL_LibretroFitMode mode)
Sets how the frame is fit into the destination rectangle when rendered.
struct SDL_Libretro SDL_Libretro
A libretro frontend context: one loaded core, its game, and the settings that carry across cores.
float SDL_Libretro_GetAspectRatio(const SDL_Libretro *lr)
Gets the aspect ratio for the libretro context.
SDL_Renderer * SDL_Libretro_GetRenderer(const SDL_Libretro *lr)
Get the renderer the context draws into.
void SDL_Libretro_GetSize(const SDL_Libretro *lr, int *w, int *h)
Gets the width and height of the core's current video frame.
bool SDL_Libretro_SetRenderer(SDL_Libretro *lr, SDL_Renderer *renderer)
Set the renderer the libretro context draws into.
SDL_Texture * SDL_Libretro_GetTexture(const SDL_Libretro *lr)
Get the streaming texture that holds the core's most recent video frame.
bool SDL_Libretro_SetScaleMode(SDL_Libretro *lr, SDL_ScaleMode mode)
Sets the texture scale mode used when the libretro frame is scaled.
@ SDL_LIBRETRO_FIT_STRETCH
Ignore the aspect ratio, and stretch the image to match the destination rectangle.
@ SDL_LIBRETRO_FIT_FIRST
The default fit mode to fit within the destination.
@ SDL_LIBRETRO_FIT_INTEGER
Keep the same aspect ratio, while keeping integer scaling within the confines of the destination.
@ SDL_LIBRETRO_FIT_COUNT
The number of possible fit modes.
@ SDL_LIBRETRO_FIT_ASPECT
Keep the same aspect ratio, and fit within the confines of the destination.