summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/render/SDL_render.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/render/SDL_render.c
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/render/SDL_render.c')
-rw-r--r--contrib/SDL-3.2.8/src/render/SDL_render.c5676
1 files changed, 5676 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/render/SDL_render.c b/contrib/SDL-3.2.8/src/render/SDL_render.c
new file mode 100644
index 0000000..82ecb88
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/render/SDL_render.c
@@ -0,0 +1,5676 @@
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// The SDL 2D rendering system
24
25#include "SDL_sysrender.h"
26#include "SDL_render_debug_font.h"
27#include "software/SDL_render_sw_c.h"
28#include "../events/SDL_windowevents_c.h"
29#include "../video/SDL_pixels_c.h"
30#include "../video/SDL_video_c.h"
31
32#ifdef SDL_PLATFORM_ANDROID
33#include "../core/android/SDL_android.h"
34#include "../video/android/SDL_androidevents.h"
35#endif
36
37/* as a courtesy to iOS apps, we don't try to draw when in the background, as
38that will crash the app. However, these apps _should_ have used
39SDL_AddEventWatch to catch SDL_EVENT_WILL_ENTER_BACKGROUND events and stopped
40drawing themselves. Other platforms still draw, as the compositor can use it,
41and more importantly: drawing to render targets isn't lost. But I still think
42this should probably be removed at some point in the future. --ryan. */
43#if defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS) || defined(SDL_PLATFORM_ANDROID)
44#define DONT_DRAW_WHILE_HIDDEN 1
45#else
46#define DONT_DRAW_WHILE_HIDDEN 0
47#endif
48
49#define SDL_PROP_WINDOW_RENDERER_POINTER "SDL.internal.window.renderer"
50#define SDL_PROP_TEXTURE_PARENT_POINTER "SDL.internal.texture.parent"
51
52#define CHECK_RENDERER_MAGIC_BUT_NOT_DESTROYED_FLAG(renderer, result) \
53 if (!SDL_ObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER)) { \
54 SDL_InvalidParamError("renderer"); \
55 return result; \
56 }
57
58#define CHECK_RENDERER_MAGIC(renderer, result) \
59 CHECK_RENDERER_MAGIC_BUT_NOT_DESTROYED_FLAG(renderer, result); \
60 if ((renderer)->destroyed) { \
61 SDL_SetError("Renderer's window has been destroyed, can't use further"); \
62 return result; \
63 }
64
65#define CHECK_TEXTURE_MAGIC(texture, result) \
66 if (!SDL_ObjectValid(texture, SDL_OBJECT_TYPE_TEXTURE)) { \
67 SDL_InvalidParamError("texture"); \
68 return result; \
69 }
70
71// Predefined blend modes
72#define SDL_COMPOSE_BLENDMODE(srcColorFactor, dstColorFactor, colorOperation, \
73 srcAlphaFactor, dstAlphaFactor, alphaOperation) \
74 (SDL_BlendMode)(((Uint32)(colorOperation) << 0) | \
75 ((Uint32)(srcColorFactor) << 4) | \
76 ((Uint32)(dstColorFactor) << 8) | \
77 ((Uint32)(alphaOperation) << 16) | \
78 ((Uint32)(srcAlphaFactor) << 20) | \
79 ((Uint32)(dstAlphaFactor) << 24))
80
81#define SDL_BLENDMODE_NONE_FULL \
82 SDL_COMPOSE_BLENDMODE(SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ZERO, SDL_BLENDOPERATION_ADD, \
83 SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ZERO, SDL_BLENDOPERATION_ADD)
84
85#define SDL_BLENDMODE_BLEND_FULL \
86 SDL_COMPOSE_BLENDMODE(SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD, \
87 SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD)
88
89#define SDL_BLENDMODE_BLEND_PREMULTIPLIED_FULL \
90 SDL_COMPOSE_BLENDMODE(SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD, \
91 SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD)
92
93#define SDL_BLENDMODE_ADD_FULL \
94 SDL_COMPOSE_BLENDMODE(SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_ADD, \
95 SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_ADD)
96
97#define SDL_BLENDMODE_ADD_PREMULTIPLIED_FULL \
98 SDL_COMPOSE_BLENDMODE(SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_ADD, \
99 SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_ADD)
100
101#define SDL_BLENDMODE_MOD_FULL \
102 SDL_COMPOSE_BLENDMODE(SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_SRC_COLOR, SDL_BLENDOPERATION_ADD, \
103 SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_ADD)
104
105#define SDL_BLENDMODE_MUL_FULL \
106 SDL_COMPOSE_BLENDMODE(SDL_BLENDFACTOR_DST_COLOR, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD, \
107 SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_ADD)
108
109#ifndef SDL_RENDER_DISABLED
110static const SDL_RenderDriver *render_drivers[] = {
111#ifdef SDL_VIDEO_RENDER_D3D11
112 &D3D11_RenderDriver,
113#endif
114#ifdef SDL_VIDEO_RENDER_D3D12
115 &D3D12_RenderDriver,
116#endif
117#ifdef SDL_VIDEO_RENDER_D3D
118 &D3D_RenderDriver,
119#endif
120#ifdef SDL_VIDEO_RENDER_METAL
121 &METAL_RenderDriver,
122#endif
123#ifdef SDL_VIDEO_RENDER_OGL
124 &GL_RenderDriver,
125#endif
126#ifdef SDL_VIDEO_RENDER_OGL_ES2
127 &GLES2_RenderDriver,
128#endif
129#ifdef SDL_VIDEO_RENDER_PS2
130 &PS2_RenderDriver,
131#endif
132#ifdef SDL_VIDEO_RENDER_PSP
133 &PSP_RenderDriver,
134#endif
135#ifdef SDL_VIDEO_RENDER_VITA_GXM
136 &VITA_GXM_RenderDriver,
137#endif
138#ifdef SDL_VIDEO_RENDER_VULKAN
139 &VULKAN_RenderDriver,
140#endif
141#ifdef SDL_VIDEO_RENDER_GPU
142 &GPU_RenderDriver,
143#endif
144#ifdef SDL_VIDEO_RENDER_SW
145 &SW_RenderDriver,
146#endif
147 NULL
148};
149#endif // !SDL_RENDER_DISABLED
150
151static SDL_Renderer *SDL_renderers;
152
153static const int rect_index_order[] = { 0, 1, 2, 0, 2, 3 };
154
155void SDL_QuitRender(void)
156{
157 while (SDL_renderers) {
158 SDL_DestroyRenderer(SDL_renderers);
159 }
160}
161
162bool SDL_AddSupportedTextureFormat(SDL_Renderer *renderer, SDL_PixelFormat format)
163{
164 SDL_PixelFormat *texture_formats = (SDL_PixelFormat *)SDL_realloc((void *)renderer->texture_formats, (renderer->num_texture_formats + 2) * sizeof(SDL_PixelFormat));
165 if (!texture_formats) {
166 return false;
167 }
168 texture_formats[renderer->num_texture_formats++] = format;
169 texture_formats[renderer->num_texture_formats] = SDL_PIXELFORMAT_UNKNOWN;
170 renderer->texture_formats = texture_formats;
171 SDL_SetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, texture_formats);
172 return true;
173}
174
175void SDL_SetupRendererColorspace(SDL_Renderer *renderer, SDL_PropertiesID props)
176{
177 renderer->output_colorspace = (SDL_Colorspace)SDL_GetNumberProperty(props, SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB);
178}
179
180bool SDL_RenderingLinearSpace(SDL_Renderer *renderer)
181{
182 SDL_Colorspace colorspace;
183
184 if (renderer->target) {
185 colorspace = renderer->target->colorspace;
186 } else {
187 colorspace = renderer->output_colorspace;
188 }
189 if (colorspace == SDL_COLORSPACE_SRGB_LINEAR) {
190 return true;
191 }
192 return false;
193}
194
195void SDL_ConvertToLinear(SDL_FColor *color)
196{
197 color->r = SDL_sRGBtoLinear(color->r);
198 color->g = SDL_sRGBtoLinear(color->g);
199 color->b = SDL_sRGBtoLinear(color->b);
200}
201
202void SDL_ConvertFromLinear(SDL_FColor *color)
203{
204 color->r = SDL_sRGBfromLinear(color->r);
205 color->g = SDL_sRGBfromLinear(color->g);
206 color->b = SDL_sRGBfromLinear(color->b);
207}
208
209static SDL_INLINE void DebugLogRenderCommands(const SDL_RenderCommand *cmd)
210{
211#if 0
212 unsigned int i = 1;
213 SDL_Log("Render commands to flush:");
214 while (cmd) {
215 switch (cmd->command) {
216 case SDL_RENDERCMD_NO_OP:
217 SDL_Log(" %u. no-op", i++);
218 break;
219
220 case SDL_RENDERCMD_SETVIEWPORT:
221 SDL_Log(" %u. set viewport (first=%u, rect={(%d, %d), %dx%d})", i++,
222 (unsigned int) cmd->data.viewport.first,
223 cmd->data.viewport.rect.x, cmd->data.viewport.rect.y,
224 cmd->data.viewport.rect.w, cmd->data.viewport.rect.h);
225 break;
226
227 case SDL_RENDERCMD_SETCLIPRECT:
228 SDL_Log(" %u. set cliprect (enabled=%s, rect={(%d, %d), %dx%d})", i++,
229 cmd->data.cliprect.enabled ? "true" : "false",
230 cmd->data.cliprect.rect.x, cmd->data.cliprect.rect.y,
231 cmd->data.cliprect.rect.w, cmd->data.cliprect.rect.h);
232 break;
233
234 case SDL_RENDERCMD_SETDRAWCOLOR:
235 SDL_Log(" %u. set draw color (first=%u, r=%d, g=%d, b=%d, a=%d, color_scale=%g)", i++,
236 (unsigned int) cmd->data.color.first,
237 (int) cmd->data.color.color.r, (int) cmd->data.color.color.g,
238 (int) cmd->data.color.color.b, (int) cmd->data.color.color.a, cmd->data.color.color_scale);
239 break;
240
241 case SDL_RENDERCMD_CLEAR:
242 SDL_Log(" %u. clear (first=%u, r=%d, g=%d, b=%d, a=%d, color_scale=%g)", i++,
243 (unsigned int) cmd->data.color.first,
244 (int) cmd->data.color.color.r, (int) cmd->data.color.color.g,
245 (int) cmd->data.color.color.b, (int) cmd->data.color.color.a, cmd->data.color.color_scale);
246 break;
247
248 case SDL_RENDERCMD_DRAW_POINTS:
249 SDL_Log(" %u. draw points (first=%u, count=%u, r=%d, g=%d, b=%d, a=%d, blend=%d, color_scale=%g)", i++,
250 (unsigned int) cmd->data.draw.first,
251 (unsigned int) cmd->data.draw.count,
252 (int) cmd->data.draw.color.r, (int) cmd->data.draw.color.g,
253 (int) cmd->data.draw.color.b, (int) cmd->data.draw.color.a,
254 (int) cmd->data.draw.blend, cmd->data.draw.color_scale);
255 break;
256
257 case SDL_RENDERCMD_DRAW_LINES:
258 SDL_Log(" %u. draw lines (first=%u, count=%u, r=%d, g=%d, b=%d, a=%d, blend=%d, color_scale=%g)", i++,
259 (unsigned int) cmd->data.draw.first,
260 (unsigned int) cmd->data.draw.count,
261 (int) cmd->data.draw.color.r, (int) cmd->data.draw.color.g,
262 (int) cmd->data.draw.color.b, (int) cmd->data.draw.color.a,
263 (int) cmd->data.draw.blend, cmd->data.draw.color_scale);
264 break;
265
266 case SDL_RENDERCMD_FILL_RECTS:
267 SDL_Log(" %u. fill rects (first=%u, count=%u, r=%d, g=%d, b=%d, a=%d, blend=%d, color_scale=%g)", i++,
268 (unsigned int) cmd->data.draw.first,
269 (unsigned int) cmd->data.draw.count,
270 (int) cmd->data.draw.color.r, (int) cmd->data.draw.color.g,
271 (int) cmd->data.draw.color.b, (int) cmd->data.draw.color.a,
272 (int) cmd->data.draw.blend, cmd->data.draw.color_scale);
273 break;
274
275 case SDL_RENDERCMD_COPY:
276 SDL_Log(" %u. copy (first=%u, count=%u, r=%d, g=%d, b=%d, a=%d, blend=%d, color_scale=%g, tex=%p)", i++,
277 (unsigned int) cmd->data.draw.first,
278 (unsigned int) cmd->data.draw.count,
279 (int) cmd->data.draw.color.r, (int) cmd->data.draw.color.g,
280 (int) cmd->data.draw.color.b, (int) cmd->data.draw.color.a,
281 (int) cmd->data.draw.blend, cmd->data.draw.color_scale, cmd->data.draw.texture);
282 break;
283
284
285 case SDL_RENDERCMD_COPY_EX:
286 SDL_Log(" %u. copyex (first=%u, count=%u, r=%d, g=%d, b=%d, a=%d, blend=%d, color_scale=%g, tex=%p)", i++,
287 (unsigned int) cmd->data.draw.first,
288 (unsigned int) cmd->data.draw.count,
289 (int) cmd->data.draw.color.r, (int) cmd->data.draw.color.g,
290 (int) cmd->data.draw.color.b, (int) cmd->data.draw.color.a,
291 (int) cmd->data.draw.blend, cmd->data.draw.color_scale, cmd->data.draw.texture);
292 break;
293
294 case SDL_RENDERCMD_GEOMETRY:
295 SDL_Log(" %u. geometry (first=%u, count=%u, r=%d, g=%d, b=%d, a=%d, blend=%d, color_scale=%g, tex=%p)", i++,
296 (unsigned int) cmd->data.draw.first,
297 (unsigned int) cmd->data.draw.count,
298 (int) cmd->data.draw.color.r, (int) cmd->data.draw.color.g,
299 (int) cmd->data.draw.color.b, (int) cmd->data.draw.color.a,
300 (int) cmd->data.draw.blend, cmd->data.draw.color_scale, cmd->data.draw.texture);
301 break;
302
303 }
304 cmd = cmd->next;
305 }
306#endif
307}
308
309static bool FlushRenderCommands(SDL_Renderer *renderer)
310{
311 bool result;
312
313 SDL_assert((renderer->render_commands == NULL) == (renderer->render_commands_tail == NULL));
314
315 if (!renderer->render_commands) { // nothing to do!
316 SDL_assert(renderer->vertex_data_used == 0);
317 return true;
318 }
319
320 DebugLogRenderCommands(renderer->render_commands);
321
322 result = renderer->RunCommandQueue(renderer, renderer->render_commands, renderer->vertex_data, renderer->vertex_data_used);
323
324 // Move the whole render command queue to the unused pool so we can reuse them next time.
325 if (renderer->render_commands_tail) {
326 renderer->render_commands_tail->next = renderer->render_commands_pool;
327 renderer->render_commands_pool = renderer->render_commands;
328 renderer->render_commands_tail = NULL;
329 renderer->render_commands = NULL;
330 }
331 renderer->vertex_data_used = 0;
332 renderer->render_command_generation++;
333 renderer->color_queued = false;
334 renderer->viewport_queued = false;
335 renderer->cliprect_queued = false;
336 return result;
337}
338
339static bool FlushRenderCommandsIfTextureNeeded(SDL_Texture *texture)
340{
341 SDL_Renderer *renderer = texture->renderer;
342 if (texture->last_command_generation == renderer->render_command_generation) {
343 // the current command queue depends on this texture, flush the queue now before it changes
344 return FlushRenderCommands(renderer);
345 }
346 return true;
347}
348
349bool SDL_FlushRenderer(SDL_Renderer *renderer)
350{
351 if (!FlushRenderCommands(renderer)) {
352 return false;
353 }
354 renderer->InvalidateCachedState(renderer);
355 return true;
356}
357
358void *SDL_AllocateRenderVertices(SDL_Renderer *renderer, size_t numbytes, size_t alignment, size_t *offset)
359{
360 const size_t needed = renderer->vertex_data_used + numbytes + alignment;
361 const size_t current_offset = renderer->vertex_data_used;
362
363 const size_t aligner = (alignment && ((current_offset & (alignment - 1)) != 0)) ? (alignment - (current_offset & (alignment - 1))) : 0;
364 const size_t aligned = current_offset + aligner;
365
366 if (renderer->vertex_data_allocation < needed) {
367 const size_t current_allocation = renderer->vertex_data ? renderer->vertex_data_allocation : 1024;
368 size_t newsize = current_allocation * 2;
369 void *ptr;
370 while (newsize < needed) {
371 newsize *= 2;
372 }
373
374 ptr = SDL_realloc(renderer->vertex_data, newsize);
375
376 if (!ptr) {
377 return NULL;
378 }
379 renderer->vertex_data = ptr;
380 renderer->vertex_data_allocation = newsize;
381 }
382
383 if (offset) {
384 *offset = aligned;
385 }
386
387 renderer->vertex_data_used += aligner + numbytes;
388
389 return ((Uint8 *)renderer->vertex_data) + aligned;
390}
391
392static SDL_RenderCommand *AllocateRenderCommand(SDL_Renderer *renderer)
393{
394 SDL_RenderCommand *result = NULL;
395
396 result = renderer->render_commands_pool;
397 if (result) {
398 renderer->render_commands_pool = result->next;
399 result->next = NULL;
400 } else {
401 result = (SDL_RenderCommand *)SDL_calloc(1, sizeof(*result));
402 if (!result) {
403 return NULL;
404 }
405 }
406
407 SDL_assert((renderer->render_commands == NULL) == (renderer->render_commands_tail == NULL));
408 if (renderer->render_commands_tail) {
409 renderer->render_commands_tail->next = result;
410 } else {
411 renderer->render_commands = result;
412 }
413 renderer->render_commands_tail = result;
414
415 return result;
416}
417
418static void UpdatePixelViewport(SDL_Renderer *renderer, SDL_RenderViewState *view)
419{
420 view->pixel_viewport.x = (int)SDL_floorf((view->viewport.x * view->current_scale.x) + view->logical_offset.x);
421 view->pixel_viewport.y = (int)SDL_floorf((view->viewport.y * view->current_scale.y) + view->logical_offset.y);
422 if (view->viewport.w >= 0) {
423 view->pixel_viewport.w = (int)SDL_ceilf(view->viewport.w * view->current_scale.x);
424 } else {
425 view->pixel_viewport.w = view->pixel_w;
426 }
427 if (view->viewport.h >= 0) {
428 view->pixel_viewport.h = (int)SDL_ceilf(view->viewport.h * view->current_scale.y);
429 } else {
430 view->pixel_viewport.h = view->pixel_h;
431 }
432}
433
434static bool QueueCmdSetViewport(SDL_Renderer *renderer)
435{
436 bool result = true;
437
438 SDL_Rect viewport = renderer->view->pixel_viewport;
439
440 if (!renderer->viewport_queued ||
441 SDL_memcmp(&viewport, &renderer->last_queued_viewport, sizeof(viewport)) != 0) {
442 SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
443 if (cmd) {
444 cmd->command = SDL_RENDERCMD_SETVIEWPORT;
445 cmd->data.viewport.first = 0; // render backend will fill this in.
446 SDL_copyp(&cmd->data.viewport.rect, &viewport);
447 result = renderer->QueueSetViewport(renderer, cmd);
448 if (!result) {
449 cmd->command = SDL_RENDERCMD_NO_OP;
450 } else {
451 SDL_copyp(&renderer->last_queued_viewport, &viewport);
452 renderer->viewport_queued = true;
453 }
454 } else {
455 result = false;
456 }
457 }
458 return result;
459}
460
461static void UpdatePixelClipRect(SDL_Renderer *renderer, SDL_RenderViewState *view)
462{
463 const float scale_x = view->current_scale.x;
464 const float scale_y = view->current_scale.y;
465 view->pixel_clip_rect.x = (int)SDL_floorf(view->clip_rect.x * scale_x);
466 view->pixel_clip_rect.y = (int)SDL_floorf(view->clip_rect.y * scale_y);
467 view->pixel_clip_rect.w = (int)SDL_ceilf(view->clip_rect.w * scale_x);
468 view->pixel_clip_rect.h = (int)SDL_ceilf(view->clip_rect.h * scale_y);
469}
470
471static bool QueueCmdSetClipRect(SDL_Renderer *renderer)
472{
473 bool result = true;
474
475 const SDL_RenderViewState *view = renderer->view;
476 SDL_Rect clip_rect = view->pixel_clip_rect;
477 if (!renderer->cliprect_queued ||
478 view->clipping_enabled != renderer->last_queued_cliprect_enabled ||
479 SDL_memcmp(&clip_rect, &renderer->last_queued_cliprect, sizeof(clip_rect)) != 0) {
480 SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
481 if (cmd) {
482 cmd->command = SDL_RENDERCMD_SETCLIPRECT;
483 cmd->data.cliprect.enabled = view->clipping_enabled;
484 SDL_copyp(&cmd->data.cliprect.rect, &clip_rect);
485 SDL_copyp(&renderer->last_queued_cliprect, &clip_rect);
486 renderer->last_queued_cliprect_enabled = view->clipping_enabled;
487 renderer->cliprect_queued = true;
488 } else {
489 result = false;
490 }
491 }
492 return result;
493}
494
495static bool QueueCmdSetDrawColor(SDL_Renderer *renderer, SDL_FColor *color)
496{
497 bool result = true;
498
499 if (!renderer->color_queued ||
500 color->r != renderer->last_queued_color.r ||
501 color->g != renderer->last_queued_color.g ||
502 color->b != renderer->last_queued_color.b ||
503 color->a != renderer->last_queued_color.a) {
504 SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
505 result = false;
506
507 if (cmd) {
508 cmd->command = SDL_RENDERCMD_SETDRAWCOLOR;
509 cmd->data.color.first = 0; // render backend will fill this in.
510 cmd->data.color.color_scale = renderer->color_scale;
511 cmd->data.color.color = *color;
512 result = renderer->QueueSetDrawColor(renderer, cmd);
513 if (!result) {
514 cmd->command = SDL_RENDERCMD_NO_OP;
515 } else {
516 renderer->last_queued_color = *color;
517 renderer->color_queued = true;
518 }
519 }
520 }
521 return result;
522}
523
524static bool QueueCmdClear(SDL_Renderer *renderer)
525{
526 SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
527 if (!cmd) {
528 return false;
529 }
530
531 cmd->command = SDL_RENDERCMD_CLEAR;
532 cmd->data.color.first = 0;
533 cmd->data.color.color_scale = renderer->color_scale;
534 cmd->data.color.color = renderer->color;
535 return true;
536}
537
538static SDL_RenderCommand *PrepQueueCmdDraw(SDL_Renderer *renderer, const SDL_RenderCommandType cmdtype, SDL_Texture *texture)
539{
540 SDL_RenderCommand *cmd = NULL;
541 bool result = true;
542 SDL_FColor *color;
543 SDL_BlendMode blendMode;
544
545 if (texture) {
546 color = &texture->color;
547 blendMode = texture->blendMode;
548 } else {
549 color = &renderer->color;
550 blendMode = renderer->blendMode;
551 }
552
553 if (cmdtype != SDL_RENDERCMD_GEOMETRY) {
554 result = QueueCmdSetDrawColor(renderer, color);
555 }
556
557 /* Set the viewport and clip rect directly before draws, so the backends
558 * don't have to worry about that state not being valid at draw time. */
559 if (result && !renderer->viewport_queued) {
560 result = QueueCmdSetViewport(renderer);
561 }
562 if (result && !renderer->cliprect_queued) {
563 result = QueueCmdSetClipRect(renderer);
564 }
565
566 if (result) {
567 cmd = AllocateRenderCommand(renderer);
568 if (cmd) {
569 cmd->command = cmdtype;
570 cmd->data.draw.first = 0; // render backend will fill this in.
571 cmd->data.draw.count = 0; // render backend will fill this in.
572 cmd->data.draw.color_scale = renderer->color_scale;
573 cmd->data.draw.color = *color;
574 cmd->data.draw.blend = blendMode;
575 cmd->data.draw.texture = texture;
576 cmd->data.draw.texture_address_mode = SDL_TEXTURE_ADDRESS_CLAMP;
577 }
578 }
579 return cmd;
580}
581
582static bool QueueCmdDrawPoints(SDL_Renderer *renderer, const SDL_FPoint *points, const int count)
583{
584 SDL_RenderCommand *cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_DRAW_POINTS, NULL);
585 bool result = false;
586 if (cmd) {
587 result = renderer->QueueDrawPoints(renderer, cmd, points, count);
588 if (!result) {
589 cmd->command = SDL_RENDERCMD_NO_OP;
590 }
591 }
592 return result;
593}
594
595static bool QueueCmdDrawLines(SDL_Renderer *renderer, const SDL_FPoint *points, const int count)
596{
597 SDL_RenderCommand *cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_DRAW_LINES, NULL);
598 bool result = false;
599 if (cmd) {
600 result = renderer->QueueDrawLines(renderer, cmd, points, count);
601 if (!result) {
602 cmd->command = SDL_RENDERCMD_NO_OP;
603 }
604 }
605 return result;
606}
607
608static bool QueueCmdFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, const int count)
609{
610 SDL_RenderCommand *cmd;
611 bool result = false;
612 const int use_rendergeometry = (!renderer->QueueFillRects);
613
614 cmd = PrepQueueCmdDraw(renderer, (use_rendergeometry ? SDL_RENDERCMD_GEOMETRY : SDL_RENDERCMD_FILL_RECTS), NULL);
615
616 if (cmd) {
617 if (use_rendergeometry) {
618 bool isstack1;
619 bool isstack2;
620 float *xy = SDL_small_alloc(float, 4 * 2 * count, &isstack1);
621 int *indices = SDL_small_alloc(int, 6 * count, &isstack2);
622
623 if (xy && indices) {
624 int i;
625 float *ptr_xy = xy;
626 int *ptr_indices = indices;
627 const int xy_stride = 2 * sizeof(float);
628 const int num_vertices = 4 * count;
629 const int num_indices = 6 * count;
630 const int size_indices = 4;
631 int cur_index = 0;
632
633 for (i = 0; i < count; ++i) {
634 float minx, miny, maxx, maxy;
635
636 minx = rects[i].x;
637 miny = rects[i].y;
638 maxx = rects[i].x + rects[i].w;
639 maxy = rects[i].y + rects[i].h;
640
641 *ptr_xy++ = minx;
642 *ptr_xy++ = miny;
643 *ptr_xy++ = maxx;
644 *ptr_xy++ = miny;
645 *ptr_xy++ = maxx;
646 *ptr_xy++ = maxy;
647 *ptr_xy++ = minx;
648 *ptr_xy++ = maxy;
649
650 *ptr_indices++ = cur_index + rect_index_order[0];
651 *ptr_indices++ = cur_index + rect_index_order[1];
652 *ptr_indices++ = cur_index + rect_index_order[2];
653 *ptr_indices++ = cur_index + rect_index_order[3];
654 *ptr_indices++ = cur_index + rect_index_order[4];
655 *ptr_indices++ = cur_index + rect_index_order[5];
656 cur_index += 4;
657 }
658
659 result = renderer->QueueGeometry(renderer, cmd, NULL,
660 xy, xy_stride, &renderer->color, 0 /* color_stride */, NULL, 0,
661 num_vertices, indices, num_indices, size_indices,
662 1.0f, 1.0f);
663
664 if (!result) {
665 cmd->command = SDL_RENDERCMD_NO_OP;
666 }
667 }
668 SDL_small_free(xy, isstack1);
669 SDL_small_free(indices, isstack2);
670
671 } else {
672 result = renderer->QueueFillRects(renderer, cmd, rects, count);
673 if (!result) {
674 cmd->command = SDL_RENDERCMD_NO_OP;
675 }
676 }
677 }
678 return result;
679}
680
681static bool QueueCmdCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect)
682{
683 SDL_RenderCommand *cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_COPY, texture);
684 bool result = false;
685 if (cmd) {
686 result = renderer->QueueCopy(renderer, cmd, texture, srcrect, dstrect);
687 if (!result) {
688 cmd->command = SDL_RENDERCMD_NO_OP;
689 }
690 }
691 return result;
692}
693
694static bool QueueCmdCopyEx(SDL_Renderer *renderer, SDL_Texture *texture,
695 const SDL_FRect *srcquad, const SDL_FRect *dstrect,
696 const double angle, const SDL_FPoint *center, const SDL_FlipMode flip, float scale_x, float scale_y)
697{
698 SDL_RenderCommand *cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_COPY_EX, texture);
699 bool result = false;
700 if (cmd) {
701 result = renderer->QueueCopyEx(renderer, cmd, texture, srcquad, dstrect, angle, center, flip, scale_x, scale_y);
702 if (!result) {
703 cmd->command = SDL_RENDERCMD_NO_OP;
704 }
705 }
706 return result;
707}
708
709static bool QueueCmdGeometry(SDL_Renderer *renderer, SDL_Texture *texture,
710 const float *xy, int xy_stride,
711 const SDL_FColor *color, int color_stride,
712 const float *uv, int uv_stride,
713 int num_vertices,
714 const void *indices, int num_indices, int size_indices,
715 float scale_x, float scale_y, SDL_TextureAddressMode texture_address_mode)
716{
717 SDL_RenderCommand *cmd;
718 bool result = false;
719 cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_GEOMETRY, texture);
720 if (cmd) {
721 cmd->data.draw.texture_address_mode = texture_address_mode;
722 result = renderer->QueueGeometry(renderer, cmd, texture,
723 xy, xy_stride,
724 color, color_stride, uv, uv_stride,
725 num_vertices, indices, num_indices, size_indices,
726 scale_x, scale_y);
727 if (!result) {
728 cmd->command = SDL_RENDERCMD_NO_OP;
729 }
730 }
731 return result;
732}
733
734static void UpdateMainViewDimensions(SDL_Renderer *renderer)
735{
736 int window_w = 0, window_h = 0;
737
738 if (renderer->window) {
739 SDL_GetWindowSize(renderer->window, &window_w, &window_h);
740 }
741
742 SDL_GetRenderOutputSize(renderer, &renderer->main_view.pixel_w, &renderer->main_view.pixel_h);
743
744 if (window_w > 0 && window_h > 0) {
745 renderer->dpi_scale.x = (float)renderer->main_view.pixel_w / window_w;
746 renderer->dpi_scale.y = (float)renderer->main_view.pixel_h / window_h;
747 } else {
748 renderer->dpi_scale.x = 1.0f;
749 renderer->dpi_scale.y = 1.0f;
750 }
751 UpdatePixelViewport(renderer, &renderer->main_view);
752}
753
754static void UpdateColorScale(SDL_Renderer *renderer)
755{
756 float SDR_white_point;
757 if (renderer->target) {
758 SDR_white_point = renderer->target->SDR_white_point;
759 } else {
760 SDR_white_point = renderer->SDR_white_point;
761 }
762 renderer->color_scale = renderer->desired_color_scale * SDR_white_point;
763}
764
765static void UpdateHDRProperties(SDL_Renderer *renderer)
766{
767 SDL_PropertiesID window_props;
768 SDL_PropertiesID renderer_props;
769
770 window_props = SDL_GetWindowProperties(renderer->window);
771 if (!window_props) {
772 return;
773 }
774
775 renderer_props = SDL_GetRendererProperties(renderer);
776 if (!renderer_props) {
777 return;
778 }
779
780 if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) {
781 renderer->SDR_white_point = SDL_GetFloatProperty(window_props, SDL_PROP_WINDOW_SDR_WHITE_LEVEL_FLOAT, 1.0f);
782 renderer->HDR_headroom = SDL_GetFloatProperty(window_props, SDL_PROP_WINDOW_HDR_HEADROOM_FLOAT, 1.0f);
783 } else {
784 renderer->SDR_white_point = 1.0f;
785 renderer->HDR_headroom = 1.0f;
786 }
787
788 if (renderer->HDR_headroom > 1.0f) {
789 SDL_SetBooleanProperty(renderer_props, SDL_PROP_RENDERER_HDR_ENABLED_BOOLEAN, true);
790 } else {
791 SDL_SetBooleanProperty(renderer_props, SDL_PROP_RENDERER_HDR_ENABLED_BOOLEAN, false);
792 }
793 SDL_SetFloatProperty(renderer_props, SDL_PROP_RENDERER_SDR_WHITE_POINT_FLOAT, renderer->SDR_white_point);
794 SDL_SetFloatProperty(renderer_props, SDL_PROP_RENDERER_HDR_HEADROOM_FLOAT, renderer->HDR_headroom);
795
796 UpdateColorScale(renderer);
797}
798
799static void UpdateLogicalPresentation(SDL_Renderer *renderer);
800
801
802int SDL_GetNumRenderDrivers(void)
803{
804#ifndef SDL_RENDER_DISABLED
805 return SDL_arraysize(render_drivers) - 1;
806#else
807 return 0;
808#endif
809}
810
811const char *SDL_GetRenderDriver(int index)
812{
813#ifndef SDL_RENDER_DISABLED
814 if (index < 0 || index >= SDL_GetNumRenderDrivers()) {
815 SDL_InvalidParamError("index");
816 return NULL;
817 }
818 return render_drivers[index]->name;
819#else
820 SDL_SetError("SDL not built with rendering support");
821 return NULL;
822#endif
823}
824
825static bool SDL_RendererEventWatch(void *userdata, SDL_Event *event)
826{
827 SDL_Renderer *renderer = (SDL_Renderer *)userdata;
828 SDL_Window *window = renderer->window;
829
830 if (renderer->WindowEvent) {
831 renderer->WindowEvent(renderer, &event->window);
832 }
833
834 if (event->type == SDL_EVENT_WINDOW_RESIZED ||
835 event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED ||
836 event->type == SDL_EVENT_WINDOW_METAL_VIEW_RESIZED) {
837 SDL_RenderViewState *view = renderer->view;
838 renderer->view = &renderer->main_view; // only update the main_view (the window framebuffer) for window changes.
839 UpdateLogicalPresentation(renderer);
840 renderer->view = view; // put us back on whatever the current render target's actual view is.
841 } else if (event->type == SDL_EVENT_WINDOW_HIDDEN) {
842 renderer->hidden = true;
843 } else if (event->type == SDL_EVENT_WINDOW_SHOWN) {
844 if (!(SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED)) {
845 renderer->hidden = false;
846 }
847 } else if (event->type == SDL_EVENT_WINDOW_MINIMIZED) {
848 renderer->hidden = true;
849 } else if (event->type == SDL_EVENT_WINDOW_RESTORED ||
850 event->type == SDL_EVENT_WINDOW_MAXIMIZED) {
851 if (!(SDL_GetWindowFlags(window) & SDL_WINDOW_HIDDEN)) {
852 renderer->hidden = false;
853 }
854 } else if (event->type == SDL_EVENT_WINDOW_DISPLAY_CHANGED ||
855 event->type == SDL_EVENT_WINDOW_HDR_STATE_CHANGED) {
856 UpdateHDRProperties(renderer);
857 }
858 return true;
859}
860
861bool SDL_CreateWindowAndRenderer(const char *title, int width, int height, SDL_WindowFlags window_flags, SDL_Window **window, SDL_Renderer **renderer)
862{
863 bool hidden = (window_flags & SDL_WINDOW_HIDDEN) != 0;
864
865 if (!window) {
866 return SDL_InvalidParamError("window");
867 }
868
869 if (!renderer) {
870 return SDL_InvalidParamError("renderer");
871 }
872
873 // Hide the window so if the renderer recreates it, we don't get a visual flash on screen
874 window_flags |= SDL_WINDOW_HIDDEN;
875 *window = SDL_CreateWindow(title, width, height, window_flags);
876 if (!*window) {
877 *renderer = NULL;
878 return false;
879 }
880
881 *renderer = SDL_CreateRenderer(*window, NULL);
882 if (!*renderer) {
883 SDL_DestroyWindow(*window);
884 *window = NULL;
885 return false;
886 }
887
888 if (!hidden) {
889 SDL_ShowWindow(*window);
890 }
891
892 return true;
893}
894
895#ifndef SDL_RENDER_DISABLED
896static SDL_INLINE void VerifyDrawQueueFunctions(const SDL_Renderer *renderer)
897{
898 /* all of these functions are required to be implemented, even as no-ops, so we don't
899 have to check that they aren't NULL over and over. */
900 SDL_assert(renderer->QueueSetViewport != NULL);
901 SDL_assert(renderer->QueueSetDrawColor != NULL);
902 SDL_assert(renderer->QueueDrawPoints != NULL);
903 SDL_assert(renderer->QueueDrawLines != NULL || renderer->QueueGeometry != NULL);
904 SDL_assert(renderer->QueueFillRects != NULL || renderer->QueueGeometry != NULL);
905 SDL_assert(renderer->QueueCopy != NULL || renderer->QueueGeometry != NULL);
906 SDL_assert(renderer->RunCommandQueue != NULL);
907}
908
909static SDL_RenderLineMethod SDL_GetRenderLineMethod(void)
910{
911 const char *hint = SDL_GetHint(SDL_HINT_RENDER_LINE_METHOD);
912
913 int method = 0;
914 if (hint) {
915 method = SDL_atoi(hint);
916 }
917 switch (method) {
918 case 1:
919 return SDL_RENDERLINEMETHOD_POINTS;
920 case 2:
921 return SDL_RENDERLINEMETHOD_LINES;
922 case 3:
923 return SDL_RENDERLINEMETHOD_GEOMETRY;
924 default:
925 return SDL_RENDERLINEMETHOD_POINTS;
926 }
927}
928
929static void SDL_CalculateSimulatedVSyncInterval(SDL_Renderer *renderer, SDL_Window *window)
930{
931 SDL_DisplayID displayID = SDL_GetDisplayForWindow(window);
932 const SDL_DisplayMode *mode;
933 int refresh_num, refresh_den;
934
935 if (displayID == 0) {
936 displayID = SDL_GetPrimaryDisplay();
937 }
938 mode = SDL_GetDesktopDisplayMode(displayID);
939 if (mode && mode->refresh_rate_numerator > 0 && mode->refresh_rate_denominator > 0) {
940 refresh_num = mode->refresh_rate_numerator;
941 refresh_den = mode->refresh_rate_denominator;
942 } else {
943 // Pick a good default refresh rate
944 refresh_num = 60;
945 refresh_den = 1;
946 }
947 // Flip numerator and denominator to change from framerate to interval
948 renderer->simulate_vsync_interval_ns = (SDL_NS_PER_SECOND * refresh_den) / refresh_num;
949}
950
951#endif // !SDL_RENDER_DISABLED
952
953
954SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
955{
956#ifndef SDL_RENDER_DISABLED
957 SDL_Window *window = (SDL_Window *)SDL_GetPointerProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, NULL);
958 SDL_Surface *surface = (SDL_Surface *)SDL_GetPointerProperty(props, SDL_PROP_RENDERER_CREATE_SURFACE_POINTER, NULL);
959 const char *driver_name = SDL_GetStringProperty(props, SDL_PROP_RENDERER_CREATE_NAME_STRING, NULL);
960 const char *hint;
961 SDL_PropertiesID new_props;
962
963#ifdef SDL_PLATFORM_ANDROID
964 if (!Android_WaitActiveAndLockActivity()) {
965 return NULL;
966 }
967#endif
968
969 SDL_Renderer *renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(*renderer));
970 if (!renderer) {
971 goto error;
972 }
973
974 SDL_SetObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER, true);
975
976 if ((!window && !surface) || (window && surface)) {
977 SDL_InvalidParamError("window");
978 goto error;
979 }
980
981 if (window && SDL_WindowHasSurface(window)) {
982 SDL_SetError("Surface already associated with window");
983 goto error;
984 }
985
986 if (window && SDL_GetRenderer(window)) {
987 SDL_SetError("Renderer already associated with window");
988 goto error;
989 }
990
991 hint = SDL_GetHint(SDL_HINT_RENDER_VSYNC);
992 if (hint && *hint) {
993 SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, SDL_GetHintBoolean(SDL_HINT_RENDER_VSYNC, true));
994 }
995
996 if (surface) {
997#ifdef SDL_VIDEO_RENDER_SW
998 const bool rc = SW_CreateRendererForSurface(renderer, surface, props);
999#else
1000 const bool rc = SDL_SetError("SDL not built with software renderer");
1001#endif
1002 if (!rc) {
1003 goto error;
1004 }
1005 } else {
1006 bool rc = false;
1007 if (!driver_name) {
1008 driver_name = SDL_GetHint(SDL_HINT_RENDER_DRIVER);
1009 }
1010
1011 if (driver_name && *driver_name != 0) {
1012 const char *driver_attempt = driver_name;
1013 while (driver_attempt && *driver_attempt != 0 && !rc) {
1014 const char *driver_attempt_end = SDL_strchr(driver_attempt, ',');
1015 const size_t driver_attempt_len = (driver_attempt_end) ? (driver_attempt_end - driver_attempt) : SDL_strlen(driver_attempt);
1016
1017 for (int i = 0; render_drivers[i]; i++) {
1018 const SDL_RenderDriver *driver = render_drivers[i];
1019 if ((driver_attempt_len == SDL_strlen(driver->name)) && (SDL_strncasecmp(driver->name, driver_attempt, driver_attempt_len) == 0)) {
1020 rc = driver->CreateRenderer(renderer, window, props);
1021 if (rc) {
1022 break;
1023 }
1024 }
1025 }
1026
1027 driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL;
1028 }
1029 } else {
1030 for (int i = 0; render_drivers[i]; i++) {
1031 const SDL_RenderDriver *driver = render_drivers[i];
1032 rc = driver->CreateRenderer(renderer, window, props);
1033 if (rc) {
1034 break;
1035 }
1036 SDL_DestroyRendererWithoutFreeing(renderer);
1037 SDL_zerop(renderer); // make sure we don't leave function pointers from a previous CreateRenderer() in this struct.
1038 }
1039 }
1040
1041 if (!rc) {
1042 if (driver_name) {
1043 SDL_SetError("%s not available", driver_name);
1044 } else {
1045 SDL_SetError("Couldn't find matching render driver");
1046 }
1047 goto error;
1048 }
1049 }
1050
1051 VerifyDrawQueueFunctions(renderer);
1052
1053 renderer->window = window;
1054 renderer->target_mutex = SDL_CreateMutex();
1055 if (surface) {
1056 renderer->main_view.pixel_w = surface->w;
1057 renderer->main_view.pixel_h = surface->h;
1058 }
1059 renderer->main_view.viewport.w = -1;
1060 renderer->main_view.viewport.h = -1;
1061 renderer->main_view.scale.x = 1.0f;
1062 renderer->main_view.scale.y = 1.0f;
1063 renderer->main_view.logical_scale.x = 1.0f;
1064 renderer->main_view.logical_scale.y = 1.0f;
1065 renderer->main_view.current_scale.x = 1.0f;
1066 renderer->main_view.current_scale.y = 1.0f;
1067 renderer->view = &renderer->main_view;
1068 renderer->dpi_scale.x = 1.0f;
1069 renderer->dpi_scale.y = 1.0f;
1070 UpdatePixelViewport(renderer, &renderer->main_view);
1071 UpdatePixelClipRect(renderer, &renderer->main_view);
1072 UpdateMainViewDimensions(renderer);
1073
1074 // new textures start at zero, so we start at 1 so first render doesn't flush by accident.
1075 renderer->render_command_generation = 1;
1076
1077 if (renderer->software) {
1078 // Software renderer always uses line method, for speed
1079 renderer->line_method = SDL_RENDERLINEMETHOD_LINES;
1080 } else {
1081 renderer->line_method = SDL_GetRenderLineMethod();
1082 }
1083
1084 renderer->SDR_white_point = 1.0f;
1085 renderer->HDR_headroom = 1.0f;
1086 renderer->desired_color_scale = 1.0f;
1087 renderer->color_scale = 1.0f;
1088
1089 if (window) {
1090 if (SDL_GetWindowFlags(window) & SDL_WINDOW_TRANSPARENT) {
1091 renderer->transparent_window = true;
1092 }
1093
1094 if (SDL_GetWindowFlags(window) & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) {
1095 renderer->hidden = true;
1096 }
1097 }
1098
1099 new_props = SDL_GetRendererProperties(renderer);
1100 SDL_SetStringProperty(new_props, SDL_PROP_RENDERER_NAME_STRING, renderer->name);
1101 if (window) {
1102 SDL_SetPointerProperty(new_props, SDL_PROP_RENDERER_WINDOW_POINTER, window);
1103 }
1104 if (surface) {
1105 SDL_SetPointerProperty(new_props, SDL_PROP_RENDERER_SURFACE_POINTER, surface);
1106 }
1107 SDL_SetNumberProperty(new_props, SDL_PROP_RENDERER_OUTPUT_COLORSPACE_NUMBER, renderer->output_colorspace);
1108 UpdateHDRProperties(renderer);
1109
1110 if (window) {
1111 SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_RENDERER_POINTER, renderer);
1112 SDL_AddWindowRenderer(window, renderer);
1113 }
1114
1115 SDL_SetRenderViewport(renderer, NULL);
1116
1117 if (window) {
1118 SDL_AddWindowEventWatch(SDL_WINDOW_EVENT_WATCH_NORMAL, SDL_RendererEventWatch, renderer);
1119 }
1120
1121 int vsync = (int)SDL_GetNumberProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 0);
1122 if (!SDL_SetRenderVSync(renderer, vsync)) {
1123 if (vsync == 0) {
1124 // Some renderers require vsync enabled
1125 SDL_SetRenderVSync(renderer, 1);
1126 }
1127 }
1128 SDL_CalculateSimulatedVSyncInterval(renderer, window);
1129
1130 SDL_LogInfo(SDL_LOG_CATEGORY_RENDER,
1131 "Created renderer: %s", renderer->name);
1132
1133 renderer->next = SDL_renderers;
1134 SDL_renderers = renderer;
1135
1136#ifdef SDL_PLATFORM_ANDROID
1137 Android_UnlockActivityMutex();
1138#endif
1139
1140 SDL_ClearError();
1141
1142 return renderer;
1143
1144error:
1145#ifdef SDL_PLATFORM_ANDROID
1146 Android_UnlockActivityMutex();
1147#endif
1148
1149 if (renderer) {
1150 SDL_DestroyRenderer(renderer);
1151 }
1152 return NULL;
1153
1154#else
1155 SDL_SetError("SDL not built with rendering support");
1156 return NULL;
1157#endif
1158}
1159
1160SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, const char *name)
1161{
1162 SDL_Renderer *renderer;
1163 SDL_PropertiesID props = SDL_CreateProperties();
1164 SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, window);
1165 SDL_SetStringProperty(props, SDL_PROP_RENDERER_CREATE_NAME_STRING, name);
1166 renderer = SDL_CreateRendererWithProperties(props);
1167 SDL_DestroyProperties(props);
1168 return renderer;
1169}
1170
1171SDL_Renderer *SDL_CreateSoftwareRenderer(SDL_Surface *surface)
1172{
1173#ifdef SDL_VIDEO_RENDER_SW
1174 SDL_Renderer *renderer;
1175
1176 if (!surface) {
1177 SDL_InvalidParamError("surface");
1178 return NULL;
1179 }
1180
1181 SDL_PropertiesID props = SDL_CreateProperties();
1182 SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_SURFACE_POINTER, surface);
1183 renderer = SDL_CreateRendererWithProperties(props);
1184 SDL_DestroyProperties(props);
1185 return renderer;
1186#else
1187 SDL_SetError("SDL not built with rendering support");
1188 return NULL;
1189#endif // !SDL_RENDER_DISABLED
1190}
1191
1192SDL_Renderer *SDL_GetRenderer(SDL_Window *window)
1193{
1194 return (SDL_Renderer *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_RENDERER_POINTER, NULL);
1195}
1196
1197SDL_Window *SDL_GetRenderWindow(SDL_Renderer *renderer)
1198{
1199 CHECK_RENDERER_MAGIC(renderer, NULL);
1200 return renderer->window;
1201}
1202
1203const char *SDL_GetRendererName(SDL_Renderer *renderer)
1204{
1205 CHECK_RENDERER_MAGIC(renderer, NULL);
1206
1207 return SDL_GetPersistentString(renderer->name);
1208}
1209
1210SDL_PropertiesID SDL_GetRendererProperties(SDL_Renderer *renderer)
1211{
1212 CHECK_RENDERER_MAGIC(renderer, 0);
1213
1214 if (renderer->props == 0) {
1215 renderer->props = SDL_CreateProperties();
1216 }
1217 return renderer->props;
1218}
1219
1220bool SDL_GetRenderOutputSize(SDL_Renderer *renderer, int *w, int *h)
1221{
1222 if (w) {
1223 *w = 0;
1224 }
1225 if (h) {
1226 *h = 0;
1227 }
1228
1229 CHECK_RENDERER_MAGIC(renderer, false);
1230
1231 if (renderer->GetOutputSize) {
1232 return renderer->GetOutputSize(renderer, w, h);
1233 } else if (renderer->window) {
1234 return SDL_GetWindowSizeInPixels(renderer->window, w, h);
1235 } else {
1236 SDL_assert(!"This should never happen");
1237 return SDL_SetError("Renderer doesn't support querying output size");
1238 }
1239}
1240
1241bool SDL_GetCurrentRenderOutputSize(SDL_Renderer *renderer, int *w, int *h)
1242{
1243 if (w) {
1244 *w = 0;
1245 }
1246 if (h) {
1247 *h = 0;
1248 }
1249
1250 CHECK_RENDERER_MAGIC(renderer, false);
1251
1252 const SDL_RenderViewState *view = renderer->view;
1253 if (w) {
1254 *w = view->pixel_w;
1255 }
1256 if (h) {
1257 *h = view->pixel_h;
1258 }
1259 return true;
1260}
1261
1262static bool IsSupportedBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode)
1263{
1264 switch (blendMode) {
1265 // These are required to be supported by all renderers
1266 case SDL_BLENDMODE_NONE:
1267 case SDL_BLENDMODE_BLEND:
1268 case SDL_BLENDMODE_BLEND_PREMULTIPLIED:
1269 case SDL_BLENDMODE_ADD:
1270 case SDL_BLENDMODE_ADD_PREMULTIPLIED:
1271 case SDL_BLENDMODE_MOD:
1272 case SDL_BLENDMODE_MUL:
1273 return true;
1274
1275 default:
1276 return renderer->SupportsBlendMode && renderer->SupportsBlendMode(renderer, blendMode);
1277 }
1278}
1279
1280static bool IsSupportedFormat(SDL_Renderer *renderer, SDL_PixelFormat format)
1281{
1282 int i;
1283
1284 for (i = 0; i < renderer->num_texture_formats; ++i) {
1285 if (renderer->texture_formats[i] == format) {
1286 return true;
1287 }
1288 }
1289 return false;
1290}
1291
1292static SDL_PixelFormat GetClosestSupportedFormat(SDL_Renderer *renderer, SDL_PixelFormat format)
1293{
1294 int i;
1295
1296 if (format == SDL_PIXELFORMAT_MJPG) {
1297 // We'll decode to SDL_PIXELFORMAT_NV12 or SDL_PIXELFORMAT_RGBA32
1298 for (i = 0; i < renderer->num_texture_formats; ++i) {
1299 if (renderer->texture_formats[i] == SDL_PIXELFORMAT_NV12) {
1300 return renderer->texture_formats[i];
1301 }
1302 }
1303 for (i = 0; i < renderer->num_texture_formats; ++i) {
1304 if (renderer->texture_formats[i] == SDL_PIXELFORMAT_RGBA32) {
1305 return renderer->texture_formats[i];
1306 }
1307 }
1308 } else if (SDL_ISPIXELFORMAT_FOURCC(format)) {
1309 // Look for an exact match
1310 for (i = 0; i < renderer->num_texture_formats; ++i) {
1311 if (renderer->texture_formats[i] == format) {
1312 return renderer->texture_formats[i];
1313 }
1314 }
1315 } else if (SDL_ISPIXELFORMAT_10BIT(format) || SDL_ISPIXELFORMAT_FLOAT(format)) {
1316 if (SDL_ISPIXELFORMAT_10BIT(format)) {
1317 for (i = 0; i < renderer->num_texture_formats; ++i) {
1318 if (SDL_ISPIXELFORMAT_10BIT(renderer->texture_formats[i])) {
1319 return renderer->texture_formats[i];
1320 }
1321 }
1322 }
1323 for (i = 0; i < renderer->num_texture_formats; ++i) {
1324 if (SDL_ISPIXELFORMAT_FLOAT(renderer->texture_formats[i])) {
1325 return renderer->texture_formats[i];
1326 }
1327 }
1328 } else {
1329 bool hasAlpha = SDL_ISPIXELFORMAT_ALPHA(format);
1330
1331 // We just want to match the first format that has the same channels
1332 for (i = 0; i < renderer->num_texture_formats; ++i) {
1333 if (!SDL_ISPIXELFORMAT_FOURCC(renderer->texture_formats[i]) &&
1334 SDL_ISPIXELFORMAT_ALPHA(renderer->texture_formats[i]) == hasAlpha) {
1335 return renderer->texture_formats[i];
1336 }
1337 }
1338 }
1339 return renderer->texture_formats[0];
1340}
1341
1342SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_PropertiesID props)
1343{
1344 SDL_Texture *texture;
1345 SDL_PixelFormat format = (SDL_PixelFormat)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, SDL_PIXELFORMAT_UNKNOWN);
1346 SDL_TextureAccess access = (SDL_TextureAccess)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STATIC);
1347 int w = (int)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, 0);
1348 int h = (int)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, 0);
1349 SDL_Colorspace default_colorspace;
1350 bool texture_is_fourcc_and_target;
1351
1352 CHECK_RENDERER_MAGIC(renderer, NULL);
1353
1354 if (!format) {
1355 format = renderer->texture_formats[0];
1356 }
1357 if (SDL_BYTESPERPIXEL(format) == 0) {
1358 SDL_SetError("Invalid texture format");
1359 return NULL;
1360 }
1361 if (SDL_ISPIXELFORMAT_INDEXED(format)) {
1362 if (!IsSupportedFormat(renderer, format)) {
1363 SDL_SetError("Palettized textures are not supported");
1364 return NULL;
1365 }
1366 }
1367 if (w <= 0 || h <= 0) {
1368 SDL_SetError("Texture dimensions can't be 0");
1369 return NULL;
1370 }
1371 int max_texture_size = (int)SDL_GetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 0);
1372 if (max_texture_size && (w > max_texture_size || h > max_texture_size)) {
1373 SDL_SetError("Texture dimensions are limited to %dx%d", max_texture_size, max_texture_size);
1374 return NULL;
1375 }
1376
1377 default_colorspace = SDL_GetDefaultColorspaceForFormat(format);
1378
1379 texture = (SDL_Texture *)SDL_calloc(1, sizeof(*texture));
1380 if (!texture) {
1381 return NULL;
1382 }
1383 texture->refcount = 1;
1384 SDL_SetObjectValid(texture, SDL_OBJECT_TYPE_TEXTURE, true);
1385 texture->colorspace = (SDL_Colorspace)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, default_colorspace);
1386 texture->format = format;
1387 texture->access = access;
1388 texture->w = w;
1389 texture->h = h;
1390 texture->color.r = 1.0f;
1391 texture->color.g = 1.0f;
1392 texture->color.b = 1.0f;
1393 texture->color.a = 1.0f;
1394 texture->blendMode = SDL_ISPIXELFORMAT_ALPHA(format) ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE;
1395 texture->scaleMode = SDL_SCALEMODE_LINEAR;
1396 texture->view.pixel_w = w;
1397 texture->view.pixel_h = h;
1398 texture->view.viewport.w = -1;
1399 texture->view.viewport.h = -1;
1400 texture->view.scale.x = 1.0f;
1401 texture->view.scale.y = 1.0f;
1402 texture->view.logical_scale.x = 1.0f;
1403 texture->view.logical_scale.y = 1.0f;
1404 texture->view.current_scale.x = 1.0f;
1405 texture->view.current_scale.y = 1.0f;
1406 texture->renderer = renderer;
1407 texture->next = renderer->textures;
1408 if (renderer->textures) {
1409 renderer->textures->prev = texture;
1410 }
1411 renderer->textures = texture;
1412
1413 UpdatePixelViewport(renderer, &texture->view);
1414 UpdatePixelClipRect(renderer, &texture->view);
1415
1416 texture->SDR_white_point = SDL_GetFloatProperty(props, SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT, SDL_GetDefaultSDRWhitePoint(texture->colorspace));
1417 texture->HDR_headroom = SDL_GetFloatProperty(props, SDL_PROP_TEXTURE_CREATE_HDR_HEADROOM_FLOAT, SDL_GetDefaultHDRHeadroom(texture->colorspace));
1418
1419 // FOURCC format cannot be used directly by renderer back-ends for target texture
1420 texture_is_fourcc_and_target = (access == SDL_TEXTUREACCESS_TARGET && SDL_ISPIXELFORMAT_FOURCC(format));
1421
1422 if (!texture_is_fourcc_and_target && IsSupportedFormat(renderer, format)) {
1423 if (!renderer->CreateTexture(renderer, texture, props)) {
1424 SDL_DestroyTexture(texture);
1425 return NULL;
1426 }
1427 } else {
1428 SDL_PixelFormat closest_format;
1429 SDL_PropertiesID native_props = SDL_CreateProperties();
1430
1431 if (!texture_is_fourcc_and_target) {
1432 closest_format = GetClosestSupportedFormat(renderer, format);
1433 } else {
1434 closest_format = renderer->texture_formats[0];
1435 }
1436
1437 if (format == SDL_PIXELFORMAT_MJPG && closest_format == SDL_PIXELFORMAT_NV12) {
1438 SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, SDL_COLORSPACE_JPEG);
1439 } else {
1440 default_colorspace = SDL_GetDefaultColorspaceForFormat(closest_format);
1441 if (SDL_COLORSPACETYPE(texture->colorspace) == SDL_COLORSPACETYPE(default_colorspace)) {
1442 SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, texture->colorspace);
1443 } else {
1444 SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, default_colorspace);
1445 }
1446 }
1447 SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, closest_format);
1448 SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, texture->access);
1449 SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, texture->w);
1450 SDL_SetNumberProperty(native_props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, texture->h);
1451
1452 texture->native = SDL_CreateTextureWithProperties(renderer, native_props);
1453 SDL_DestroyProperties(native_props);
1454 if (!texture->native) {
1455 SDL_DestroyTexture(texture);
1456 return NULL;
1457 }
1458
1459 SDL_SetPointerProperty(SDL_GetTextureProperties(texture->native), SDL_PROP_TEXTURE_PARENT_POINTER, texture);
1460
1461 // Swap textures to have texture before texture->native in the list
1462 texture->native->next = texture->next;
1463 if (texture->native->next) {
1464 texture->native->next->prev = texture->native;
1465 }
1466 texture->prev = texture->native->prev;
1467 if (texture->prev) {
1468 texture->prev->next = texture;
1469 }
1470 texture->native->prev = texture;
1471 texture->next = texture->native;
1472 renderer->textures = texture;
1473
1474 if (texture->format == SDL_PIXELFORMAT_MJPG) {
1475 // We have a custom decode + upload path for this
1476 } else if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) {
1477#ifdef SDL_HAVE_YUV
1478 texture->yuv = SDL_SW_CreateYUVTexture(texture->format, texture->colorspace, w, h);
1479#else
1480 SDL_SetError("SDL not built with YUV support");
1481#endif
1482 if (!texture->yuv) {
1483 SDL_DestroyTexture(texture);
1484 return NULL;
1485 }
1486 } else if (access == SDL_TEXTUREACCESS_STREAMING) {
1487 // The pitch is 4 byte aligned
1488 texture->pitch = (((w * SDL_BYTESPERPIXEL(format)) + 3) & ~3);
1489 texture->pixels = SDL_calloc(1, (size_t)texture->pitch * h);
1490 if (!texture->pixels) {
1491 SDL_DestroyTexture(texture);
1492 return NULL;
1493 }
1494 }
1495 }
1496
1497 // Now set the properties for the new texture
1498 props = SDL_GetTextureProperties(texture);
1499 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_COLORSPACE_NUMBER, texture->colorspace);
1500 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_FORMAT_NUMBER, texture->format);
1501 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_ACCESS_NUMBER, texture->access);
1502 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_WIDTH_NUMBER, texture->w);
1503 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_HEIGHT_NUMBER, texture->h);
1504 SDL_SetFloatProperty(props, SDL_PROP_TEXTURE_SDR_WHITE_POINT_FLOAT, texture->SDR_white_point);
1505 if (texture->HDR_headroom > 0.0f) {
1506 SDL_SetFloatProperty(props, SDL_PROP_TEXTURE_HDR_HEADROOM_FLOAT, texture->HDR_headroom);
1507 }
1508 return texture;
1509}
1510
1511SDL_Texture *SDL_CreateTexture(SDL_Renderer *renderer, SDL_PixelFormat format, SDL_TextureAccess access, int w, int h)
1512{
1513 SDL_Texture *texture;
1514 SDL_PropertiesID props = SDL_CreateProperties();
1515 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, format);
1516 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, access);
1517 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, w);
1518 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, h);
1519 texture = SDL_CreateTextureWithProperties(renderer, props);
1520 SDL_DestroyProperties(props);
1521 return texture;
1522}
1523
1524static bool SDL_UpdateTextureFromSurface(SDL_Texture *texture, SDL_Rect *rect, SDL_Surface *surface)
1525{
1526 SDL_TextureAccess access;
1527 bool direct_update;
1528 SDL_PixelFormat tex_format;
1529 SDL_PropertiesID surface_props;
1530 SDL_PropertiesID tex_props;
1531 SDL_Colorspace surface_colorspace = SDL_COLORSPACE_UNKNOWN;
1532 SDL_Colorspace texture_colorspace = SDL_COLORSPACE_UNKNOWN;
1533
1534 if (texture == NULL || surface == NULL) {
1535 return false;
1536 }
1537
1538 tex_props = SDL_GetTextureProperties(texture);
1539 if (!tex_props) {
1540 return false;
1541 }
1542
1543 surface_props = SDL_GetSurfaceProperties(surface);
1544 if (!surface_props) {
1545 return false;
1546 }
1547
1548 tex_format = (SDL_PixelFormat)SDL_GetNumberProperty(tex_props, SDL_PROP_TEXTURE_FORMAT_NUMBER, 0);
1549 access = (SDL_TextureAccess)SDL_GetNumberProperty(tex_props, SDL_PROP_TEXTURE_ACCESS_NUMBER, 0);
1550
1551 if (access != SDL_TEXTUREACCESS_STATIC && access != SDL_TEXTUREACCESS_STREAMING) {
1552 return false;
1553 }
1554
1555 surface_colorspace = SDL_GetSurfaceColorspace(surface);
1556 texture_colorspace = surface_colorspace;
1557
1558 if (surface_colorspace == SDL_COLORSPACE_SRGB_LINEAR ||
1559 SDL_COLORSPACETRANSFER(surface_colorspace) == SDL_TRANSFER_CHARACTERISTICS_PQ) {
1560 if (SDL_ISPIXELFORMAT_FLOAT(tex_format)) {
1561 texture_colorspace = SDL_COLORSPACE_SRGB_LINEAR;
1562 } else if (SDL_ISPIXELFORMAT_10BIT(tex_format)) {
1563 texture_colorspace = SDL_COLORSPACE_HDR10;
1564 } else {
1565 texture_colorspace = SDL_COLORSPACE_SRGB;
1566 }
1567 }
1568
1569 if (tex_format == surface->format && texture_colorspace == surface_colorspace) {
1570 if (SDL_ISPIXELFORMAT_ALPHA(surface->format) && SDL_SurfaceHasColorKey(surface)) {
1571 /* Surface and Renderer formats are identical.
1572 * Intermediate conversion is needed to convert color key to alpha (SDL_ConvertColorkeyToAlpha()). */
1573 direct_update = false;
1574 } else {
1575 // Update Texture directly
1576 direct_update = true;
1577 }
1578 } else {
1579 // Surface and Renderer formats are different, it needs an intermediate conversion.
1580 direct_update = false;
1581 }
1582
1583 if (direct_update) {
1584 if (SDL_MUSTLOCK(surface)) {
1585 SDL_LockSurface(surface);
1586 SDL_UpdateTexture(texture, rect, surface->pixels, surface->pitch);
1587 SDL_UnlockSurface(surface);
1588 } else {
1589 SDL_UpdateTexture(texture, rect, surface->pixels, surface->pitch);
1590 }
1591 } else {
1592 SDL_Surface *temp = NULL;
1593
1594 // Set up a destination surface for the texture update
1595 temp = SDL_ConvertSurfaceAndColorspace(surface, tex_format, NULL, texture_colorspace, surface_props);
1596 if (temp) {
1597 SDL_UpdateTexture(texture, NULL, temp->pixels, temp->pitch);
1598 SDL_DestroySurface(temp);
1599 } else {
1600 return false;
1601 }
1602 }
1603
1604 {
1605 Uint8 r, g, b, a;
1606 SDL_BlendMode blendMode;
1607
1608 SDL_GetSurfaceColorMod(surface, &r, &g, &b);
1609 SDL_SetTextureColorMod(texture, r, g, b);
1610
1611 SDL_GetSurfaceAlphaMod(surface, &a);
1612 SDL_SetTextureAlphaMod(texture, a);
1613
1614 if (SDL_SurfaceHasColorKey(surface)) {
1615 // We converted to a texture with alpha format
1616 SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
1617 } else {
1618 SDL_GetSurfaceBlendMode(surface, &blendMode);
1619 SDL_SetTextureBlendMode(texture, blendMode);
1620 }
1621 }
1622
1623 return true;
1624}
1625
1626SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *surface)
1627{
1628 bool needAlpha;
1629 int i;
1630 SDL_PixelFormat format = SDL_PIXELFORMAT_UNKNOWN;
1631 SDL_Palette *palette;
1632 SDL_Texture *texture;
1633 SDL_PropertiesID props;
1634 SDL_Colorspace surface_colorspace = SDL_COLORSPACE_UNKNOWN;
1635 SDL_Colorspace texture_colorspace = SDL_COLORSPACE_UNKNOWN;
1636
1637 CHECK_RENDERER_MAGIC(renderer, NULL);
1638
1639 if (!SDL_SurfaceValid(surface)) {
1640 SDL_InvalidParamError("SDL_CreateTextureFromSurface(): surface");
1641 return NULL;
1642 }
1643
1644 // See what the best texture format is
1645 if (SDL_ISPIXELFORMAT_ALPHA(surface->format) || SDL_SurfaceHasColorKey(surface)) {
1646 needAlpha = true;
1647 } else {
1648 needAlpha = false;
1649 }
1650
1651 // If Palette contains alpha values, promotes to alpha format
1652 palette = SDL_GetSurfacePalette(surface);
1653 if (palette) {
1654 bool is_opaque, has_alpha_channel;
1655 SDL_DetectPalette(palette, &is_opaque, &has_alpha_channel);
1656 if (!is_opaque) {
1657 needAlpha = true;
1658 }
1659 }
1660
1661 surface_colorspace = SDL_GetSurfaceColorspace(surface);
1662
1663 // Try to have the best pixel format for the texture
1664 // No alpha, but a colorkey => promote to alpha
1665 if (!SDL_ISPIXELFORMAT_ALPHA(surface->format) && SDL_SurfaceHasColorKey(surface)) {
1666 if (surface->format == SDL_PIXELFORMAT_XRGB8888) {
1667 for (i = 0; i < renderer->num_texture_formats; ++i) {
1668 if (renderer->texture_formats[i] == SDL_PIXELFORMAT_ARGB8888) {
1669 format = SDL_PIXELFORMAT_ARGB8888;
1670 break;
1671 }
1672 }
1673 } else if (surface->format == SDL_PIXELFORMAT_XBGR8888) {
1674 for (i = 0; i < renderer->num_texture_formats; ++i) {
1675 if (renderer->texture_formats[i] == SDL_PIXELFORMAT_ABGR8888) {
1676 format = SDL_PIXELFORMAT_ABGR8888;
1677 break;
1678 }
1679 }
1680 }
1681 } else {
1682 // Exact match would be fine
1683 for (i = 0; i < renderer->num_texture_formats; ++i) {
1684 if (renderer->texture_formats[i] == surface->format) {
1685 format = surface->format;
1686 break;
1687 }
1688 }
1689 }
1690
1691 // Look for 10-bit pixel formats if needed
1692 if (format == SDL_PIXELFORMAT_UNKNOWN && SDL_ISPIXELFORMAT_10BIT(surface->format)) {
1693 for (i = 0; i < renderer->num_texture_formats; ++i) {
1694 if (SDL_ISPIXELFORMAT_10BIT(renderer->texture_formats[i])) {
1695 format = renderer->texture_formats[i];
1696 break;
1697 }
1698 }
1699 }
1700
1701 // Look for floating point pixel formats if needed
1702 if (format == SDL_PIXELFORMAT_UNKNOWN &&
1703 (SDL_ISPIXELFORMAT_10BIT(surface->format) || SDL_ISPIXELFORMAT_FLOAT(surface->format))) {
1704 for (i = 0; i < renderer->num_texture_formats; ++i) {
1705 if (SDL_ISPIXELFORMAT_FLOAT(renderer->texture_formats[i])) {
1706 format = renderer->texture_formats[i];
1707 break;
1708 }
1709 }
1710 }
1711
1712 // Fallback, choose a valid pixel format
1713 if (format == SDL_PIXELFORMAT_UNKNOWN) {
1714 format = renderer->texture_formats[0];
1715 for (i = 0; i < renderer->num_texture_formats; ++i) {
1716 if (!SDL_ISPIXELFORMAT_FOURCC(renderer->texture_formats[i]) &&
1717 SDL_ISPIXELFORMAT_ALPHA(renderer->texture_formats[i]) == needAlpha) {
1718 format = renderer->texture_formats[i];
1719 break;
1720 }
1721 }
1722 }
1723
1724 if (surface_colorspace == SDL_COLORSPACE_SRGB_LINEAR ||
1725 SDL_COLORSPACETRANSFER(surface_colorspace) == SDL_TRANSFER_CHARACTERISTICS_PQ) {
1726 if (SDL_ISPIXELFORMAT_FLOAT(format)) {
1727 texture_colorspace = SDL_COLORSPACE_SRGB_LINEAR;
1728 } else if (SDL_ISPIXELFORMAT_10BIT(format)) {
1729 texture_colorspace = SDL_COLORSPACE_HDR10;
1730 } else {
1731 texture_colorspace = SDL_COLORSPACE_SRGB;
1732 }
1733 }
1734
1735 props = SDL_CreateProperties();
1736 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, texture_colorspace);
1737 if (surface_colorspace == texture_colorspace) {
1738 SDL_SetFloatProperty(props, SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT,
1739 SDL_GetSurfaceSDRWhitePoint(surface, surface_colorspace));
1740 }
1741 SDL_SetFloatProperty(props, SDL_PROP_TEXTURE_CREATE_HDR_HEADROOM_FLOAT,
1742 SDL_GetSurfaceHDRHeadroom(surface, surface_colorspace));
1743 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, format);
1744 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STATIC);
1745 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, surface->w);
1746 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, surface->h);
1747 texture = SDL_CreateTextureWithProperties(renderer, props);
1748 SDL_DestroyProperties(props);
1749 if (!texture) {
1750 return NULL;
1751 }
1752
1753 if (!SDL_UpdateTextureFromSurface(texture, NULL, surface)) {
1754 SDL_DestroyTexture(texture);
1755 return NULL;
1756 }
1757
1758 return texture;
1759}
1760
1761SDL_Renderer *SDL_GetRendererFromTexture(SDL_Texture *texture)
1762{
1763 CHECK_TEXTURE_MAGIC(texture, NULL);
1764
1765 return texture->renderer;
1766}
1767
1768SDL_PropertiesID SDL_GetTextureProperties(SDL_Texture *texture)
1769{
1770 CHECK_TEXTURE_MAGIC(texture, 0);
1771
1772 if (texture->props == 0) {
1773 texture->props = SDL_CreateProperties();
1774 }
1775 return texture->props;
1776}
1777
1778bool SDL_GetTextureSize(SDL_Texture *texture, float *w, float *h)
1779{
1780 if (w) {
1781 *w = 0;
1782 }
1783 if (h) {
1784 *h = 0;
1785 }
1786
1787 CHECK_TEXTURE_MAGIC(texture, false);
1788
1789 if (w) {
1790 *w = (float)texture->w;
1791 }
1792 if (h) {
1793 *h = (float)texture->h;
1794 }
1795 return true;
1796}
1797
1798bool SDL_SetTextureColorMod(SDL_Texture *texture, Uint8 r, Uint8 g, Uint8 b)
1799{
1800 const float fR = (float)r / 255.0f;
1801 const float fG = (float)g / 255.0f;
1802 const float fB = (float)b / 255.0f;
1803
1804 return SDL_SetTextureColorModFloat(texture, fR, fG, fB);
1805}
1806
1807bool SDL_SetTextureColorModFloat(SDL_Texture *texture, float r, float g, float b)
1808{
1809 CHECK_TEXTURE_MAGIC(texture, false);
1810
1811 texture->color.r = r;
1812 texture->color.g = g;
1813 texture->color.b = b;
1814 if (texture->native) {
1815 return SDL_SetTextureColorModFloat(texture->native, r, g, b);
1816 }
1817 return true;
1818}
1819
1820bool SDL_GetTextureColorMod(SDL_Texture *texture, Uint8 *r, Uint8 *g, Uint8 *b)
1821{
1822 float fR = 1.0f, fG = 1.0f, fB = 1.0f;
1823
1824 if (!SDL_GetTextureColorModFloat(texture, &fR, &fG, &fB)) {
1825 if (r) {
1826 *r = 255;
1827 }
1828 if (g) {
1829 *g = 255;
1830 }
1831 if (b) {
1832 *b = 255;
1833 }
1834 return false;
1835 }
1836
1837 if (r) {
1838 *r = (Uint8)SDL_roundf(SDL_clamp(fR, 0.0f, 1.0f) * 255.0f);
1839 }
1840 if (g) {
1841 *g = (Uint8)SDL_roundf(SDL_clamp(fG, 0.0f, 1.0f) * 255.0f);
1842 }
1843 if (b) {
1844 *b = (Uint8)SDL_roundf(SDL_clamp(fB, 0.0f, 1.0f) * 255.0f);
1845 }
1846 return true;
1847}
1848
1849bool SDL_GetTextureColorModFloat(SDL_Texture *texture, float *r, float *g, float *b)
1850{
1851 SDL_FColor color;
1852
1853 if (r) {
1854 *r = 1.0f;
1855 }
1856 if (g) {
1857 *g = 1.0f;
1858 }
1859 if (b) {
1860 *b = 1.0f;
1861 }
1862
1863 CHECK_TEXTURE_MAGIC(texture, false);
1864
1865 color = texture->color;
1866
1867 if (r) {
1868 *r = color.r;
1869 }
1870 if (g) {
1871 *g = color.g;
1872 }
1873 if (b) {
1874 *b = color.b;
1875 }
1876 return true;
1877}
1878
1879bool SDL_SetTextureAlphaMod(SDL_Texture *texture, Uint8 alpha)
1880{
1881 const float fA = (float)alpha / 255.0f;
1882
1883 return SDL_SetTextureAlphaModFloat(texture, fA);
1884}
1885
1886bool SDL_SetTextureAlphaModFloat(SDL_Texture *texture, float alpha)
1887{
1888 CHECK_TEXTURE_MAGIC(texture, false);
1889
1890 texture->color.a = alpha;
1891 if (texture->native) {
1892 return SDL_SetTextureAlphaModFloat(texture->native, alpha);
1893 }
1894 return true;
1895}
1896
1897bool SDL_GetTextureAlphaMod(SDL_Texture *texture, Uint8 *alpha)
1898{
1899 float fA = 1.0f;
1900
1901 if (!SDL_GetTextureAlphaModFloat(texture, &fA)) {
1902 if (alpha) {
1903 *alpha = 255;
1904 }
1905 return false;
1906 }
1907
1908 if (alpha) {
1909 *alpha = (Uint8)SDL_roundf(SDL_clamp(fA, 0.0f, 1.0f) * 255.0f);
1910 }
1911 return true;
1912}
1913
1914bool SDL_GetTextureAlphaModFloat(SDL_Texture *texture, float *alpha)
1915{
1916 if (alpha) {
1917 *alpha = 1.0f;
1918 }
1919
1920 CHECK_TEXTURE_MAGIC(texture, false);
1921
1922 if (alpha) {
1923 *alpha = texture->color.a;
1924 }
1925 return true;
1926}
1927
1928bool SDL_SetTextureBlendMode(SDL_Texture *texture, SDL_BlendMode blendMode)
1929{
1930 SDL_Renderer *renderer;
1931
1932 CHECK_TEXTURE_MAGIC(texture, false);
1933
1934 if (blendMode == SDL_BLENDMODE_INVALID) {
1935 return SDL_InvalidParamError("blendMode");
1936 }
1937
1938 renderer = texture->renderer;
1939 if (!IsSupportedBlendMode(renderer, blendMode)) {
1940 return SDL_Unsupported();
1941 }
1942 texture->blendMode = blendMode;
1943 if (texture->native) {
1944 return SDL_SetTextureBlendMode(texture->native, blendMode);
1945 }
1946 return true;
1947}
1948
1949bool SDL_GetTextureBlendMode(SDL_Texture *texture, SDL_BlendMode *blendMode)
1950{
1951 if (blendMode) {
1952 *blendMode = SDL_BLENDMODE_INVALID;
1953 }
1954
1955 CHECK_TEXTURE_MAGIC(texture, false);
1956
1957 if (blendMode) {
1958 *blendMode = texture->blendMode;
1959 }
1960 return true;
1961}
1962
1963bool SDL_SetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode scaleMode)
1964{
1965 SDL_Renderer *renderer;
1966
1967 CHECK_TEXTURE_MAGIC(texture, false);
1968
1969 if (scaleMode != SDL_SCALEMODE_NEAREST &&
1970 scaleMode != SDL_SCALEMODE_LINEAR) {
1971 return SDL_InvalidParamError("scaleMode");
1972 }
1973
1974 renderer = texture->renderer;
1975 texture->scaleMode = scaleMode;
1976 if (texture->native) {
1977 return SDL_SetTextureScaleMode(texture->native, scaleMode);
1978 } else {
1979 renderer->SetTextureScaleMode(renderer, texture, scaleMode);
1980 }
1981 return true;
1982}
1983
1984bool SDL_GetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode *scaleMode)
1985{
1986 if (scaleMode) {
1987 *scaleMode = SDL_SCALEMODE_LINEAR;
1988 }
1989
1990 CHECK_TEXTURE_MAGIC(texture, false);
1991
1992 if (scaleMode) {
1993 *scaleMode = texture->scaleMode;
1994 }
1995 return true;
1996}
1997
1998#ifdef SDL_HAVE_YUV
1999static bool SDL_UpdateTextureYUV(SDL_Texture *texture, const SDL_Rect *rect,
2000 const void *pixels, int pitch)
2001{
2002 SDL_Texture *native = texture->native;
2003 SDL_Rect full_rect;
2004
2005 if (!SDL_SW_UpdateYUVTexture(texture->yuv, rect, pixels, pitch)) {
2006 return false;
2007 }
2008
2009 full_rect.x = 0;
2010 full_rect.y = 0;
2011 full_rect.w = texture->w;
2012 full_rect.h = texture->h;
2013 rect = &full_rect;
2014
2015 if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
2016 // We can lock the texture and copy to it
2017 void *native_pixels = NULL;
2018 int native_pitch = 0;
2019
2020 if (!SDL_LockTexture(native, rect, &native_pixels, &native_pitch)) {
2021 return false;
2022 }
2023 SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format,
2024 rect->w, rect->h, native_pixels, native_pitch);
2025 SDL_UnlockTexture(native);
2026 } else {
2027 // Use a temporary buffer for updating
2028 const int temp_pitch = (((rect->w * SDL_BYTESPERPIXEL(native->format)) + 3) & ~3);
2029 const size_t alloclen = (size_t)rect->h * temp_pitch;
2030 if (alloclen > 0) {
2031 void *temp_pixels = SDL_malloc(alloclen);
2032 if (!temp_pixels) {
2033 return false;
2034 }
2035 SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format,
2036 rect->w, rect->h, temp_pixels, temp_pitch);
2037 SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch);
2038 SDL_free(temp_pixels);
2039 }
2040 }
2041 return true;
2042}
2043#endif // SDL_HAVE_YUV
2044
2045static bool SDL_UpdateTextureNative(SDL_Texture *texture, const SDL_Rect *rect,
2046 const void *pixels, int pitch)
2047{
2048 SDL_Texture *native = texture->native;
2049
2050 if (!rect->w || !rect->h) {
2051 return true; // nothing to do.
2052 }
2053
2054 if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
2055 // We can lock the texture and copy to it
2056 void *native_pixels = NULL;
2057 int native_pitch = 0;
2058
2059 if (!SDL_LockTexture(native, rect, &native_pixels, &native_pitch)) {
2060 return false;
2061 }
2062 SDL_ConvertPixelsAndColorspace(rect->w, rect->h,
2063 texture->format, texture->colorspace, 0, pixels, pitch,
2064 native->format, native->colorspace, 0, native_pixels, native_pitch);
2065 SDL_UnlockTexture(native);
2066 } else {
2067 // Use a temporary buffer for updating
2068 const int temp_pitch = (((rect->w * SDL_BYTESPERPIXEL(native->format)) + 3) & ~3);
2069 const size_t alloclen = (size_t)rect->h * temp_pitch;
2070 if (alloclen > 0) {
2071 void *temp_pixels = SDL_malloc(alloclen);
2072 if (!temp_pixels) {
2073 return false;
2074 }
2075 SDL_ConvertPixelsAndColorspace(rect->w, rect->h,
2076 texture->format, texture->colorspace, 0, pixels, pitch,
2077 native->format, native->colorspace, 0, temp_pixels, temp_pitch);
2078 SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch);
2079 SDL_free(temp_pixels);
2080 }
2081 }
2082 return true;
2083}
2084
2085bool SDL_UpdateTexture(SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch)
2086{
2087 SDL_Rect real_rect;
2088
2089 CHECK_TEXTURE_MAGIC(texture, false);
2090
2091 if (!pixels) {
2092 return SDL_InvalidParamError("pixels");
2093 }
2094 if (!pitch) {
2095 return SDL_InvalidParamError("pitch");
2096 }
2097
2098 real_rect.x = 0;
2099 real_rect.y = 0;
2100 real_rect.w = texture->w;
2101 real_rect.h = texture->h;
2102 if (rect) {
2103 if (!SDL_GetRectIntersection(rect, &real_rect, &real_rect)) {
2104 return true;
2105 }
2106 }
2107
2108 if (real_rect.w == 0 || real_rect.h == 0) {
2109 return true; // nothing to do.
2110#ifdef SDL_HAVE_YUV
2111 } else if (texture->yuv) {
2112 return SDL_UpdateTextureYUV(texture, &real_rect, pixels, pitch);
2113#endif
2114 } else if (texture->native) {
2115 return SDL_UpdateTextureNative(texture, &real_rect, pixels, pitch);
2116 } else {
2117 SDL_Renderer *renderer = texture->renderer;
2118 if (!FlushRenderCommandsIfTextureNeeded(texture)) {
2119 return false;
2120 }
2121 return renderer->UpdateTexture(renderer, texture, &real_rect, pixels, pitch);
2122 }
2123}
2124
2125#ifdef SDL_HAVE_YUV
2126static bool SDL_UpdateTextureYUVPlanar(SDL_Texture *texture, const SDL_Rect *rect,
2127 const Uint8 *Yplane, int Ypitch,
2128 const Uint8 *Uplane, int Upitch,
2129 const Uint8 *Vplane, int Vpitch)
2130{
2131 SDL_Texture *native = texture->native;
2132 SDL_Rect full_rect;
2133
2134 if (!SDL_SW_UpdateYUVTexturePlanar(texture->yuv, rect, Yplane, Ypitch, Uplane, Upitch, Vplane, Vpitch)) {
2135 return false;
2136 }
2137
2138 full_rect.x = 0;
2139 full_rect.y = 0;
2140 full_rect.w = texture->w;
2141 full_rect.h = texture->h;
2142 rect = &full_rect;
2143
2144 if (!rect->w || !rect->h) {
2145 return true; // nothing to do.
2146 }
2147
2148 if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
2149 // We can lock the texture and copy to it
2150 void *native_pixels = NULL;
2151 int native_pitch = 0;
2152
2153 if (!SDL_LockTexture(native, rect, &native_pixels, &native_pitch)) {
2154 return false;
2155 }
2156 SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format,
2157 rect->w, rect->h, native_pixels, native_pitch);
2158 SDL_UnlockTexture(native);
2159 } else {
2160 // Use a temporary buffer for updating
2161 const int temp_pitch = (((rect->w * SDL_BYTESPERPIXEL(native->format)) + 3) & ~3);
2162 const size_t alloclen = (size_t)rect->h * temp_pitch;
2163 if (alloclen > 0) {
2164 void *temp_pixels = SDL_malloc(alloclen);
2165 if (!temp_pixels) {
2166 return false;
2167 }
2168 SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format,
2169 rect->w, rect->h, temp_pixels, temp_pitch);
2170 SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch);
2171 SDL_free(temp_pixels);
2172 }
2173 }
2174 return true;
2175}
2176
2177static bool SDL_UpdateTextureNVPlanar(SDL_Texture *texture, const SDL_Rect *rect,
2178 const Uint8 *Yplane, int Ypitch,
2179 const Uint8 *UVplane, int UVpitch)
2180{
2181 SDL_Texture *native = texture->native;
2182 SDL_Rect full_rect;
2183
2184 if (!SDL_SW_UpdateNVTexturePlanar(texture->yuv, rect, Yplane, Ypitch, UVplane, UVpitch)) {
2185 return false;
2186 }
2187
2188 full_rect.x = 0;
2189 full_rect.y = 0;
2190 full_rect.w = texture->w;
2191 full_rect.h = texture->h;
2192 rect = &full_rect;
2193
2194 if (!rect->w || !rect->h) {
2195 return true; // nothing to do.
2196 }
2197
2198 if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
2199 // We can lock the texture and copy to it
2200 void *native_pixels = NULL;
2201 int native_pitch = 0;
2202
2203 if (!SDL_LockTexture(native, rect, &native_pixels, &native_pitch)) {
2204 return false;
2205 }
2206 SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format,
2207 rect->w, rect->h, native_pixels, native_pitch);
2208 SDL_UnlockTexture(native);
2209 } else {
2210 // Use a temporary buffer for updating
2211 const int temp_pitch = (((rect->w * SDL_BYTESPERPIXEL(native->format)) + 3) & ~3);
2212 const size_t alloclen = (size_t)rect->h * temp_pitch;
2213 if (alloclen > 0) {
2214 void *temp_pixels = SDL_malloc(alloclen);
2215 if (!temp_pixels) {
2216 return false;
2217 }
2218 SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format,
2219 rect->w, rect->h, temp_pixels, temp_pitch);
2220 SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch);
2221 SDL_free(temp_pixels);
2222 }
2223 }
2224 return true;
2225}
2226
2227#endif // SDL_HAVE_YUV
2228
2229bool SDL_UpdateYUVTexture(SDL_Texture *texture, const SDL_Rect *rect,
2230 const Uint8 *Yplane, int Ypitch,
2231 const Uint8 *Uplane, int Upitch,
2232 const Uint8 *Vplane, int Vpitch)
2233{
2234#ifdef SDL_HAVE_YUV
2235 SDL_Renderer *renderer;
2236 SDL_Rect real_rect;
2237
2238 CHECK_TEXTURE_MAGIC(texture, false);
2239
2240 if (!Yplane) {
2241 return SDL_InvalidParamError("Yplane");
2242 }
2243 if (!Ypitch) {
2244 return SDL_InvalidParamError("Ypitch");
2245 }
2246 if (!Uplane) {
2247 return SDL_InvalidParamError("Uplane");
2248 }
2249 if (!Upitch) {
2250 return SDL_InvalidParamError("Upitch");
2251 }
2252 if (!Vplane) {
2253 return SDL_InvalidParamError("Vplane");
2254 }
2255 if (!Vpitch) {
2256 return SDL_InvalidParamError("Vpitch");
2257 }
2258
2259 if (texture->format != SDL_PIXELFORMAT_YV12 &&
2260 texture->format != SDL_PIXELFORMAT_IYUV) {
2261 return SDL_SetError("Texture format must by YV12 or IYUV");
2262 }
2263
2264 real_rect.x = 0;
2265 real_rect.y = 0;
2266 real_rect.w = texture->w;
2267 real_rect.h = texture->h;
2268 if (rect) {
2269 SDL_GetRectIntersection(rect, &real_rect, &real_rect);
2270 }
2271
2272 if (real_rect.w == 0 || real_rect.h == 0) {
2273 return true; // nothing to do.
2274 }
2275
2276 if (texture->yuv) {
2277 return SDL_UpdateTextureYUVPlanar(texture, &real_rect, Yplane, Ypitch, Uplane, Upitch, Vplane, Vpitch);
2278 } else {
2279 SDL_assert(!texture->native);
2280 renderer = texture->renderer;
2281 SDL_assert(renderer->UpdateTextureYUV);
2282 if (renderer->UpdateTextureYUV) {
2283 if (!FlushRenderCommandsIfTextureNeeded(texture)) {
2284 return false;
2285 }
2286 return renderer->UpdateTextureYUV(renderer, texture, &real_rect, Yplane, Ypitch, Uplane, Upitch, Vplane, Vpitch);
2287 } else {
2288 return SDL_Unsupported();
2289 }
2290 }
2291#else
2292 return false;
2293#endif
2294}
2295
2296bool SDL_UpdateNVTexture(SDL_Texture *texture, const SDL_Rect *rect,
2297 const Uint8 *Yplane, int Ypitch,
2298 const Uint8 *UVplane, int UVpitch)
2299{
2300#ifdef SDL_HAVE_YUV
2301 SDL_Renderer *renderer;
2302 SDL_Rect real_rect;
2303
2304 CHECK_TEXTURE_MAGIC(texture, false);
2305
2306 if (!Yplane) {
2307 return SDL_InvalidParamError("Yplane");
2308 }
2309 if (!Ypitch) {
2310 return SDL_InvalidParamError("Ypitch");
2311 }
2312 if (!UVplane) {
2313 return SDL_InvalidParamError("UVplane");
2314 }
2315 if (!UVpitch) {
2316 return SDL_InvalidParamError("UVpitch");
2317 }
2318
2319 if (texture->format != SDL_PIXELFORMAT_NV12 &&
2320 texture->format != SDL_PIXELFORMAT_NV21) {
2321 return SDL_SetError("Texture format must by NV12 or NV21");
2322 }
2323
2324 real_rect.x = 0;
2325 real_rect.y = 0;
2326 real_rect.w = texture->w;
2327 real_rect.h = texture->h;
2328 if (rect) {
2329 SDL_GetRectIntersection(rect, &real_rect, &real_rect);
2330 }
2331
2332 if (real_rect.w == 0 || real_rect.h == 0) {
2333 return true; // nothing to do.
2334 }
2335
2336 if (texture->yuv) {
2337 return SDL_UpdateTextureNVPlanar(texture, &real_rect, Yplane, Ypitch, UVplane, UVpitch);
2338 } else {
2339 SDL_assert(!texture->native);
2340 renderer = texture->renderer;
2341 SDL_assert(renderer->UpdateTextureNV);
2342 if (renderer->UpdateTextureNV) {
2343 if (!FlushRenderCommandsIfTextureNeeded(texture)) {
2344 return false;
2345 }
2346 return renderer->UpdateTextureNV(renderer, texture, &real_rect, Yplane, Ypitch, UVplane, UVpitch);
2347 } else {
2348 return SDL_Unsupported();
2349 }
2350 }
2351#else
2352 return false;
2353#endif
2354}
2355
2356#ifdef SDL_HAVE_YUV
2357static bool SDL_LockTextureYUV(SDL_Texture *texture, const SDL_Rect *rect,
2358 void **pixels, int *pitch)
2359{
2360 return SDL_SW_LockYUVTexture(texture->yuv, rect, pixels, pitch);
2361}
2362#endif // SDL_HAVE_YUV
2363
2364static bool SDL_LockTextureNative(SDL_Texture *texture, const SDL_Rect *rect,
2365 void **pixels, int *pitch)
2366{
2367 texture->locked_rect = *rect;
2368 *pixels = (void *)((Uint8 *)texture->pixels +
2369 rect->y * texture->pitch +
2370 rect->x * SDL_BYTESPERPIXEL(texture->format));
2371 *pitch = texture->pitch;
2372 return true;
2373}
2374
2375bool SDL_LockTexture(SDL_Texture *texture, const SDL_Rect *rect, void **pixels, int *pitch)
2376{
2377 SDL_Rect full_rect;
2378
2379 CHECK_TEXTURE_MAGIC(texture, false);
2380
2381 if (texture->access != SDL_TEXTUREACCESS_STREAMING) {
2382 return SDL_SetError("SDL_LockTexture(): texture must be streaming");
2383 }
2384
2385 if (!rect) {
2386 full_rect.x = 0;
2387 full_rect.y = 0;
2388 full_rect.w = texture->w;
2389 full_rect.h = texture->h;
2390 rect = &full_rect;
2391 }
2392
2393#ifdef SDL_HAVE_YUV
2394 if (texture->yuv) {
2395 if (!FlushRenderCommandsIfTextureNeeded(texture)) {
2396 return false;
2397 }
2398 return SDL_LockTextureYUV(texture, rect, pixels, pitch);
2399 } else
2400#endif
2401 if (texture->native) {
2402 // Calls a real SDL_LockTexture/SDL_UnlockTexture on unlock, flushing then.
2403 return SDL_LockTextureNative(texture, rect, pixels, pitch);
2404 } else {
2405 SDL_Renderer *renderer = texture->renderer;
2406 if (!FlushRenderCommandsIfTextureNeeded(texture)) {
2407 return false;
2408 }
2409 return renderer->LockTexture(renderer, texture, rect, pixels, pitch);
2410 }
2411}
2412
2413bool SDL_LockTextureToSurface(SDL_Texture *texture, const SDL_Rect *rect, SDL_Surface **surface)
2414{
2415 SDL_Rect real_rect;
2416 void *pixels = NULL;
2417 int pitch = 0; // fix static analysis
2418
2419 if (!texture || !surface) {
2420 return false;
2421 }
2422
2423 real_rect.x = 0;
2424 real_rect.y = 0;
2425 real_rect.w = texture->w;
2426 real_rect.h = texture->h;
2427 if (rect) {
2428 SDL_GetRectIntersection(rect, &real_rect, &real_rect);
2429 }
2430
2431 if (!SDL_LockTexture(texture, &real_rect, &pixels, &pitch)) {
2432 return false;
2433 }
2434
2435 texture->locked_surface = SDL_CreateSurfaceFrom(real_rect.w, real_rect.h, texture->format, pixels, pitch);
2436 if (!texture->locked_surface) {
2437 SDL_UnlockTexture(texture);
2438 return false;
2439 }
2440
2441 *surface = texture->locked_surface;
2442 return true;
2443}
2444
2445#ifdef SDL_HAVE_YUV
2446static void SDL_UnlockTextureYUV(SDL_Texture *texture)
2447{
2448 SDL_Texture *native = texture->native;
2449 void *native_pixels = NULL;
2450 int native_pitch = 0;
2451 SDL_Rect rect;
2452
2453 rect.x = 0;
2454 rect.y = 0;
2455 rect.w = texture->w;
2456 rect.h = texture->h;
2457
2458 if (!SDL_LockTexture(native, &rect, &native_pixels, &native_pitch)) {
2459 return;
2460 }
2461 SDL_SW_CopyYUVToRGB(texture->yuv, &rect, native->format,
2462 rect.w, rect.h, native_pixels, native_pitch);
2463 SDL_UnlockTexture(native);
2464}
2465#endif // SDL_HAVE_YUV
2466
2467static void SDL_UnlockTextureNative(SDL_Texture *texture)
2468{
2469 SDL_Texture *native = texture->native;
2470 void *native_pixels = NULL;
2471 int native_pitch = 0;
2472 const SDL_Rect *rect = &texture->locked_rect;
2473 const void *pixels = (void *)((Uint8 *)texture->pixels +
2474 rect->y * texture->pitch +
2475 rect->x * SDL_BYTESPERPIXEL(texture->format));
2476 int pitch = texture->pitch;
2477
2478 if (!SDL_LockTexture(native, rect, &native_pixels, &native_pitch)) {
2479 return;
2480 }
2481 SDL_ConvertPixels(rect->w, rect->h,
2482 texture->format, pixels, pitch,
2483 native->format, native_pixels, native_pitch);
2484 SDL_UnlockTexture(native);
2485}
2486
2487void SDL_UnlockTexture(SDL_Texture *texture)
2488{
2489 CHECK_TEXTURE_MAGIC(texture,);
2490
2491 if (texture->access != SDL_TEXTUREACCESS_STREAMING) {
2492 return;
2493 }
2494#ifdef SDL_HAVE_YUV
2495 if (texture->yuv) {
2496 SDL_UnlockTextureYUV(texture);
2497 } else
2498#endif
2499 if (texture->native) {
2500 SDL_UnlockTextureNative(texture);
2501 } else {
2502 SDL_Renderer *renderer = texture->renderer;
2503 renderer->UnlockTexture(renderer, texture);
2504 }
2505
2506 SDL_DestroySurface(texture->locked_surface);
2507 texture->locked_surface = NULL;
2508}
2509
2510bool SDL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
2511{
2512 // texture == NULL is valid and means reset the target to the window
2513 if (texture) {
2514 CHECK_TEXTURE_MAGIC(texture, false);
2515 if (renderer != texture->renderer) {
2516 return SDL_SetError("Texture was not created with this renderer");
2517 }
2518 if (texture->access != SDL_TEXTUREACCESS_TARGET) {
2519 return SDL_SetError("Texture not created with SDL_TEXTUREACCESS_TARGET");
2520 }
2521 if (texture->native) {
2522 // Always render to the native texture
2523 texture = texture->native;
2524 }
2525 }
2526
2527 if (texture == renderer->target) {
2528 // Nothing to do!
2529 return true;
2530 }
2531
2532 FlushRenderCommands(renderer); // time to send everything to the GPU!
2533
2534 SDL_LockMutex(renderer->target_mutex);
2535
2536 renderer->target = texture;
2537 if (texture) {
2538 renderer->view = &texture->view;
2539 } else {
2540 renderer->view = &renderer->main_view;
2541 }
2542 UpdateColorScale(renderer);
2543
2544 if (!renderer->SetRenderTarget(renderer, texture)) {
2545 SDL_UnlockMutex(renderer->target_mutex);
2546 return false;
2547 }
2548
2549 SDL_UnlockMutex(renderer->target_mutex);
2550
2551 if (!QueueCmdSetViewport(renderer)) {
2552 return false;
2553 }
2554 if (!QueueCmdSetClipRect(renderer)) {
2555 return false;
2556 }
2557
2558 // All set!
2559 return true;
2560}
2561
2562SDL_Texture *SDL_GetRenderTarget(SDL_Renderer *renderer)
2563{
2564 CHECK_RENDERER_MAGIC(renderer, NULL);
2565 if (!renderer->target) {
2566 return NULL;
2567 }
2568 return (SDL_Texture *) SDL_GetPointerProperty(SDL_GetTextureProperties(renderer->target), SDL_PROP_TEXTURE_PARENT_POINTER, renderer->target);
2569}
2570
2571static void UpdateLogicalPresentation(SDL_Renderer *renderer)
2572{
2573 SDL_RenderViewState *view = renderer->view;
2574 const bool is_main_view = (view == &renderer->main_view);
2575 const float logical_w = view->logical_w;
2576 const float logical_h = view->logical_h;
2577 int iwidth, iheight;
2578
2579 if (renderer->target) {
2580 iwidth = (int)renderer->target->w;
2581 iheight = (int)renderer->target->h;
2582 } else {
2583 SDL_GetRenderOutputSize(renderer, &iwidth, &iheight);
2584 }
2585
2586 view->logical_src_rect.x = 0.0f;
2587 view->logical_src_rect.y = 0.0f;
2588 view->logical_src_rect.w = logical_w;
2589 view->logical_src_rect.h = logical_h;
2590
2591 if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_DISABLED) {
2592 view->logical_dst_rect.x = 0.0f;
2593 view->logical_dst_rect.y = 0.0f;
2594 view->logical_dst_rect.w = iwidth;
2595 view->logical_dst_rect.h = iheight;
2596 view->logical_offset.x = view->logical_offset.y = 0.0f;
2597 view->logical_scale.x = view->logical_scale.y = 1.0f;
2598 view->current_scale.x = view->scale.x; // skip the multiplications against 1.0f.
2599 view->current_scale.y = view->scale.y;
2600 } else {
2601 const float output_w = (float)iwidth;
2602 const float output_h = (float)iheight;
2603 const float want_aspect = logical_w / logical_h;
2604 const float real_aspect = output_w / output_h;
2605
2606 if ((logical_w <= 0.0f) || (logical_h <= 0.0f)) {
2607 view->logical_dst_rect.x = 0.0f;
2608 view->logical_dst_rect.y = 0.0f;
2609 view->logical_dst_rect.w = output_w;
2610 view->logical_dst_rect.h = output_h;
2611 } else if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_INTEGER_SCALE) {
2612 float scale;
2613 if (want_aspect > real_aspect) {
2614 scale = (float)((int)output_w / (int)logical_w); // This an integer division!
2615 } else {
2616 scale = (float)((int)output_h / (int)logical_h); // This an integer division!
2617 }
2618
2619 if (scale < 1.0f) {
2620 scale = 1.0f;
2621 }
2622
2623 view->logical_dst_rect.w = SDL_floorf(logical_w * scale);
2624 view->logical_dst_rect.x = (output_w - view->logical_dst_rect.w) / 2.0f;
2625 view->logical_dst_rect.h = SDL_floorf(logical_h * scale);
2626 view->logical_dst_rect.y = (output_h - view->logical_dst_rect.h) / 2.0f;
2627
2628 } else if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_STRETCH || SDL_fabsf(want_aspect - real_aspect) < 0.0001f) {
2629 view->logical_dst_rect.x = 0.0f;
2630 view->logical_dst_rect.y = 0.0f;
2631 view->logical_dst_rect.w = output_w;
2632 view->logical_dst_rect.h = output_h;
2633
2634 } else if (want_aspect > real_aspect) {
2635 if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) {
2636 // We want a wider aspect ratio than is available - letterbox it
2637 const float scale = output_w / logical_w;
2638 view->logical_dst_rect.x = 0.0f;
2639 view->logical_dst_rect.w = output_w;
2640 view->logical_dst_rect.h = SDL_floorf(logical_h * scale);
2641 view->logical_dst_rect.y = (output_h - view->logical_dst_rect.h) / 2.0f;
2642 } else { // view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_OVERSCAN
2643 /* We want a wider aspect ratio than is available -
2644 zoom so logical height matches the real height
2645 and the width will grow off the screen
2646 */
2647 const float scale = output_h / logical_h;
2648 view->logical_dst_rect.y = 0.0f;
2649 view->logical_dst_rect.h = output_h;
2650 view->logical_dst_rect.w = SDL_floorf(logical_w * scale);
2651 view->logical_dst_rect.x = (output_w - view->logical_dst_rect.w) / 2.0f;
2652 }
2653 } else {
2654 if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) {
2655 // We want a narrower aspect ratio than is available - use side-bars
2656 const float scale = output_h / logical_h;
2657 view->logical_dst_rect.y = 0.0f;
2658 view->logical_dst_rect.h = output_h;
2659 view->logical_dst_rect.w = SDL_floorf(logical_w * scale);
2660 view->logical_dst_rect.x = (output_w - view->logical_dst_rect.w) / 2.0f;
2661 } else { // view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_OVERSCAN
2662 /* We want a narrower aspect ratio than is available -
2663 zoom so logical width matches the real width
2664 and the height will grow off the screen
2665 */
2666 const float scale = output_w / logical_w;
2667 view->logical_dst_rect.x = 0.0f;
2668 view->logical_dst_rect.w = output_w;
2669 view->logical_dst_rect.h = SDL_floorf(logical_h * scale);
2670 view->logical_dst_rect.y = (output_h - view->logical_dst_rect.h) / 2.0f;
2671 }
2672 }
2673
2674 view->logical_scale.x = (logical_w > 0.0f) ? view->logical_dst_rect.w / logical_w : 0.0f;
2675 view->logical_scale.y = (logical_h > 0.0f) ? view->logical_dst_rect.h / logical_h : 0.0f;
2676 view->current_scale.x = view->scale.x * view->logical_scale.x;
2677 view->current_scale.y = view->scale.y * view->logical_scale.y;
2678 view->logical_offset.x = view->logical_dst_rect.x;
2679 view->logical_offset.y = view->logical_dst_rect.y;
2680 }
2681
2682 if (is_main_view) {
2683 // This makes sure the dpi_scale is right. It also sets pixel_w and pixel_h, but we're going to change them directly below here.
2684 UpdateMainViewDimensions(renderer);
2685 }
2686
2687 view->pixel_w = (int) view->logical_dst_rect.w;
2688 view->pixel_h = (int) view->logical_dst_rect.h;
2689 UpdatePixelViewport(renderer, view);
2690 UpdatePixelClipRect(renderer, view);
2691}
2692
2693bool SDL_SetRenderLogicalPresentation(SDL_Renderer *renderer, int w, int h, SDL_RendererLogicalPresentation mode)
2694{
2695 CHECK_RENDERER_MAGIC(renderer, false);
2696
2697 SDL_RenderViewState *view = renderer->view;
2698 view->logical_presentation_mode = mode;
2699 view->logical_w = w;
2700 view->logical_h = h;
2701
2702 UpdateLogicalPresentation(renderer);
2703
2704 return true;
2705}
2706
2707bool SDL_GetRenderLogicalPresentation(SDL_Renderer *renderer, int *w, int *h, SDL_RendererLogicalPresentation *mode)
2708{
2709 #define SETVAL(ptr, val) if (ptr) { *ptr = val; }
2710
2711 SETVAL(w, 0);
2712 SETVAL(h, 0);
2713 SETVAL(mode, SDL_LOGICAL_PRESENTATION_DISABLED);
2714
2715 CHECK_RENDERER_MAGIC(renderer, false);
2716
2717 const SDL_RenderViewState *view = renderer->view;
2718 SETVAL(w, view->logical_w);
2719 SETVAL(h, view->logical_h);
2720 SETVAL(mode, view->logical_presentation_mode);
2721
2722 #undef SETVAL
2723
2724 return true;
2725}
2726
2727bool SDL_GetRenderLogicalPresentationRect(SDL_Renderer *renderer, SDL_FRect *rect)
2728{
2729 if (rect) {
2730 SDL_zerop(rect);
2731 }
2732
2733 CHECK_RENDERER_MAGIC(renderer, false);
2734
2735 if (rect) {
2736 SDL_copyp(rect, &renderer->view->logical_dst_rect);
2737 }
2738 return true;
2739}
2740
2741static void SDL_RenderLogicalBorders(SDL_Renderer *renderer, const SDL_FRect *dst)
2742{
2743 const SDL_RenderViewState *view = renderer->view;
2744
2745 if (dst->x > 0.0f || dst->y > 0.0f) {
2746 SDL_BlendMode saved_blend_mode = renderer->blendMode;
2747 SDL_FColor saved_color = renderer->color;
2748
2749 SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
2750 SDL_SetRenderDrawColorFloat(renderer, 0.0f, 0.0f, 0.0f, 1.0f);
2751
2752 if (dst->x > 0.0f) {
2753 SDL_FRect rect;
2754
2755 rect.x = 0.0f;
2756 rect.y = 0.0f;
2757 rect.w = dst->x;
2758 rect.h = (float)view->pixel_h;
2759 SDL_RenderFillRect(renderer, &rect);
2760
2761 rect.x = dst->x + dst->w;
2762 rect.w = (float)view->pixel_w - rect.x;
2763 SDL_RenderFillRect(renderer, &rect);
2764 }
2765
2766 if (dst->y > 0.0f) {
2767 SDL_FRect rect;
2768
2769 rect.x = 0.0f;
2770 rect.y = 0.0f;
2771 rect.w = (float)view->pixel_w;
2772 rect.h = dst->y;
2773 SDL_RenderFillRect(renderer, &rect);
2774
2775 rect.y = dst->y + dst->h;
2776 rect.h = (float)view->pixel_h - rect.y;
2777 SDL_RenderFillRect(renderer, &rect);
2778 }
2779
2780 SDL_SetRenderDrawBlendMode(renderer, saved_blend_mode);
2781 SDL_SetRenderDrawColorFloat(renderer, saved_color.r, saved_color.g, saved_color.b, saved_color.a);
2782 }
2783}
2784
2785static void SDL_RenderLogicalPresentation(SDL_Renderer *renderer)
2786{
2787 SDL_assert(renderer->view == &renderer->main_view);
2788
2789 SDL_RenderViewState *view = &renderer->main_view;
2790 const SDL_RendererLogicalPresentation mode = view->logical_presentation_mode;
2791 if (mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) {
2792 // save off some state we're going to trample.
2793 const int logical_w = view->logical_w;
2794 const int logical_h = view->logical_h;
2795 const float scale_x = view->scale.x;
2796 const float scale_y = view->scale.y;
2797 const bool clipping_enabled = view->clipping_enabled;
2798 SDL_Rect orig_viewport, orig_cliprect;
2799 const SDL_FRect logical_dst_rect = view->logical_dst_rect;
2800
2801 SDL_copyp(&orig_viewport, &view->viewport);
2802 if (clipping_enabled) {
2803 SDL_copyp(&orig_cliprect, &view->clip_rect);
2804 }
2805
2806 // trample some state.
2807 SDL_SetRenderLogicalPresentation(renderer, logical_w, logical_h, SDL_LOGICAL_PRESENTATION_DISABLED);
2808 SDL_SetRenderViewport(renderer, NULL);
2809 if (clipping_enabled) {
2810 SDL_SetRenderClipRect(renderer, NULL);
2811 }
2812 SDL_SetRenderScale(renderer, 1.0f, 1.0f);
2813
2814 // draw the borders.
2815 SDL_RenderLogicalBorders(renderer, &logical_dst_rect);
2816
2817 // now set everything back.
2818 view->logical_presentation_mode = mode;
2819 SDL_SetRenderViewport(renderer, &orig_viewport);
2820 if (clipping_enabled) {
2821 SDL_SetRenderClipRect(renderer, &orig_cliprect);
2822 }
2823 SDL_SetRenderScale(renderer, scale_x, scale_y);
2824
2825 SDL_SetRenderLogicalPresentation(renderer, logical_w, logical_h, mode);
2826 }
2827}
2828
2829static bool SDL_RenderVectorFromWindow(SDL_Renderer *renderer, float window_dx, float window_dy, float *dx, float *dy)
2830{
2831 // Convert from window coordinates to pixels within the window
2832 window_dx *= renderer->dpi_scale.x;
2833 window_dy *= renderer->dpi_scale.y;
2834
2835 // Convert from pixels within the window to pixels within the view
2836 const SDL_RenderViewState *view = &renderer->main_view;
2837 if (view->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED) {
2838 const SDL_FRect *src = &view->logical_src_rect;
2839 const SDL_FRect *dst = &view->logical_dst_rect;
2840 window_dx = (window_dx * src->w) / dst->w;
2841 window_dy = (window_dy * src->h) / dst->h;
2842 }
2843
2844 window_dx /= view->scale.x;
2845 window_dy /= view->scale.y;
2846
2847 *dx = window_dx;
2848 *dy = window_dy;
2849 return true;
2850}
2851
2852bool SDL_RenderCoordinatesFromWindow(SDL_Renderer *renderer, float window_x, float window_y, float *x, float *y)
2853{
2854 float render_x, render_y;
2855
2856 CHECK_RENDERER_MAGIC(renderer, false);
2857
2858 // Convert from window coordinates to pixels within the window
2859 render_x = window_x * renderer->dpi_scale.x;
2860 render_y = window_y * renderer->dpi_scale.y;
2861
2862 // Convert from pixels within the window to pixels within the view
2863 const SDL_RenderViewState *view = &renderer->main_view;
2864 if (view->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED) {
2865 const SDL_FRect *src = &view->logical_src_rect;
2866 const SDL_FRect *dst = &view->logical_dst_rect;
2867 render_x = ((render_x - dst->x) * src->w) / dst->w;
2868 render_y = ((render_y - dst->y) * src->h) / dst->h;
2869 }
2870
2871 render_x = (render_x / view->scale.x) - view->viewport.x;
2872 render_y = (render_y / view->scale.y) - view->viewport.y;
2873
2874 if (x) {
2875 *x = render_x;
2876 }
2877 if (y) {
2878 *y = render_y;
2879 }
2880 return true;
2881}
2882
2883bool SDL_RenderCoordinatesToWindow(SDL_Renderer *renderer, float x, float y, float *window_x, float *window_y)
2884{
2885 CHECK_RENDERER_MAGIC(renderer, false);
2886
2887 const SDL_RenderViewState *view = &renderer->main_view;
2888 x = (view->viewport.x + x) * view->scale.x;
2889 y = (view->viewport.y + y) * view->scale.y;
2890
2891 // Convert from render coordinates to pixels within the window
2892 if (view->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED) {
2893 const SDL_FRect *src = &view->logical_src_rect;
2894 const SDL_FRect *dst = &view->logical_dst_rect;
2895 x = dst->x + ((x * dst->w) / src->w);
2896 y = dst->y + ((y * dst->h) / src->h);
2897 }
2898
2899 // Convert from pixels within the window to window coordinates
2900 x /= renderer->dpi_scale.x;
2901 y /= renderer->dpi_scale.y;
2902
2903 if (window_x) {
2904 *window_x = x;
2905 }
2906 if (window_y) {
2907 *window_y = y;
2908 }
2909 return true;
2910}
2911
2912bool SDL_ConvertEventToRenderCoordinates(SDL_Renderer *renderer, SDL_Event *event)
2913{
2914 CHECK_RENDERER_MAGIC(renderer, false);
2915
2916 if (event->type == SDL_EVENT_MOUSE_MOTION) {
2917 SDL_Window *window = SDL_GetWindowFromID(event->motion.windowID);
2918 if (window == renderer->window) {
2919 SDL_RenderCoordinatesFromWindow(renderer, event->motion.x, event->motion.y, &event->motion.x, &event->motion.y);
2920 SDL_RenderVectorFromWindow(renderer, event->motion.xrel, event->motion.yrel, &event->motion.xrel, &event->motion.yrel);
2921 }
2922 } else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN ||
2923 event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
2924 SDL_Window *window = SDL_GetWindowFromID(event->button.windowID);
2925 if (window == renderer->window) {
2926 SDL_RenderCoordinatesFromWindow(renderer, event->button.x, event->button.y, &event->button.x, &event->button.y);
2927 }
2928 } else if (event->type == SDL_EVENT_MOUSE_WHEEL) {
2929 SDL_Window *window = SDL_GetWindowFromID(event->wheel.windowID);
2930 if (window == renderer->window) {
2931 SDL_RenderCoordinatesFromWindow(renderer, event->wheel.mouse_x,
2932 event->wheel.mouse_y,
2933 &event->wheel.mouse_x,
2934 &event->wheel.mouse_y);
2935 }
2936 } else if (event->type == SDL_EVENT_FINGER_DOWN ||
2937 event->type == SDL_EVENT_FINGER_UP ||
2938 event->type == SDL_EVENT_FINGER_CANCELED ||
2939 event->type == SDL_EVENT_FINGER_MOTION) {
2940 // FIXME: Are these events guaranteed to be window relative?
2941 if (renderer->window) {
2942 int w, h;
2943 if (!SDL_GetWindowSize(renderer->window, &w, &h)) {
2944 return false;
2945 }
2946 SDL_RenderCoordinatesFromWindow(renderer, event->tfinger.x * w, event->tfinger.y * h, &event->tfinger.x, &event->tfinger.y);
2947 SDL_RenderVectorFromWindow(renderer, event->tfinger.dx * w, event->tfinger.dy * h, &event->tfinger.dx, &event->tfinger.dy);
2948 }
2949 } else if (event->type == SDL_EVENT_PEN_MOTION) {
2950 SDL_Window *window = SDL_GetWindowFromID(event->pmotion.windowID);
2951 if (window == renderer->window) {
2952 SDL_RenderCoordinatesFromWindow(renderer, event->pmotion.x, event->pmotion.y, &event->pmotion.x, &event->pmotion.y);
2953 }
2954 } else if ((event->type == SDL_EVENT_PEN_DOWN) || (event->type == SDL_EVENT_PEN_UP)) {
2955 SDL_Window *window = SDL_GetWindowFromID(event->ptouch.windowID);
2956 if (window == renderer->window) {
2957 SDL_RenderCoordinatesFromWindow(renderer, event->ptouch.x, event->ptouch.y, &event->ptouch.x, &event->ptouch.y);
2958 }
2959 } else if ((event->type == SDL_EVENT_PEN_BUTTON_DOWN) || (event->type == SDL_EVENT_PEN_BUTTON_UP)) {
2960 SDL_Window *window = SDL_GetWindowFromID(event->pbutton.windowID);
2961 if (window == renderer->window) {
2962 SDL_RenderCoordinatesFromWindow(renderer, event->pbutton.x, event->pbutton.y, &event->pbutton.x, &event->pbutton.y);
2963 }
2964 } else if (event->type == SDL_EVENT_PEN_AXIS) {
2965 SDL_Window *window = SDL_GetWindowFromID(event->paxis.windowID);
2966 if (window == renderer->window) {
2967 SDL_RenderCoordinatesFromWindow(renderer, event->paxis.x, event->paxis.y, &event->paxis.x, &event->paxis.y);
2968 }
2969 } else if (event->type == SDL_EVENT_DROP_POSITION ||
2970 event->type == SDL_EVENT_DROP_FILE ||
2971 event->type == SDL_EVENT_DROP_TEXT ||
2972 event->type == SDL_EVENT_DROP_COMPLETE) {
2973 SDL_Window *window = SDL_GetWindowFromID(event->drop.windowID);
2974 if (window == renderer->window) {
2975 SDL_RenderCoordinatesFromWindow(renderer, event->drop.x, event->drop.y, &event->drop.x, &event->drop.y);
2976 }
2977 }
2978 return true;
2979}
2980
2981bool SDL_SetRenderViewport(SDL_Renderer *renderer, const SDL_Rect *rect)
2982{
2983 CHECK_RENDERER_MAGIC(renderer, false);
2984
2985 SDL_RenderViewState *view = renderer->view;
2986 if (rect) {
2987 if ((rect->w < 0) || (rect->h < 0)) {
2988 return SDL_SetError("rect has a negative size");
2989 }
2990 SDL_copyp(&view->viewport, rect);
2991 } else {
2992 view->viewport.x = view->viewport.y = 0;
2993 view->viewport.w = view->viewport.h = -1;
2994 }
2995 UpdatePixelViewport(renderer, view);
2996
2997 return QueueCmdSetViewport(renderer);
2998}
2999
3000bool SDL_GetRenderViewport(SDL_Renderer *renderer, SDL_Rect *rect)
3001{
3002 if (rect) {
3003 SDL_zerop(rect);
3004 }
3005
3006 CHECK_RENDERER_MAGIC(renderer, false);
3007
3008 if (rect) {
3009 const SDL_RenderViewState *view = renderer->view;
3010 rect->x = view->viewport.x;
3011 rect->y = view->viewport.y;
3012 if (view->viewport.w >= 0) {
3013 rect->w = view->viewport.w;
3014 } else {
3015 rect->w = (int)SDL_ceilf(view->pixel_w / view->current_scale.x);
3016 }
3017 if (view->viewport.h >= 0) {
3018 rect->h = view->viewport.h;
3019 } else {
3020 rect->h = (int)SDL_ceilf(view->pixel_h / view->current_scale.y);
3021 }
3022 }
3023 return true;
3024}
3025
3026bool SDL_RenderViewportSet(SDL_Renderer *renderer)
3027{
3028 CHECK_RENDERER_MAGIC(renderer, false);
3029
3030 const SDL_RenderViewState *view = renderer->view;
3031 return (view->viewport.w >= 0 && view->viewport.h >= 0);
3032}
3033
3034static void GetRenderViewportSize(SDL_Renderer *renderer, SDL_FRect *rect)
3035{
3036 const SDL_RenderViewState *view = renderer->view;
3037 const float scale_x = view->current_scale.x;
3038 const float scale_y = view->current_scale.y;
3039
3040 rect->x = 0.0f;
3041 rect->y = 0.0f;
3042
3043 if (view->viewport.w >= 0) {
3044 rect->w = (float)view->viewport.w;
3045 } else {
3046 rect->w = view->pixel_w / scale_x;
3047 }
3048
3049 if (view->viewport.h >= 0) {
3050 rect->h = (float)view->viewport.h;
3051 } else {
3052 rect->h = view->pixel_h / scale_y;
3053 }
3054}
3055
3056bool SDL_GetRenderSafeArea(SDL_Renderer *renderer, SDL_Rect *rect)
3057{
3058 if (rect) {
3059 SDL_zerop(rect);
3060 }
3061
3062 CHECK_RENDERER_MAGIC(renderer, false);
3063
3064 if (renderer->target || !renderer->window) {
3065 // The entire viewport is safe for rendering
3066 return SDL_GetRenderViewport(renderer, rect);
3067 }
3068
3069 if (rect) {
3070 // Get the window safe rect
3071 SDL_Rect safe;
3072 if (!SDL_GetWindowSafeArea(renderer->window, &safe)) {
3073 return false;
3074 }
3075
3076 // Convert the coordinates into the render space
3077 float minx = (float)safe.x;
3078 float miny = (float)safe.y;
3079 float maxx = (float)safe.x + safe.w;
3080 float maxy = (float)safe.y + safe.h;
3081 if (!SDL_RenderCoordinatesFromWindow(renderer, minx, miny, &minx, &miny) ||
3082 !SDL_RenderCoordinatesFromWindow(renderer, maxx, maxy, &maxx, &maxy)) {
3083 return false;
3084 }
3085
3086 rect->x = (int)SDL_ceilf(minx);
3087 rect->y = (int)SDL_ceilf(miny);
3088 rect->w = (int)SDL_ceilf(maxx - minx);
3089 rect->h = (int)SDL_ceilf(maxy - miny);
3090
3091 // Clip with the viewport
3092 SDL_Rect viewport;
3093 if (!SDL_GetRenderViewport(renderer, &viewport)) {
3094 return false;
3095 }
3096 if (!SDL_GetRectIntersection(rect, &viewport, rect)) {
3097 return SDL_SetError("No safe area within viewport");
3098 }
3099 }
3100 return true;
3101}
3102
3103bool SDL_SetRenderClipRect(SDL_Renderer *renderer, const SDL_Rect *rect)
3104{
3105 CHECK_RENDERER_MAGIC(renderer, false)
3106
3107 SDL_RenderViewState *view = renderer->view;
3108 if (rect && rect->w >= 0 && rect->h >= 0) {
3109 view->clipping_enabled = true;
3110 SDL_copyp(&view->clip_rect, rect);
3111 } else {
3112 view->clipping_enabled = false;
3113 SDL_zero(view->clip_rect);
3114 }
3115 UpdatePixelClipRect(renderer, view);
3116
3117 return QueueCmdSetClipRect(renderer);
3118}
3119
3120bool SDL_GetRenderClipRect(SDL_Renderer *renderer, SDL_Rect *rect)
3121{
3122 if (rect) {
3123 SDL_zerop(rect);
3124 }
3125
3126 CHECK_RENDERER_MAGIC(renderer, false)
3127
3128 if (rect) {
3129 SDL_copyp(rect, &renderer->view->clip_rect);
3130 }
3131 return true;
3132}
3133
3134bool SDL_RenderClipEnabled(SDL_Renderer *renderer)
3135{
3136 CHECK_RENDERER_MAGIC(renderer, false)
3137 return renderer->view->clipping_enabled;
3138}
3139
3140bool SDL_SetRenderScale(SDL_Renderer *renderer, float scaleX, float scaleY)
3141{
3142 bool result = true;
3143
3144 CHECK_RENDERER_MAGIC(renderer, false);
3145
3146 SDL_RenderViewState *view = renderer->view;
3147
3148 if ((view->scale.x == scaleX) && (view->scale.y == scaleY)) {
3149 return true;
3150 }
3151
3152 view->scale.x = scaleX;
3153 view->scale.y = scaleY;
3154 view->current_scale.x = scaleX * view->logical_scale.x;
3155 view->current_scale.y = scaleY * view->logical_scale.y;
3156 UpdatePixelViewport(renderer, view);
3157 UpdatePixelClipRect(renderer, view);
3158
3159 // The scale affects the existing viewport and clip rectangle
3160 result &= QueueCmdSetViewport(renderer);
3161 result &= QueueCmdSetClipRect(renderer);
3162 return result;
3163}
3164
3165bool SDL_GetRenderScale(SDL_Renderer *renderer, float *scaleX, float *scaleY)
3166{
3167 if (scaleX) {
3168 *scaleX = 1.0f;
3169 }
3170 if (scaleY) {
3171 *scaleY = 1.0f;
3172 }
3173
3174 CHECK_RENDERER_MAGIC(renderer, false);
3175
3176 const SDL_RenderViewState *view = renderer->view;
3177
3178 if (scaleX) {
3179 *scaleX = view->scale.x;
3180 }
3181 if (scaleY) {
3182 *scaleY = view->scale.y;
3183 }
3184 return true;
3185}
3186
3187bool SDL_SetRenderDrawColor(SDL_Renderer *renderer, Uint8 r, Uint8 g, Uint8 b, Uint8 a)
3188{
3189 const float fR = (float)r / 255.0f;
3190 const float fG = (float)g / 255.0f;
3191 const float fB = (float)b / 255.0f;
3192 const float fA = (float)a / 255.0f;
3193
3194 return SDL_SetRenderDrawColorFloat(renderer, fR, fG, fB, fA);
3195}
3196
3197bool SDL_SetRenderDrawColorFloat(SDL_Renderer *renderer, float r, float g, float b, float a)
3198{
3199 CHECK_RENDERER_MAGIC(renderer, false);
3200
3201 renderer->color.r = r;
3202 renderer->color.g = g;
3203 renderer->color.b = b;
3204 renderer->color.a = a;
3205 return true;
3206}
3207
3208bool SDL_GetRenderDrawColor(SDL_Renderer *renderer, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a)
3209{
3210 float fR, fG, fB, fA;
3211
3212 if (!SDL_GetRenderDrawColorFloat(renderer, &fR, &fG, &fB, &fA)) {
3213 if (r) {
3214 *r = 0;
3215 }
3216 if (g) {
3217 *g = 0;
3218 }
3219 if (b) {
3220 *b = 0;
3221 }
3222 if (a) {
3223 *a = 0;
3224 }
3225 return false;
3226 }
3227
3228 if (r) {
3229 *r = (Uint8)(fR * 255.0f);
3230 }
3231 if (g) {
3232 *g = (Uint8)(fG * 255.0f);
3233 }
3234 if (b) {
3235 *b = (Uint8)(fB * 255.0f);
3236 }
3237 if (a) {
3238 *a = (Uint8)(fA * 255.0f);
3239 }
3240 return true;
3241}
3242
3243bool SDL_GetRenderDrawColorFloat(SDL_Renderer *renderer, float *r, float *g, float *b, float *a)
3244{
3245 SDL_FColor color;
3246
3247 if (r) {
3248 *r = 0.0f;
3249 }
3250 if (g) {
3251 *g = 0.0f;
3252 }
3253 if (b) {
3254 *b = 0.0f;
3255 }
3256 if (a) {
3257 *a = 0.0f;
3258 }
3259
3260 CHECK_RENDERER_MAGIC(renderer, false);
3261
3262 color = renderer->color;
3263
3264 if (r) {
3265 *r = color.r;
3266 }
3267 if (g) {
3268 *g = color.g;
3269 }
3270 if (b) {
3271 *b = color.b;
3272 }
3273 if (a) {
3274 *a = color.a;
3275 }
3276 return true;
3277}
3278
3279bool SDL_SetRenderColorScale(SDL_Renderer *renderer, float scale)
3280{
3281 CHECK_RENDERER_MAGIC(renderer, false);
3282
3283 renderer->desired_color_scale = scale;
3284 UpdateColorScale(renderer);
3285 return true;
3286}
3287
3288bool SDL_GetRenderColorScale(SDL_Renderer *renderer, float *scale)
3289{
3290 if (scale) {
3291 *scale = 1.0f;
3292 }
3293
3294 CHECK_RENDERER_MAGIC(renderer, false);
3295
3296 if (scale) {
3297 *scale = renderer->desired_color_scale;
3298 }
3299 return true;
3300}
3301
3302bool SDL_SetRenderDrawBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode)
3303{
3304 CHECK_RENDERER_MAGIC(renderer, false);
3305
3306 if (blendMode == SDL_BLENDMODE_INVALID) {
3307 return SDL_InvalidParamError("blendMode");
3308 }
3309
3310 if (blendMode == SDL_BLENDMODE_INVALID) {
3311 return SDL_InvalidParamError("blendMode");
3312 }
3313
3314 if (!IsSupportedBlendMode(renderer, blendMode)) {
3315 return SDL_Unsupported();
3316 }
3317
3318 renderer->blendMode = blendMode;
3319 return true;
3320}
3321
3322bool SDL_GetRenderDrawBlendMode(SDL_Renderer *renderer, SDL_BlendMode *blendMode)
3323{
3324 if (blendMode) {
3325 *blendMode = SDL_BLENDMODE_INVALID;
3326 }
3327
3328 CHECK_RENDERER_MAGIC(renderer, false);
3329
3330 if (blendMode) {
3331 *blendMode = renderer->blendMode;
3332 }
3333 return true;
3334}
3335
3336bool SDL_RenderClear(SDL_Renderer *renderer)
3337{
3338 CHECK_RENDERER_MAGIC(renderer, false);
3339
3340 return QueueCmdClear(renderer);
3341}
3342
3343bool SDL_RenderPoint(SDL_Renderer *renderer, float x, float y)
3344{
3345 SDL_FPoint fpoint;
3346 fpoint.x = x;
3347 fpoint.y = y;
3348 return SDL_RenderPoints(renderer, &fpoint, 1);
3349}
3350
3351static bool RenderPointsWithRects(SDL_Renderer *renderer, const SDL_FPoint *fpoints, const int count)
3352{
3353 bool result;
3354 bool isstack;
3355 SDL_FRect *frects;
3356 int i;
3357
3358 if (count < 1) {
3359 return true;
3360 }
3361
3362 frects = SDL_small_alloc(SDL_FRect, count, &isstack);
3363 if (!frects) {
3364 return false;
3365 }
3366
3367 const SDL_RenderViewState *view = renderer->view;
3368 const float scale_x = view->current_scale.x;
3369 const float scale_y = view->current_scale.y;
3370 for (i = 0; i < count; ++i) {
3371 frects[i].x = fpoints[i].x * scale_x;
3372 frects[i].y = fpoints[i].y * scale_y;
3373 frects[i].w = scale_x;
3374 frects[i].h = scale_y;
3375 }
3376
3377 result = QueueCmdFillRects(renderer, frects, count);
3378
3379 SDL_small_free(frects, isstack);
3380
3381 return result;
3382}
3383
3384bool SDL_RenderPoints(SDL_Renderer *renderer, const SDL_FPoint *points, int count)
3385{
3386 bool result;
3387
3388 CHECK_RENDERER_MAGIC(renderer, false);
3389
3390 if (!points) {
3391 return SDL_InvalidParamError("SDL_RenderPoints(): points");
3392 }
3393 if (count < 1) {
3394 return true;
3395 }
3396
3397#if DONT_DRAW_WHILE_HIDDEN
3398 // Don't draw while we're hidden
3399 if (renderer->hidden) {
3400 return true;
3401 }
3402#endif
3403
3404 const SDL_RenderViewState *view = renderer->view;
3405 if ((view->current_scale.x != 1.0f) || (view->current_scale.y != 1.0f)) {
3406 result = RenderPointsWithRects(renderer, points, count);
3407 } else {
3408 result = QueueCmdDrawPoints(renderer, points, count);
3409 }
3410 return result;
3411}
3412
3413bool SDL_RenderLine(SDL_Renderer *renderer, float x1, float y1, float x2, float y2)
3414{
3415 SDL_FPoint points[2];
3416 points[0].x = x1;
3417 points[0].y = y1;
3418 points[1].x = x2;
3419 points[1].y = y2;
3420 return SDL_RenderLines(renderer, points, 2);
3421}
3422
3423static bool RenderLineBresenham(SDL_Renderer *renderer, int x1, int y1, int x2, int y2, bool draw_last)
3424{
3425 const SDL_RenderViewState *view = renderer->view;
3426 const int MAX_PIXELS = SDL_max(view->pixel_w, view->pixel_h) * 4;
3427 int i, deltax, deltay, numpixels;
3428 int d, dinc1, dinc2;
3429 int x, xinc1, xinc2;
3430 int y, yinc1, yinc2;
3431 bool result;
3432 bool isstack;
3433 SDL_FPoint *points;
3434 SDL_Rect viewport;
3435
3436 /* the backend might clip this further to the clipping rect, but we
3437 just want a basic safety against generating millions of points for
3438 massive lines. */
3439 viewport = view->pixel_viewport;
3440 viewport.x = 0;
3441 viewport.y = 0;
3442 if (!SDL_GetRectAndLineIntersection(&viewport, &x1, &y1, &x2, &y2)) {
3443 return true;
3444 }
3445
3446 deltax = SDL_abs(x2 - x1);
3447 deltay = SDL_abs(y2 - y1);
3448
3449 if (deltax >= deltay) {
3450 numpixels = deltax + 1;
3451 d = (2 * deltay) - deltax;
3452 dinc1 = deltay * 2;
3453 dinc2 = (deltay - deltax) * 2;
3454 xinc1 = 1;
3455 xinc2 = 1;
3456 yinc1 = 0;
3457 yinc2 = 1;
3458 } else {
3459 numpixels = deltay + 1;
3460 d = (2 * deltax) - deltay;
3461 dinc1 = deltax * 2;
3462 dinc2 = (deltax - deltay) * 2;
3463 xinc1 = 0;
3464 xinc2 = 1;
3465 yinc1 = 1;
3466 yinc2 = 1;
3467 }
3468
3469 if (x1 > x2) {
3470 xinc1 = -xinc1;
3471 xinc2 = -xinc2;
3472 }
3473 if (y1 > y2) {
3474 yinc1 = -yinc1;
3475 yinc2 = -yinc2;
3476 }
3477
3478 x = x1;
3479 y = y1;
3480
3481 if (!draw_last) {
3482 --numpixels;
3483 }
3484
3485 if (numpixels > MAX_PIXELS) {
3486 return SDL_SetError("Line too long (tried to draw %d pixels, max %d)", numpixels, MAX_PIXELS);
3487 }
3488
3489 points = SDL_small_alloc(SDL_FPoint, numpixels, &isstack);
3490 if (!points) {
3491 return false;
3492 }
3493 for (i = 0; i < numpixels; ++i) {
3494 points[i].x = (float)x;
3495 points[i].y = (float)y;
3496
3497 if (d < 0) {
3498 d += dinc1;
3499 x += xinc1;
3500 y += yinc1;
3501 } else {
3502 d += dinc2;
3503 x += xinc2;
3504 y += yinc2;
3505 }
3506 }
3507
3508 if ((view->current_scale.x != 1.0f) || (view->current_scale.y != 1.0f)) {
3509 result = RenderPointsWithRects(renderer, points, numpixels);
3510 } else {
3511 result = QueueCmdDrawPoints(renderer, points, numpixels);
3512 }
3513
3514 SDL_small_free(points, isstack);
3515
3516 return result;
3517}
3518
3519static bool RenderLinesWithRectsF(SDL_Renderer *renderer, const SDL_FPoint *points, const int count)
3520{
3521 const SDL_RenderViewState *view = renderer->view;
3522 const float scale_x = view->current_scale.x;
3523 const float scale_y = view->current_scale.y;
3524 SDL_FRect *frect;
3525 SDL_FRect *frects;
3526 int i, nrects = 0;
3527 bool result = true;
3528 bool isstack;
3529 bool drew_line = false;
3530 bool draw_last = false;
3531
3532 frects = SDL_small_alloc(SDL_FRect, count - 1, &isstack);
3533 if (!frects) {
3534 return false;
3535 }
3536
3537 for (i = 0; i < count - 1; ++i) {
3538 bool same_x = (points[i].x == points[i + 1].x);
3539 bool same_y = (points[i].y == points[i + 1].y);
3540
3541 if (i == (count - 2)) {
3542 if (!drew_line || points[i + 1].x != points[0].x || points[i + 1].y != points[0].y) {
3543 draw_last = true;
3544 }
3545 } else {
3546 if (same_x && same_y) {
3547 continue;
3548 }
3549 }
3550 if (same_x) {
3551 const float minY = SDL_min(points[i].y, points[i + 1].y);
3552 const float maxY = SDL_max(points[i].y, points[i + 1].y);
3553
3554 frect = &frects[nrects++];
3555 frect->x = points[i].x * scale_x;
3556 frect->y = minY * scale_y;
3557 frect->w = scale_x;
3558 frect->h = (maxY - minY + draw_last) * scale_y;
3559 if (!draw_last && points[i + 1].y < points[i].y) {
3560 frect->y += scale_y;
3561 }
3562 } else if (same_y) {
3563 const float minX = SDL_min(points[i].x, points[i + 1].x);
3564 const float maxX = SDL_max(points[i].x, points[i + 1].x);
3565
3566 frect = &frects[nrects++];
3567 frect->x = minX * scale_x;
3568 frect->y = points[i].y * scale_y;
3569 frect->w = (maxX - minX + draw_last) * scale_x;
3570 frect->h = scale_y;
3571 if (!draw_last && points[i + 1].x < points[i].x) {
3572 frect->x += scale_x;
3573 }
3574 } else {
3575 result &= RenderLineBresenham(renderer, (int)SDL_roundf(points[i].x), (int)SDL_roundf(points[i].y),
3576 (int)SDL_roundf(points[i + 1].x), (int)SDL_roundf(points[i + 1].y), draw_last);
3577 }
3578 drew_line = true;
3579 }
3580
3581 if (nrects) {
3582 result &= QueueCmdFillRects(renderer, frects, nrects);
3583 }
3584
3585 SDL_small_free(frects, isstack);
3586
3587 return result;
3588}
3589
3590bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count)
3591{
3592 bool result = true;
3593
3594 CHECK_RENDERER_MAGIC(renderer, false);
3595
3596 if (!points) {
3597 return SDL_InvalidParamError("SDL_RenderLines(): points");
3598 }
3599 if (count < 2) {
3600 return true;
3601 }
3602
3603#if DONT_DRAW_WHILE_HIDDEN
3604 // Don't draw while we're hidden
3605 if (renderer->hidden) {
3606 return true;
3607 }
3608#endif
3609
3610 SDL_RenderViewState *view = renderer->view;
3611 const bool islogical = ((view == &renderer->main_view) && (view->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED));
3612
3613 if (islogical || (renderer->line_method == SDL_RENDERLINEMETHOD_GEOMETRY)) {
3614 const float scale_x = view->current_scale.x;
3615 const float scale_y = view->current_scale.y;
3616 bool isstack1;
3617 bool isstack2;
3618 float *xy = SDL_small_alloc(float, 4 * 2 * count, &isstack1);
3619 int *indices = SDL_small_alloc(int, (4) * 3 * (count - 1) + (2) * 3 * (count), &isstack2);
3620
3621 if (xy && indices) {
3622 int i;
3623 float *ptr_xy = xy;
3624 int *ptr_indices = indices;
3625 const int xy_stride = 2 * sizeof(float);
3626 int num_vertices = 4 * count;
3627 int num_indices = 0;
3628 const int size_indices = 4;
3629 int cur_index = -4;
3630 const int is_looping = (points[0].x == points[count - 1].x && points[0].y == points[count - 1].y);
3631 SDL_FPoint p; // previous point
3632 p.x = p.y = 0.0f;
3633 /* p q
3634
3635 0----1------ 4----5
3636 | \ |``\ | \ |
3637 | \ | ` `\| \ |
3638 3----2-------7----6
3639 */
3640 for (i = 0; i < count; ++i) {
3641 SDL_FPoint q = points[i]; // current point
3642
3643 q.x *= scale_x;
3644 q.y *= scale_y;
3645
3646 *ptr_xy++ = q.x;
3647 *ptr_xy++ = q.y;
3648 *ptr_xy++ = q.x + scale_x;
3649 *ptr_xy++ = q.y;
3650 *ptr_xy++ = q.x + scale_x;
3651 *ptr_xy++ = q.y + scale_y;
3652 *ptr_xy++ = q.x;
3653 *ptr_xy++ = q.y + scale_y;
3654
3655#define ADD_TRIANGLE(i1, i2, i3) \
3656 *ptr_indices++ = cur_index + (i1); \
3657 *ptr_indices++ = cur_index + (i2); \
3658 *ptr_indices++ = cur_index + (i3); \
3659 num_indices += 3;
3660
3661 // closed polyline, don´t draw twice the point
3662 if (i || is_looping == 0) {
3663 ADD_TRIANGLE(4, 5, 6)
3664 ADD_TRIANGLE(4, 6, 7)
3665 }
3666
3667 // first point only, no segment
3668 if (i == 0) {
3669 p = q;
3670 cur_index += 4;
3671 continue;
3672 }
3673
3674 // draw segment
3675 if (p.y == q.y) {
3676 if (p.x < q.x) {
3677 ADD_TRIANGLE(1, 4, 7)
3678 ADD_TRIANGLE(1, 7, 2)
3679 } else {
3680 ADD_TRIANGLE(5, 0, 3)
3681 ADD_TRIANGLE(5, 3, 6)
3682 }
3683 } else if (p.x == q.x) {
3684 if (p.y < q.y) {
3685 ADD_TRIANGLE(2, 5, 4)
3686 ADD_TRIANGLE(2, 4, 3)
3687 } else {
3688 ADD_TRIANGLE(6, 1, 0)
3689 ADD_TRIANGLE(6, 0, 7)
3690 }
3691 } else {
3692 if (p.y < q.y) {
3693 if (p.x < q.x) {
3694 ADD_TRIANGLE(1, 5, 4)
3695 ADD_TRIANGLE(1, 4, 2)
3696 ADD_TRIANGLE(2, 4, 7)
3697 ADD_TRIANGLE(2, 7, 3)
3698 } else {
3699 ADD_TRIANGLE(4, 0, 5)
3700 ADD_TRIANGLE(5, 0, 3)
3701 ADD_TRIANGLE(5, 3, 6)
3702 ADD_TRIANGLE(6, 3, 2)
3703 }
3704 } else {
3705 if (p.x < q.x) {
3706 ADD_TRIANGLE(0, 4, 7)
3707 ADD_TRIANGLE(0, 7, 1)
3708 ADD_TRIANGLE(1, 7, 6)
3709 ADD_TRIANGLE(1, 6, 2)
3710 } else {
3711 ADD_TRIANGLE(6, 5, 1)
3712 ADD_TRIANGLE(6, 1, 0)
3713 ADD_TRIANGLE(7, 6, 0)
3714 ADD_TRIANGLE(7, 0, 3)
3715 }
3716 }
3717 }
3718
3719 p = q;
3720 cur_index += 4;
3721 }
3722
3723 result = QueueCmdGeometry(renderer, NULL,
3724 xy, xy_stride, &renderer->color, 0 /* color_stride */, NULL, 0,
3725 num_vertices, indices, num_indices, size_indices,
3726 1.0f, 1.0f, SDL_TEXTURE_ADDRESS_CLAMP);
3727 }
3728
3729 SDL_small_free(xy, isstack1);
3730 SDL_small_free(indices, isstack2);
3731
3732 } else if (renderer->line_method == SDL_RENDERLINEMETHOD_POINTS) {
3733 result = RenderLinesWithRectsF(renderer, points, count);
3734 } else if (view->scale.x != 1.0f || view->scale.y != 1.0f) { /* we checked for logical scale elsewhere. */
3735 result = RenderLinesWithRectsF(renderer, points, count);
3736 } else {
3737 result = QueueCmdDrawLines(renderer, points, count);
3738 }
3739
3740 return result;
3741}
3742
3743bool SDL_RenderRect(SDL_Renderer *renderer, const SDL_FRect *rect)
3744{
3745 SDL_FRect frect;
3746 SDL_FPoint points[5];
3747
3748 CHECK_RENDERER_MAGIC(renderer, false);
3749
3750 // If 'rect' == NULL, then outline the whole surface
3751 if (!rect) {
3752 GetRenderViewportSize(renderer, &frect);
3753 rect = &frect;
3754 }
3755
3756 points[0].x = rect->x;
3757 points[0].y = rect->y;
3758 points[1].x = rect->x + rect->w - 1;
3759 points[1].y = rect->y;
3760 points[2].x = rect->x + rect->w - 1;
3761 points[2].y = rect->y + rect->h - 1;
3762 points[3].x = rect->x;
3763 points[3].y = rect->y + rect->h - 1;
3764 points[4].x = rect->x;
3765 points[4].y = rect->y;
3766 return SDL_RenderLines(renderer, points, 5);
3767}
3768
3769bool SDL_RenderRects(SDL_Renderer *renderer, const SDL_FRect *rects, int count)
3770{
3771 int i;
3772
3773 CHECK_RENDERER_MAGIC(renderer, false);
3774
3775 if (!rects) {
3776 return SDL_InvalidParamError("SDL_RenderRects(): rects");
3777 }
3778 if (count < 1) {
3779 return true;
3780 }
3781
3782#if DONT_DRAW_WHILE_HIDDEN
3783 // Don't draw while we're hidden
3784 if (renderer->hidden) {
3785 return true;
3786 }
3787#endif
3788
3789 for (i = 0; i < count; ++i) {
3790 if (!SDL_RenderRect(renderer, &rects[i])) {
3791 return false;
3792 }
3793 }
3794 return true;
3795}
3796
3797bool SDL_RenderFillRect(SDL_Renderer *renderer, const SDL_FRect *rect)
3798{
3799 SDL_FRect frect;
3800
3801 CHECK_RENDERER_MAGIC(renderer, false);
3802
3803 // If 'rect' == NULL, then fill the whole surface
3804 if (!rect) {
3805 GetRenderViewportSize(renderer, &frect);
3806 rect = &frect;
3807 }
3808 return SDL_RenderFillRects(renderer, rect, 1);
3809}
3810
3811bool SDL_RenderFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, int count)
3812{
3813 SDL_FRect *frects;
3814 int i;
3815 bool result;
3816 bool isstack;
3817
3818 CHECK_RENDERER_MAGIC(renderer, false);
3819
3820 if (!rects) {
3821 return SDL_InvalidParamError("SDL_RenderFillRects(): rects");
3822 }
3823 if (count < 1) {
3824 return true;
3825 }
3826
3827#if DONT_DRAW_WHILE_HIDDEN
3828 // Don't draw while we're hidden
3829 if (renderer->hidden) {
3830 return true;
3831 }
3832#endif
3833
3834 frects = SDL_small_alloc(SDL_FRect, count, &isstack);
3835 if (!frects) {
3836 return false;
3837 }
3838
3839 const SDL_RenderViewState *view = renderer->view;
3840 const float scale_x = view->current_scale.x;
3841 const float scale_y = view->current_scale.y;
3842 for (i = 0; i < count; ++i) {
3843 frects[i].x = rects[i].x * scale_x;
3844 frects[i].y = rects[i].y * scale_y;
3845 frects[i].w = rects[i].w * scale_x;
3846 frects[i].h = rects[i].h * scale_y;
3847 }
3848
3849 result = QueueCmdFillRects(renderer, frects, count);
3850
3851 SDL_small_free(frects, isstack);
3852
3853 return result;
3854}
3855
3856static bool SDL_RenderTextureInternal(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect)
3857{
3858 const SDL_RenderViewState *view = renderer->view;
3859 const float scale_x = view->current_scale.x;
3860 const float scale_y = view->current_scale.y;
3861 const bool use_rendergeometry = (!renderer->QueueCopy);
3862 bool result;
3863
3864 if (use_rendergeometry) {
3865 float xy[8];
3866 const int xy_stride = 2 * sizeof(float);
3867 float uv[8];
3868 const int uv_stride = 2 * sizeof(float);
3869 const int num_vertices = 4;
3870 const int *indices = rect_index_order;
3871 const int num_indices = 6;
3872 const int size_indices = 4;
3873 float minu, minv, maxu, maxv;
3874 float minx, miny, maxx, maxy;
3875
3876 minu = srcrect->x / texture->w;
3877 minv = srcrect->y / texture->h;
3878 maxu = (srcrect->x + srcrect->w) / texture->w;
3879 maxv = (srcrect->y + srcrect->h) / texture->h;
3880
3881 minx = dstrect->x;
3882 miny = dstrect->y;
3883 maxx = dstrect->x + dstrect->w;
3884 maxy = dstrect->y + dstrect->h;
3885
3886 uv[0] = minu;
3887 uv[1] = minv;
3888 uv[2] = maxu;
3889 uv[3] = minv;
3890 uv[4] = maxu;
3891 uv[5] = maxv;
3892 uv[6] = minu;
3893 uv[7] = maxv;
3894
3895 xy[0] = minx;
3896 xy[1] = miny;
3897 xy[2] = maxx;
3898 xy[3] = miny;
3899 xy[4] = maxx;
3900 xy[5] = maxy;
3901 xy[6] = minx;
3902 xy[7] = maxy;
3903
3904 result = QueueCmdGeometry(renderer, texture,
3905 xy, xy_stride, &texture->color, 0 /* color_stride */, uv, uv_stride,
3906 num_vertices, indices, num_indices, size_indices,
3907 scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP);
3908 } else {
3909 const SDL_FRect rect = { dstrect->x * scale_x, dstrect->y * scale_y, dstrect->w * scale_x, dstrect->h * scale_y };
3910 result = QueueCmdCopy(renderer, texture, srcrect, &rect);
3911 }
3912 return result;
3913}
3914
3915bool SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect)
3916{
3917 CHECK_RENDERER_MAGIC(renderer, false);
3918 CHECK_TEXTURE_MAGIC(texture, false);
3919
3920 if (renderer != texture->renderer) {
3921 return SDL_SetError("Texture was not created with this renderer");
3922 }
3923
3924#if DONT_DRAW_WHILE_HIDDEN
3925 // Don't draw while we're hidden
3926 if (renderer->hidden) {
3927 return true;
3928 }
3929#endif
3930
3931 SDL_FRect real_srcrect;
3932 real_srcrect.x = 0.0f;
3933 real_srcrect.y = 0.0f;
3934 real_srcrect.w = (float)texture->w;
3935 real_srcrect.h = (float)texture->h;
3936 if (srcrect) {
3937 if (!SDL_GetRectIntersectionFloat(srcrect, &real_srcrect, &real_srcrect) ||
3938 real_srcrect.w == 0.0f || real_srcrect.h == 0.0f) {
3939 return true;
3940 }
3941 }
3942
3943 SDL_FRect full_dstrect;
3944 if (!dstrect) {
3945 GetRenderViewportSize(renderer, &full_dstrect);
3946 dstrect = &full_dstrect;
3947 }
3948
3949 if (texture->native) {
3950 texture = texture->native;
3951 }
3952
3953 texture->last_command_generation = renderer->render_command_generation;
3954
3955 return SDL_RenderTextureInternal(renderer, texture, &real_srcrect, dstrect);
3956}
3957
3958bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture,
3959 const SDL_FRect *srcrect, const SDL_FPoint *origin, const SDL_FPoint *right, const SDL_FPoint *down)
3960{
3961 SDL_FRect real_srcrect;
3962 SDL_FRect real_dstrect;
3963 bool result;
3964
3965 CHECK_RENDERER_MAGIC(renderer, false);
3966 CHECK_TEXTURE_MAGIC(texture, false);
3967
3968 if (renderer != texture->renderer) {
3969 return SDL_SetError("Texture was not created with this renderer");
3970 }
3971 if (!renderer->QueueCopyEx && !renderer->QueueGeometry) {
3972 return SDL_SetError("Renderer does not support RenderCopyEx");
3973 }
3974
3975#if DONT_DRAW_WHILE_HIDDEN
3976 // Don't draw while we're hidden
3977 if (renderer->hidden) {
3978 return true;
3979 }
3980#endif
3981
3982 real_srcrect.x = 0.0f;
3983 real_srcrect.y = 0.0f;
3984 real_srcrect.w = (float)texture->w;
3985 real_srcrect.h = (float)texture->h;
3986 if (srcrect) {
3987 if (!SDL_GetRectIntersectionFloat(srcrect, &real_srcrect, &real_srcrect)) {
3988 return true;
3989 }
3990 }
3991
3992 GetRenderViewportSize(renderer, &real_dstrect);
3993
3994 if (texture->native) {
3995 texture = texture->native;
3996 }
3997
3998 texture->last_command_generation = renderer->render_command_generation;
3999
4000 const SDL_RenderViewState *view = renderer->view;
4001 const float scale_x = view->current_scale.x;
4002 const float scale_y = view->current_scale.y;
4003
4004 {
4005 float xy[8];
4006 const int xy_stride = 2 * sizeof(float);
4007 float uv[8];
4008 const int uv_stride = 2 * sizeof(float);
4009 const int num_vertices = 4;
4010 const int *indices = rect_index_order;
4011 const int num_indices = 6;
4012 const int size_indices = 4;
4013
4014 float minu = real_srcrect.x / texture->w;
4015 float minv = real_srcrect.y / texture->h;
4016 float maxu = (real_srcrect.x + real_srcrect.w) / texture->w;
4017 float maxv = (real_srcrect.y + real_srcrect.h) / texture->h;
4018
4019 uv[0] = minu;
4020 uv[1] = minv;
4021 uv[2] = maxu;
4022 uv[3] = minv;
4023 uv[4] = maxu;
4024 uv[5] = maxv;
4025 uv[6] = minu;
4026 uv[7] = maxv;
4027
4028 // (minx, miny)
4029 if (origin) {
4030 xy[0] = origin->x;
4031 xy[1] = origin->y;
4032 } else {
4033 xy[0] = real_dstrect.x;
4034 xy[1] = real_dstrect.y;
4035 }
4036
4037 // (maxx, miny)
4038 if (right) {
4039 xy[2] = right->x;
4040 xy[3] = right->y;
4041 } else {
4042 xy[2] = real_dstrect.x + real_dstrect.w;
4043 xy[3] = real_dstrect.y;
4044 }
4045
4046 // (minx, maxy)
4047 if (down) {
4048 xy[6] = down->x;
4049 xy[7] = down->y;
4050 } else {
4051 xy[6] = real_dstrect.x;
4052 xy[7] = real_dstrect.y + real_dstrect.h;
4053 }
4054
4055 // (maxx, maxy)
4056 if (origin || right || down) {
4057 xy[4] = xy[2] + xy[6] - xy[0];
4058 xy[5] = xy[3] + xy[7] - xy[1];
4059 } else {
4060 xy[4] = real_dstrect.x + real_dstrect.w;
4061 xy[5] = real_dstrect.y + real_dstrect.h;
4062 }
4063
4064 result = QueueCmdGeometry(
4065 renderer, texture,
4066 xy, xy_stride,
4067 &texture->color, 0 /* color_stride */,
4068 uv, uv_stride,
4069 num_vertices, indices, num_indices, size_indices,
4070 scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP
4071 );
4072 }
4073 return result;
4074}
4075
4076bool SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture,
4077 const SDL_FRect *srcrect, const SDL_FRect *dstrect,
4078 const double angle, const SDL_FPoint *center, const SDL_FlipMode flip)
4079{
4080 SDL_FRect real_srcrect;
4081 SDL_FPoint real_center;
4082 bool result;
4083
4084 if (flip == SDL_FLIP_NONE && (int)(angle / 360) == angle / 360) { // fast path when we don't need rotation or flipping
4085 return SDL_RenderTexture(renderer, texture, srcrect, dstrect);
4086 }
4087
4088 CHECK_RENDERER_MAGIC(renderer, false);
4089 CHECK_TEXTURE_MAGIC(texture, false);
4090
4091 if (renderer != texture->renderer) {
4092 return SDL_SetError("Texture was not created with this renderer");
4093 }
4094 if (!renderer->QueueCopyEx && !renderer->QueueGeometry) {
4095 return SDL_SetError("Renderer does not support RenderCopyEx");
4096 }
4097
4098#if DONT_DRAW_WHILE_HIDDEN
4099 // Don't draw while we're hidden
4100 if (renderer->hidden) {
4101 return true;
4102 }
4103#endif
4104
4105 real_srcrect.x = 0.0f;
4106 real_srcrect.y = 0.0f;
4107 real_srcrect.w = (float)texture->w;
4108 real_srcrect.h = (float)texture->h;
4109 if (srcrect) {
4110 if (!SDL_GetRectIntersectionFloat(srcrect, &real_srcrect, &real_srcrect)) {
4111 return true;
4112 }
4113 }
4114
4115 // We don't intersect the dstrect with the viewport as RenderCopy does because of potential rotation clipping issues... TODO: should we?
4116 SDL_FRect full_dstrect;
4117 if (!dstrect) {
4118 GetRenderViewportSize(renderer, &full_dstrect);
4119 dstrect = &full_dstrect;
4120 }
4121
4122 if (texture->native) {
4123 texture = texture->native;
4124 }
4125
4126 if (center) {
4127 real_center = *center;
4128 } else {
4129 real_center.x = dstrect->w / 2.0f;
4130 real_center.y = dstrect->h / 2.0f;
4131 }
4132
4133 texture->last_command_generation = renderer->render_command_generation;
4134
4135 const SDL_RenderViewState *view = renderer->view;
4136 const float scale_x = view->current_scale.x;
4137 const float scale_y = view->current_scale.y;
4138
4139 const bool use_rendergeometry = (!renderer->QueueCopyEx);
4140 if (use_rendergeometry) {
4141 float xy[8];
4142 const int xy_stride = 2 * sizeof(float);
4143 float uv[8];
4144 const int uv_stride = 2 * sizeof(float);
4145 const int num_vertices = 4;
4146 const int *indices = rect_index_order;
4147 const int num_indices = 6;
4148 const int size_indices = 4;
4149 float minu, minv, maxu, maxv;
4150 float minx, miny, maxx, maxy;
4151 float centerx, centery;
4152
4153 float s_minx, s_miny, s_maxx, s_maxy;
4154 float c_minx, c_miny, c_maxx, c_maxy;
4155
4156 const float radian_angle = (float)((SDL_PI_D * angle) / 180.0);
4157 const float s = SDL_sinf(radian_angle);
4158 const float c = SDL_cosf(radian_angle);
4159
4160 minu = real_srcrect.x / texture->w;
4161 minv = real_srcrect.y / texture->h;
4162 maxu = (real_srcrect.x + real_srcrect.w) / texture->w;
4163 maxv = (real_srcrect.y + real_srcrect.h) / texture->h;
4164
4165 centerx = real_center.x + dstrect->x;
4166 centery = real_center.y + dstrect->y;
4167
4168 if (flip & SDL_FLIP_HORIZONTAL) {
4169 minx = dstrect->x + dstrect->w;
4170 maxx = dstrect->x;
4171 } else {
4172 minx = dstrect->x;
4173 maxx = dstrect->x + dstrect->w;
4174 }
4175
4176 if (flip & SDL_FLIP_VERTICAL) {
4177 miny = dstrect->y + dstrect->h;
4178 maxy = dstrect->y;
4179 } else {
4180 miny = dstrect->y;
4181 maxy = dstrect->y + dstrect->h;
4182 }
4183
4184 uv[0] = minu;
4185 uv[1] = minv;
4186 uv[2] = maxu;
4187 uv[3] = minv;
4188 uv[4] = maxu;
4189 uv[5] = maxv;
4190 uv[6] = minu;
4191 uv[7] = maxv;
4192
4193 /* apply rotation with 2x2 matrix ( c -s )
4194 * ( s c ) */
4195 s_minx = s * (minx - centerx);
4196 s_miny = s * (miny - centery);
4197 s_maxx = s * (maxx - centerx);
4198 s_maxy = s * (maxy - centery);
4199 c_minx = c * (minx - centerx);
4200 c_miny = c * (miny - centery);
4201 c_maxx = c * (maxx - centerx);
4202 c_maxy = c * (maxy - centery);
4203
4204 // (minx, miny)
4205 xy[0] = (c_minx - s_miny) + centerx;
4206 xy[1] = (s_minx + c_miny) + centery;
4207 // (maxx, miny)
4208 xy[2] = (c_maxx - s_miny) + centerx;
4209 xy[3] = (s_maxx + c_miny) + centery;
4210 // (maxx, maxy)
4211 xy[4] = (c_maxx - s_maxy) + centerx;
4212 xy[5] = (s_maxx + c_maxy) + centery;
4213 // (minx, maxy)
4214 xy[6] = (c_minx - s_maxy) + centerx;
4215 xy[7] = (s_minx + c_maxy) + centery;
4216
4217 result = QueueCmdGeometry(renderer, texture,
4218 xy, xy_stride, &texture->color, 0 /* color_stride */, uv, uv_stride,
4219 num_vertices, indices, num_indices, size_indices,
4220 scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP);
4221 } else {
4222 result = QueueCmdCopyEx(renderer, texture, &real_srcrect, dstrect, angle, &real_center, flip, scale_x, scale_y);
4223 }
4224 return result;
4225}
4226
4227static bool SDL_RenderTextureTiled_Wrap(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float scale, const SDL_FRect *dstrect)
4228{
4229 float xy[8];
4230 const int xy_stride = 2 * sizeof(float);
4231 float uv[8];
4232 const int uv_stride = 2 * sizeof(float);
4233 const int num_vertices = 4;
4234 const int *indices = rect_index_order;
4235 const int num_indices = 6;
4236 const int size_indices = 4;
4237 float minu, minv, maxu, maxv;
4238 float minx, miny, maxx, maxy;
4239
4240 minu = 0.0f;
4241 minv = 0.0f;
4242 maxu = dstrect->w / (srcrect->w * scale);
4243 maxv = dstrect->h / (srcrect->h * scale);
4244
4245 minx = dstrect->x;
4246 miny = dstrect->y;
4247 maxx = dstrect->x + dstrect->w;
4248 maxy = dstrect->y + dstrect->h;
4249
4250 uv[0] = minu;
4251 uv[1] = minv;
4252 uv[2] = maxu;
4253 uv[3] = minv;
4254 uv[4] = maxu;
4255 uv[5] = maxv;
4256 uv[6] = minu;
4257 uv[7] = maxv;
4258
4259 xy[0] = minx;
4260 xy[1] = miny;
4261 xy[2] = maxx;
4262 xy[3] = miny;
4263 xy[4] = maxx;
4264 xy[5] = maxy;
4265 xy[6] = minx;
4266 xy[7] = maxy;
4267
4268 const SDL_RenderViewState *view = renderer->view;
4269 return QueueCmdGeometry(renderer, texture,
4270 xy, xy_stride, &texture->color, 0 /* color_stride */, uv, uv_stride,
4271 num_vertices, indices, num_indices, size_indices,
4272 view->current_scale.x, view->current_scale.y, SDL_TEXTURE_ADDRESS_WRAP);
4273}
4274
4275static bool SDL_RenderTextureTiled_Iterate(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float scale, const SDL_FRect *dstrect)
4276{
4277 float tile_width = srcrect->w * scale;
4278 float tile_height = srcrect->h * scale;
4279 float float_rows, float_cols;
4280 float remaining_w = SDL_modff(dstrect->w / tile_width, &float_cols);
4281 float remaining_h = SDL_modff(dstrect->h / tile_height, &float_rows);
4282 float remaining_src_w = remaining_w * srcrect->w;
4283 float remaining_src_h = remaining_h * srcrect->h;
4284 float remaining_dst_w = remaining_w * tile_width;
4285 float remaining_dst_h = remaining_h * tile_height;
4286 int rows = (int)float_rows;
4287 int cols = (int)float_cols;
4288 SDL_FRect curr_src, curr_dst;
4289
4290 SDL_copyp(&curr_src, srcrect);
4291 curr_dst.y = dstrect->y;
4292 curr_dst.w = tile_width;
4293 curr_dst.h = tile_height;
4294 for (int y = 0; y < rows; ++y) {
4295 curr_dst.x = dstrect->x;
4296 for (int x = 0; x < cols; ++x) {
4297 if (!SDL_RenderTextureInternal(renderer, texture, &curr_src, &curr_dst)) {
4298 return false;
4299 }
4300 curr_dst.x += curr_dst.w;
4301 }
4302 if (remaining_dst_w > 0.0f) {
4303 curr_src.w = remaining_src_w;
4304 curr_dst.w = remaining_dst_w;
4305 if (!SDL_RenderTextureInternal(renderer, texture, &curr_src, &curr_dst)) {
4306 return false;
4307 }
4308 curr_src.w = srcrect->w;
4309 curr_dst.w = tile_width;
4310 }
4311 curr_dst.y += curr_dst.h;
4312 }
4313 if (remaining_dst_h > 0.0f) {
4314 curr_src.h = remaining_src_h;
4315 curr_dst.h = remaining_dst_h;
4316 curr_dst.x = dstrect->x;
4317 for (int x = 0; x < cols; ++x) {
4318 if (!SDL_RenderTextureInternal(renderer, texture, &curr_src, &curr_dst)) {
4319 return false;
4320 }
4321 curr_dst.x += curr_dst.w;
4322 }
4323 if (remaining_dst_w > 0.0f) {
4324 curr_src.w = remaining_src_w;
4325 curr_dst.w = remaining_dst_w;
4326 if (!SDL_RenderTextureInternal(renderer, texture, &curr_src, &curr_dst)) {
4327 return false;
4328 }
4329 }
4330 }
4331 return true;
4332}
4333
4334bool SDL_RenderTextureTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float scale, const SDL_FRect *dstrect)
4335{
4336 SDL_FRect real_srcrect;
4337
4338 CHECK_RENDERER_MAGIC(renderer, false);
4339 CHECK_TEXTURE_MAGIC(texture, false);
4340
4341 if (renderer != texture->renderer) {
4342 return SDL_SetError("Texture was not created with this renderer");
4343 }
4344
4345 if (scale <= 0.0f) {
4346 return SDL_InvalidParamError("scale");
4347 }
4348
4349#if DONT_DRAW_WHILE_HIDDEN
4350 // Don't draw while we're hidden
4351 if (renderer->hidden) {
4352 return true;
4353 }
4354#endif
4355
4356 real_srcrect.x = 0.0f;
4357 real_srcrect.y = 0.0f;
4358 real_srcrect.w = (float)texture->w;
4359 real_srcrect.h = (float)texture->h;
4360 if (srcrect) {
4361 if (!SDL_GetRectIntersectionFloat(srcrect, &real_srcrect, &real_srcrect)) {
4362 return true;
4363 }
4364 }
4365
4366 SDL_FRect full_dstrect;
4367 if (!dstrect) {
4368 GetRenderViewportSize(renderer, &full_dstrect);
4369 dstrect = &full_dstrect;
4370 }
4371
4372 if (texture->native) {
4373 texture = texture->native;
4374 }
4375
4376 texture->last_command_generation = renderer->render_command_generation;
4377
4378 // See if we can use geometry with repeating texture coordinates
4379 if (!renderer->software &&
4380 (!srcrect ||
4381 (real_srcrect.x == 0.0f && real_srcrect.y == 0.0f &&
4382 real_srcrect.w == (float)texture->w && real_srcrect.h == (float)texture->h))) {
4383 return SDL_RenderTextureTiled_Wrap(renderer, texture, &real_srcrect, scale, dstrect);
4384 } else {
4385 return SDL_RenderTextureTiled_Iterate(renderer, texture, &real_srcrect, scale, dstrect);
4386 }
4387}
4388
4389bool SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect)
4390{
4391 SDL_FRect full_src, full_dst;
4392 SDL_FRect curr_src, curr_dst;
4393 float dst_left_width;
4394 float dst_right_width;
4395 float dst_top_height;
4396 float dst_bottom_height;
4397
4398 CHECK_RENDERER_MAGIC(renderer, false);
4399 CHECK_TEXTURE_MAGIC(texture, false);
4400
4401 if (renderer != texture->renderer) {
4402 return SDL_SetError("Texture was not created with this renderer");
4403 }
4404
4405 if (!srcrect) {
4406 full_src.x = 0;
4407 full_src.y = 0;
4408 full_src.w = (float)texture->w;
4409 full_src.h = (float)texture->h;
4410 srcrect = &full_src;
4411 }
4412
4413 if (!dstrect) {
4414 GetRenderViewportSize(renderer, &full_dst);
4415 dstrect = &full_dst;
4416 }
4417
4418 if (scale <= 0.0f || scale == 1.0f) {
4419 dst_left_width = SDL_ceilf(left_width);
4420 dst_right_width = SDL_ceilf(right_width);
4421 dst_top_height = SDL_ceilf(top_height);
4422 dst_bottom_height = SDL_ceilf(bottom_height);
4423 } else {
4424 dst_left_width = SDL_ceilf(left_width * scale);
4425 dst_right_width = SDL_ceilf(right_width * scale);
4426 dst_top_height = SDL_ceilf(top_height * scale);
4427 dst_bottom_height = SDL_ceilf(bottom_height * scale);
4428 }
4429
4430 // Center
4431 curr_src.x = srcrect->x + left_width;
4432 curr_src.y = srcrect->y + top_height;
4433 curr_src.w = srcrect->w - left_width - right_width;
4434 curr_src.h = srcrect->h - top_height - bottom_height;
4435 curr_dst.x = dstrect->x + dst_left_width;
4436 curr_dst.y = dstrect->y + dst_top_height;
4437 curr_dst.w = dstrect->w - dst_left_width - dst_right_width;
4438 curr_dst.h = dstrect->h - dst_top_height - dst_bottom_height;
4439 if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) {
4440 return false;
4441 }
4442
4443 // Upper-left corner
4444 curr_src.x = srcrect->x;
4445 curr_src.y = srcrect->y;
4446 curr_src.w = left_width;
4447 curr_src.h = top_height;
4448 curr_dst.x = dstrect->x;
4449 curr_dst.y = dstrect->y;
4450 curr_dst.w = dst_left_width;
4451 curr_dst.h = dst_top_height;
4452 if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) {
4453 return false;
4454 }
4455
4456 // Upper-right corner
4457 curr_src.x = srcrect->x + srcrect->w - right_width;
4458 curr_src.w = right_width;
4459 curr_dst.x = dstrect->x + dstrect->w - dst_right_width;
4460 curr_dst.w = dst_right_width;
4461 if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) {
4462 return false;
4463 }
4464
4465 // Lower-right corner
4466 curr_src.y = srcrect->y + srcrect->h - bottom_height;
4467 curr_src.h = bottom_height;
4468 curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height;
4469 curr_dst.h = dst_bottom_height;
4470 if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) {
4471 return false;
4472 }
4473
4474 // Lower-left corner
4475 curr_src.x = srcrect->x;
4476 curr_src.w = left_width;
4477 curr_dst.x = dstrect->x;
4478 curr_dst.w = dst_left_width;
4479 if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) {
4480 return false;
4481 }
4482
4483 // Left
4484 curr_src.y = srcrect->y + top_height;
4485 curr_src.h = srcrect->h - top_height - bottom_height;
4486 curr_dst.y = dstrect->y + dst_top_height;
4487 curr_dst.h = dstrect->h - dst_top_height - dst_bottom_height;
4488 if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) {
4489 return false;
4490 }
4491
4492 // Right
4493 curr_src.x = srcrect->x + srcrect->w - right_width;
4494 curr_src.w = right_width;
4495 curr_dst.x = dstrect->x + dstrect->w - dst_right_width;
4496 curr_dst.w = dst_right_width;
4497 if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) {
4498 return false;
4499 }
4500
4501 // Top
4502 curr_src.x = srcrect->x + left_width;
4503 curr_src.y = srcrect->y;
4504 curr_src.w = srcrect->w - left_width - right_width;
4505 curr_src.h = top_height;
4506 curr_dst.x = dstrect->x + dst_left_width;
4507 curr_dst.y = dstrect->y;
4508 curr_dst.w = dstrect->w - dst_left_width - dst_right_width;
4509 curr_dst.h = dst_top_height;
4510 if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) {
4511 return false;
4512 }
4513
4514 // Bottom
4515 curr_src.y = srcrect->y + srcrect->h - bottom_height;
4516 curr_src.h = bottom_height;
4517 curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height;
4518 curr_dst.h = dst_bottom_height;
4519 if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) {
4520 return false;
4521 }
4522
4523 return true;
4524}
4525
4526bool SDL_RenderGeometry(SDL_Renderer *renderer,
4527 SDL_Texture *texture,
4528 const SDL_Vertex *vertices, int num_vertices,
4529 const int *indices, int num_indices)
4530{
4531 if (vertices) {
4532 const float *xy = &vertices->position.x;
4533 int xy_stride = sizeof(SDL_Vertex);
4534 const SDL_FColor *color = &vertices->color;
4535 int color_stride = sizeof(SDL_Vertex);
4536 const float *uv = &vertices->tex_coord.x;
4537 int uv_stride = sizeof(SDL_Vertex);
4538 int size_indices = 4;
4539 return SDL_RenderGeometryRaw(renderer, texture, xy, xy_stride, color, color_stride, uv, uv_stride, num_vertices, indices, num_indices, size_indices);
4540 } else {
4541 return SDL_InvalidParamError("vertices");
4542 }
4543}
4544
4545#ifdef SDL_VIDEO_RENDER_SW
4546static int remap_one_indice(
4547 int prev,
4548 int k,
4549 SDL_Texture *texture,
4550 const float *xy, int xy_stride,
4551 const SDL_FColor *color, int color_stride,
4552 const float *uv, int uv_stride)
4553{
4554 const float *xy0_, *xy1_, *uv0_, *uv1_;
4555 const SDL_FColor *col0_, *col1_;
4556 xy0_ = (const float *)((const char *)xy + prev * xy_stride);
4557 xy1_ = (const float *)((const char *)xy + k * xy_stride);
4558 if (xy0_[0] != xy1_[0]) {
4559 return k;
4560 }
4561 if (xy0_[1] != xy1_[1]) {
4562 return k;
4563 }
4564 if (texture) {
4565 uv0_ = (const float *)((const char *)uv + prev * uv_stride);
4566 uv1_ = (const float *)((const char *)uv + k * uv_stride);
4567 if (uv0_[0] != uv1_[0]) {
4568 return k;
4569 }
4570 if (uv0_[1] != uv1_[1]) {
4571 return k;
4572 }
4573 }
4574 col0_ = (const SDL_FColor *)((const char *)color + prev * color_stride);
4575 col1_ = (const SDL_FColor *)((const char *)color + k * color_stride);
4576
4577 if (SDL_memcmp(col0_, col1_, sizeof(*col0_)) != 0) {
4578 return k;
4579 }
4580
4581 return prev;
4582}
4583
4584static int remap_indices(
4585 int prev[3],
4586 int k,
4587 SDL_Texture *texture,
4588 const float *xy, int xy_stride,
4589 const SDL_FColor *color, int color_stride,
4590 const float *uv, int uv_stride)
4591{
4592 int i;
4593 if (prev[0] == -1) {
4594 return k;
4595 }
4596
4597 for (i = 0; i < 3; i++) {
4598 int new_k = remap_one_indice(prev[i], k, texture, xy, xy_stride, color, color_stride, uv, uv_stride);
4599 if (new_k != k) {
4600 return new_k;
4601 }
4602 }
4603 return k;
4604}
4605
4606#define DEBUG_SW_RENDER_GEOMETRY 0
4607// For the software renderer, try to reinterpret triangles as SDL_Rect
4608static bool SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer,
4609 SDL_Texture *texture,
4610 const float *xy, int xy_stride,
4611 const SDL_FColor *color, int color_stride,
4612 const float *uv, int uv_stride,
4613 int num_vertices,
4614 const void *indices, int num_indices, int size_indices)
4615{
4616 int i;
4617 bool result = true;
4618 int count = indices ? num_indices : num_vertices;
4619 int prev[3]; // Previous triangle vertex indices
4620 float texw = 0.0f, texh = 0.0f;
4621 SDL_BlendMode blendMode = SDL_BLENDMODE_NONE;
4622 float r = 0, g = 0, b = 0, a = 0;
4623 const SDL_RenderViewState *view = renderer->view;
4624 const float scale_x = view->current_scale.x;
4625 const float scale_y = view->current_scale.y;
4626
4627 // Save
4628 SDL_GetRenderDrawBlendMode(renderer, &blendMode);
4629 SDL_GetRenderDrawColorFloat(renderer, &r, &g, &b, &a);
4630
4631 if (texture) {
4632 SDL_GetTextureSize(texture, &texw, &texh);
4633 }
4634
4635 prev[0] = -1;
4636 prev[1] = -1;
4637 prev[2] = -1;
4638 size_indices = indices ? size_indices : 0;
4639
4640 for (i = 0; i < count; i += 3) {
4641 int k0, k1, k2; // Current triangle indices
4642 int is_quad = 1;
4643#if DEBUG_SW_RENDER_GEOMETRY
4644 int is_uniform = 1;
4645 int is_rectangle = 1;
4646#endif
4647 int A = -1; // Top left vertex
4648 int B = -1; // Bottom right vertex
4649 int C = -1; // Third vertex of current triangle
4650 int C2 = -1; // Last, vertex of previous triangle
4651
4652 if (size_indices == 4) {
4653 k0 = ((const Uint32 *)indices)[i];
4654 k1 = ((const Uint32 *)indices)[i + 1];
4655 k2 = ((const Uint32 *)indices)[i + 2];
4656 } else if (size_indices == 2) {
4657 k0 = ((const Uint16 *)indices)[i];
4658 k1 = ((const Uint16 *)indices)[i + 1];
4659 k2 = ((const Uint16 *)indices)[i + 2];
4660 } else if (size_indices == 1) {
4661 k0 = ((const Uint8 *)indices)[i];
4662 k1 = ((const Uint8 *)indices)[i + 1];
4663 k2 = ((const Uint8 *)indices)[i + 2];
4664 } else {
4665 /* Vertices were not provided by indices. Maybe some are duplicated.
4666 * We try to indentificate the duplicates by comparing with the previous three vertices */
4667 k0 = remap_indices(prev, i, texture, xy, xy_stride, color, color_stride, uv, uv_stride);
4668 k1 = remap_indices(prev, i + 1, texture, xy, xy_stride, color, color_stride, uv, uv_stride);
4669 k2 = remap_indices(prev, i + 2, texture, xy, xy_stride, color, color_stride, uv, uv_stride);
4670 }
4671
4672 if (prev[0] == -1) {
4673 prev[0] = k0;
4674 prev[1] = k1;
4675 prev[2] = k2;
4676 continue;
4677 }
4678
4679 /* Two triangles forming a quadialateral,
4680 * prev and current triangles must have exactly 2 common vertices */
4681 {
4682 int cnt = 0, j = 3;
4683 while (j--) {
4684 int p = prev[j];
4685 if (p == k0 || p == k1 || p == k2) {
4686 cnt++;
4687 }
4688 }
4689 is_quad = (cnt == 2);
4690 }
4691
4692 // Identify vertices
4693 if (is_quad) {
4694 const float *xy0_, *xy1_, *xy2_;
4695 float x0, x1, x2;
4696 float y0, y1, y2;
4697 xy0_ = (const float *)((const char *)xy + k0 * xy_stride);
4698 xy1_ = (const float *)((const char *)xy + k1 * xy_stride);
4699 xy2_ = (const float *)((const char *)xy + k2 * xy_stride);
4700 x0 = xy0_[0];
4701 y0 = xy0_[1];
4702 x1 = xy1_[0];
4703 y1 = xy1_[1];
4704 x2 = xy2_[0];
4705 y2 = xy2_[1];
4706
4707 // Find top-left
4708 if (x0 <= x1 && y0 <= y1) {
4709 if (x0 <= x2 && y0 <= y2) {
4710 A = k0;
4711 } else {
4712 A = k2;
4713 }
4714 } else {
4715 if (x1 <= x2 && y1 <= y2) {
4716 A = k1;
4717 } else {
4718 A = k2;
4719 }
4720 }
4721
4722 // Find bottom-right
4723 if (x0 >= x1 && y0 >= y1) {
4724 if (x0 >= x2 && y0 >= y2) {
4725 B = k0;
4726 } else {
4727 B = k2;
4728 }
4729 } else {
4730 if (x1 >= x2 && y1 >= y2) {
4731 B = k1;
4732 } else {
4733 B = k2;
4734 }
4735 }
4736
4737 // Find C
4738 if (k0 != A && k0 != B) {
4739 C = k0;
4740 } else if (k1 != A && k1 != B) {
4741 C = k1;
4742 } else {
4743 C = k2;
4744 }
4745
4746 // Find C2
4747 if (prev[0] != A && prev[0] != B) {
4748 C2 = prev[0];
4749 } else if (prev[1] != A && prev[1] != B) {
4750 C2 = prev[1];
4751 } else {
4752 C2 = prev[2];
4753 }
4754
4755 xy0_ = (const float *)((const char *)xy + A * xy_stride);
4756 xy1_ = (const float *)((const char *)xy + B * xy_stride);
4757 xy2_ = (const float *)((const char *)xy + C * xy_stride);
4758 x0 = xy0_[0];
4759 y0 = xy0_[1];
4760 x1 = xy1_[0];
4761 y1 = xy1_[1];
4762 x2 = xy2_[0];
4763 y2 = xy2_[1];
4764
4765 // Check if triangle A B C is rectangle
4766 if ((x0 == x2 && y1 == y2) || (y0 == y2 && x1 == x2)) {
4767 // ok
4768 } else {
4769 is_quad = 0;
4770#if DEBUG_SW_RENDER_GEOMETRY
4771 is_rectangle = 0;
4772#endif
4773 }
4774
4775 xy2_ = (const float *)((const char *)xy + C2 * xy_stride);
4776 x2 = xy2_[0];
4777 y2 = xy2_[1];
4778
4779 // Check if triangle A B C2 is rectangle
4780 if ((x0 == x2 && y1 == y2) || (y0 == y2 && x1 == x2)) {
4781 // ok
4782 } else {
4783 is_quad = 0;
4784#if DEBUG_SW_RENDER_GEOMETRY
4785 is_rectangle = 0;
4786#endif
4787 }
4788 }
4789
4790 // Check if uniformly colored
4791 if (is_quad) {
4792 const SDL_FColor *col0_ = (const SDL_FColor *)((const char *)color + A * color_stride);
4793 const SDL_FColor *col1_ = (const SDL_FColor *)((const char *)color + B * color_stride);
4794 const SDL_FColor *col2_ = (const SDL_FColor *)((const char *)color + C * color_stride);
4795 const SDL_FColor *col3_ = (const SDL_FColor *)((const char *)color + C2 * color_stride);
4796 if (SDL_memcmp(col0_, col1_, sizeof(*col0_)) == 0 &&
4797 SDL_memcmp(col0_, col2_, sizeof(*col0_)) == 0 &&
4798 SDL_memcmp(col0_, col3_, sizeof(*col0_)) == 0) {
4799 // ok
4800 } else {
4801 is_quad = 0;
4802#if DEBUG_SW_RENDER_GEOMETRY
4803 is_uniform = 0;
4804#endif
4805 }
4806 }
4807
4808 // Start rendering rect
4809 if (is_quad) {
4810 SDL_FRect s;
4811 SDL_FRect d;
4812 const float *xy0_, *xy1_, *uv0_, *uv1_;
4813 const SDL_FColor *col0_ = (const SDL_FColor *)((const char *)color + k0 * color_stride);
4814
4815 xy0_ = (const float *)((const char *)xy + A * xy_stride);
4816 xy1_ = (const float *)((const char *)xy + B * xy_stride);
4817
4818 if (texture) {
4819 uv0_ = (const float *)((const char *)uv + A * uv_stride);
4820 uv1_ = (const float *)((const char *)uv + B * uv_stride);
4821 s.x = uv0_[0] * texw;
4822 s.y = uv0_[1] * texh;
4823 s.w = uv1_[0] * texw - s.x;
4824 s.h = uv1_[1] * texh - s.y;
4825 } else {
4826 s.x = s.y = s.w = s.h = 0;
4827 }
4828
4829 d.x = xy0_[0];
4830 d.y = xy0_[1];
4831 d.w = xy1_[0] - d.x;
4832 d.h = xy1_[1] - d.y;
4833
4834 // Rect + texture
4835 if (texture && s.w != 0 && s.h != 0) {
4836 SDL_SetTextureAlphaModFloat(texture, col0_->a);
4837 SDL_SetTextureColorModFloat(texture, col0_->r, col0_->g, col0_->b);
4838 if (s.w > 0 && s.h > 0) {
4839 SDL_RenderTexture(renderer, texture, &s, &d);
4840 } else {
4841 int flags = 0;
4842 if (s.w < 0) {
4843 flags |= SDL_FLIP_HORIZONTAL;
4844 s.w *= -1;
4845 s.x -= s.w;
4846 }
4847 if (s.h < 0) {
4848 flags |= SDL_FLIP_VERTICAL;
4849 s.h *= -1;
4850 s.y -= s.h;
4851 }
4852 SDL_RenderTextureRotated(renderer, texture, &s, &d, 0, NULL, (SDL_FlipMode)flags);
4853 }
4854
4855#if DEBUG_SW_RENDER_GEOMETRY
4856 SDL_Log("Rect-COPY: RGB %f %f %f - Alpha:%f - texture=%p: src=(%d,%d, %d x %d) dst (%f, %f, %f x %f)", col0_->r, col0_->g, col0_->b, col0_->a,
4857 (void *)texture, s.x, s.y, s.w, s.h, d.x, d.y, d.w, d.h);
4858#endif
4859 } else if (d.w != 0.0f && d.h != 0.0f) { // Rect, no texture
4860 SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
4861 SDL_SetRenderDrawColorFloat(renderer, col0_->r, col0_->g, col0_->b, col0_->a);
4862 SDL_RenderFillRect(renderer, &d);
4863#if DEBUG_SW_RENDER_GEOMETRY
4864 SDL_Log("Rect-FILL: RGB %f %f %f - Alpha:%f - texture=%p: dst (%f, %f, %f x %f)", col0_->r, col0_->g, col0_->b, col0_->a,
4865 (void *)texture, d.x, d.y, d.w, d.h);
4866 } else {
4867 SDL_Log("Rect-DISMISS: RGB %f %f %f - Alpha:%f - texture=%p: src=(%d,%d, %d x %d) dst (%f, %f, %f x %f)", col0_->r, col0_->g, col0_->b, col0_->a,
4868 (void *)texture, s.x, s.y, s.w, s.h, d.x, d.y, d.w, d.h);
4869#endif
4870 }
4871
4872 prev[0] = -1;
4873 } else {
4874 // Render triangles
4875 if (prev[0] != -1) {
4876#if DEBUG_SW_RENDER_GEOMETRY
4877 SDL_Log("Triangle %d %d %d - is_uniform:%d is_rectangle:%d", prev[0], prev[1], prev[2], is_uniform, is_rectangle);
4878#endif
4879 result = QueueCmdGeometry(renderer, texture,
4880 xy, xy_stride, color, color_stride, uv, uv_stride,
4881 num_vertices, prev, 3, 4,
4882 scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP);
4883 if (!result) {
4884 goto end;
4885 }
4886 }
4887
4888 prev[0] = k0;
4889 prev[1] = k1;
4890 prev[2] = k2;
4891 }
4892 } // End for (), next triangle
4893
4894 if (prev[0] != -1) {
4895 // flush the last triangle
4896#if DEBUG_SW_RENDER_GEOMETRY
4897 SDL_Log("Last triangle %d %d %d", prev[0], prev[1], prev[2]);
4898#endif
4899 result = QueueCmdGeometry(renderer, texture,
4900 xy, xy_stride, color, color_stride, uv, uv_stride,
4901 num_vertices, prev, 3, 4,
4902 scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP);
4903 if (!result) {
4904 goto end;
4905 }
4906 }
4907
4908end:
4909 // Restore
4910 SDL_SetRenderDrawBlendMode(renderer, blendMode);
4911 SDL_SetRenderDrawColorFloat(renderer, r, g, b, a);
4912
4913 return result;
4914}
4915#endif // SDL_VIDEO_RENDER_SW
4916
4917bool SDL_RenderGeometryRaw(SDL_Renderer *renderer,
4918 SDL_Texture *texture,
4919 const float *xy, int xy_stride,
4920 const SDL_FColor *color, int color_stride,
4921 const float *uv, int uv_stride,
4922 int num_vertices,
4923 const void *indices, int num_indices, int size_indices)
4924{
4925 int i;
4926 int count = indices ? num_indices : num_vertices;
4927 SDL_TextureAddressMode texture_address_mode;
4928
4929 CHECK_RENDERER_MAGIC(renderer, false);
4930
4931 if (!renderer->QueueGeometry) {
4932 return SDL_Unsupported();
4933 }
4934
4935 if (texture) {
4936 CHECK_TEXTURE_MAGIC(texture, false);
4937
4938 if (renderer != texture->renderer) {
4939 return SDL_SetError("Texture was not created with this renderer");
4940 }
4941 }
4942
4943 if (!xy) {
4944 return SDL_InvalidParamError("xy");
4945 }
4946
4947 if (!color) {
4948 return SDL_InvalidParamError("color");
4949 }
4950
4951 if (texture && !uv) {
4952 return SDL_InvalidParamError("uv");
4953 }
4954
4955 if (count % 3 != 0) {
4956 return SDL_InvalidParamError(indices ? "num_indices" : "num_vertices");
4957 }
4958
4959 if (indices) {
4960 if (size_indices != 1 && size_indices != 2 && size_indices != 4) {
4961 return SDL_InvalidParamError("size_indices");
4962 }
4963 } else {
4964 size_indices = 0;
4965 }
4966
4967#if DONT_DRAW_WHILE_HIDDEN
4968 // Don't draw while we're hidden
4969 if (renderer->hidden) {
4970 return true;
4971 }
4972#endif
4973
4974 if (num_vertices < 3) {
4975 return true;
4976 }
4977
4978 if (texture && texture->native) {
4979 texture = texture->native;
4980 }
4981
4982 texture_address_mode = renderer->texture_address_mode;
4983 if (texture_address_mode == SDL_TEXTURE_ADDRESS_AUTO && texture) {
4984 texture_address_mode = SDL_TEXTURE_ADDRESS_CLAMP;
4985 for (i = 0; i < num_vertices; ++i) {
4986 const float *uv_ = (const float *)((const char *)uv + i * uv_stride);
4987 float u = uv_[0];
4988 float v = uv_[1];
4989 if (u < 0.0f || v < 0.0f || u > 1.0f || v > 1.0f) {
4990 texture_address_mode = SDL_TEXTURE_ADDRESS_WRAP;
4991 break;
4992 }
4993 }
4994 }
4995
4996 if (indices) {
4997 for (i = 0; i < num_indices; ++i) {
4998 int j;
4999 if (size_indices == 4) {
5000 j = ((const Uint32 *)indices)[i];
5001 } else if (size_indices == 2) {
5002 j = ((const Uint16 *)indices)[i];
5003 } else {
5004 j = ((const Uint8 *)indices)[i];
5005 }
5006 if (j < 0 || j >= num_vertices) {
5007 return SDL_SetError("Values of 'indices' out of bounds");
5008 }
5009 }
5010 }
5011
5012 if (texture) {
5013 texture->last_command_generation = renderer->render_command_generation;
5014 }
5015
5016 // For the software renderer, try to reinterpret triangles as SDL_Rect
5017#ifdef SDL_VIDEO_RENDER_SW
5018 if (renderer->software && texture_address_mode == SDL_TEXTURE_ADDRESS_CLAMP) {
5019 return SDL_SW_RenderGeometryRaw(renderer, texture,
5020 xy, xy_stride, color, color_stride, uv, uv_stride, num_vertices,
5021 indices, num_indices, size_indices);
5022 }
5023#endif
5024
5025 const SDL_RenderViewState *view = renderer->view;
5026 return QueueCmdGeometry(renderer, texture,
5027 xy, xy_stride, color, color_stride, uv, uv_stride,
5028 num_vertices, indices, num_indices, size_indices,
5029 view->current_scale.x, view->current_scale.y,
5030 texture_address_mode);
5031}
5032
5033SDL_Surface *SDL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect)
5034{
5035 CHECK_RENDERER_MAGIC(renderer, NULL);
5036
5037 if (!renderer->RenderReadPixels) {
5038 SDL_Unsupported();
5039 return NULL;
5040 }
5041
5042 FlushRenderCommands(renderer); // we need to render before we read the results.
5043
5044 SDL_Rect real_rect = renderer->view->pixel_viewport;
5045
5046 if (rect) {
5047 if (!SDL_GetRectIntersection(rect, &real_rect, &real_rect)) {
5048 SDL_SetError("Can't read outside the current viewport");
5049 return NULL;
5050 }
5051 }
5052
5053 SDL_Surface *surface = renderer->RenderReadPixels(renderer, &real_rect);
5054 if (surface) {
5055 SDL_PropertiesID props = SDL_GetSurfaceProperties(surface);
5056
5057 if (renderer->target) {
5058 SDL_Texture *target = renderer->target;
5059 SDL_Texture *parent = SDL_GetPointerProperty(SDL_GetTextureProperties(target), SDL_PROP_TEXTURE_PARENT_POINTER, NULL);
5060 SDL_PixelFormat expected_format = (parent ? parent->format : target->format);
5061
5062 SDL_SetFloatProperty(props, SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT, target->SDR_white_point);
5063 SDL_SetFloatProperty(props, SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT, target->HDR_headroom);
5064
5065 // Set the expected surface format
5066 if ((surface->format == SDL_PIXELFORMAT_ARGB8888 && expected_format == SDL_PIXELFORMAT_XRGB8888) ||
5067 (surface->format == SDL_PIXELFORMAT_RGBA8888 && expected_format == SDL_PIXELFORMAT_RGBX8888) ||
5068 (surface->format == SDL_PIXELFORMAT_ABGR8888 && expected_format == SDL_PIXELFORMAT_XBGR8888) ||
5069 (surface->format == SDL_PIXELFORMAT_BGRA8888 && expected_format == SDL_PIXELFORMAT_BGRX8888)) {
5070 surface->format = expected_format;
5071 surface->fmt = SDL_GetPixelFormatDetails(expected_format);
5072 }
5073 } else {
5074 SDL_SetFloatProperty(props, SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT, renderer->SDR_white_point);
5075 SDL_SetFloatProperty(props, SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT, renderer->HDR_headroom);
5076 }
5077 }
5078 return surface;
5079}
5080
5081static void SDL_RenderApplyWindowShape(SDL_Renderer *renderer)
5082{
5083 SDL_Surface *shape = (SDL_Surface *)SDL_GetPointerProperty(SDL_GetWindowProperties(renderer->window), SDL_PROP_WINDOW_SHAPE_POINTER, NULL);
5084 if (shape != renderer->shape_surface) {
5085 if (renderer->shape_texture) {
5086 SDL_DestroyTexture(renderer->shape_texture);
5087 renderer->shape_texture = NULL;
5088 }
5089
5090 if (shape) {
5091 // There's nothing we can do if this fails, so just keep on going
5092 renderer->shape_texture = SDL_CreateTextureFromSurface(renderer, shape);
5093
5094 SDL_SetTextureBlendMode(renderer->shape_texture,
5095 SDL_ComposeCustomBlendMode(
5096 SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDOPERATION_ADD,
5097 SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDOPERATION_ADD));
5098 }
5099 renderer->shape_surface = shape;
5100 }
5101
5102 if (renderer->shape_texture) {
5103 SDL_RenderTexture(renderer, renderer->shape_texture, NULL, NULL);
5104 }
5105}
5106
5107static void SDL_SimulateRenderVSync(SDL_Renderer *renderer)
5108{
5109 Uint64 now, elapsed;
5110 const Uint64 interval = renderer->simulate_vsync_interval_ns;
5111
5112 if (!interval) {
5113 // We can't do sub-ns delay, so just return here
5114 return;
5115 }
5116
5117 now = SDL_GetTicksNS();
5118 elapsed = (now - renderer->last_present);
5119 if (elapsed < interval) {
5120 Uint64 duration = (interval - elapsed);
5121 SDL_DelayPrecise(duration);
5122 now = SDL_GetTicksNS();
5123 }
5124
5125 elapsed = (now - renderer->last_present);
5126 if (!renderer->last_present || elapsed > SDL_MS_TO_NS(1000)) {
5127 // It's been too long, reset the presentation timeline
5128 renderer->last_present = now;
5129 } else {
5130 renderer->last_present += (elapsed / interval) * interval;
5131 }
5132}
5133
5134bool SDL_RenderPresent(SDL_Renderer *renderer)
5135{
5136 bool presented = true;
5137
5138 CHECK_RENDERER_MAGIC(renderer, false);
5139
5140 SDL_Texture *target = renderer->target;
5141 if (target) {
5142 SDL_SetRenderTarget(renderer, NULL);
5143 }
5144
5145 SDL_RenderLogicalPresentation(renderer);
5146
5147 if (renderer->transparent_window) {
5148 SDL_RenderApplyWindowShape(renderer);
5149 }
5150
5151 FlushRenderCommands(renderer); // time to send everything to the GPU!
5152
5153#if DONT_DRAW_WHILE_HIDDEN
5154 // Don't present while we're hidden
5155 if (renderer->hidden) {
5156 presented = false;
5157 } else
5158#endif
5159 if (!renderer->RenderPresent(renderer)) {
5160 presented = false;
5161 }
5162
5163 if (target) {
5164 SDL_SetRenderTarget(renderer, target);
5165 }
5166
5167 if (renderer->simulate_vsync ||
5168 (!presented && renderer->wanted_vsync)) {
5169 SDL_SimulateRenderVSync(renderer);
5170 }
5171 return true;
5172}
5173
5174static void SDL_DestroyTextureInternal(SDL_Texture *texture, bool is_destroying)
5175{
5176 SDL_Renderer *renderer;
5177
5178 SDL_DestroyProperties(texture->props);
5179
5180 renderer = texture->renderer;
5181 if (is_destroying) {
5182 // Renderer get destroyed, avoid to queue more commands
5183 } else {
5184 if (texture == renderer->target) {
5185 SDL_SetRenderTarget(renderer, NULL); // implies command queue flush
5186 } else {
5187 FlushRenderCommandsIfTextureNeeded(texture);
5188 }
5189 }
5190
5191 SDL_SetObjectValid(texture, SDL_OBJECT_TYPE_TEXTURE, false);
5192
5193 if (texture->next) {
5194 texture->next->prev = texture->prev;
5195 }
5196 if (texture->prev) {
5197 texture->prev->next = texture->next;
5198 } else {
5199 renderer->textures = texture->next;
5200 }
5201
5202 if (texture->native) {
5203 SDL_DestroyTextureInternal(texture->native, is_destroying);
5204 }
5205#ifdef SDL_HAVE_YUV
5206 if (texture->yuv) {
5207 SDL_SW_DestroyYUVTexture(texture->yuv);
5208 }
5209#endif
5210 SDL_free(texture->pixels);
5211
5212 renderer->DestroyTexture(renderer, texture);
5213
5214 SDL_DestroySurface(texture->locked_surface);
5215 texture->locked_surface = NULL;
5216
5217 SDL_free(texture);
5218}
5219
5220void SDL_DestroyTexture(SDL_Texture *texture)
5221{
5222 CHECK_TEXTURE_MAGIC(texture, );
5223
5224 if (--texture->refcount > 0) {
5225 return;
5226 }
5227
5228 SDL_DestroyTextureInternal(texture, false /* is_destroying */);
5229}
5230
5231static void SDL_DiscardAllCommands(SDL_Renderer *renderer)
5232{
5233 SDL_RenderCommand *cmd;
5234
5235 if (renderer->render_commands_tail) {
5236 renderer->render_commands_tail->next = renderer->render_commands_pool;
5237 cmd = renderer->render_commands;
5238 } else {
5239 cmd = renderer->render_commands_pool;
5240 }
5241
5242 renderer->render_commands_pool = NULL;
5243 renderer->render_commands_tail = NULL;
5244 renderer->render_commands = NULL;
5245 renderer->vertex_data_used = 0;
5246
5247 while (cmd) {
5248 SDL_RenderCommand *next = cmd->next;
5249 SDL_free(cmd);
5250 cmd = next;
5251 }
5252}
5253
5254void SDL_DestroyRendererWithoutFreeing(SDL_Renderer *renderer)
5255{
5256 SDL_assert(renderer != NULL);
5257 SDL_assert(!renderer->destroyed);
5258
5259 renderer->destroyed = true;
5260
5261 SDL_RemoveWindowEventWatch(SDL_WINDOW_EVENT_WATCH_NORMAL, SDL_RendererEventWatch, renderer);
5262
5263 if (renderer->window) {
5264 SDL_PropertiesID props = SDL_GetWindowProperties(renderer->window);
5265 if (SDL_GetPointerProperty(props, SDL_PROP_WINDOW_RENDERER_POINTER, NULL) == renderer) {
5266 SDL_ClearProperty(props, SDL_PROP_WINDOW_RENDERER_POINTER);
5267 }
5268 SDL_RemoveWindowRenderer(renderer->window, renderer);
5269 }
5270
5271 if (renderer->software) {
5272 // Make sure all drawing to a surface is complete
5273 FlushRenderCommands(renderer);
5274 }
5275 SDL_DiscardAllCommands(renderer);
5276
5277 if (renderer->debug_char_texture_atlas) {
5278 SDL_DestroyTexture(renderer->debug_char_texture_atlas);
5279 renderer->debug_char_texture_atlas = NULL;
5280 }
5281
5282 // Free existing textures for this renderer
5283 while (renderer->textures) {
5284 SDL_Texture *tex = renderer->textures;
5285 SDL_DestroyTextureInternal(renderer->textures, true /* is_destroying */);
5286 SDL_assert(tex != renderer->textures); // satisfy static analysis.
5287 }
5288
5289 // Clean up renderer-specific resources
5290 if (renderer->DestroyRenderer) {
5291 renderer->DestroyRenderer(renderer);
5292 }
5293
5294 if (renderer->target_mutex) {
5295 SDL_DestroyMutex(renderer->target_mutex);
5296 renderer->target_mutex = NULL;
5297 }
5298 if (renderer->vertex_data) {
5299 SDL_free(renderer->vertex_data);
5300 renderer->vertex_data = NULL;
5301 }
5302 if (renderer->texture_formats) {
5303 SDL_free(renderer->texture_formats);
5304 renderer->texture_formats = NULL;
5305 }
5306 if (renderer->props) {
5307 SDL_DestroyProperties(renderer->props);
5308 renderer->props = 0;
5309 }
5310}
5311
5312void SDL_DestroyRenderer(SDL_Renderer *renderer)
5313{
5314 CHECK_RENDERER_MAGIC_BUT_NOT_DESTROYED_FLAG(renderer,);
5315
5316 // if we've already destroyed the renderer through SDL_DestroyWindow, we just need
5317 // to free the renderer pointer. This lets apps destroy the window and renderer
5318 // in either order.
5319 if (!renderer->destroyed) {
5320 SDL_DestroyRendererWithoutFreeing(renderer);
5321 }
5322
5323 SDL_Renderer *curr = SDL_renderers;
5324 SDL_Renderer *prev = NULL;
5325 while (curr) {
5326 if (curr == renderer) {
5327 if (prev) {
5328 prev->next = renderer->next;
5329 } else {
5330 SDL_renderers = renderer->next;
5331 }
5332 break;
5333 }
5334 prev = curr;
5335 curr = curr->next;
5336 }
5337
5338 SDL_SetObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER, false); // It's no longer magical...
5339
5340 SDL_free(renderer);
5341}
5342
5343void *SDL_GetRenderMetalLayer(SDL_Renderer *renderer)
5344{
5345 CHECK_RENDERER_MAGIC(renderer, NULL);
5346
5347 if (renderer->GetMetalLayer) {
5348 FlushRenderCommands(renderer); // in case the app is going to mess with it.
5349 return renderer->GetMetalLayer(renderer);
5350 }
5351 return NULL;
5352}
5353
5354void *SDL_GetRenderMetalCommandEncoder(SDL_Renderer *renderer)
5355{
5356 CHECK_RENDERER_MAGIC(renderer, NULL);
5357
5358 if (renderer->GetMetalCommandEncoder) {
5359 FlushRenderCommands(renderer); // in case the app is going to mess with it.
5360 return renderer->GetMetalCommandEncoder(renderer);
5361 }
5362 return NULL;
5363}
5364
5365bool SDL_AddVulkanRenderSemaphores(SDL_Renderer *renderer, Uint32 wait_stage_mask, Sint64 wait_semaphore, Sint64 signal_semaphore)
5366{
5367 CHECK_RENDERER_MAGIC(renderer, false);
5368
5369 if (!renderer->AddVulkanRenderSemaphores) {
5370 return SDL_Unsupported();
5371 }
5372 return renderer->AddVulkanRenderSemaphores(renderer, wait_stage_mask, wait_semaphore, signal_semaphore);
5373}
5374
5375static SDL_BlendMode SDL_GetShortBlendMode(SDL_BlendMode blendMode)
5376{
5377 if (blendMode == SDL_BLENDMODE_NONE_FULL) {
5378 return SDL_BLENDMODE_NONE;
5379 }
5380 if (blendMode == SDL_BLENDMODE_BLEND_FULL) {
5381 return SDL_BLENDMODE_BLEND;
5382 }
5383 if (blendMode == SDL_BLENDMODE_BLEND_PREMULTIPLIED_FULL) {
5384 return SDL_BLENDMODE_BLEND_PREMULTIPLIED;
5385 }
5386 if (blendMode == SDL_BLENDMODE_ADD_FULL) {
5387 return SDL_BLENDMODE_ADD;
5388 }
5389 if (blendMode == SDL_BLENDMODE_ADD_PREMULTIPLIED_FULL) {
5390 return SDL_BLENDMODE_ADD_PREMULTIPLIED;
5391 }
5392 if (blendMode == SDL_BLENDMODE_MOD_FULL) {
5393 return SDL_BLENDMODE_MOD;
5394 }
5395 if (blendMode == SDL_BLENDMODE_MUL_FULL) {
5396 return SDL_BLENDMODE_MUL;
5397 }
5398 return blendMode;
5399}
5400
5401static SDL_BlendMode SDL_GetLongBlendMode(SDL_BlendMode blendMode)
5402{
5403 if (blendMode == SDL_BLENDMODE_NONE) {
5404 return SDL_BLENDMODE_NONE_FULL;
5405 }
5406 if (blendMode == SDL_BLENDMODE_BLEND) {
5407 return SDL_BLENDMODE_BLEND_FULL;
5408 }
5409 if (blendMode == SDL_BLENDMODE_BLEND_PREMULTIPLIED) {
5410 return SDL_BLENDMODE_BLEND_PREMULTIPLIED_FULL;
5411 }
5412 if (blendMode == SDL_BLENDMODE_ADD) {
5413 return SDL_BLENDMODE_ADD_FULL;
5414 }
5415 if (blendMode == SDL_BLENDMODE_ADD_PREMULTIPLIED) {
5416 return SDL_BLENDMODE_ADD_PREMULTIPLIED_FULL;
5417 }
5418 if (blendMode == SDL_BLENDMODE_MOD) {
5419 return SDL_BLENDMODE_MOD_FULL;
5420 }
5421 if (blendMode == SDL_BLENDMODE_MUL) {
5422 return SDL_BLENDMODE_MUL_FULL;
5423 }
5424 return blendMode;
5425}
5426
5427SDL_BlendMode SDL_ComposeCustomBlendMode(SDL_BlendFactor srcColorFactor, SDL_BlendFactor dstColorFactor,
5428 SDL_BlendOperation colorOperation,
5429 SDL_BlendFactor srcAlphaFactor, SDL_BlendFactor dstAlphaFactor,
5430 SDL_BlendOperation alphaOperation)
5431{
5432 SDL_BlendMode blendMode = SDL_COMPOSE_BLENDMODE(srcColorFactor, dstColorFactor, colorOperation,
5433 srcAlphaFactor, dstAlphaFactor, alphaOperation);
5434 return SDL_GetShortBlendMode(blendMode);
5435}
5436
5437SDL_BlendFactor SDL_GetBlendModeSrcColorFactor(SDL_BlendMode blendMode)
5438{
5439 blendMode = SDL_GetLongBlendMode(blendMode);
5440 return (SDL_BlendFactor)(((Uint32)blendMode >> 4) & 0xF);
5441}
5442
5443SDL_BlendFactor SDL_GetBlendModeDstColorFactor(SDL_BlendMode blendMode)
5444{
5445 blendMode = SDL_GetLongBlendMode(blendMode);
5446 return (SDL_BlendFactor)(((Uint32)blendMode >> 8) & 0xF);
5447}
5448
5449SDL_BlendOperation SDL_GetBlendModeColorOperation(SDL_BlendMode blendMode)
5450{
5451 blendMode = SDL_GetLongBlendMode(blendMode);
5452 return (SDL_BlendOperation)(((Uint32)blendMode >> 0) & 0xF);
5453}
5454
5455SDL_BlendFactor SDL_GetBlendModeSrcAlphaFactor(SDL_BlendMode blendMode)
5456{
5457 blendMode = SDL_GetLongBlendMode(blendMode);
5458 return (SDL_BlendFactor)(((Uint32)blendMode >> 20) & 0xF);
5459}
5460
5461SDL_BlendFactor SDL_GetBlendModeDstAlphaFactor(SDL_BlendMode blendMode)
5462{
5463 blendMode = SDL_GetLongBlendMode(blendMode);
5464 return (SDL_BlendFactor)(((Uint32)blendMode >> 24) & 0xF);
5465}
5466
5467SDL_BlendOperation SDL_GetBlendModeAlphaOperation(SDL_BlendMode blendMode)
5468{
5469 blendMode = SDL_GetLongBlendMode(blendMode);
5470 return (SDL_BlendOperation)(((Uint32)blendMode >> 16) & 0xF);
5471}
5472
5473bool SDL_SetRenderVSync(SDL_Renderer *renderer, int vsync)
5474{
5475 CHECK_RENDERER_MAGIC(renderer, false);
5476
5477 renderer->wanted_vsync = vsync ? true : false;
5478
5479 // for the software renderer, forward the call to the WindowTexture renderer
5480#ifdef SDL_VIDEO_RENDER_SW
5481 if (renderer->software) {
5482 if (!renderer->window) {
5483 if (!vsync) {
5484 return true;
5485 } else {
5486 return SDL_Unsupported();
5487 }
5488 }
5489 if (SDL_SetWindowTextureVSync(NULL, renderer->window, vsync)) {
5490 renderer->simulate_vsync = false;
5491 return true;
5492 }
5493 }
5494#endif
5495
5496 if (!renderer->SetVSync) {
5497 switch (vsync) {
5498 case 0:
5499 renderer->simulate_vsync = false;
5500 break;
5501 case 1:
5502 renderer->simulate_vsync = true;
5503 break;
5504 default:
5505 return SDL_Unsupported();
5506 }
5507 } else if (!renderer->SetVSync(renderer, vsync)) {
5508 if (vsync == 1) {
5509 renderer->simulate_vsync = true;
5510 } else {
5511 return false;
5512 }
5513 }
5514 SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_VSYNC_NUMBER, vsync);
5515 return true;
5516}
5517
5518bool SDL_GetRenderVSync(SDL_Renderer *renderer, int *vsync)
5519{
5520 if (vsync) {
5521 *vsync = 0;
5522 }
5523
5524 CHECK_RENDERER_MAGIC(renderer, false);
5525
5526 if (vsync) {
5527 *vsync = (int)SDL_GetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_VSYNC_NUMBER, 0);
5528 }
5529 return true;
5530}
5531
5532
5533#define SDL_DEBUG_FONT_GLYPHS_PER_ROW 14
5534
5535static bool CreateDebugTextAtlas(SDL_Renderer *renderer)
5536{
5537 SDL_assert(renderer->debug_char_texture_atlas == NULL); // don't double-create it!
5538
5539 const int charWidth = SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE;
5540 const int charHeight = SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE;
5541
5542 // actually make each glyph two pixels taller/wider, to prevent scaling artifacts.
5543 const int rows = (SDL_DEBUG_FONT_NUM_GLYPHS / SDL_DEBUG_FONT_GLYPHS_PER_ROW) + 1;
5544 SDL_Surface *atlas = SDL_CreateSurface((charWidth + 2) * SDL_DEBUG_FONT_GLYPHS_PER_ROW, rows * (charHeight + 2), SDL_PIXELFORMAT_RGBA8888);
5545 if (!atlas) {
5546 return false;
5547 }
5548
5549 const int pitch = atlas->pitch;
5550 SDL_memset(atlas->pixels, '\0', atlas->h * atlas->pitch);
5551
5552 int column = 0;
5553 int row = 0;
5554 for (int glyph = 0; glyph < SDL_DEBUG_FONT_NUM_GLYPHS; glyph++) {
5555 // find top-left of this glyph in destination surface. The +2's account for glyph padding.
5556 Uint8 *linepos = (((Uint8 *)atlas->pixels) + ((row * (charHeight + 2) + 1) * pitch)) + ((column * (charWidth + 2) + 1) * sizeof (Uint32));
5557 const Uint8 *charpos = SDL_RenderDebugTextFontData + (glyph * 8);
5558
5559 // Draw the glyph to the surface...
5560 for (int iy = 0; iy < charHeight; iy++) {
5561 Uint32 *curpos = (Uint32 *)linepos;
5562 for (int ix = 0; ix < charWidth; ix++) {
5563 if ((*charpos) & (1 << ix)) {
5564 *curpos = 0xffffffff;
5565 } else {
5566 *curpos = 0;
5567 }
5568 ++curpos;
5569 }
5570 linepos += pitch;
5571 ++charpos;
5572 }
5573
5574 // move to next position (and if too far, start the next row).
5575 column++;
5576 if (column >= SDL_DEBUG_FONT_GLYPHS_PER_ROW) {
5577 row++;
5578 column = 0;
5579 }
5580 }
5581
5582 SDL_assert((row < rows) || ((row == rows) && (column == 0))); // make sure we didn't overflow the surface.
5583
5584 // Convert temp surface into texture
5585 SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, atlas);
5586 if (texture) {
5587 SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST);
5588 renderer->debug_char_texture_atlas = texture;
5589 }
5590 SDL_DestroySurface(atlas);
5591
5592 return texture != NULL;
5593}
5594
5595static bool DrawDebugCharacter(SDL_Renderer *renderer, float x, float y, Uint32 c)
5596{
5597 SDL_assert(renderer->debug_char_texture_atlas != NULL); // should have been created by now!
5598
5599 const int charWidth = SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE;
5600 const int charHeight = SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE;
5601
5602 // Character index in cache
5603 Uint32 ci = c;
5604 if ((ci <= 32) || ((ci >= 127) && (ci <= 160))) {
5605 return true; // these are just completely blank chars, don't bother doing anything.
5606 } else if (ci >= SDL_DEBUG_FONT_NUM_GLYPHS) {
5607 ci = SDL_DEBUG_FONT_NUM_GLYPHS - 1; // use our "not a valid/supported character" glyph.
5608 } else if (ci < 127) {
5609 ci -= 33; // adjust for the 33 blank glyphs at the start
5610 } else {
5611 ci -= 67; // adjust for the 33 blank glyphs at the start AND the 34 gap in the middle.
5612 }
5613
5614 const float src_x = (float) (((ci % SDL_DEBUG_FONT_GLYPHS_PER_ROW) * (charWidth + 2)) + 1);
5615 const float src_y = (float) (((ci / SDL_DEBUG_FONT_GLYPHS_PER_ROW) * (charHeight + 2)) + 1);
5616
5617 // Draw texture onto destination
5618 const SDL_FRect srect = { src_x, src_y, (float) charWidth, (float) charHeight };
5619 const SDL_FRect drect = { x, y, (float) charWidth, (float) charHeight };
5620 return SDL_RenderTexture(renderer, renderer->debug_char_texture_atlas, &srect, &drect);
5621}
5622
5623bool SDL_RenderDebugText(SDL_Renderer *renderer, float x, float y, const char *s)
5624{
5625 CHECK_RENDERER_MAGIC(renderer, false);
5626
5627 // Allocate a texture atlas for this renderer if needed.
5628 if (!renderer->debug_char_texture_atlas) {
5629 if (!CreateDebugTextAtlas(renderer)) {
5630 return false;
5631 }
5632 }
5633
5634 bool result = true;
5635
5636 Uint8 r, g, b, a;
5637 result &= SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
5638 result &= SDL_SetTextureColorMod(renderer->debug_char_texture_atlas, r, g, b);
5639 result &= SDL_SetTextureAlphaMod(renderer->debug_char_texture_atlas, a);
5640
5641 float curx = x;
5642 Uint32 ch;
5643
5644 while (result && ((ch = SDL_StepUTF8(&s, NULL)) != 0)) {
5645 result &= DrawDebugCharacter(renderer, curx, y, ch);
5646 curx += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE;
5647 }
5648
5649 return result;
5650}
5651
5652bool SDL_RenderDebugTextFormat(SDL_Renderer *renderer, float x, float y, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
5653{
5654 va_list ap;
5655 va_start(ap, fmt);
5656
5657 // fast path to avoid unnecessary allocation and copy. If you're going through the dynapi, there's a good chance
5658 // you _always_ hit this path, since it probably had to process varargs before calling into the jumptable.
5659 if (SDL_strcmp(fmt, "%s") == 0) {
5660 const char *str = va_arg(ap, const char *);
5661 va_end(ap);
5662 return SDL_RenderDebugText(renderer, x, y, str);
5663 }
5664
5665 char *str = NULL;
5666 const int rc = SDL_vasprintf(&str, fmt, ap);
5667 va_end(ap);
5668
5669 if (rc == -1) {
5670 return false;
5671 }
5672
5673 const bool retval = SDL_RenderDebugText(renderer, x, y, str);
5674 SDL_free(str);
5675 return retval;
5676}