1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_RENDER_SW
24
25#include "../SDL_sysrender.h"
26#include "SDL_render_sw_c.h"
27
28#include "SDL_draw.h"
29#include "SDL_blendfillrect.h"
30#include "SDL_blendline.h"
31#include "SDL_blendpoint.h"
32#include "SDL_drawline.h"
33#include "SDL_drawpoint.h"
34#include "SDL_rotate.h"
35#include "SDL_triangle.h"
36#include "../../video/SDL_pixels_c.h"
37
38// SDL surface based renderer implementation
39
40typedef struct
41{
42 const SDL_Rect *viewport;
43 const SDL_Rect *cliprect;
44 bool surface_cliprect_dirty;
45 SDL_Color color;
46} SW_DrawStateCache;
47
48typedef struct
49{
50 SDL_Surface *surface;
51 SDL_Surface *window;
52} SW_RenderData;
53
54static SDL_Surface *SW_ActivateRenderer(SDL_Renderer *renderer)
55{
56 SW_RenderData *data = (SW_RenderData *)renderer->internal;
57
58 if (!data->surface) {
59 data->surface = data->window;
60 }
61 if (!data->surface) {
62 SDL_Surface *surface = SDL_GetWindowSurface(renderer->window);
63 if (surface) {
64 data->surface = data->window = surface;
65 }
66 }
67 return data->surface;
68}
69
70static void SW_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event)
71{
72 SW_RenderData *data = (SW_RenderData *)renderer->internal;
73
74 if (event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED) {
75 data->surface = NULL;
76 data->window = NULL;
77 }
78}
79
80static bool SW_GetOutputSize(SDL_Renderer *renderer, int *w, int *h)
81{
82 SW_RenderData *data = (SW_RenderData *)renderer->internal;
83
84 if (data->surface) {
85 if (w) {
86 *w = data->surface->w;
87 }
88 if (h) {
89 *h = data->surface->h;
90 }
91 return true;
92 }
93
94 if (renderer->window) {
95 SDL_GetWindowSizeInPixels(renderer->window, w, h);
96 return true;
97 }
98
99 return SDL_SetError("Software renderer doesn't have an output surface");
100}
101
102static bool SW_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props)
103{
104 SDL_Surface *surface = SDL_CreateSurface(texture->w, texture->h, texture->format);
105 Uint8 r, g, b, a;
106
107 if (!SDL_SurfaceValid(surface)) {
108 return SDL_SetError("Cannot create surface");
109 }
110 texture->internal = surface;
111 r = (Uint8)SDL_roundf(SDL_clamp(texture->color.r, 0.0f, 1.0f) * 255.0f);
112 g = (Uint8)SDL_roundf(SDL_clamp(texture->color.g, 0.0f, 1.0f) * 255.0f);
113 b = (Uint8)SDL_roundf(SDL_clamp(texture->color.b, 0.0f, 1.0f) * 255.0f);
114 a = (Uint8)SDL_roundf(SDL_clamp(texture->color.a, 0.0f, 1.0f) * 255.0f);
115 SDL_SetSurfaceColorMod(surface, r, g, b);
116 SDL_SetSurfaceAlphaMod(surface, a);
117 SDL_SetSurfaceBlendMode(surface, texture->blendMode);
118
119 /* Only RLE encode textures without an alpha channel since the RLE coder
120 * discards the color values of pixels with an alpha value of zero.
121 */
122 if (texture->access == SDL_TEXTUREACCESS_STATIC && !SDL_ISPIXELFORMAT_ALPHA(surface->format)) {
123 SDL_SetSurfaceRLE(surface, 1);
124 }
125
126 return true;
127}
128
129static bool SW_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
130 const SDL_Rect *rect, const void *pixels, int pitch)
131{
132 SDL_Surface *surface = (SDL_Surface *)texture->internal;
133 Uint8 *src, *dst;
134 int row;
135 size_t length;
136
137 if (SDL_MUSTLOCK(surface)) {
138 if (!SDL_LockSurface(surface)) {
139 return false;
140 }
141 }
142 src = (Uint8 *)pixels;
143 dst = (Uint8 *)surface->pixels +
144 rect->y * surface->pitch +
145 rect->x * surface->fmt->bytes_per_pixel;
146 length = (size_t)rect->w * surface->fmt->bytes_per_pixel;
147 for (row = 0; row < rect->h; ++row) {
148 SDL_memcpy(dst, src, length);
149 src += pitch;
150 dst += surface->pitch;
151 }
152 if (SDL_MUSTLOCK(surface)) {
153 SDL_UnlockSurface(surface);
154 }
155 return true;
156}
157
158static bool SW_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture,
159 const SDL_Rect *rect, void **pixels, int *pitch)
160{
161 SDL_Surface *surface = (SDL_Surface *)texture->internal;
162
163 *pixels =
164 (void *)((Uint8 *)surface->pixels + rect->y * surface->pitch +
165 rect->x * surface->fmt->bytes_per_pixel);
166 *pitch = surface->pitch;
167 return true;
168}
169
170static void SW_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
171{
172}
173
174static bool SW_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
175{
176 SW_RenderData *data = (SW_RenderData *)renderer->internal;
177
178 if (texture) {
179 data->surface = (SDL_Surface *)texture->internal;
180 } else {
181 data->surface = data->window;
182 }
183 return true;
184}
185
186static bool SW_QueueNoOp(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
187{
188 return true; // nothing to do in this backend.
189}
190
191static bool SW_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count)
192{
193 SDL_Point *verts = (SDL_Point *)SDL_AllocateRenderVertices(renderer, count * sizeof(SDL_Point), 0, &cmd->data.draw.first);
194 int i;
195
196 if (!verts) {
197 return false;
198 }
199
200 cmd->data.draw.count = count;
201
202 for (i = 0; i < count; i++, verts++, points++) {
203 verts->x = (int)points->x;
204 verts->y = (int)points->y;
205 }
206
207 return true;
208}
209
210static bool SW_QueueFillRects(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FRect *rects, int count)
211{
212 SDL_Rect *verts = (SDL_Rect *)SDL_AllocateRenderVertices(renderer, count * sizeof(SDL_Rect), 0, &cmd->data.draw.first);
213 int i;
214
215 if (!verts) {
216 return false;
217 }
218
219 cmd->data.draw.count = count;
220
221 for (i = 0; i < count; i++, verts++, rects++) {
222 verts->x = (int)rects->x;
223 verts->y = (int)rects->y;
224 verts->w = SDL_max((int)rects->w, 1);
225 verts->h = SDL_max((int)rects->h, 1);
226 }
227
228 return true;
229}
230
231static bool SW_QueueCopy(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture,
232 const SDL_FRect *srcrect, const SDL_FRect *dstrect)
233{
234 SDL_Rect *verts = (SDL_Rect *)SDL_AllocateRenderVertices(renderer, 2 * sizeof(SDL_Rect), 0, &cmd->data.draw.first);
235
236 if (!verts) {
237 return false;
238 }
239
240 cmd->data.draw.count = 1;
241
242 verts->x = (int)srcrect->x;
243 verts->y = (int)srcrect->y;
244 verts->w = (int)srcrect->w;
245 verts->h = (int)srcrect->h;
246 verts++;
247
248 verts->x = (int)dstrect->x;
249 verts->y = (int)dstrect->y;
250 verts->w = (int)dstrect->w;
251 verts->h = (int)dstrect->h;
252
253 return true;
254}
255
256typedef struct CopyExData
257{
258 SDL_Rect srcrect;
259 SDL_Rect dstrect;
260 double angle;
261 SDL_FPoint center;
262 SDL_FlipMode flip;
263 float scale_x;
264 float scale_y;
265} CopyExData;
266
267static bool SW_QueueCopyEx(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture,
268 const SDL_FRect *srcrect, const SDL_FRect *dstrect,
269 const double angle, const SDL_FPoint *center, const SDL_FlipMode flip, float scale_x, float scale_y)
270{
271 CopyExData *verts = (CopyExData *)SDL_AllocateRenderVertices(renderer, sizeof(CopyExData), 0, &cmd->data.draw.first);
272
273 if (!verts) {
274 return false;
275 }
276
277 cmd->data.draw.count = 1;
278
279 verts->srcrect.x = (int)srcrect->x;
280 verts->srcrect.y = (int)srcrect->y;
281 verts->srcrect.w = (int)srcrect->w;
282 verts->srcrect.h = (int)srcrect->h;
283 verts->dstrect.x = (int)dstrect->x;
284 verts->dstrect.y = (int)dstrect->y;
285 verts->dstrect.w = (int)dstrect->w;
286 verts->dstrect.h = (int)dstrect->h;
287 verts->angle = angle;
288 SDL_copyp(&verts->center, center);
289 verts->flip = flip;
290 verts->scale_x = scale_x;
291 verts->scale_y = scale_y;
292
293 return true;
294}
295
296static bool Blit_to_Screen(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *surface, SDL_Rect *dstrect,
297 float scale_x, float scale_y, SDL_ScaleMode scaleMode)
298{
299 bool result;
300 // Renderer scaling, if needed
301 if (scale_x != 1.0f || scale_y != 1.0f) {
302 SDL_Rect r;
303 r.x = (int)((float)dstrect->x * scale_x);
304 r.y = (int)((float)dstrect->y * scale_y);
305 r.w = (int)((float)dstrect->w * scale_x);
306 r.h = (int)((float)dstrect->h * scale_y);
307 result = SDL_BlitSurfaceScaled(src, srcrect, surface, &r, scaleMode);
308 } else {
309 result = SDL_BlitSurface(src, srcrect, surface, dstrect);
310 }
311 return result;
312}
313
314static bool SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Texture *texture,
315 const SDL_Rect *srcrect, const SDL_Rect *final_rect,
316 const double angle, const SDL_FPoint *center, const SDL_FlipMode flip, float scale_x, float scale_y, const SDL_ScaleMode scaleMode)
317{
318 SDL_Surface *src = (SDL_Surface *)texture->internal;
319 SDL_Rect tmp_rect;
320 SDL_Surface *src_clone, *src_rotated, *src_scaled;
321 SDL_Surface *mask = NULL, *mask_rotated = NULL;
322 bool result = true;
323 SDL_BlendMode blendmode;
324 Uint8 alphaMod, rMod, gMod, bMod;
325 int applyModulation = false;
326 int blitRequired = false;
327 int isOpaque = false;
328
329 if (!SDL_SurfaceValid(surface)) {
330 return false;
331 }
332
333 tmp_rect.x = 0;
334 tmp_rect.y = 0;
335 tmp_rect.w = final_rect->w;
336 tmp_rect.h = final_rect->h;
337
338 /* It is possible to encounter an RLE encoded surface here and locking it is
339 * necessary because this code is going to access the pixel buffer directly.
340 */
341 if (SDL_MUSTLOCK(src)) {
342 if (!SDL_LockSurface(src)) {
343 return false;
344 }
345 }
346
347 /* Clone the source surface but use its pixel buffer directly.
348 * The original source surface must be treated as read-only.
349 */
350 src_clone = SDL_CreateSurfaceFrom(src->w, src->h, src->format, src->pixels, src->pitch);
351 if (!src_clone) {
352 if (SDL_MUSTLOCK(src)) {
353 SDL_UnlockSurface(src);
354 }
355 return false;
356 }
357
358 SDL_GetSurfaceBlendMode(src, &blendmode);
359 SDL_GetSurfaceAlphaMod(src, &alphaMod);
360 SDL_GetSurfaceColorMod(src, &rMod, &gMod, &bMod);
361
362 // SDLgfx_rotateSurface only accepts 32-bit surfaces with a 8888 layout. Everything else has to be converted.
363 if (src->fmt->bits_per_pixel != 32 || SDL_PIXELLAYOUT(src->format) != SDL_PACKEDLAYOUT_8888 || !SDL_ISPIXELFORMAT_ALPHA(src->format)) {
364 blitRequired = true;
365 }
366
367 // If scaling and cropping is necessary, it has to be taken care of before the rotation.
368 if (!(srcrect->w == final_rect->w && srcrect->h == final_rect->h && srcrect->x == 0 && srcrect->y == 0)) {
369 blitRequired = true;
370 }
371
372 // srcrect is not selecting the whole src surface, so cropping is needed
373 if (!(srcrect->w == src->w && srcrect->h == src->h && srcrect->x == 0 && srcrect->y == 0)) {
374 blitRequired = true;
375 }
376
377 // The color and alpha modulation has to be applied before the rotation when using the NONE, MOD or MUL blend modes.
378 if ((blendmode == SDL_BLENDMODE_NONE || blendmode == SDL_BLENDMODE_MOD || blendmode == SDL_BLENDMODE_MUL) && (alphaMod & rMod & gMod & bMod) != 255) {
379 applyModulation = true;
380 SDL_SetSurfaceAlphaMod(src_clone, alphaMod);
381 SDL_SetSurfaceColorMod(src_clone, rMod, gMod, bMod);
382 }
383
384 // Opaque surfaces are much easier to handle with the NONE blend mode.
385 if (blendmode == SDL_BLENDMODE_NONE && !SDL_ISPIXELFORMAT_ALPHA(src->format) && alphaMod == 255) {
386 isOpaque = true;
387 }
388
389 /* The NONE blend mode requires a mask for non-opaque surfaces. This mask will be used
390 * to clear the pixels in the destination surface. The other steps are explained below.
391 */
392 if (blendmode == SDL_BLENDMODE_NONE && !isOpaque) {
393 mask = SDL_CreateSurface(final_rect->w, final_rect->h, SDL_PIXELFORMAT_ARGB8888);
394 if (!mask) {
395 result = false;
396 } else {
397 SDL_SetSurfaceBlendMode(mask, SDL_BLENDMODE_MOD);
398 }
399 }
400
401 /* Create a new surface should there be a format mismatch or if scaling, cropping,
402 * or modulation is required. It's possible to use the source surface directly otherwise.
403 */
404 if (result && (blitRequired || applyModulation)) {
405 SDL_Rect scale_rect = tmp_rect;
406 src_scaled = SDL_CreateSurface(final_rect->w, final_rect->h, SDL_PIXELFORMAT_ARGB8888);
407 if (!src_scaled) {
408 result = false;
409 } else {
410 SDL_SetSurfaceBlendMode(src_clone, SDL_BLENDMODE_NONE);
411 result = SDL_BlitSurfaceScaled(src_clone, srcrect, src_scaled, &scale_rect, scaleMode);
412 SDL_DestroySurface(src_clone);
413 src_clone = src_scaled;
414 src_scaled = NULL;
415 }
416 }
417
418 // SDLgfx_rotateSurface is going to make decisions depending on the blend mode.
419 SDL_SetSurfaceBlendMode(src_clone, blendmode);
420
421 if (result) {
422 SDL_Rect rect_dest;
423 double cangle, sangle;
424
425 SDLgfx_rotozoomSurfaceSizeTrig(tmp_rect.w, tmp_rect.h, angle, center,
426 &rect_dest, &cangle, &sangle);
427 src_rotated = SDLgfx_rotateSurface(src_clone, angle,
428 (scaleMode == SDL_SCALEMODE_NEAREST || scaleMode == SDL_SCALEMODE_PIXELART) ? 0 : 1, flip & SDL_FLIP_HORIZONTAL, flip & SDL_FLIP_VERTICAL,
429 &rect_dest, cangle, sangle, center);
430 if (!src_rotated) {
431 result = false;
432 }
433 if (result && mask) {
434 // The mask needed for the NONE blend mode gets rotated with the same parameters.
435 mask_rotated = SDLgfx_rotateSurface(mask, angle,
436 false, 0, 0,
437 &rect_dest, cangle, sangle, center);
438 if (!mask_rotated) {
439 result = false;
440 }
441 }
442 if (result) {
443
444 tmp_rect.x = final_rect->x + rect_dest.x;
445 tmp_rect.y = final_rect->y + rect_dest.y;
446 tmp_rect.w = rect_dest.w;
447 tmp_rect.h = rect_dest.h;
448
449 /* The NONE blend mode needs some special care with non-opaque surfaces.
450 * Other blend modes or opaque surfaces can be blitted directly.
451 */
452 if (blendmode != SDL_BLENDMODE_NONE || isOpaque) {
453 if (applyModulation == false) {
454 // If the modulation wasn't already applied, make it happen now.
455 SDL_SetSurfaceAlphaMod(src_rotated, alphaMod);
456 SDL_SetSurfaceColorMod(src_rotated, rMod, gMod, bMod);
457 }
458 // Renderer scaling, if needed
459 result = Blit_to_Screen(src_rotated, NULL, surface, &tmp_rect, scale_x, scale_y, scaleMode);
460 } else {
461 /* The NONE blend mode requires three steps to get the pixels onto the destination surface.
462 * First, the area where the rotated pixels will be blitted to get set to zero.
463 * This is accomplished by simply blitting a mask with the NONE blend mode.
464 * The colorkey set by the rotate function will discard the correct pixels.
465 */
466 SDL_Rect mask_rect = tmp_rect;
467 SDL_SetSurfaceBlendMode(mask_rotated, SDL_BLENDMODE_NONE);
468 // Renderer scaling, if needed
469 result = Blit_to_Screen(mask_rotated, NULL, surface, &mask_rect, scale_x, scale_y, scaleMode);
470 if (result) {
471 /* The next step copies the alpha value. This is done with the BLEND blend mode and
472 * by modulating the source colors with 0. Since the destination is all zeros, this
473 * will effectively set the destination alpha to the source alpha.
474 */
475 SDL_SetSurfaceColorMod(src_rotated, 0, 0, 0);
476 mask_rect = tmp_rect;
477 // Renderer scaling, if needed
478 result = Blit_to_Screen(src_rotated, NULL, surface, &mask_rect, scale_x, scale_y, scaleMode);
479 if (result) {
480 /* The last step gets the color values in place. The ADD blend mode simply adds them to
481 * the destination (where the color values are all zero). However, because the ADD blend
482 * mode modulates the colors with the alpha channel, a surface without an alpha mask needs
483 * to be created. This makes all source pixels opaque and the colors get copied correctly.
484 */
485 SDL_Surface *src_rotated_rgb = SDL_CreateSurfaceFrom(src_rotated->w, src_rotated->h, src_rotated->format, src_rotated->pixels, src_rotated->pitch);
486 if (!src_rotated_rgb) {
487 result = false;
488 } else {
489 SDL_SetSurfaceBlendMode(src_rotated_rgb, SDL_BLENDMODE_ADD);
490 // Renderer scaling, if needed
491 result = Blit_to_Screen(src_rotated_rgb, NULL, surface, &tmp_rect, scale_x, scale_y, scaleMode);
492 SDL_DestroySurface(src_rotated_rgb);
493 }
494 }
495 }
496 SDL_DestroySurface(mask_rotated);
497 }
498 if (src_rotated) {
499 SDL_DestroySurface(src_rotated);
500 }
501 }
502 }
503
504 if (SDL_MUSTLOCK(src)) {
505 SDL_UnlockSurface(src);
506 }
507 if (mask) {
508 SDL_DestroySurface(mask);
509 }
510 if (src_clone) {
511 SDL_DestroySurface(src_clone);
512 }
513 return result;
514}
515
516typedef struct GeometryFillData
517{
518 SDL_Point dst;
519 SDL_Color color;
520} GeometryFillData;
521
522typedef struct GeometryCopyData
523{
524 SDL_Point src;
525 SDL_Point dst;
526 SDL_Color color;
527} GeometryCopyData;
528
529static bool SW_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture,
530 const float *xy, int xy_stride, const SDL_FColor *color, int color_stride, const float *uv, int uv_stride,
531 int num_vertices, const void *indices, int num_indices, int size_indices,
532 float scale_x, float scale_y)
533{
534 int i;
535 int count = indices ? num_indices : num_vertices;
536 void *verts;
537 size_t sz = texture ? sizeof(GeometryCopyData) : sizeof(GeometryFillData);
538 const float color_scale = cmd->data.draw.color_scale;
539
540 verts = SDL_AllocateRenderVertices(renderer, count * sz, 0, &cmd->data.draw.first);
541 if (!verts) {
542 return false;
543 }
544
545 cmd->data.draw.count = count;
546 size_indices = indices ? size_indices : 0;
547
548 if (texture) {
549 GeometryCopyData *ptr = (GeometryCopyData *)verts;
550 for (i = 0; i < count; i++) {
551 int j;
552 float *xy_;
553 SDL_FColor col_;
554 float *uv_;
555 if (size_indices == 4) {
556 j = ((const Uint32 *)indices)[i];
557 } else if (size_indices == 2) {
558 j = ((const Uint16 *)indices)[i];
559 } else if (size_indices == 1) {
560 j = ((const Uint8 *)indices)[i];
561 } else {
562 j = i;
563 }
564
565 xy_ = (float *)((char *)xy + j * xy_stride);
566 col_ = *(SDL_FColor *)((char *)color + j * color_stride);
567
568 uv_ = (float *)((char *)uv + j * uv_stride);
569
570 ptr->src.x = (int)(uv_[0] * texture->w);
571 ptr->src.y = (int)(uv_[1] * texture->h);
572
573 ptr->dst.x = (int)(xy_[0] * scale_x);
574 ptr->dst.y = (int)(xy_[1] * scale_y);
575 trianglepoint_2_fixedpoint(&ptr->dst);
576
577 ptr->color.r = (Uint8)SDL_roundf(SDL_clamp(col_.r * color_scale, 0.0f, 1.0f) * 255.0f);
578 ptr->color.g = (Uint8)SDL_roundf(SDL_clamp(col_.g * color_scale, 0.0f, 1.0f) * 255.0f);
579 ptr->color.b = (Uint8)SDL_roundf(SDL_clamp(col_.b * color_scale, 0.0f, 1.0f) * 255.0f);
580 ptr->color.a = (Uint8)SDL_roundf(SDL_clamp(col_.a, 0.0f, 1.0f) * 255.0f);
581
582 ptr++;
583 }
584 } else {
585 GeometryFillData *ptr = (GeometryFillData *)verts;
586
587 for (i = 0; i < count; i++) {
588 int j;
589 float *xy_;
590 SDL_FColor col_;
591 if (size_indices == 4) {
592 j = ((const Uint32 *)indices)[i];
593 } else if (size_indices == 2) {
594 j = ((const Uint16 *)indices)[i];
595 } else if (size_indices == 1) {
596 j = ((const Uint8 *)indices)[i];
597 } else {
598 j = i;
599 }
600
601 xy_ = (float *)((char *)xy + j * xy_stride);
602 col_ = *(SDL_FColor *)((char *)color + j * color_stride);
603
604 ptr->dst.x = (int)(xy_[0] * scale_x);
605 ptr->dst.y = (int)(xy_[1] * scale_y);
606 trianglepoint_2_fixedpoint(&ptr->dst);
607
608 ptr->color.r = (Uint8)SDL_roundf(SDL_clamp(col_.r * color_scale, 0.0f, 1.0f) * 255.0f);
609 ptr->color.g = (Uint8)SDL_roundf(SDL_clamp(col_.g * color_scale, 0.0f, 1.0f) * 255.0f);
610 ptr->color.b = (Uint8)SDL_roundf(SDL_clamp(col_.b * color_scale, 0.0f, 1.0f) * 255.0f);
611 ptr->color.a = (Uint8)SDL_roundf(SDL_clamp(col_.a, 0.0f, 1.0f) * 255.0f);
612
613 ptr++;
614 }
615 }
616 return true;
617}
618
619static void PrepTextureForCopy(const SDL_RenderCommand *cmd, SW_DrawStateCache *drawstate)
620{
621 const Uint8 r = drawstate->color.r;
622 const Uint8 g = drawstate->color.g;
623 const Uint8 b = drawstate->color.b;
624 const Uint8 a = drawstate->color.a;
625 const SDL_BlendMode blend = cmd->data.draw.blend;
626 SDL_Texture *texture = cmd->data.draw.texture;
627 SDL_Surface *surface = (SDL_Surface *)texture->internal;
628 const bool colormod = ((r & g & b) != 0xFF);
629 const bool alphamod = (a != 0xFF);
630 const bool blending = ((blend == SDL_BLENDMODE_ADD) || (blend == SDL_BLENDMODE_MOD) || (blend == SDL_BLENDMODE_MUL));
631
632 if (colormod || alphamod || blending) {
633 SDL_SetSurfaceRLE(surface, 0);
634 }
635
636 // !!! FIXME: we can probably avoid some of these calls.
637 SDL_SetSurfaceColorMod(surface, r, g, b);
638 SDL_SetSurfaceAlphaMod(surface, a);
639 SDL_SetSurfaceBlendMode(surface, blend);
640}
641
642static void SetDrawState(SDL_Surface *surface, SW_DrawStateCache *drawstate)
643{
644 if (drawstate->surface_cliprect_dirty) {
645 const SDL_Rect *viewport = drawstate->viewport;
646 const SDL_Rect *cliprect = drawstate->cliprect;
647 SDL_assert_release(viewport != NULL); // the higher level should have forced a SDL_RENDERCMD_SETVIEWPORT
648
649 if (cliprect && viewport) {
650 SDL_Rect clip_rect;
651 clip_rect.x = cliprect->x + viewport->x;
652 clip_rect.y = cliprect->y + viewport->y;
653 clip_rect.w = cliprect->w;
654 clip_rect.h = cliprect->h;
655 SDL_GetRectIntersection(viewport, &clip_rect, &clip_rect);
656 SDL_SetSurfaceClipRect(surface, &clip_rect);
657 } else {
658 SDL_SetSurfaceClipRect(surface, drawstate->viewport);
659 }
660 drawstate->surface_cliprect_dirty = false;
661 }
662}
663
664static void SW_InvalidateCachedState(SDL_Renderer *renderer)
665{
666 // SW_DrawStateCache only lives during SW_RunCommandQueue, so nothing to do here!
667}
668
669
670static bool SW_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize)
671{
672 SDL_Surface *surface = SW_ActivateRenderer(renderer);
673 SW_DrawStateCache drawstate;
674
675 if (!SDL_SurfaceValid(surface)) {
676 return false;
677 }
678
679 drawstate.viewport = NULL;
680 drawstate.cliprect = NULL;
681 drawstate.surface_cliprect_dirty = true;
682 drawstate.color.r = 0;
683 drawstate.color.g = 0;
684 drawstate.color.b = 0;
685 drawstate.color.a = 0;
686
687 while (cmd) {
688 switch (cmd->command) {
689 case SDL_RENDERCMD_SETDRAWCOLOR:
690 {
691 drawstate.color.r = (Uint8)SDL_roundf(SDL_clamp(cmd->data.color.color.r * cmd->data.color.color_scale, 0.0f, 1.0f) * 255.0f);
692 drawstate.color.g = (Uint8)SDL_roundf(SDL_clamp(cmd->data.color.color.g * cmd->data.color.color_scale, 0.0f, 1.0f) * 255.0f);
693 drawstate.color.b = (Uint8)SDL_roundf(SDL_clamp(cmd->data.color.color.b * cmd->data.color.color_scale, 0.0f, 1.0f) * 255.0f);
694 drawstate.color.a = (Uint8)SDL_roundf(SDL_clamp(cmd->data.color.color.a, 0.0f, 1.0f) * 255.0f);
695 break;
696 }
697
698 case SDL_RENDERCMD_SETVIEWPORT:
699 {
700 drawstate.viewport = &cmd->data.viewport.rect;
701 drawstate.surface_cliprect_dirty = true;
702 break;
703 }
704
705 case SDL_RENDERCMD_SETCLIPRECT:
706 {
707 drawstate.cliprect = cmd->data.cliprect.enabled ? &cmd->data.cliprect.rect : NULL;
708 drawstate.surface_cliprect_dirty = true;
709 break;
710 }
711
712 case SDL_RENDERCMD_CLEAR:
713 {
714 const Uint8 r = (Uint8)SDL_roundf(SDL_clamp(cmd->data.color.color.r * cmd->data.color.color_scale, 0.0f, 1.0f) * 255.0f);
715 const Uint8 g = (Uint8)SDL_roundf(SDL_clamp(cmd->data.color.color.g * cmd->data.color.color_scale, 0.0f, 1.0f) * 255.0f);
716 const Uint8 b = (Uint8)SDL_roundf(SDL_clamp(cmd->data.color.color.b * cmd->data.color.color_scale, 0.0f, 1.0f) * 255.0f);
717 const Uint8 a = (Uint8)SDL_roundf(SDL_clamp(cmd->data.color.color.a, 0.0f, 1.0f) * 255.0f);
718 // By definition the clear ignores the clip rect
719 SDL_SetSurfaceClipRect(surface, NULL);
720 SDL_FillSurfaceRect(surface, NULL, SDL_MapSurfaceRGBA(surface, r, g, b, a));
721 drawstate.surface_cliprect_dirty = true;
722 break;
723 }
724
725 case SDL_RENDERCMD_DRAW_POINTS:
726 {
727 const Uint8 r = drawstate.color.r;
728 const Uint8 g = drawstate.color.g;
729 const Uint8 b = drawstate.color.b;
730 const Uint8 a = drawstate.color.a;
731 const int count = (int)cmd->data.draw.count;
732 SDL_Point *verts = (SDL_Point *)(((Uint8 *)vertices) + cmd->data.draw.first);
733 const SDL_BlendMode blend = cmd->data.draw.blend;
734 SetDrawState(surface, &drawstate);
735
736 // Apply viewport
737 if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) {
738 int i;
739 for (i = 0; i < count; i++) {
740 verts[i].x += drawstate.viewport->x;
741 verts[i].y += drawstate.viewport->y;
742 }
743 }
744
745 if (blend == SDL_BLENDMODE_NONE) {
746 SDL_DrawPoints(surface, verts, count, SDL_MapSurfaceRGBA(surface, r, g, b, a));
747 } else {
748 SDL_BlendPoints(surface, verts, count, blend, r, g, b, a);
749 }
750 break;
751 }
752
753 case SDL_RENDERCMD_DRAW_LINES:
754 {
755 const Uint8 r = drawstate.color.r;
756 const Uint8 g = drawstate.color.g;
757 const Uint8 b = drawstate.color.b;
758 const Uint8 a = drawstate.color.a;
759 const int count = (int)cmd->data.draw.count;
760 SDL_Point *verts = (SDL_Point *)(((Uint8 *)vertices) + cmd->data.draw.first);
761 const SDL_BlendMode blend = cmd->data.draw.blend;
762 SetDrawState(surface, &drawstate);
763
764 // Apply viewport
765 if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) {
766 int i;
767 for (i = 0; i < count; i++) {
768 verts[i].x += drawstate.viewport->x;
769 verts[i].y += drawstate.viewport->y;
770 }
771 }
772
773 if (blend == SDL_BLENDMODE_NONE) {
774 SDL_DrawLines(surface, verts, count, SDL_MapSurfaceRGBA(surface, r, g, b, a));
775 } else {
776 SDL_BlendLines(surface, verts, count, blend, r, g, b, a);
777 }
778 break;
779 }
780
781 case SDL_RENDERCMD_FILL_RECTS:
782 {
783 const Uint8 r = drawstate.color.r;
784 const Uint8 g = drawstate.color.g;
785 const Uint8 b = drawstate.color.b;
786 const Uint8 a = drawstate.color.a;
787 const int count = (int)cmd->data.draw.count;
788 SDL_Rect *verts = (SDL_Rect *)(((Uint8 *)vertices) + cmd->data.draw.first);
789 const SDL_BlendMode blend = cmd->data.draw.blend;
790 SetDrawState(surface, &drawstate);
791
792 // Apply viewport
793 if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) {
794 int i;
795 for (i = 0; i < count; i++) {
796 verts[i].x += drawstate.viewport->x;
797 verts[i].y += drawstate.viewport->y;
798 }
799 }
800
801 if (blend == SDL_BLENDMODE_NONE) {
802 SDL_FillSurfaceRects(surface, verts, count, SDL_MapSurfaceRGBA(surface, r, g, b, a));
803 } else {
804 SDL_BlendFillRects(surface, verts, count, blend, r, g, b, a);
805 }
806 break;
807 }
808
809 case SDL_RENDERCMD_COPY:
810 {
811 SDL_Rect *verts = (SDL_Rect *)(((Uint8 *)vertices) + cmd->data.draw.first);
812 const SDL_Rect *srcrect = verts;
813 SDL_Rect *dstrect = verts + 1;
814 SDL_Texture *texture = cmd->data.draw.texture;
815 SDL_Surface *src = (SDL_Surface *)texture->internal;
816
817 SetDrawState(surface, &drawstate);
818
819 PrepTextureForCopy(cmd, &drawstate);
820
821 // Apply viewport
822 if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) {
823 dstrect->x += drawstate.viewport->x;
824 dstrect->y += drawstate.viewport->y;
825 }
826
827 if (srcrect->w == dstrect->w && srcrect->h == dstrect->h) {
828 SDL_BlitSurface(src, srcrect, surface, dstrect);
829 } else {
830 /* If scaling is ever done, permanently disable RLE (which doesn't support scaling)
831 * to avoid potentially frequent RLE encoding/decoding.
832 */
833 SDL_SetSurfaceRLE(surface, 0);
834
835 // Prevent to do scaling + clipping on viewport boundaries as it may lose proportion
836 if (dstrect->x < 0 || dstrect->y < 0 || dstrect->x + dstrect->w > surface->w || dstrect->y + dstrect->h > surface->h) {
837 SDL_Surface *tmp = SDL_CreateSurface(dstrect->w, dstrect->h, src->format);
838 // Scale to an intermediate surface, then blit
839 if (tmp) {
840 SDL_Rect r;
841 SDL_BlendMode blendmode;
842 Uint8 alphaMod, rMod, gMod, bMod;
843
844 SDL_GetSurfaceBlendMode(src, &blendmode);
845 SDL_GetSurfaceAlphaMod(src, &alphaMod);
846 SDL_GetSurfaceColorMod(src, &rMod, &gMod, &bMod);
847
848 r.x = 0;
849 r.y = 0;
850 r.w = dstrect->w;
851 r.h = dstrect->h;
852
853 SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
854 SDL_SetSurfaceColorMod(src, 255, 255, 255);
855 SDL_SetSurfaceAlphaMod(src, 255);
856
857 SDL_BlitSurfaceScaled(src, srcrect, tmp, &r, cmd->data.draw.texture_scale_mode);
858
859 SDL_SetSurfaceColorMod(tmp, rMod, gMod, bMod);
860 SDL_SetSurfaceAlphaMod(tmp, alphaMod);
861 SDL_SetSurfaceBlendMode(tmp, blendmode);
862
863 SDL_BlitSurface(tmp, NULL, surface, dstrect);
864 SDL_DestroySurface(tmp);
865 // No need to set back r/g/b/a/blendmode to 'src' since it's done in PrepTextureForCopy()
866 }
867 } else {
868 SDL_BlitSurfaceScaled(src, srcrect, surface, dstrect, cmd->data.draw.texture_scale_mode);
869 }
870 }
871 break;
872 }
873
874 case SDL_RENDERCMD_COPY_EX:
875 {
876 CopyExData *copydata = (CopyExData *)(((Uint8 *)vertices) + cmd->data.draw.first);
877 SetDrawState(surface, &drawstate);
878 PrepTextureForCopy(cmd, &drawstate);
879
880 // Apply viewport
881 if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) {
882 copydata->dstrect.x += drawstate.viewport->x;
883 copydata->dstrect.y += drawstate.viewport->y;
884 }
885
886 SW_RenderCopyEx(renderer, surface, cmd->data.draw.texture, &copydata->srcrect,
887 &copydata->dstrect, copydata->angle, &copydata->center, copydata->flip,
888 copydata->scale_x, copydata->scale_y, cmd->data.draw.texture_scale_mode);
889 break;
890 }
891
892 case SDL_RENDERCMD_GEOMETRY:
893 {
894 int i;
895 SDL_Rect *verts = (SDL_Rect *)(((Uint8 *)vertices) + cmd->data.draw.first);
896 const int count = (int)cmd->data.draw.count;
897 SDL_Texture *texture = cmd->data.draw.texture;
898 const SDL_BlendMode blend = cmd->data.draw.blend;
899
900 SetDrawState(surface, &drawstate);
901
902 if (texture) {
903 SDL_Surface *src = (SDL_Surface *)texture->internal;
904
905 GeometryCopyData *ptr = (GeometryCopyData *)verts;
906
907 PrepTextureForCopy(cmd, &drawstate);
908
909 // Apply viewport
910 if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) {
911 SDL_Point vp;
912 vp.x = drawstate.viewport->x;
913 vp.y = drawstate.viewport->y;
914 trianglepoint_2_fixedpoint(&vp);
915 for (i = 0; i < count; i++) {
916 ptr[i].dst.x += vp.x;
917 ptr[i].dst.y += vp.y;
918 }
919 }
920
921 for (i = 0; i < count; i += 3, ptr += 3) {
922 SDL_SW_BlitTriangle(
923 src,
924 &(ptr[0].src), &(ptr[1].src), &(ptr[2].src),
925 surface,
926 &(ptr[0].dst), &(ptr[1].dst), &(ptr[2].dst),
927 ptr[0].color, ptr[1].color, ptr[2].color,
928 cmd->data.draw.texture_address_mode);
929 }
930 } else {
931 GeometryFillData *ptr = (GeometryFillData *)verts;
932
933 // Apply viewport
934 if (drawstate.viewport && (drawstate.viewport->x || drawstate.viewport->y)) {
935 SDL_Point vp;
936 vp.x = drawstate.viewport->x;
937 vp.y = drawstate.viewport->y;
938 trianglepoint_2_fixedpoint(&vp);
939 for (i = 0; i < count; i++) {
940 ptr[i].dst.x += vp.x;
941 ptr[i].dst.y += vp.y;
942 }
943 }
944
945 for (i = 0; i < count; i += 3, ptr += 3) {
946 SDL_SW_FillTriangle(surface, &(ptr[0].dst), &(ptr[1].dst), &(ptr[2].dst), blend, ptr[0].color, ptr[1].color, ptr[2].color);
947 }
948 }
949 break;
950 }
951
952 case SDL_RENDERCMD_NO_OP:
953 break;
954 }
955
956 cmd = cmd->next;
957 }
958
959 return true;
960}
961
962static SDL_Surface *SW_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect)
963{
964 SDL_Surface *surface = SW_ActivateRenderer(renderer);
965 void *pixels;
966
967 if (!SDL_SurfaceValid(surface)) {
968 return NULL;
969 }
970
971 /* NOTE: The rect is already adjusted according to the viewport by
972 * SDL_RenderReadPixels.
973 */
974
975 if (rect->x < 0 || rect->x + rect->w > surface->w ||
976 rect->y < 0 || rect->y + rect->h > surface->h) {
977 SDL_SetError("Tried to read outside of surface bounds");
978 return NULL;
979 }
980
981 pixels = (void *)((Uint8 *)surface->pixels +
982 rect->y * surface->pitch +
983 rect->x * surface->fmt->bytes_per_pixel);
984
985 return SDL_DuplicatePixels(rect->w, rect->h, surface->format, SDL_COLORSPACE_SRGB, pixels, surface->pitch);
986}
987
988static bool SW_RenderPresent(SDL_Renderer *renderer)
989{
990 SDL_Window *window = renderer->window;
991
992 if (!window) {
993 return false;
994 }
995 return SDL_UpdateWindowSurface(window);
996}
997
998static void SW_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture)
999{
1000 SDL_Surface *surface = (SDL_Surface *)texture->internal;
1001
1002 SDL_DestroySurface(surface);
1003}
1004
1005static void SW_DestroyRenderer(SDL_Renderer *renderer)
1006{
1007 SDL_Window *window = renderer->window;
1008 SW_RenderData *data = (SW_RenderData *)renderer->internal;
1009
1010 if (window) {
1011 SDL_DestroyWindowSurface(window);
1012 }
1013 SDL_free(data);
1014}
1015
1016static void SW_SelectBestFormats(SDL_Renderer *renderer, SDL_PixelFormat format)
1017{
1018 // Prefer the format used by the framebuffer by default.
1019 SDL_AddSupportedTextureFormat(renderer, format);
1020
1021 switch (format) {
1022 case SDL_PIXELFORMAT_XRGB4444:
1023 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB4444);
1024 break;
1025 case SDL_PIXELFORMAT_XBGR4444:
1026 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR4444);
1027 break;
1028 case SDL_PIXELFORMAT_ARGB4444:
1029 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XRGB4444);
1030 break;
1031 case SDL_PIXELFORMAT_ABGR4444:
1032 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XBGR4444);
1033 break;
1034
1035 case SDL_PIXELFORMAT_XRGB1555:
1036 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB1555);
1037 break;
1038 case SDL_PIXELFORMAT_XBGR1555:
1039 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR1555);
1040 break;
1041 case SDL_PIXELFORMAT_ARGB1555:
1042 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XRGB1555);
1043 break;
1044 case SDL_PIXELFORMAT_ABGR1555:
1045 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XBGR1555);
1046 break;
1047
1048 case SDL_PIXELFORMAT_XRGB8888:
1049 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB8888);
1050 break;
1051 case SDL_PIXELFORMAT_RGBX8888:
1052 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA8888);
1053 break;
1054 case SDL_PIXELFORMAT_XBGR8888:
1055 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR8888);
1056 break;
1057 case SDL_PIXELFORMAT_BGRX8888:
1058 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRA8888);
1059 break;
1060 case SDL_PIXELFORMAT_ARGB8888:
1061 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XRGB8888);
1062 break;
1063 case SDL_PIXELFORMAT_RGBA8888:
1064 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBX8888);
1065 break;
1066 case SDL_PIXELFORMAT_ABGR8888:
1067 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XBGR8888);
1068 break;
1069 case SDL_PIXELFORMAT_BGRA8888:
1070 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRX8888);
1071 break;
1072 default:
1073 break;
1074 }
1075
1076 /* Ensure that we always have a SDL_PACKEDLAYOUT_8888 format. Having a matching component order increases the
1077 * chances of getting a fast path for blitting.
1078 */
1079 if (SDL_ISPIXELFORMAT_PACKED(format)) {
1080 if (SDL_PIXELLAYOUT(format) != SDL_PACKEDLAYOUT_8888) {
1081 switch (SDL_PIXELORDER(format)) {
1082 case SDL_PACKEDORDER_BGRX:
1083 case SDL_PACKEDORDER_BGRA:
1084 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRX8888);
1085 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRA8888);
1086 break;
1087 case SDL_PACKEDORDER_RGBX:
1088 case SDL_PACKEDORDER_RGBA:
1089 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBX8888);
1090 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA8888);
1091 break;
1092 case SDL_PACKEDORDER_XBGR:
1093 case SDL_PACKEDORDER_ABGR:
1094 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XBGR8888);
1095 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR8888);
1096 break;
1097 case SDL_PACKEDORDER_XRGB:
1098 case SDL_PACKEDORDER_ARGB:
1099 default:
1100 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XRGB8888);
1101 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB8888);
1102 break;
1103 }
1104 }
1105 } else {
1106 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XRGB8888);
1107 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB8888);
1108 }
1109}
1110
1111bool SW_CreateRendererForSurface(SDL_Renderer *renderer, SDL_Surface *surface, SDL_PropertiesID create_props)
1112{
1113 SW_RenderData *data;
1114
1115 if (!SDL_SurfaceValid(surface)) {
1116 return SDL_InvalidParamError("surface");
1117 }
1118
1119 renderer->software = true;
1120
1121 data = (SW_RenderData *)SDL_calloc(1, sizeof(*data));
1122 if (!data) {
1123 return false;
1124 }
1125 data->surface = surface;
1126 data->window = surface;
1127
1128 renderer->WindowEvent = SW_WindowEvent;
1129 renderer->GetOutputSize = SW_GetOutputSize;
1130 renderer->CreateTexture = SW_CreateTexture;
1131 renderer->UpdateTexture = SW_UpdateTexture;
1132 renderer->LockTexture = SW_LockTexture;
1133 renderer->UnlockTexture = SW_UnlockTexture;
1134 renderer->SetRenderTarget = SW_SetRenderTarget;
1135 renderer->QueueSetViewport = SW_QueueNoOp;
1136 renderer->QueueSetDrawColor = SW_QueueNoOp;
1137 renderer->QueueDrawPoints = SW_QueueDrawPoints;
1138 renderer->QueueDrawLines = SW_QueueDrawPoints; // lines and points queue vertices the same way.
1139 renderer->QueueFillRects = SW_QueueFillRects;
1140 renderer->QueueCopy = SW_QueueCopy;
1141 renderer->QueueCopyEx = SW_QueueCopyEx;
1142 renderer->QueueGeometry = SW_QueueGeometry;
1143 renderer->InvalidateCachedState = SW_InvalidateCachedState;
1144 renderer->RunCommandQueue = SW_RunCommandQueue;
1145 renderer->RenderReadPixels = SW_RenderReadPixels;
1146 renderer->RenderPresent = SW_RenderPresent;
1147 renderer->DestroyTexture = SW_DestroyTexture;
1148 renderer->DestroyRenderer = SW_DestroyRenderer;
1149 renderer->internal = data;
1150 SW_InvalidateCachedState(renderer);
1151
1152 renderer->name = SW_RenderDriver.name;
1153
1154 SW_SelectBestFormats(renderer, surface->format);
1155
1156 SDL_SetupRendererColorspace(renderer, create_props);
1157
1158 if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) {
1159 return SDL_SetError("Unsupported output colorspace");
1160 }
1161
1162 return true;
1163}
1164
1165static bool SW_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_PropertiesID create_props)
1166{
1167 // Set the vsync hint based on our flags, if it's not already set
1168 const char *hint = SDL_GetHint(SDL_HINT_RENDER_VSYNC);
1169 const bool no_hint_set = (!hint || !*hint);
1170
1171 if (no_hint_set) {
1172 if (SDL_GetBooleanProperty(create_props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 0)) {
1173 SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");
1174 } else {
1175 SDL_SetHint(SDL_HINT_RENDER_VSYNC, "0");
1176 }
1177 }
1178
1179 SDL_Surface *surface = SDL_GetWindowSurface(window);
1180
1181 // Reset the vsync hint if we set it above
1182 if (no_hint_set) {
1183 SDL_SetHint(SDL_HINT_RENDER_VSYNC, "");
1184 }
1185
1186 if (!SDL_SurfaceValid(surface)) {
1187 return false;
1188 }
1189
1190 return SW_CreateRendererForSurface(renderer, surface, create_props);
1191}
1192
1193SDL_RenderDriver SW_RenderDriver = {
1194 SW_CreateRenderer, SDL_SOFTWARE_RENDERER
1195};
1196
1197#endif // SDL_VIDEO_RENDER_SW
1198