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