SDL_Libretro
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
15static SDL_PixelFormat SDL_Libretro_GetTextureFormat(enum retro_pixel_format fmt) {
16 switch (fmt) {
17 case RETRO_PIXEL_FORMAT_XRGB8888: return SDL_PIXELFORMAT_XRGB8888;
18 case RETRO_PIXEL_FORMAT_RGB565: return SDL_PIXELFORMAT_RGB565;
19 case RETRO_PIXEL_FORMAT_0RGB1555: return SDL_PIXELFORMAT_XRGB1555;
20 default: return SDL_PIXELFORMAT_RGB565;
21 }
22}
23
31static void SDL_Libretro_ReleaseSoftwareFramebuffer(SDL_Libretro* lr) {
32 if (!lr->core.softwareFramebufferPixels) {
33 return;
34 }
35 if (lr->core.texture) {
36 SDL_UnlockTexture(lr->core.texture);
37 }
38 lr->core.softwareFramebufferPixels = NULL;
39}
40
46static bool SDL_Libretro_InitVideo(SDL_Libretro* lr) {
47 if (!lr || !lr->renderer) return false;
48
49 // Make sure we're starting a clean video context.
50 SDL_Libretro_CloseVideo(lr);
51
52 // If there is no desired width/height, select an arbitrary one.
53 if (lr->core.width == 0 || lr->core.height == 0) {
54 lr->core.width = 320;
55 lr->core.height = 240;
56 }
57
58 // Build the Texture.
59 lr->core.texture = SDL_CreateTexture(lr->renderer,
60 SDL_Libretro_GetTextureFormat(lr->core.pixelFormat),
61 SDL_TEXTUREACCESS_STREAMING,
62 lr->core.width, lr->core.height);
63
64 if (!lr->core.texture) {
65 SDL_SetError("[SDL_Libretro] Failed to create texture: %s", SDL_GetError());
66 return false;
67 }
68 lr->core.videoReinitPending = false;
69
70 // Scale Mode
71 #if SDL_VERSION_ATLEAST(3, 4, 0)
72 if (lr->core.textureScaleMode == SDL_SCALEMODE_NEAREST)
73 lr->core.textureScaleMode = SDL_SCALEMODE_PIXELART; // SDL >= 3.4
74 #endif
75 SDL_SetTextureScaleMode(lr->core.texture, lr->core.textureScaleMode);
76 return true;
77}
78
84static void SDL_Libretro_CloseVideo(SDL_Libretro* lr) {
85 if (!lr) return;
86
87 if (lr->core.texture) {
88 SDL_Libretro_ReleaseSoftwareFramebuffer(lr);
89 SDL_DestroyTexture(lr->core.texture);
90 lr->core.texture = NULL;
91 }
92}
93
94static void SDL_Libretro_VideoRefresh(const void* data, unsigned width, unsigned height, size_t pitch) {
95 SDL_Libretro* lr = SDL_Libretro_active;
96 if (!lr) return;
97
98 // Software framebuffer (RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER)
99 if (lr->core.softwareFramebufferPixels) {
100 void* swfb = lr->core.softwareFramebufferPixels;
101 SDL_Libretro_ReleaseSoftwareFramebuffer(lr);
102 if (data == swfb) return;
103 }
104
105 if (!data) return;
106
107 // 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.
108 if (data == RETRO_HW_FRAME_BUFFER_VALID) return;
109
110 // Rebuild the texture when the core's frame dimensions change.
111 if (width != lr->core.width || height != lr->core.height) {
112 lr->core.width = width;
113 lr->core.height = height;
114 if (!SDL_Libretro_InitVideo(lr)) return;
115 }
116
117 // Make sure the texture is workable.
118 if (!lr->core.texture) return;
119
120 // Copy straight into the texture's backing memory to avoid the extra staging copy SDL_UpdateTexture() makes.
121 void* pixels = NULL;
122 int lockPitch = 0;
123 if (!SDL_LockTexture(lr->core.texture, NULL, &pixels, &lockPitch)) {
124 return;
125 }
126
127 // The core's `pitch` and the texture's locked pitch can differ, so copy row by row.
128 const Uint8* src = (const Uint8*)data;
129 Uint8* dst = (Uint8*)pixels;
130 size_t rowBytes = pitch < (size_t)lockPitch ? pitch : (size_t)lockPitch;
131 if (pitch == (size_t)lockPitch) {
132 // The strides match, so we can do a straight copy.
133 SDL_memcpy(dst, src, rowBytes * height);
134 } else {
135 for (size_t y = 0; y < height; y++) {
136 SDL_memcpy(dst + y * lockPitch, src + y * pitch, rowBytes);
137 }
138 }
139
140 SDL_UnlockTexture(lr->core.texture);
141}
142
152bool SDL_Libretro_SetRenderer(SDL_Libretro* lr, SDL_Renderer* renderer) {
153 if (!lr || !renderer) {
154 SDL_SetError("[SDL_Libretro] Invalid context");
155 return false;
156 }
157
158 bool changed = (lr->renderer != renderer);
159 lr->renderer = renderer;
160 lr->window = SDL_GetRenderWindow(renderer);
161
162 // Build the texture if a game is loaded but has none yet (deferred init after
163 // a renderer-less LoadGame), or rebuild it against the new renderer on a swap
164 // (a texture is bound to the renderer that created it). With no game loaded,
165 // LoadGame's InitVideo handles it once the geometry is known.
166 if (lr->core.gameLoaded && (!lr->core.texture || changed)) {
167 return SDL_Libretro_InitVideo(lr);
168 }
169
170 return true;
171}
172
179SDL_Renderer* SDL_Libretro_GetRenderer(const SDL_Libretro* lr) {
180 return lr ? lr->renderer : NULL;
181}
182
183SDL_Texture* SDL_Libretro_GetTexture(const SDL_Libretro* lr) {
184 return (lr && lr->core.texture) ? lr->core.texture : NULL;
185}
186
192SDL_Surface* SDL_Libretro_CreateSurface(const SDL_Libretro* lr) {
193 if (!lr || !lr->core.texture) return NULL;
194
195 void* pixels = NULL;
196 int pitch = 0;
197 if (!SDL_LockTexture(lr->core.texture, NULL, &pixels, &pitch)) return NULL;
198
199 SDL_PixelFormat fmt = SDL_Libretro_GetTextureFormat(lr->core.pixelFormat);
200 SDL_Surface* ref = SDL_CreateSurfaceFrom((int)lr->core.width, (int)lr->core.height, fmt, pixels, pitch);
201 SDL_Surface* out = NULL;
202 if (ref) {
203 out = SDL_CreateSurface((int)lr->core.width, (int)lr->core.height, fmt);
204 if (out) SDL_BlitSurface(ref, NULL, out, NULL);
205 SDL_DestroySurface(ref);
206 }
207
208 SDL_UnlockTexture(lr->core.texture);
209 return out;
210}
211
220static void SDL_Libretro_FitRect(const SDL_Libretro* lr, SDL_FRect* rect, bool rotated) {
221 SDL_FRect avail = *rect;
222
223 if (lr->fitMode != SDL_LIBRETRO_FIT_STRETCH) {
224 // Content aspect ratio, falling back to the frame dimensions.
225 float srcAspect = lr->core.aspectRatio;
226 if (srcAspect <= 0.0f && lr->core.width > 0 && lr->core.height > 0) {
227 srcAspect = (float)lr->core.width / (float)lr->core.height;
228 }
229
230 // Letterbox to the effective aspect ratio within the available area.
231 float aspect = (rotated && srcAspect > 0.0f) ? 1.0f / srcAspect : srcAspect;
232 if (aspect > 0.0f) {
233 if (aspect > avail.w / avail.h) {
234 rect->h = avail.w / aspect;
235 rect->y = avail.y + (avail.h - rect->h) * 0.5f;
236 } else {
237 rect->w = avail.h * aspect;
238 rect->x = avail.x + (avail.w - rect->w) * 0.5f;
239 }
240 }
241
242 // Integer scaling: a quarter turn maps the core's width to the vertical
243 // extent and its height to the horizontal.
244 if (lr->fitMode == SDL_LIBRETRO_FIT_INTEGER && lr->core.width > 0 && lr->core.height > 0) {
245 float coreW = rotated ? (float)lr->core.height : (float)lr->core.width;
246 float coreH = rotated ? (float)lr->core.width : (float)lr->core.height;
247 int scale = SDL_max(1, SDL_min((int)(rect->w / coreW), (int)(rect->h / coreH)));
248 float w = coreW * (float)scale;
249 float h = coreH * (float)scale;
250 rect->x += (rect->w - w) * 0.5f;
251 rect->y += (rect->h - h) * 0.5f;
252 rect->w = w;
253 rect->h = h;
254 }
255 }
256}
257
268bool SDL_Libretro_Render(SDL_Renderer* renderer, SDL_Libretro* lr, const SDL_FRect* dstRect) {
269 if (!lr || !renderer) return false;
270
271 // Adopt the renderer if it changed or isn't initialized.
272 if (renderer != lr->renderer && !SDL_Libretro_SetRenderer(lr, renderer)) return false;
273
274 // Requires a texture to render.
275 if (!lr->core.texture) return false;
276
277 // Determine the destination area: the given rect, or the full render output.
278 if (dstRect) {
279 lr->core.renderDstRect = *dstRect;
280 } else {
281 int w, h;
282 if (!SDL_GetRenderOutputSize(renderer, &w, &h)) return false;
283 lr->core.renderDstRect = (SDL_FRect){ 0.0f, 0.0f, (float)w, (float)h };
284 }
285
286 // A 90/270 turn swaps the image's on-screen extents.
287 bool rotated = (lr->core.rotation & 1) != 0;
288
289 // Fit the destination to optimally fit within the renderer.
290 SDL_Libretro_FitRect(lr, &lr->core.renderDstRect, rotated);
291
292 // libretro SET_ROTATION is counter-clockwise, but SDL_RenderTextureRotated's
293 // angle is clockwise, so negate it.
294 double angle = -(lr->core.rotation * 90.0);
295
296 if (rotated) {
297 SDL_FRect dst = lr->core.renderDstRect;
298 dst.w = lr->core.renderDstRect.h;
299 dst.h = lr->core.renderDstRect.w;
300 dst.x = lr->core.renderDstRect.x + (lr->core.renderDstRect.w - dst.w) * 0.5f;
301 dst.y = lr->core.renderDstRect.y + (lr->core.renderDstRect.h - dst.h) * 0.5f;
302 return SDL_RenderTextureRotated(renderer, lr->core.texture, NULL, &dst, angle, NULL, SDL_FLIP_NONE);
303 }
304
305 return SDL_RenderTextureRotated(renderer, lr->core.texture, NULL, &lr->core.renderDstRect, angle, NULL, SDL_FLIP_NONE);
306}
307
311void SDL_Libretro_GetSize(const SDL_Libretro* lr, int* w, int* h) {
312 if (w) *w = lr ? (int)lr->core.width : 0;
313 if (h) *h = lr ? (int)lr->core.height : 0;
314}
315
319float SDL_Libretro_GetAspectRatio(const SDL_Libretro* lr) {
320 return lr ? lr->core.aspectRatio : 0.0f;
321}
322
323double SDL_Libretro_GetFPS(const SDL_Libretro* lr) {
324 return lr ? lr->core.fps : 0.0;
325}
326
327int SDL_Libretro_GetRotation(const SDL_Libretro* lr) {
328 return lr ? lr->core.rotation * 90 : 0;
329}
330
334void SDL_Libretro_SetFitMode(SDL_Libretro* lr, SDL_LibretroFitMode mode) {
335 if (lr && lr->fitMode != mode) {
336 lr->fitMode = mode;
337 }
338}
339
340SDL_LibretroFitMode SDL_Libretro_GetFitMode(const SDL_Libretro* lr) {
341 return lr ? lr->fitMode : SDL_LIBRETRO_FIT_ASPECT;
342}
343
344#endif /* SDL_LIBRETRO_VIDEO_IMPL_ONCE */
bool SDL_Libretro_Render(SDL_Renderer *renderer, SDL_Libretro *lr, const SDL_FRect *dstRect)
Render the libretro context in the given renderer.
SDL_Surface * SDL_Libretro_CreateSurface(const SDL_Libretro *lr)
Creates a new surface from the current libretro context.
SDL_LibretroFitMode
When rendering the libretro context, determine how to display within the destination.
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 heigh tof the given libretro context.
bool SDL_Libretro_SetRenderer(SDL_Libretro *lr, SDL_Renderer *renderer)
Set the renderer the libretro context draws into.
void SDL_Libretro_SetFitMode(SDL_Libretro *lr, SDL_LibretroFitMode mode)
Sets the desired scale mode for the libretro context when it's displayed.
@ SDL_LIBRETRO_FIT_STRETCH
Keep the same aspect ratio, while keeping integer scaling within the confines of the destination.
@ SDL_LIBRETRO_FIT_INTEGER
Keep the same aspect ratio, and fit within the confines of the destination.