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