1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_RENDER_GPU
24
25#include "../../video/SDL_pixels_c.h"
26#include "../SDL_d3dmath.h"
27#include "../SDL_sysrender.h"
28#include "SDL_gpu_util.h"
29#include "SDL_pipeline_gpu.h"
30#include "SDL_shaders_gpu.h"
31
32typedef struct GPU_VertexShaderUniformData
33{
34 Float4X4 mvp;
35 SDL_FColor color;
36} GPU_VertexShaderUniformData;
37
38typedef struct GPU_FragmentShaderUniformData
39{
40 float texel_width;
41 float texel_height;
42 float texture_width;
43 float texture_height;
44} GPU_FragmentShaderUniformData;
45
46typedef struct GPU_RenderData
47{
48 SDL_GPUDevice *device;
49 GPU_Shaders shaders;
50 GPU_PipelineCache pipeline_cache;
51
52 struct
53 {
54 SDL_GPUTexture *texture;
55 SDL_GPUTextureFormat format;
56 Uint32 width;
57 Uint32 height;
58 } backbuffer;
59
60 struct
61 {
62 SDL_GPUSwapchainComposition composition;
63 SDL_GPUPresentMode present_mode;
64 } swapchain;
65
66 struct
67 {
68 SDL_GPUTransferBuffer *transfer_buf;
69 SDL_GPUBuffer *buffer;
70 Uint32 buffer_size;
71 } vertices;
72
73 struct
74 {
75 SDL_GPURenderPass *render_pass;
76 SDL_Texture *render_target;
77 SDL_GPUCommandBuffer *command_buffer;
78 SDL_GPUColorTargetInfo color_attachment;
79 SDL_GPUViewport viewport;
80 SDL_Rect scissor;
81 SDL_FColor draw_color;
82 bool scissor_enabled;
83 bool scissor_was_enabled;
84 } state;
85
86 SDL_GPUSampler *samplers[3][2];
87} GPU_RenderData;
88
89typedef struct GPU_TextureData
90{
91 SDL_GPUTexture *texture;
92 SDL_GPUTextureFormat format;
93 GPU_FragmentShaderID shader;
94 void *pixels;
95 int pitch;
96 SDL_Rect locked_rect;
97} GPU_TextureData;
98
99static bool GPU_SupportsBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode)
100{
101 SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode);
102 SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode);
103 SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode);
104 SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode);
105 SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode);
106 SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode);
107
108 if (GPU_ConvertBlendFactor(srcColorFactor) == SDL_GPU_BLENDFACTOR_INVALID ||
109 GPU_ConvertBlendFactor(srcAlphaFactor) == SDL_GPU_BLENDFACTOR_INVALID ||
110 GPU_ConvertBlendOperation(colorOperation) == SDL_GPU_BLENDOP_INVALID ||
111 GPU_ConvertBlendFactor(dstColorFactor) == SDL_GPU_BLENDFACTOR_INVALID ||
112 GPU_ConvertBlendFactor(dstAlphaFactor) == SDL_GPU_BLENDFACTOR_INVALID ||
113 GPU_ConvertBlendOperation(alphaOperation) == SDL_GPU_BLENDOP_INVALID) {
114 return false;
115 }
116
117 return true;
118}
119
120static SDL_GPUTextureFormat PixFormatToTexFormat(SDL_PixelFormat pixel_format)
121{
122 switch (pixel_format) {
123 case SDL_PIXELFORMAT_BGRA32:
124 case SDL_PIXELFORMAT_BGRX32:
125 return SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
126 case SDL_PIXELFORMAT_RGBA32:
127 case SDL_PIXELFORMAT_RGBX32:
128 return SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
129
130 // YUV TODO
131 case SDL_PIXELFORMAT_YV12:
132 case SDL_PIXELFORMAT_IYUV:
133 case SDL_PIXELFORMAT_NV12:
134 case SDL_PIXELFORMAT_NV21:
135 case SDL_PIXELFORMAT_UYVY:
136 default:
137 return SDL_GPU_TEXTUREFORMAT_INVALID;
138 }
139}
140
141static SDL_PixelFormat TexFormatToPixFormat(SDL_GPUTextureFormat tex_format)
142{
143 switch (tex_format) {
144 case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM:
145 return SDL_PIXELFORMAT_RGBA32;
146 case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM:
147 return SDL_PIXELFORMAT_BGRA32;
148 case SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM:
149 return SDL_PIXELFORMAT_BGR565;
150 case SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM:
151 return SDL_PIXELFORMAT_BGRA5551;
152 case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM:
153 return SDL_PIXELFORMAT_BGRA4444;
154 case SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM:
155 return SDL_PIXELFORMAT_ABGR2101010;
156 case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM:
157 return SDL_PIXELFORMAT_RGBA64;
158 case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM:
159 return SDL_PIXELFORMAT_RGBA32;
160 case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT:
161 return SDL_PIXELFORMAT_RGBA64_FLOAT;
162 case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT:
163 return SDL_PIXELFORMAT_RGBA128_FLOAT;
164 case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT:
165 return SDL_PIXELFORMAT_RGBA32;
166 case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT:
167 return SDL_PIXELFORMAT_RGBA64;
168 case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB:
169 return SDL_PIXELFORMAT_RGBA32;
170 case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB:
171 return SDL_PIXELFORMAT_BGRA32;
172 default:
173 return SDL_PIXELFORMAT_UNKNOWN;
174 }
175}
176
177static bool GPU_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props)
178{
179 GPU_RenderData *renderdata = (GPU_RenderData *)renderer->internal;
180 GPU_TextureData *data;
181 SDL_GPUTextureFormat format;
182 SDL_GPUTextureUsageFlags usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
183
184 format = PixFormatToTexFormat(texture->format);
185
186 if (format == SDL_GPU_TEXTUREFORMAT_INVALID) {
187 return SDL_SetError("Texture format %s not supported by SDL_GPU",
188 SDL_GetPixelFormatName(texture->format));
189 }
190
191 data = (GPU_TextureData *)SDL_calloc(1, sizeof(*data));
192 if (!data) {
193 return false;
194 }
195
196 if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
197 size_t size;
198 data->pitch = texture->w * SDL_BYTESPERPIXEL(texture->format);
199 size = (size_t)texture->h * data->pitch;
200 if (texture->format == SDL_PIXELFORMAT_YV12 ||
201 texture->format == SDL_PIXELFORMAT_IYUV) {
202 // Need to add size for the U and V planes
203 size += 2 * ((texture->h + 1) / 2) * ((data->pitch + 1) / 2);
204 }
205 if (texture->format == SDL_PIXELFORMAT_NV12 ||
206 texture->format == SDL_PIXELFORMAT_NV21) {
207 // Need to add size for the U/V plane
208 size += 2 * ((texture->h + 1) / 2) * ((data->pitch + 1) / 2);
209 }
210 data->pixels = SDL_calloc(1, size);
211 if (!data->pixels) {
212 SDL_free(data);
213 return false;
214 }
215
216 // TODO allocate a persistent transfer buffer
217 }
218
219 if (texture->access == SDL_TEXTUREACCESS_TARGET) {
220 usage |= SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
221 }
222
223 texture->internal = data;
224 SDL_GPUTextureCreateInfo tci;
225 SDL_zero(tci);
226 tci.format = format;
227 tci.layer_count_or_depth = 1;
228 tci.num_levels = 1;
229 tci.usage = usage;
230 tci.width = texture->w;
231 tci.height = texture->h;
232 tci.sample_count = SDL_GPU_SAMPLECOUNT_1;
233
234 data->format = format;
235 data->texture = SDL_CreateGPUTexture(renderdata->device, &tci);
236
237 if (!data->texture) {
238 return false;
239 }
240
241 if (texture->format == SDL_PIXELFORMAT_RGBA32 || texture->format == SDL_PIXELFORMAT_BGRA32) {
242 data->shader = FRAG_SHADER_TEXTURE_RGBA;
243 } else {
244 data->shader = FRAG_SHADER_TEXTURE_RGB;
245 }
246
247 return true;
248}
249
250static bool GPU_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
251 const SDL_Rect *rect, const void *pixels, int pitch)
252{
253 GPU_RenderData *renderdata = (GPU_RenderData *)renderer->internal;
254 GPU_TextureData *data = (GPU_TextureData *)texture->internal;
255 const Uint32 texturebpp = SDL_BYTESPERPIXEL(texture->format);
256
257 size_t row_size, data_size;
258
259 if (!SDL_size_mul_check_overflow(rect->w, texturebpp, &row_size) ||
260 !SDL_size_mul_check_overflow(rect->h, row_size, &data_size)) {
261 return SDL_SetError("update size overflow");
262 }
263
264 SDL_GPUTransferBufferCreateInfo tbci;
265 SDL_zero(tbci);
266 tbci.size = (Uint32)data_size;
267 tbci.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
268
269 SDL_GPUTransferBuffer *tbuf = SDL_CreateGPUTransferBuffer(renderdata->device, &tbci);
270
271 if (tbuf == NULL) {
272 return false;
273 }
274
275 Uint8 *output = SDL_MapGPUTransferBuffer(renderdata->device, tbuf, false);
276
277 if ((size_t)pitch == row_size) {
278 SDL_memcpy(output, pixels, data_size);
279 } else {
280 // FIXME is negative pitch supposed to work?
281 // If not, maybe use SDL_GPUTextureTransferInfo::pixels_per_row instead of this
282 const Uint8 *input = pixels;
283
284 for (int i = 0; i < rect->h; ++i) {
285 SDL_memcpy(output, input, row_size);
286 output += row_size;
287 input += pitch;
288 }
289 }
290
291 SDL_UnmapGPUTransferBuffer(renderdata->device, tbuf);
292
293 SDL_GPUCommandBuffer *cbuf = renderdata->state.command_buffer;
294 SDL_GPUCopyPass *cpass = SDL_BeginGPUCopyPass(cbuf);
295
296 SDL_GPUTextureTransferInfo tex_src;
297 SDL_zero(tex_src);
298 tex_src.transfer_buffer = tbuf;
299 tex_src.rows_per_layer = rect->h;
300 tex_src.pixels_per_row = rect->w;
301
302 SDL_GPUTextureRegion tex_dst;
303 SDL_zero(tex_dst);
304 tex_dst.texture = data->texture;
305 tex_dst.x = rect->x;
306 tex_dst.y = rect->y;
307 tex_dst.w = rect->w;
308 tex_dst.h = rect->h;
309 tex_dst.d = 1;
310
311 SDL_UploadToGPUTexture(cpass, &tex_src, &tex_dst, false);
312 SDL_EndGPUCopyPass(cpass);
313 SDL_ReleaseGPUTransferBuffer(renderdata->device, tbuf);
314
315 return true;
316}
317
318static bool GPU_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture,
319 const SDL_Rect *rect, void **pixels, int *pitch)
320{
321 GPU_TextureData *data = (GPU_TextureData *)texture->internal;
322
323 data->locked_rect = *rect;
324 *pixels =
325 (void *)((Uint8 *)data->pixels + rect->y * data->pitch +
326 rect->x * SDL_BYTESPERPIXEL(texture->format));
327 *pitch = data->pitch;
328 return true;
329}
330
331static void GPU_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
332{
333 GPU_TextureData *data = (GPU_TextureData *)texture->internal;
334 const SDL_Rect *rect;
335 void *pixels;
336
337 rect = &data->locked_rect;
338 pixels =
339 (void *)((Uint8 *)data->pixels + rect->y * data->pitch +
340 rect->x * SDL_BYTESPERPIXEL(texture->format));
341 GPU_UpdateTexture(renderer, texture, rect, pixels, data->pitch);
342}
343
344static bool GPU_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
345{
346 GPU_RenderData *data = (GPU_RenderData *)renderer->internal;
347
348 data->state.render_target = texture;
349
350 return true;
351}
352
353static bool GPU_QueueNoOp(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
354{
355 return true; // nothing to do in this backend.
356}
357
358static SDL_FColor GetDrawCmdColor(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
359{
360 SDL_FColor color = cmd->data.color.color;
361
362 if (SDL_RenderingLinearSpace(renderer)) {
363 SDL_ConvertToLinear(&color);
364 }
365
366 color.r *= cmd->data.color.color_scale;
367 color.g *= cmd->data.color.color_scale;
368 color.b *= cmd->data.color.color_scale;
369
370 return color;
371}
372
373static bool GPU_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count)
374{
375 float *verts = (float *)SDL_AllocateRenderVertices(renderer, count * 2 * sizeof(float), 0, &cmd->data.draw.first);
376
377 if (!verts) {
378 return false;
379 }
380
381 cmd->data.draw.count = count;
382 for (int i = 0; i < count; i++) {
383 *(verts++) = 0.5f + points[i].x;
384 *(verts++) = 0.5f + points[i].y;
385 }
386
387 return true;
388}
389
390static bool GPU_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture,
391 const float *xy, int xy_stride, const SDL_FColor *color, int color_stride, const float *uv, int uv_stride,
392 int num_vertices, const void *indices, int num_indices, int size_indices,
393 float scale_x, float scale_y)
394{
395 int i;
396 int count = indices ? num_indices : num_vertices;
397 float *verts;
398 size_t sz = 2 * sizeof(float) + 4 * sizeof(float) + (texture ? 2 : 0) * sizeof(float);
399 const float color_scale = cmd->data.draw.color_scale;
400 bool convert_color = SDL_RenderingLinearSpace(renderer);
401
402 verts = (float *)SDL_AllocateRenderVertices(renderer, count * sz, 0, &cmd->data.draw.first);
403 if (!verts) {
404 return false;
405 }
406
407 cmd->data.draw.count = count;
408 size_indices = indices ? size_indices : 0;
409
410 for (i = 0; i < count; i++) {
411 int j;
412 float *xy_;
413 SDL_FColor col_;
414 if (size_indices == 4) {
415 j = ((const Uint32 *)indices)[i];
416 } else if (size_indices == 2) {
417 j = ((const Uint16 *)indices)[i];
418 } else if (size_indices == 1) {
419 j = ((const Uint8 *)indices)[i];
420 } else {
421 j = i;
422 }
423
424 xy_ = (float *)((char *)xy + j * xy_stride);
425
426 *(verts++) = xy_[0] * scale_x;
427 *(verts++) = xy_[1] * scale_y;
428
429 col_ = *(SDL_FColor *)((char *)color + j * color_stride);
430 if (convert_color) {
431 SDL_ConvertToLinear(&col_);
432 }
433
434 // FIXME: The Vulkan backend doesn't multiply by color_scale. GL does. I'm not sure which one is wrong.
435 // ANSWER: The color scale should be applied in linear space when using the scRGB colorspace. This is done in shaders in the Vulkan backend.
436 *(verts++) = col_.r * color_scale;
437 *(verts++) = col_.g * color_scale;
438 *(verts++) = col_.b * color_scale;
439 *(verts++) = col_.a;
440
441 if (texture) {
442 float *uv_ = (float *)((char *)uv + j * uv_stride);
443 *(verts++) = uv_[0];
444 *(verts++) = uv_[1];
445 }
446 }
447 return true;
448}
449
450static void GPU_InvalidateCachedState(SDL_Renderer *renderer)
451{
452 GPU_RenderData *data = (GPU_RenderData *)renderer->internal;
453
454 data->state.render_target = NULL;
455 data->state.scissor_enabled = false;
456}
457
458static SDL_GPURenderPass *RestartRenderPass(GPU_RenderData *data)
459{
460 if (data->state.render_pass) {
461 SDL_EndGPURenderPass(data->state.render_pass);
462 }
463
464 data->state.render_pass = SDL_BeginGPURenderPass(
465 data->state.command_buffer, &data->state.color_attachment, 1, NULL);
466
467 // *** FIXME ***
468 // This is busted. We should be able to know which load op to use.
469 // LOAD is incorrect behavior most of the time, unless we had to break a render pass.
470 // -cosmonaut
471 data->state.color_attachment.load_op = SDL_GPU_LOADOP_LOAD;
472 data->state.scissor_was_enabled = false;
473
474 return data->state.render_pass;
475}
476
477static void PushVertexUniforms(GPU_RenderData *data, SDL_RenderCommand *cmd)
478{
479 GPU_VertexShaderUniformData uniforms;
480 SDL_zero(uniforms);
481 uniforms.mvp.m[0][0] = 2.0f / data->state.viewport.w;
482 uniforms.mvp.m[1][1] = -2.0f / data->state.viewport.h;
483 uniforms.mvp.m[2][2] = 1.0f;
484 uniforms.mvp.m[3][0] = -1.0f;
485 uniforms.mvp.m[3][1] = 1.0f;
486 uniforms.mvp.m[3][3] = 1.0f;
487
488 uniforms.color = data->state.draw_color;
489
490 SDL_PushGPUVertexUniformData(data->state.command_buffer, 0, &uniforms, sizeof(uniforms));
491}
492
493static void PushFragmentUniforms(GPU_RenderData *data, SDL_RenderCommand *cmd)
494{
495 if (cmd->data.draw.texture &&
496 cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) {
497 SDL_Texture *texture = cmd->data.draw.texture;
498 GPU_FragmentShaderUniformData uniforms;
499 SDL_zero(uniforms);
500 uniforms.texture_width = texture->w;
501 uniforms.texture_height = texture->h;
502 uniforms.texel_width = 1.0f / uniforms.texture_width;
503 uniforms.texel_height = 1.0f / uniforms.texture_height;
504 SDL_PushGPUFragmentUniformData(data->state.command_buffer, 0, &uniforms, sizeof(uniforms));
505 }
506}
507
508static SDL_GPUSampler **SamplerPointer(GPU_RenderData *data, SDL_TextureAddressMode address_mode, SDL_ScaleMode scale_mode)
509{
510 SDL_assert(scale_mode < SDL_arraysize(data->samplers));
511 SDL_assert((address_mode - 1) < SDL_arraysize(data->samplers[0]));
512 return &data->samplers[scale_mode][address_mode - 1];
513}
514
515static void SetViewportAndScissor(GPU_RenderData *data)
516{
517 SDL_SetGPUViewport(data->state.render_pass, &data->state.viewport);
518
519 if (data->state.scissor_enabled) {
520 SDL_SetGPUScissor(data->state.render_pass, &data->state.scissor);
521 data->state.scissor_was_enabled = true;
522 } else if (data->state.scissor_was_enabled) {
523 SDL_Rect r;
524 r.x = (int)data->state.viewport.x;
525 r.y = (int)data->state.viewport.y;
526 r.w = (int)data->state.viewport.w;
527 r.h = (int)data->state.viewport.h;
528 SDL_SetGPUScissor(data->state.render_pass, &r);
529 data->state.scissor_was_enabled = false;
530 }
531}
532
533static void Draw(
534 GPU_RenderData *data, SDL_RenderCommand *cmd,
535 Uint32 num_verts,
536 Uint32 offset,
537 SDL_GPUPrimitiveType prim)
538{
539 if (!data->state.render_pass || data->state.color_attachment.load_op == SDL_GPU_LOADOP_CLEAR) {
540 RestartRenderPass(data);
541 }
542
543 SDL_GPURenderPass *pass = data->state.render_pass;
544 SDL_GPURenderState *custom_state = cmd->data.draw.gpu_render_state;
545 SDL_GPUShader *custom_frag_shader = custom_state ? custom_state->fragment_shader : NULL;
546 GPU_VertexShaderID v_shader;
547 GPU_FragmentShaderID f_shader;
548
549 if (prim == SDL_GPU_PRIMITIVETYPE_TRIANGLELIST) {
550 SDL_Texture *texture = cmd->data.draw.texture;
551 if (texture) {
552 v_shader = VERT_SHADER_TRI_TEXTURE;
553 if (texture->format == SDL_PIXELFORMAT_RGBA32 || texture->format == SDL_PIXELFORMAT_BGRA32) {
554 if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) {
555 f_shader = FRAG_SHADER_TEXTURE_RGBA_PIXELART;
556 } else {
557 f_shader = FRAG_SHADER_TEXTURE_RGBA;
558 }
559 } else {
560 if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) {
561 f_shader = FRAG_SHADER_TEXTURE_RGB_PIXELART;
562 } else {
563 f_shader = FRAG_SHADER_TEXTURE_RGB;
564 }
565 }
566 } else {
567 v_shader = VERT_SHADER_TRI_COLOR;
568 f_shader = FRAG_SHADER_COLOR;
569 }
570 } else {
571 v_shader = VERT_SHADER_LINEPOINT;
572 f_shader = FRAG_SHADER_COLOR;
573 }
574
575 if (custom_frag_shader) {
576 f_shader = FRAG_SHADER_TEXTURE_CUSTOM;
577 data->shaders.frag_shaders[FRAG_SHADER_TEXTURE_CUSTOM] = custom_frag_shader;
578 }
579
580 GPU_PipelineParameters pipe_params;
581 SDL_zero(pipe_params);
582 pipe_params.blend_mode = cmd->data.draw.blend;
583 pipe_params.vert_shader = v_shader;
584 pipe_params.frag_shader = f_shader;
585 pipe_params.primitive_type = prim;
586 pipe_params.custom_frag_shader = custom_frag_shader;
587
588 if (data->state.render_target) {
589 pipe_params.attachment_format = ((GPU_TextureData *)data->state.render_target->internal)->format;
590 } else {
591 pipe_params.attachment_format = data->backbuffer.format;
592 }
593
594 SDL_GPUGraphicsPipeline *pipe = GPU_GetPipeline(&data->pipeline_cache, &data->shaders, data->device, &pipe_params);
595 if (!pipe) {
596 return;
597 }
598
599 SDL_BindGPUGraphicsPipeline(pass, pipe);
600
601 Uint32 sampler_slot = 0;
602 if (cmd->data.draw.texture) {
603 GPU_TextureData *tdata = (GPU_TextureData *)cmd->data.draw.texture->internal;
604 SDL_GPUTextureSamplerBinding sampler_bind;
605 SDL_zero(sampler_bind);
606 sampler_bind.sampler = *SamplerPointer(data, cmd->data.draw.texture_address_mode, cmd->data.draw.texture_scale_mode);
607 sampler_bind.texture = tdata->texture;
608 SDL_BindGPUFragmentSamplers(pass, sampler_slot++, &sampler_bind, 1);
609 }
610 if (custom_state) {
611 if (custom_state->num_sampler_bindings > 0) {
612 SDL_BindGPUFragmentSamplers(pass, sampler_slot, custom_state->sampler_bindings, custom_state->num_sampler_bindings);
613 }
614 if (custom_state->num_storage_textures > 0) {
615 SDL_BindGPUFragmentStorageTextures(pass, 0, custom_state->storage_textures, custom_state->num_storage_textures);
616 }
617 if (custom_state->num_storage_buffers > 0) {
618 SDL_BindGPUFragmentStorageBuffers(pass, 0, custom_state->storage_buffers, custom_state->num_storage_buffers);
619 }
620 if (custom_state->num_uniform_buffers > 0) {
621 for (int i = 0; i < custom_state->num_uniform_buffers; i++) {
622 SDL_GPURenderStateUniformBuffer *ub = &custom_state->uniform_buffers[i];
623 SDL_PushGPUFragmentUniformData(data->state.command_buffer, ub->slot_index, ub->data, ub->length);
624 }
625 }
626 } else {
627 PushFragmentUniforms(data, cmd);
628 }
629
630 SDL_GPUBufferBinding buffer_bind;
631 SDL_zero(buffer_bind);
632 buffer_bind.buffer = data->vertices.buffer;
633 buffer_bind.offset = offset;
634 SDL_BindGPUVertexBuffers(pass, 0, &buffer_bind, 1);
635 PushVertexUniforms(data, cmd);
636
637 SetViewportAndScissor(data);
638
639 SDL_DrawGPUPrimitives(pass, num_verts, 1, 0, 0);
640}
641
642static void ReleaseVertexBuffer(GPU_RenderData *data)
643{
644 if (data->vertices.buffer) {
645 SDL_ReleaseGPUBuffer(data->device, data->vertices.buffer);
646 }
647
648 if (data->vertices.transfer_buf) {
649 SDL_ReleaseGPUTransferBuffer(data->device, data->vertices.transfer_buf);
650 }
651
652 data->vertices.buffer_size = 0;
653}
654
655static bool InitVertexBuffer(GPU_RenderData *data, Uint32 size)
656{
657 SDL_GPUBufferCreateInfo bci;
658 SDL_zero(bci);
659 bci.size = size;
660 bci.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
661
662 data->vertices.buffer = SDL_CreateGPUBuffer(data->device, &bci);
663
664 if (!data->vertices.buffer) {
665 return false;
666 }
667
668 SDL_GPUTransferBufferCreateInfo tbci;
669 SDL_zero(tbci);
670 tbci.size = size;
671 tbci.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
672
673 data->vertices.transfer_buf = SDL_CreateGPUTransferBuffer(data->device, &tbci);
674
675 if (!data->vertices.transfer_buf) {
676 return false;
677 }
678
679 return true;
680}
681
682static bool UploadVertices(GPU_RenderData *data, void *vertices, size_t vertsize)
683{
684 if (vertsize == 0) {
685 return true;
686 }
687
688 if (vertsize > data->vertices.buffer_size) {
689 ReleaseVertexBuffer(data);
690 if (!InitVertexBuffer(data, (Uint32)vertsize)) {
691 return false;
692 }
693 }
694
695 void *staging_buf = SDL_MapGPUTransferBuffer(data->device, data->vertices.transfer_buf, true);
696 SDL_memcpy(staging_buf, vertices, vertsize);
697 SDL_UnmapGPUTransferBuffer(data->device, data->vertices.transfer_buf);
698
699 SDL_GPUCopyPass *pass = SDL_BeginGPUCopyPass(data->state.command_buffer);
700
701 if (!pass) {
702 return false;
703 }
704
705 SDL_GPUTransferBufferLocation src;
706 SDL_zero(src);
707 src.transfer_buffer = data->vertices.transfer_buf;
708
709 SDL_GPUBufferRegion dst;
710 SDL_zero(dst);
711 dst.buffer = data->vertices.buffer;
712 dst.size = (Uint32)vertsize;
713
714 SDL_UploadToGPUBuffer(pass, &src, &dst, true);
715 SDL_EndGPUCopyPass(pass);
716
717 return true;
718}
719
720// *** FIXME ***
721// We might be able to run these data uploads on a separate command buffer
722// which would allow us to avoid breaking render passes.
723// Honestly I'm a little skeptical of this entire approach,
724// we already have a command buffer structure
725// so it feels weird to be deferring the operations manually.
726// We could also fairly easily run the geometry transformations
727// on compute shaders instead of the CPU, which would be a HUGE performance win.
728// -cosmonaut
729static bool GPU_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize)
730{
731 GPU_RenderData *data = (GPU_RenderData *)renderer->internal;
732
733 if (!UploadVertices(data, vertices, vertsize)) {
734 return false;
735 }
736
737 data->state.color_attachment.load_op = SDL_GPU_LOADOP_LOAD;
738
739 if (renderer->target) {
740 GPU_TextureData *tdata = renderer->target->internal;
741 data->state.color_attachment.texture = tdata->texture;
742 } else {
743 data->state.color_attachment.texture = data->backbuffer.texture;
744 }
745
746 if (!data->state.color_attachment.texture) {
747 return SDL_SetError("Render target texture is NULL");
748 }
749
750 while (cmd) {
751 switch (cmd->command) {
752 case SDL_RENDERCMD_SETDRAWCOLOR:
753 {
754 data->state.draw_color = GetDrawCmdColor(renderer, cmd);
755 break;
756 }
757
758 case SDL_RENDERCMD_SETVIEWPORT:
759 {
760 SDL_Rect *viewport = &cmd->data.viewport.rect;
761 data->state.viewport.x = viewport->x;
762 data->state.viewport.y = viewport->y;
763 data->state.viewport.w = viewport->w;
764 data->state.viewport.h = viewport->h;
765 break;
766 }
767
768 case SDL_RENDERCMD_SETCLIPRECT:
769 {
770 const SDL_Rect *rect = &cmd->data.cliprect.rect;
771 data->state.scissor.x = (int)data->state.viewport.x + rect->x;
772 data->state.scissor.y = (int)data->state.viewport.y + rect->y;
773 data->state.scissor.w = rect->w;
774 data->state.scissor.h = rect->h;
775 data->state.scissor_enabled = cmd->data.cliprect.enabled;
776 break;
777 }
778
779 case SDL_RENDERCMD_CLEAR:
780 {
781 data->state.color_attachment.clear_color = GetDrawCmdColor(renderer, cmd);
782 data->state.color_attachment.load_op = SDL_GPU_LOADOP_CLEAR;
783 break;
784 }
785
786 case SDL_RENDERCMD_FILL_RECTS: // unused
787 break;
788
789 case SDL_RENDERCMD_COPY: // unused
790 break;
791
792 case SDL_RENDERCMD_COPY_EX: // unused
793 break;
794
795 case SDL_RENDERCMD_DRAW_LINES:
796 {
797 Uint32 count = (Uint32)cmd->data.draw.count;
798 Uint32 offset = (Uint32)cmd->data.draw.first;
799
800 if (count > 2) {
801 // joined lines cannot be grouped
802 Draw(data, cmd, count, offset, SDL_GPU_PRIMITIVETYPE_LINESTRIP);
803 } else {
804 // let's group non joined lines
805 SDL_RenderCommand *finalcmd = cmd;
806 SDL_RenderCommand *nextcmd = cmd->next;
807 SDL_BlendMode thisblend = cmd->data.draw.blend;
808
809 while (nextcmd) {
810 const SDL_RenderCommandType nextcmdtype = nextcmd->command;
811 if (nextcmdtype != SDL_RENDERCMD_DRAW_LINES) {
812 break; // can't go any further on this draw call, different render command up next.
813 } else if (nextcmd->data.draw.count != 2) {
814 break; // can't go any further on this draw call, those are joined lines
815 } else if (nextcmd->data.draw.blend != thisblend) {
816 break; // can't go any further on this draw call, different blendmode copy up next.
817 } else {
818 finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command.
819 count += (Uint32)nextcmd->data.draw.count;
820 }
821 nextcmd = nextcmd->next;
822 }
823
824 Draw(data, cmd, count, offset, SDL_GPU_PRIMITIVETYPE_LINELIST);
825 cmd = finalcmd; // skip any copy commands we just combined in here.
826 }
827 break;
828 }
829
830 case SDL_RENDERCMD_DRAW_POINTS:
831 case SDL_RENDERCMD_GEOMETRY:
832 {
833 /* as long as we have the same copy command in a row, with the
834 same texture, we can combine them all into a single draw call. */
835 SDL_Texture *thistexture = cmd->data.draw.texture;
836 SDL_BlendMode thisblend = cmd->data.draw.blend;
837 SDL_ScaleMode thisscalemode = cmd->data.draw.texture_scale_mode;
838 SDL_TextureAddressMode thisaddressmode = cmd->data.draw.texture_address_mode;
839 const SDL_RenderCommandType thiscmdtype = cmd->command;
840 SDL_RenderCommand *finalcmd = cmd;
841 SDL_RenderCommand *nextcmd = cmd->next;
842 Uint32 count = (Uint32)cmd->data.draw.count;
843 Uint32 offset = (Uint32)cmd->data.draw.first;
844
845 while (nextcmd) {
846 const SDL_RenderCommandType nextcmdtype = nextcmd->command;
847 if (nextcmdtype != thiscmdtype) {
848 break; // can't go any further on this draw call, different render command up next.
849 } else if (nextcmd->data.draw.texture != thistexture ||
850 nextcmd->data.draw.texture_scale_mode != thisscalemode ||
851 nextcmd->data.draw.texture_address_mode != thisaddressmode ||
852 nextcmd->data.draw.blend != thisblend) {
853 // FIXME should we check address mode too?
854 break; // can't go any further on this draw call, different texture/blendmode copy up next.
855 } else {
856 finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command.
857 count += (Uint32)nextcmd->data.draw.count;
858 }
859 nextcmd = nextcmd->next;
860 }
861
862 SDL_GPUPrimitiveType prim = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; // SDL_RENDERCMD_GEOMETRY
863 if (thiscmdtype == SDL_RENDERCMD_DRAW_POINTS) {
864 prim = SDL_GPU_PRIMITIVETYPE_POINTLIST;
865 }
866
867 Draw(data, cmd, count, offset, prim);
868
869 cmd = finalcmd; // skip any copy commands we just combined in here.
870 break;
871 }
872
873 case SDL_RENDERCMD_NO_OP:
874 break;
875 }
876
877 cmd = cmd->next;
878 }
879
880 if (data->state.color_attachment.load_op == SDL_GPU_LOADOP_CLEAR) {
881 RestartRenderPass(data);
882 }
883
884 if (data->state.render_pass) {
885 SDL_EndGPURenderPass(data->state.render_pass);
886 data->state.render_pass = NULL;
887 }
888
889 return true;
890}
891
892static SDL_Surface *GPU_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect)
893{
894 GPU_RenderData *data = (GPU_RenderData *)renderer->internal;
895 SDL_GPUTexture *gpu_tex;
896 SDL_PixelFormat pixfmt;
897
898 if (data->state.render_target) {
899 SDL_Texture *texture = data->state.render_target;
900 GPU_TextureData *texdata = texture->internal;
901 gpu_tex = texdata->texture;
902 pixfmt = texture->format;
903 } else {
904 gpu_tex = data->backbuffer.texture;
905 pixfmt = TexFormatToPixFormat(data->backbuffer.format);
906
907 if (pixfmt == SDL_PIXELFORMAT_UNKNOWN) {
908 SDL_SetError("Unsupported backbuffer format");
909 return NULL;
910 }
911 }
912
913 Uint32 bpp = SDL_BYTESPERPIXEL(pixfmt);
914 size_t row_size, image_size;
915
916 if (!SDL_size_mul_check_overflow(rect->w, bpp, &row_size) ||
917 !SDL_size_mul_check_overflow(rect->h, row_size, &image_size)) {
918 SDL_SetError("read size overflow");
919 return NULL;
920 }
921
922 SDL_Surface *surface = SDL_CreateSurface(rect->w, rect->h, pixfmt);
923
924 if (!surface) {
925 return NULL;
926 }
927
928 SDL_GPUTransferBufferCreateInfo tbci;
929 SDL_zero(tbci);
930 tbci.size = (Uint32)image_size;
931 tbci.usage = SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD;
932
933 SDL_GPUTransferBuffer *tbuf = SDL_CreateGPUTransferBuffer(data->device, &tbci);
934
935 if (!tbuf) {
936 return NULL;
937 }
938
939 SDL_GPUCopyPass *pass = SDL_BeginGPUCopyPass(data->state.command_buffer);
940
941 SDL_GPUTextureRegion src;
942 SDL_zero(src);
943 src.texture = gpu_tex;
944 src.x = rect->x;
945 src.y = rect->y;
946 src.w = rect->w;
947 src.h = rect->h;
948 src.d = 1;
949
950 SDL_GPUTextureTransferInfo dst;
951 SDL_zero(dst);
952 dst.transfer_buffer = tbuf;
953 dst.rows_per_layer = rect->h;
954 dst.pixels_per_row = rect->w;
955
956 SDL_DownloadFromGPUTexture(pass, &src, &dst);
957 SDL_EndGPUCopyPass(pass);
958
959 SDL_GPUFence *fence = SDL_SubmitGPUCommandBufferAndAcquireFence(data->state.command_buffer);
960 SDL_WaitForGPUFences(data->device, true, &fence, 1);
961 SDL_ReleaseGPUFence(data->device, fence);
962 data->state.command_buffer = SDL_AcquireGPUCommandBuffer(data->device);
963
964 void *mapped_tbuf = SDL_MapGPUTransferBuffer(data->device, tbuf, false);
965
966 if ((size_t)surface->pitch == row_size) {
967 SDL_memcpy(surface->pixels, mapped_tbuf, image_size);
968 } else {
969 Uint8 *input = mapped_tbuf;
970 Uint8 *output = surface->pixels;
971
972 for (int row = 0; row < rect->h; ++row) {
973 SDL_memcpy(output, input, row_size);
974 output += surface->pitch;
975 input += row_size;
976 }
977 }
978
979 SDL_UnmapGPUTransferBuffer(data->device, tbuf);
980 SDL_ReleaseGPUTransferBuffer(data->device, tbuf);
981
982 return surface;
983}
984
985static bool CreateBackbuffer(GPU_RenderData *data, Uint32 w, Uint32 h, SDL_GPUTextureFormat fmt)
986{
987 SDL_GPUTextureCreateInfo tci;
988 SDL_zero(tci);
989 tci.width = w;
990 tci.height = h;
991 tci.format = fmt;
992 tci.layer_count_or_depth = 1;
993 tci.num_levels = 1;
994 tci.sample_count = SDL_GPU_SAMPLECOUNT_1;
995 tci.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
996
997 data->backbuffer.texture = SDL_CreateGPUTexture(data->device, &tci);
998 data->backbuffer.width = w;
999 data->backbuffer.height = h;
1000 data->backbuffer.format = fmt;
1001
1002 if (!data->backbuffer.texture) {
1003 return false;
1004 }
1005
1006 return true;
1007}
1008
1009static bool GPU_RenderPresent(SDL_Renderer *renderer)
1010{
1011 GPU_RenderData *data = (GPU_RenderData *)renderer->internal;
1012
1013 SDL_GPUTexture *swapchain;
1014 Uint32 swapchain_texture_width, swapchain_texture_height;
1015 bool result = SDL_WaitAndAcquireGPUSwapchainTexture(data->state.command_buffer, renderer->window, &swapchain, &swapchain_texture_width, &swapchain_texture_height);
1016
1017 if (!result) {
1018 SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to acquire swapchain texture: %s", SDL_GetError());
1019 }
1020
1021 if (swapchain != NULL) {
1022 SDL_GPUBlitInfo blit_info;
1023 SDL_zero(blit_info);
1024
1025 blit_info.source.texture = data->backbuffer.texture;
1026 blit_info.source.w = data->backbuffer.width;
1027 blit_info.source.h = data->backbuffer.height;
1028 blit_info.destination.texture = swapchain;
1029 blit_info.destination.w = swapchain_texture_width;
1030 blit_info.destination.h = swapchain_texture_height;
1031 blit_info.load_op = SDL_GPU_LOADOP_DONT_CARE;
1032 blit_info.filter = SDL_GPU_FILTER_LINEAR;
1033
1034 SDL_BlitGPUTexture(data->state.command_buffer, &blit_info);
1035
1036 SDL_SubmitGPUCommandBuffer(data->state.command_buffer);
1037
1038 if (swapchain_texture_width != data->backbuffer.width || swapchain_texture_height != data->backbuffer.height) {
1039 SDL_ReleaseGPUTexture(data->device, data->backbuffer.texture);
1040 CreateBackbuffer(data, swapchain_texture_width, swapchain_texture_height, SDL_GetGPUSwapchainTextureFormat(data->device, renderer->window));
1041 }
1042 } else {
1043 SDL_SubmitGPUCommandBuffer(data->state.command_buffer);
1044 }
1045
1046 data->state.command_buffer = SDL_AcquireGPUCommandBuffer(data->device);
1047
1048 return true;
1049}
1050
1051static void GPU_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture)
1052{
1053 GPU_RenderData *renderdata = (GPU_RenderData *)renderer->internal;
1054 GPU_TextureData *data = (GPU_TextureData *)texture->internal;
1055
1056 if (renderdata->state.render_target == texture) {
1057 renderdata->state.render_target = NULL;
1058 }
1059
1060 if (!data) {
1061 return;
1062 }
1063
1064 SDL_ReleaseGPUTexture(renderdata->device, data->texture);
1065 SDL_free(data->pixels);
1066 SDL_free(data);
1067 texture->internal = NULL;
1068}
1069
1070static void GPU_DestroyRenderer(SDL_Renderer *renderer)
1071{
1072 GPU_RenderData *data = (GPU_RenderData *)renderer->internal;
1073
1074 if (!data) {
1075 return;
1076 }
1077
1078 if (data->state.command_buffer) {
1079 SDL_SubmitGPUCommandBuffer(data->state.command_buffer);
1080 data->state.command_buffer = NULL;
1081 }
1082
1083 for (Uint32 i = 0; i < sizeof(data->samplers) / sizeof(SDL_GPUSampler *); ++i) {
1084 SDL_ReleaseGPUSampler(data->device, ((SDL_GPUSampler **)data->samplers)[i]);
1085 }
1086
1087 if (data->backbuffer.texture) {
1088 SDL_ReleaseGPUTexture(data->device, data->backbuffer.texture);
1089 }
1090
1091 if (renderer->window) {
1092 SDL_ReleaseWindowFromGPUDevice(data->device, renderer->window);
1093 }
1094
1095 ReleaseVertexBuffer(data);
1096 GPU_DestroyPipelineCache(&data->pipeline_cache);
1097 GPU_ReleaseShaders(&data->shaders, data->device);
1098 SDL_DestroyGPUDevice(data->device);
1099
1100 SDL_free(data);
1101}
1102
1103static bool ChoosePresentMode(SDL_GPUDevice *device, SDL_Window *window, const int vsync, SDL_GPUPresentMode *out_mode)
1104{
1105 SDL_GPUPresentMode mode;
1106
1107 switch (vsync) {
1108 case 0:
1109 mode = SDL_GPU_PRESENTMODE_MAILBOX;
1110
1111 if (!SDL_WindowSupportsGPUPresentMode(device, window, mode)) {
1112 mode = SDL_GPU_PRESENTMODE_IMMEDIATE;
1113
1114 if (!SDL_WindowSupportsGPUPresentMode(device, window, mode)) {
1115 mode = SDL_GPU_PRESENTMODE_VSYNC;
1116 }
1117 }
1118
1119 // FIXME should we return an error if both mailbox and immediate fail?
1120 break;
1121
1122 case 1:
1123 mode = SDL_GPU_PRESENTMODE_VSYNC;
1124 break;
1125
1126 default:
1127 return SDL_Unsupported();
1128 }
1129
1130 *out_mode = mode;
1131 return true;
1132}
1133
1134static bool GPU_SetVSync(SDL_Renderer *renderer, const int vsync)
1135{
1136 GPU_RenderData *data = (GPU_RenderData *)renderer->internal;
1137 SDL_GPUPresentMode mode = SDL_GPU_PRESENTMODE_VSYNC;
1138
1139 if (!ChoosePresentMode(data->device, renderer->window, vsync, &mode)) {
1140 return false;
1141 }
1142
1143 if (mode != data->swapchain.present_mode) {
1144 // XXX returns bool instead of SDL-style error code
1145 if (SDL_SetGPUSwapchainParameters(data->device, renderer->window, data->swapchain.composition, mode)) {
1146 data->swapchain.present_mode = mode;
1147 return true;
1148 } else {
1149 return false;
1150 }
1151 }
1152
1153 return true;
1154}
1155
1156static bool InitSamplers(GPU_RenderData *data)
1157{
1158 struct
1159 {
1160 struct
1161 {
1162 SDL_TextureAddressMode address_mode;
1163 SDL_ScaleMode scale_mode;
1164 } sdl;
1165 struct
1166 {
1167 SDL_GPUSamplerAddressMode address_mode;
1168 SDL_GPUFilter filter;
1169 SDL_GPUSamplerMipmapMode mipmap_mode;
1170 Uint32 anisotropy;
1171 } gpu;
1172 } configs[] = {
1173 {
1174 { SDL_TEXTURE_ADDRESS_CLAMP, SDL_SCALEMODE_NEAREST },
1175 { SDL_GPU_SAMPLERADDRESSMODE_REPEAT, SDL_GPU_FILTER_NEAREST, SDL_GPU_SAMPLERMIPMAPMODE_NEAREST, 0 },
1176 },
1177 {
1178 { SDL_TEXTURE_ADDRESS_CLAMP, SDL_SCALEMODE_LINEAR },
1179 { SDL_GPU_SAMPLERADDRESSMODE_REPEAT, SDL_GPU_FILTER_LINEAR, SDL_GPU_SAMPLERMIPMAPMODE_LINEAR, 0 },
1180 },
1181 {
1182 { SDL_TEXTURE_ADDRESS_CLAMP, SDL_SCALEMODE_PIXELART },
1183 { SDL_GPU_SAMPLERADDRESSMODE_REPEAT, SDL_GPU_FILTER_NEAREST, SDL_GPU_SAMPLERMIPMAPMODE_NEAREST, 0 },
1184 },
1185 {
1186 { SDL_TEXTURE_ADDRESS_WRAP, SDL_SCALEMODE_NEAREST },
1187 { SDL_GPU_SAMPLERADDRESSMODE_REPEAT, SDL_GPU_FILTER_NEAREST, SDL_GPU_SAMPLERMIPMAPMODE_NEAREST, 0 },
1188 },
1189 {
1190 { SDL_TEXTURE_ADDRESS_WRAP, SDL_SCALEMODE_LINEAR },
1191 { SDL_GPU_SAMPLERADDRESSMODE_REPEAT, SDL_GPU_FILTER_LINEAR, SDL_GPU_SAMPLERMIPMAPMODE_LINEAR, 0 },
1192 },
1193 {
1194 { SDL_TEXTURE_ADDRESS_WRAP, SDL_SCALEMODE_PIXELART },
1195 { SDL_GPU_SAMPLERADDRESSMODE_REPEAT, SDL_GPU_FILTER_NEAREST, SDL_GPU_SAMPLERMIPMAPMODE_NEAREST, 0 },
1196 },
1197 };
1198
1199 for (Uint32 i = 0; i < SDL_arraysize(configs); ++i) {
1200 SDL_GPUSamplerCreateInfo sci;
1201 SDL_zero(sci);
1202 sci.max_anisotropy = configs[i].gpu.anisotropy;
1203 sci.enable_anisotropy = configs[i].gpu.anisotropy > 0;
1204 sci.address_mode_u = sci.address_mode_v = sci.address_mode_w = configs[i].gpu.address_mode;
1205 sci.min_filter = sci.mag_filter = configs[i].gpu.filter;
1206 sci.mipmap_mode = configs[i].gpu.mipmap_mode;
1207
1208 SDL_GPUSampler *sampler = SDL_CreateGPUSampler(data->device, &sci);
1209
1210 if (sampler == NULL) {
1211 return false;
1212 }
1213
1214 *SamplerPointer(data, configs[i].sdl.address_mode, configs[i].sdl.scale_mode) = sampler;
1215 }
1216
1217 return true;
1218}
1219
1220static bool GPU_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_PropertiesID create_props)
1221{
1222 GPU_RenderData *data = NULL;
1223
1224 SDL_SetupRendererColorspace(renderer, create_props);
1225
1226 if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) {
1227 // TODO
1228 return SDL_SetError("Unsupported output colorspace");
1229 }
1230
1231 data = (GPU_RenderData *)SDL_calloc(1, sizeof(*data));
1232 if (!data) {
1233 return false;
1234 }
1235
1236 renderer->SupportsBlendMode = GPU_SupportsBlendMode;
1237 renderer->CreateTexture = GPU_CreateTexture;
1238 renderer->UpdateTexture = GPU_UpdateTexture;
1239 renderer->LockTexture = GPU_LockTexture;
1240 renderer->UnlockTexture = GPU_UnlockTexture;
1241 renderer->SetRenderTarget = GPU_SetRenderTarget;
1242 renderer->QueueSetViewport = GPU_QueueNoOp;
1243 renderer->QueueSetDrawColor = GPU_QueueNoOp;
1244 renderer->QueueDrawPoints = GPU_QueueDrawPoints;
1245 renderer->QueueDrawLines = GPU_QueueDrawPoints; // lines and points queue vertices the same way.
1246 renderer->QueueGeometry = GPU_QueueGeometry;
1247 renderer->InvalidateCachedState = GPU_InvalidateCachedState;
1248 renderer->RunCommandQueue = GPU_RunCommandQueue;
1249 renderer->RenderReadPixels = GPU_RenderReadPixels;
1250 renderer->RenderPresent = GPU_RenderPresent;
1251 renderer->DestroyTexture = GPU_DestroyTexture;
1252 renderer->DestroyRenderer = GPU_DestroyRenderer;
1253 renderer->SetVSync = GPU_SetVSync;
1254 renderer->internal = data;
1255 renderer->window = window;
1256 renderer->name = GPU_RenderDriver.name;
1257
1258 bool debug = SDL_GetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, false);
1259 bool lowpower = SDL_GetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, false);
1260
1261 // Prefer environment variables/hints if they exist, otherwise defer to properties
1262 debug = SDL_GetHintBoolean(SDL_HINT_RENDER_GPU_DEBUG, debug);
1263 lowpower = SDL_GetHintBoolean(SDL_HINT_RENDER_GPU_LOW_POWER, lowpower);
1264
1265 SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, debug);
1266 SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, lowpower);
1267
1268 GPU_FillSupportedShaderFormats(create_props);
1269 data->device = SDL_CreateGPUDeviceWithProperties(create_props);
1270
1271 if (!data->device) {
1272 return false;
1273 }
1274
1275 if (!GPU_InitShaders(&data->shaders, data->device)) {
1276 return false;
1277 }
1278
1279 if (!GPU_InitPipelineCache(&data->pipeline_cache, data->device)) {
1280 return false;
1281 }
1282
1283 // XXX what's a good initial size?
1284 if (!InitVertexBuffer(data, 1 << 16)) {
1285 return false;
1286 }
1287
1288 if (!InitSamplers(data)) {
1289 return false;
1290 }
1291
1292 if (!SDL_ClaimWindowForGPUDevice(data->device, window)) {
1293 return false;
1294 }
1295
1296 data->swapchain.composition = SDL_GPU_SWAPCHAINCOMPOSITION_SDR;
1297 data->swapchain.present_mode = SDL_GPU_PRESENTMODE_VSYNC;
1298
1299 int vsync = (int)SDL_GetNumberProperty(create_props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 0);
1300 ChoosePresentMode(data->device, window, vsync, &data->swapchain.present_mode);
1301
1302 SDL_SetGPUSwapchainParameters(data->device, window, data->swapchain.composition, data->swapchain.present_mode);
1303
1304 SDL_SetGPUAllowedFramesInFlight(data->device, 1);
1305
1306 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRA32);
1307 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA32);
1308 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRX32);
1309 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBX32);
1310
1311 SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 16384);
1312
1313 data->state.draw_color.r = 1.0f;
1314 data->state.draw_color.g = 1.0f;
1315 data->state.draw_color.b = 1.0f;
1316 data->state.draw_color.a = 1.0f;
1317 data->state.viewport.min_depth = 0;
1318 data->state.viewport.max_depth = 1;
1319 data->state.command_buffer = SDL_AcquireGPUCommandBuffer(data->device);
1320
1321 int w, h;
1322 SDL_GetWindowSizeInPixels(window, &w, &h);
1323
1324 if (!CreateBackbuffer(data, w, h, SDL_GetGPUSwapchainTextureFormat(data->device, window))) {
1325 return false;
1326 }
1327
1328 SDL_SetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_GPU_DEVICE_POINTER, data->device);
1329
1330 return true;
1331}
1332
1333SDL_RenderDriver GPU_RenderDriver = {
1334 GPU_CreateRenderer, "gpu"
1335};
1336
1337#endif // SDL_VIDEO_RENDER_GPU
1338