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 "SDL_gpu_util.h"
26#include "SDL_pipeline_gpu.h"
27
28#include "../SDL_sysrender.h"
29
30static Uint32 SDLCALL HashPipelineCacheKey(void *userdata, const void *key)
31{
32 const GPU_PipelineParameters *params = (const GPU_PipelineParameters *) key;
33 return SDL_murmur3_32(params, sizeof(*params), 0);
34}
35
36static bool SDLCALL MatchPipelineCacheKey(void *userdata, const void *a, const void *b)
37{
38 return (SDL_memcmp(a, b, sizeof (GPU_PipelineParameters)) == 0);
39}
40
41static void SDLCALL DestroyPipelineCacheHashItem(void *userdata, const void *key, const void *value)
42{
43 SDL_GPUGraphicsPipeline *pipeline = (SDL_GPUGraphicsPipeline *) value;
44 SDL_GPUDevice *device = (SDL_GPUDevice *) userdata;
45 SDL_ReleaseGPUGraphicsPipeline(device, pipeline);
46 SDL_free((GPU_PipelineParameters *) key);
47}
48
49bool GPU_InitPipelineCache(GPU_PipelineCache *cache, SDL_GPUDevice *device)
50{
51 cache->table = SDL_CreateHashTable(0, false, HashPipelineCacheKey, MatchPipelineCacheKey, DestroyPipelineCacheHashItem, device);
52 return (cache->table != NULL);
53}
54
55void GPU_DestroyPipelineCache(GPU_PipelineCache *cache)
56{
57 SDL_DestroyHashTable(cache->table);
58}
59
60static SDL_GPUGraphicsPipeline *MakePipeline(SDL_GPUDevice *device, GPU_Shaders *shaders, const GPU_PipelineParameters *params)
61{
62 SDL_GPUColorTargetDescription ad;
63 SDL_zero(ad);
64 ad.format = params->attachment_format;
65
66 SDL_BlendMode blend = params->blend_mode;
67 ad.blend_state.enable_blend = blend != 0;
68 ad.blend_state.color_write_mask = 0xF;
69 ad.blend_state.alpha_blend_op = GPU_ConvertBlendOperation(SDL_GetBlendModeAlphaOperation(blend));
70 ad.blend_state.dst_alpha_blendfactor = GPU_ConvertBlendFactor(SDL_GetBlendModeDstAlphaFactor(blend));
71 ad.blend_state.src_alpha_blendfactor = GPU_ConvertBlendFactor(SDL_GetBlendModeSrcAlphaFactor(blend));
72 ad.blend_state.color_blend_op = GPU_ConvertBlendOperation(SDL_GetBlendModeColorOperation(blend));
73 ad.blend_state.dst_color_blendfactor = GPU_ConvertBlendFactor(SDL_GetBlendModeDstColorFactor(blend));
74 ad.blend_state.src_color_blendfactor = GPU_ConvertBlendFactor(SDL_GetBlendModeSrcColorFactor(blend));
75
76 SDL_GPUGraphicsPipelineCreateInfo pci;
77 SDL_zero(pci);
78 pci.target_info.has_depth_stencil_target = false;
79 pci.target_info.num_color_targets = 1;
80 pci.target_info.color_target_descriptions = &ad;
81 pci.vertex_shader = GPU_GetVertexShader(shaders, params->vert_shader);
82 pci.fragment_shader = GPU_GetFragmentShader(shaders, params->frag_shader);
83 pci.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1;
84 pci.multisample_state.enable_mask = false;
85 pci.primitive_type = params->primitive_type;
86
87 pci.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE;
88 pci.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL;
89 pci.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
90
91 SDL_GPUVertexBufferDescription vertex_buffer_desc;
92 SDL_zero(vertex_buffer_desc);
93
94 Uint32 num_attribs = 0;
95 SDL_GPUVertexAttribute attribs[4];
96 SDL_zero(attribs);
97
98 bool have_attr_color = false;
99 bool have_attr_uv = false;
100
101 switch (params->vert_shader) {
102 case VERT_SHADER_TRI_TEXTURE:
103 have_attr_uv = true;
104 SDL_FALLTHROUGH;
105 case VERT_SHADER_TRI_COLOR:
106 have_attr_color = true;
107 SDL_FALLTHROUGH;
108 default:
109 break;
110 }
111
112 // Position
113 attribs[num_attribs].location = num_attribs;
114 attribs[num_attribs].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
115 attribs[num_attribs].offset = vertex_buffer_desc.pitch;
116 vertex_buffer_desc.pitch += 2 * sizeof(float);
117 num_attribs++;
118
119 if (have_attr_color) {
120 // Color
121 attribs[num_attribs].location = num_attribs;
122 attribs[num_attribs].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
123 attribs[num_attribs].offset = vertex_buffer_desc.pitch;
124 vertex_buffer_desc.pitch += 4 * sizeof(float);
125 num_attribs++;
126 }
127
128 if (have_attr_uv) {
129 // UVs
130 attribs[num_attribs].location = num_attribs;
131 attribs[num_attribs].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
132 attribs[num_attribs].offset = vertex_buffer_desc.pitch;
133 vertex_buffer_desc.pitch += 2 * sizeof(float);
134 num_attribs++;
135 }
136
137 pci.vertex_input_state.num_vertex_attributes = num_attribs;
138 pci.vertex_input_state.vertex_attributes = attribs;
139 pci.vertex_input_state.num_vertex_buffers = 1;
140 pci.vertex_input_state.vertex_buffer_descriptions = &vertex_buffer_desc;
141
142 return SDL_CreateGPUGraphicsPipeline(device, &pci);
143}
144
145SDL_GPUGraphicsPipeline *GPU_GetPipeline(GPU_PipelineCache *cache, GPU_Shaders *shaders, SDL_GPUDevice *device, const GPU_PipelineParameters *params)
146{
147 SDL_GPUGraphicsPipeline *pipeline = NULL;
148 if (!SDL_FindInHashTable(cache->table, params, (const void **) &pipeline)) {
149 bool inserted = false;
150 // !!! FIXME: why don't we have an SDL_alloc_copy function/macro?
151 GPU_PipelineParameters *paramscpy = (GPU_PipelineParameters *) SDL_malloc(sizeof (*paramscpy));
152 if (paramscpy) {
153 SDL_copyp(paramscpy, params);
154 pipeline = MakePipeline(device, shaders, params);
155 if (pipeline) {
156 inserted = SDL_InsertIntoHashTable(cache->table, paramscpy, pipeline, false);
157 }
158 }
159
160 if (!inserted) {
161 SDL_free(paramscpy);
162 if (pipeline) {
163 SDL_ReleaseGPUGraphicsPipeline(device, pipeline);
164 pipeline = NULL;
165 }
166 }
167 }
168
169 return pipeline;
170}
171
172#endif // SDL_VIDEO_RENDER_GPU
173