| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| #include "SDL_internal.h" |
| |
| #ifdef SDL_VIDEO_RENDER_GPU |
| |
| #include "../../video/SDL_pixels_c.h" |
| #include "../SDL_d3dmath.h" |
| #include "../SDL_sysrender.h" |
| #include "SDL_gpu_util.h" |
| #include "SDL_pipeline_gpu.h" |
| #include "SDL_shaders_gpu.h" |
| |
| typedef struct GPU_ShaderUniformData |
| { |
| Float4X4 mvp; |
| SDL_FColor color; |
| float texture_size[2]; |
| } GPU_ShaderUniformData; |
| |
| typedef struct GPU_RenderData |
| { |
| SDL_GPUDevice *device; |
| GPU_Shaders shaders; |
| GPU_PipelineCache pipeline_cache; |
| |
| struct |
| { |
| SDL_GPUTexture *texture; |
| SDL_GPUTextureFormat format; |
| Uint32 width; |
| Uint32 height; |
| } backbuffer; |
| |
| struct |
| { |
| SDL_GPUSwapchainComposition composition; |
| SDL_GPUPresentMode present_mode; |
| } swapchain; |
| |
| struct |
| { |
| SDL_GPUTransferBuffer *transfer_buf; |
| SDL_GPUBuffer *buffer; |
| Uint32 buffer_size; |
| } vertices; |
| |
| struct |
| { |
| SDL_GPURenderPass *render_pass; |
| SDL_Texture *render_target; |
| SDL_GPUCommandBuffer *command_buffer; |
| SDL_GPUColorTargetInfo color_attachment; |
| SDL_GPUViewport viewport; |
| SDL_Rect scissor; |
| SDL_FColor draw_color; |
| bool scissor_enabled; |
| bool scissor_was_enabled; |
| GPU_ShaderUniformData shader_data; |
| } state; |
| |
| SDL_GPUSampler *samplers[2][2]; |
| } GPU_RenderData; |
| |
| typedef struct GPU_TextureData |
| { |
| SDL_GPUTexture *texture; |
| SDL_GPUTextureFormat format; |
| GPU_FragmentShaderID shader; |
| void *pixels; |
| int pitch; |
| SDL_Rect locked_rect; |
| } GPU_TextureData; |
| |
| static bool GPU_SupportsBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode) |
| { |
| SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode); |
| SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode); |
| SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode); |
| SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode); |
| SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode); |
| SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode); |
| |
| if (GPU_ConvertBlendFactor(srcColorFactor) == SDL_GPU_BLENDFACTOR_INVALID || |
| GPU_ConvertBlendFactor(srcAlphaFactor) == SDL_GPU_BLENDFACTOR_INVALID || |
| GPU_ConvertBlendOperation(colorOperation) == SDL_GPU_BLENDOP_INVALID || |
| GPU_ConvertBlendFactor(dstColorFactor) == SDL_GPU_BLENDFACTOR_INVALID || |
| GPU_ConvertBlendFactor(dstAlphaFactor) == SDL_GPU_BLENDFACTOR_INVALID || |
| GPU_ConvertBlendOperation(alphaOperation) == SDL_GPU_BLENDOP_INVALID) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static SDL_GPUTextureFormat PixFormatToTexFormat(SDL_PixelFormat pixel_format) |
| { |
| switch (pixel_format) { |
| case SDL_PIXELFORMAT_BGRA32: |
| case SDL_PIXELFORMAT_BGRX32: |
| return SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM; |
| case SDL_PIXELFORMAT_RGBA32: |
| case SDL_PIXELFORMAT_RGBX32: |
| return SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; |
| |
| // YUV TODO |
| case SDL_PIXELFORMAT_YV12: |
| case SDL_PIXELFORMAT_IYUV: |
| case SDL_PIXELFORMAT_NV12: |
| case SDL_PIXELFORMAT_NV21: |
| case SDL_PIXELFORMAT_UYVY: |
| default: |
| return SDL_GPU_TEXTUREFORMAT_INVALID; |
| } |
| } |
| |
| static SDL_PixelFormat TexFormatToPixFormat(SDL_GPUTextureFormat tex_format) |
| { |
| switch (tex_format) { |
| case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM: |
| return SDL_PIXELFORMAT_RGBA32; |
| case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM: |
| return SDL_PIXELFORMAT_BGRA32; |
| case SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM: |
| return SDL_PIXELFORMAT_BGR565; |
| case SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM: |
| return SDL_PIXELFORMAT_BGRA5551; |
| case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM: |
| return SDL_PIXELFORMAT_BGRA4444; |
| case SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM: |
| return SDL_PIXELFORMAT_ABGR2101010; |
| case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM: |
| return SDL_PIXELFORMAT_RGBA64; |
| case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM: |
| return SDL_PIXELFORMAT_RGBA32; |
| case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT: |
| return SDL_PIXELFORMAT_RGBA64_FLOAT; |
| case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT: |
| return SDL_PIXELFORMAT_RGBA128_FLOAT; |
| case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT: |
| return SDL_PIXELFORMAT_RGBA32; |
| case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT: |
| return SDL_PIXELFORMAT_RGBA64; |
| case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB: |
| return SDL_PIXELFORMAT_RGBA32; |
| case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB: |
| return SDL_PIXELFORMAT_BGRA32; |
| default: |
| return SDL_PIXELFORMAT_UNKNOWN; |
| } |
| } |
| |
| static bool GPU_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) |
| { |
| GPU_RenderData *renderdata = (GPU_RenderData *)renderer->internal; |
| GPU_TextureData *data; |
| SDL_GPUTextureFormat format; |
| SDL_GPUTextureUsageFlags usage = SDL_GPU_TEXTUREUSAGE_SAMPLER; |
| |
| format = PixFormatToTexFormat(texture->format); |
| |
| if (format == SDL_GPU_TEXTUREFORMAT_INVALID) { |
| return SDL_SetError("Texture format %s not supported by SDL_GPU", |
| SDL_GetPixelFormatName(texture->format)); |
| } |
| |
| data = (GPU_TextureData *)SDL_calloc(1, sizeof(*data)); |
| if (!data) { |
| return false; |
| } |
| |
| if (texture->access == SDL_TEXTUREACCESS_STREAMING) { |
| size_t size; |
| data->pitch = texture->w * SDL_BYTESPERPIXEL(texture->format); |
| size = (size_t)texture->h * data->pitch; |
| if (texture->format == SDL_PIXELFORMAT_YV12 || |
| texture->format == SDL_PIXELFORMAT_IYUV) { |
| // Need to add size for the U and V planes |
| size += 2 * ((texture->h + 1) / 2) * ((data->pitch + 1) / 2); |
| } |
| if (texture->format == SDL_PIXELFORMAT_NV12 || |
| texture->format == SDL_PIXELFORMAT_NV21) { |
| // Need to add size for the U/V plane |
| size += 2 * ((texture->h + 1) / 2) * ((data->pitch + 1) / 2); |
| } |
| data->pixels = SDL_calloc(1, size); |
| if (!data->pixels) { |
| SDL_free(data); |
| return false; |
| } |
| |
| // TODO allocate a persistent transfer buffer |
| } |
| |
| if (texture->access == SDL_TEXTUREACCESS_TARGET) { |
| usage |= SDL_GPU_TEXTUREUSAGE_COLOR_TARGET; |
| } |
| |
| texture->internal = data; |
| SDL_GPUTextureCreateInfo tci; |
| SDL_zero(tci); |
| tci.format = format; |
| tci.layer_count_or_depth = 1; |
| tci.num_levels = 1; |
| tci.usage = usage; |
| tci.width = texture->w; |
| tci.height = texture->h; |
| tci.sample_count = SDL_GPU_SAMPLECOUNT_1; |
| |
| data->format = format; |
| data->texture = SDL_CreateGPUTexture(renderdata->device, &tci); |
| |
| if (!data->texture) { |
| return false; |
| } |
| |
| if (texture->format == SDL_PIXELFORMAT_RGBA32 || texture->format == SDL_PIXELFORMAT_BGRA32) { |
| data->shader = FRAG_SHADER_TEXTURE_RGBA; |
| } else { |
| data->shader = FRAG_SHADER_TEXTURE_RGB; |
| } |
| |
| return true; |
| } |
| |
| static bool GPU_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, |
| const SDL_Rect *rect, const void *pixels, int pitch) |
| { |
| GPU_RenderData *renderdata = (GPU_RenderData *)renderer->internal; |
| GPU_TextureData *data = (GPU_TextureData *)texture->internal; |
| const Uint32 texturebpp = SDL_BYTESPERPIXEL(texture->format); |
| |
| size_t row_size, data_size; |
| |
| if (!SDL_size_mul_check_overflow(rect->w, texturebpp, &row_size) || |
| !SDL_size_mul_check_overflow(rect->h, row_size, &data_size)) { |
| return SDL_SetError("update size overflow"); |
| } |
| |
| SDL_GPUTransferBufferCreateInfo tbci; |
| SDL_zero(tbci); |
| tbci.size = (Uint32)data_size; |
| tbci.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; |
| |
| SDL_GPUTransferBuffer *tbuf = SDL_CreateGPUTransferBuffer(renderdata->device, &tbci); |
| |
| if (tbuf == NULL) { |
| return false; |
| } |
| |
| Uint8 *output = SDL_MapGPUTransferBuffer(renderdata->device, tbuf, false); |
| |
| if ((size_t)pitch == row_size) { |
| SDL_memcpy(output, pixels, data_size); |
| } else { |
| // FIXME is negative pitch supposed to work? |
| // If not, maybe use SDL_GPUTextureTransferInfo::pixels_per_row instead of this |
| const Uint8 *input = pixels; |
| |
| for (int i = 0; i < rect->h; ++i) { |
| SDL_memcpy(output, input, row_size); |
| output += row_size; |
| input += pitch; |
| } |
| } |
| |
| SDL_UnmapGPUTransferBuffer(renderdata->device, tbuf); |
| |
| SDL_GPUCommandBuffer *cbuf = renderdata->state.command_buffer; |
| SDL_GPUCopyPass *cpass = SDL_BeginGPUCopyPass(cbuf); |
| |
| SDL_GPUTextureTransferInfo tex_src; |
| SDL_zero(tex_src); |
| tex_src.transfer_buffer = tbuf; |
| tex_src.rows_per_layer = rect->h; |
| tex_src.pixels_per_row = rect->w; |
| |
| SDL_GPUTextureRegion tex_dst; |
| SDL_zero(tex_dst); |
| tex_dst.texture = data->texture; |
| tex_dst.x = rect->x; |
| tex_dst.y = rect->y; |
| tex_dst.w = rect->w; |
| tex_dst.h = rect->h; |
| tex_dst.d = 1; |
| |
| SDL_UploadToGPUTexture(cpass, &tex_src, &tex_dst, false); |
| SDL_EndGPUCopyPass(cpass); |
| SDL_ReleaseGPUTransferBuffer(renderdata->device, tbuf); |
| |
| return true; |
| } |
| |
| static bool GPU_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, |
| const SDL_Rect *rect, void **pixels, int *pitch) |
| { |
| GPU_TextureData *data = (GPU_TextureData *)texture->internal; |
| |
| data->locked_rect = *rect; |
| *pixels = |
| (void *)((Uint8 *)data->pixels + rect->y * data->pitch + |
| rect->x * SDL_BYTESPERPIXEL(texture->format)); |
| *pitch = data->pitch; |
| return true; |
| } |
| |
| static void GPU_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture) |
| { |
| GPU_TextureData *data = (GPU_TextureData *)texture->internal; |
| const SDL_Rect *rect; |
| void *pixels; |
| |
| rect = &data->locked_rect; |
| pixels = |
| (void *)((Uint8 *)data->pixels + rect->y * data->pitch + |
| rect->x * SDL_BYTESPERPIXEL(texture->format)); |
| GPU_UpdateTexture(renderer, texture, rect, pixels, data->pitch); |
| } |
| |
| static void GPU_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scale_mode) |
| { |
| // nothing to do in this backend. |
| } |
| |
| static bool GPU_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) |
| { |
| GPU_RenderData *data = (GPU_RenderData *)renderer->internal; |
| |
| data->state.render_target = texture; |
| |
| return true; |
| } |
| |
| static bool GPU_QueueNoOp(SDL_Renderer *renderer, SDL_RenderCommand *cmd) |
| { |
| return true; // nothing to do in this backend. |
| } |
| |
| static SDL_FColor GetDrawCmdColor(SDL_Renderer *renderer, SDL_RenderCommand *cmd) |
| { |
| SDL_FColor color = cmd->data.color.color; |
| |
| if (SDL_RenderingLinearSpace(renderer)) { |
| SDL_ConvertToLinear(&color); |
| } |
| |
| color.r *= cmd->data.color.color_scale; |
| color.g *= cmd->data.color.color_scale; |
| color.b *= cmd->data.color.color_scale; |
| |
| return color; |
| } |
| |
| static bool GPU_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count) |
| { |
| float *verts = (float *)SDL_AllocateRenderVertices(renderer, count * 2 * sizeof(float), 0, &cmd->data.draw.first); |
| |
| if (!verts) { |
| return false; |
| } |
| |
| cmd->data.draw.count = count; |
| for (int i = 0; i < count; i++) { |
| *(verts++) = 0.5f + points[i].x; |
| *(verts++) = 0.5f + points[i].y; |
| } |
| |
| return true; |
| } |
| |
| static bool GPU_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, |
| const float *xy, int xy_stride, const SDL_FColor *color, int color_stride, const float *uv, int uv_stride, |
| int num_vertices, const void *indices, int num_indices, int size_indices, |
| float scale_x, float scale_y) |
| { |
| int i; |
| int count = indices ? num_indices : num_vertices; |
| float *verts; |
| size_t sz = 2 * sizeof(float) + 4 * sizeof(float) + (texture ? 2 : 0) * sizeof(float); |
| const float color_scale = cmd->data.draw.color_scale; |
| bool convert_color = SDL_RenderingLinearSpace(renderer); |
| |
| verts = (float *)SDL_AllocateRenderVertices(renderer, count * sz, 0, &cmd->data.draw.first); |
| if (!verts) { |
| return false; |
| } |
| |
| cmd->data.draw.count = count; |
| size_indices = indices ? size_indices : 0; |
| |
| for (i = 0; i < count; i++) { |
| int j; |
| float *xy_; |
| SDL_FColor col_; |
| if (size_indices == 4) { |
| j = ((const Uint32 *)indices)[i]; |
| } else if (size_indices == 2) { |
| j = ((const Uint16 *)indices)[i]; |
| } else if (size_indices == 1) { |
| j = ((const Uint8 *)indices)[i]; |
| } else { |
| j = i; |
| } |
| |
| xy_ = (float *)((char *)xy + j * xy_stride); |
| |
| *(verts++) = xy_[0] * scale_x; |
| *(verts++) = xy_[1] * scale_y; |
| |
| col_ = *(SDL_FColor *)((char *)color + j * color_stride); |
| if (convert_color) { |
| SDL_ConvertToLinear(&col_); |
| } |
| |
| // FIXME: The Vulkan backend doesn't multiply by color_scale. GL does. I'm not sure which one is wrong. |
| // ANSWER: The color scale should be applied in linear space when using the scRGB colorspace. This is done in shaders in the Vulkan backend. |
| *(verts++) = col_.r * color_scale; |
| *(verts++) = col_.g * color_scale; |
| *(verts++) = col_.b * color_scale; |
| *(verts++) = col_.a; |
| |
| if (texture) { |
| float *uv_ = (float *)((char *)uv + j * uv_stride); |
| *(verts++) = uv_[0] * texture->w; |
| *(verts++) = uv_[1] * texture->h; |
| } |
| } |
| return true; |
| } |
| |
| static void GPU_InvalidateCachedState(SDL_Renderer *renderer) |
| { |
| GPU_RenderData *data = (GPU_RenderData *)renderer->internal; |
| |
| data->state.render_target = NULL; |
| data->state.scissor_enabled = false; |
| } |
| |
| static SDL_GPURenderPass *RestartRenderPass(GPU_RenderData *data) |
| { |
| if (data->state.render_pass) { |
| SDL_EndGPURenderPass(data->state.render_pass); |
| } |
| |
| data->state.render_pass = SDL_BeginGPURenderPass( |
| data->state.command_buffer, &data->state.color_attachment, 1, NULL); |
| |
| // *** FIXME *** |
| // This is busted. We should be able to know which load op to use. |
| // LOAD is incorrect behavior most of the time, unless we had to break a render pass. |
| // -cosmonaut |
| data->state.color_attachment.load_op = SDL_GPU_LOADOP_LOAD; |
| data->state.scissor_was_enabled = false; |
| |
| return data->state.render_pass; |
| } |
| |
| static void PushUniforms(GPU_RenderData *data, SDL_RenderCommand *cmd) |
| { |
| GPU_ShaderUniformData uniforms; |
| SDL_zero(uniforms); |
| uniforms.mvp.m[0][0] = 2.0f / data->state.viewport.w; |
| uniforms.mvp.m[1][1] = -2.0f / data->state.viewport.h; |
| uniforms.mvp.m[2][2] = 1.0f; |
| uniforms.mvp.m[3][0] = -1.0f; |
| uniforms.mvp.m[3][1] = 1.0f; |
| uniforms.mvp.m[3][3] = 1.0f; |
| |
| uniforms.color = data->state.draw_color; |
| |
| if (cmd->data.draw.texture) { |
| uniforms.texture_size[0] = cmd->data.draw.texture->w; |
| uniforms.texture_size[1] = cmd->data.draw.texture->h; |
| } |
| |
| SDL_PushGPUVertexUniformData(data->state.command_buffer, 0, &uniforms, sizeof(uniforms)); |
| } |
| |
| static SDL_GPUSampler **SamplerPointer( |
| GPU_RenderData *data, SDL_TextureAddressMode address_mode, SDL_ScaleMode scale_mode) |
| { |
| return &data->samplers[scale_mode][address_mode - 1]; |
| } |
| |
| static void SetViewportAndScissor(GPU_RenderData *data) |
| { |
| SDL_SetGPUViewport(data->state.render_pass, &data->state.viewport); |
| |
| if (data->state.scissor_enabled) { |
| SDL_SetGPUScissor(data->state.render_pass, &data->state.scissor); |
| data->state.scissor_was_enabled = true; |
| } else if (data->state.scissor_was_enabled) { |
| SDL_Rect r; |
| r.x = (int)data->state.viewport.x; |
| r.y = (int)data->state.viewport.y; |
| r.w = (int)data->state.viewport.w; |
| r.h = (int)data->state.viewport.h; |
| SDL_SetGPUScissor(data->state.render_pass, &r); |
| data->state.scissor_was_enabled = false; |
| } |
| } |
| |
| static void Draw( |
| GPU_RenderData *data, SDL_RenderCommand *cmd, |
| Uint32 num_verts, |
| Uint32 offset, |
| SDL_GPUPrimitiveType prim) |
| { |
| if (!data->state.render_pass || data->state.color_attachment.load_op == SDL_GPU_LOADOP_CLEAR) { |
| RestartRenderPass(data); |
| } |
| |
| GPU_VertexShaderID v_shader; |
| GPU_FragmentShaderID f_shader; |
| SDL_GPURenderPass *pass = data->state.render_pass; |
| GPU_TextureData *tdata = NULL; |
| |
| if (cmd->data.draw.texture) { |
| tdata = (GPU_TextureData *)cmd->data.draw.texture->internal; |
| } |
| |
| if (prim == SDL_GPU_PRIMITIVETYPE_TRIANGLELIST) { |
| if (cmd->data.draw.texture) { |
| v_shader = VERT_SHADER_TRI_TEXTURE; |
| f_shader = tdata->shader; |
| } else { |
| v_shader = VERT_SHADER_TRI_COLOR; |
| f_shader = FRAG_SHADER_COLOR; |
| } |
| } else { |
| v_shader = VERT_SHADER_LINEPOINT; |
| f_shader = FRAG_SHADER_COLOR; |
| } |
| |
| GPU_PipelineParameters pipe_params; |
| SDL_zero(pipe_params); |
| pipe_params.blend_mode = cmd->data.draw.blend; |
| pipe_params.vert_shader = v_shader; |
| pipe_params.frag_shader = f_shader; |
| pipe_params.primitive_type = prim; |
| |
| if (data->state.render_target) { |
| pipe_params.attachment_format = ((GPU_TextureData *)data->state.render_target->internal)->format; |
| } else { |
| pipe_params.attachment_format = data->backbuffer.format; |
| } |
| |
| SDL_GPUGraphicsPipeline *pipe = GPU_GetPipeline(&data->pipeline_cache, &data->shaders, data->device, &pipe_params); |
| |
| if (!pipe) { |
| return; |
| } |
| |
| SetViewportAndScissor(data); |
| SDL_BindGPUGraphicsPipeline(data->state.render_pass, pipe); |
| |
| if (tdata) { |
| SDL_GPUTextureSamplerBinding sampler_bind; |
| SDL_zero(sampler_bind); |
| sampler_bind.sampler = *SamplerPointer(data, cmd->data.draw.texture_address_mode, cmd->data.draw.texture->scaleMode); |
| sampler_bind.texture = tdata->texture; |
| SDL_BindGPUFragmentSamplers(pass, 0, &sampler_bind, 1); |
| } |
| |
| SDL_GPUBufferBinding buffer_bind; |
| SDL_zero(buffer_bind); |
| buffer_bind.buffer = data->vertices.buffer; |
| buffer_bind.offset = offset; |
| |
| SDL_BindGPUVertexBuffers(pass, 0, &buffer_bind, 1); |
| PushUniforms(data, cmd); |
| SDL_DrawGPUPrimitives(data->state.render_pass, num_verts, 1, 0, 0); |
| } |
| |
| static void ReleaseVertexBuffer(GPU_RenderData *data) |
| { |
| if (data->vertices.buffer) { |
| SDL_ReleaseGPUBuffer(data->device, data->vertices.buffer); |
| } |
| |
| if (data->vertices.transfer_buf) { |
| SDL_ReleaseGPUTransferBuffer(data->device, data->vertices.transfer_buf); |
| } |
| |
| data->vertices.buffer_size = 0; |
| } |
| |
| static bool InitVertexBuffer(GPU_RenderData *data, Uint32 size) |
| { |
| SDL_GPUBufferCreateInfo bci; |
| SDL_zero(bci); |
| bci.size = size; |
| bci.usage = SDL_GPU_BUFFERUSAGE_VERTEX; |
| |
| data->vertices.buffer = SDL_CreateGPUBuffer(data->device, &bci); |
| |
| if (!data->vertices.buffer) { |
| return false; |
| } |
| |
| SDL_GPUTransferBufferCreateInfo tbci; |
| SDL_zero(tbci); |
| tbci.size = size; |
| tbci.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; |
| |
| data->vertices.transfer_buf = SDL_CreateGPUTransferBuffer(data->device, &tbci); |
| |
| if (!data->vertices.transfer_buf) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool UploadVertices(GPU_RenderData *data, void *vertices, size_t vertsize) |
| { |
| if (vertsize == 0) { |
| return true; |
| } |
| |
| if (vertsize > data->vertices.buffer_size) { |
| ReleaseVertexBuffer(data); |
| if (!InitVertexBuffer(data, (Uint32)vertsize)) { |
| return false; |
| } |
| } |
| |
| void *staging_buf = SDL_MapGPUTransferBuffer(data->device, data->vertices.transfer_buf, true); |
| SDL_memcpy(staging_buf, vertices, vertsize); |
| SDL_UnmapGPUTransferBuffer(data->device, data->vertices.transfer_buf); |
| |
| SDL_GPUCopyPass *pass = SDL_BeginGPUCopyPass(data->state.command_buffer); |
| |
| if (!pass) { |
| return false; |
| } |
| |
| SDL_GPUTransferBufferLocation src; |
| SDL_zero(src); |
| src.transfer_buffer = data->vertices.transfer_buf; |
| |
| SDL_GPUBufferRegion dst; |
| SDL_zero(dst); |
| dst.buffer = data->vertices.buffer; |
| dst.size = (Uint32)vertsize; |
| |
| SDL_UploadToGPUBuffer(pass, &src, &dst, true); |
| SDL_EndGPUCopyPass(pass); |
| |
| return true; |
| } |
| |
| // *** FIXME *** |
| // We might be able to run these data uploads on a separate command buffer |
| // which would allow us to avoid breaking render passes. |
| // Honestly I'm a little skeptical of this entire approach, |
| // we already have a command buffer structure |
| // so it feels weird to be deferring the operations manually. |
| // We could also fairly easily run the geometry transformations |
| // on compute shaders instead of the CPU, which would be a HUGE performance win. |
| // -cosmonaut |
| static bool GPU_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) |
| { |
| GPU_RenderData *data = (GPU_RenderData *)renderer->internal; |
| |
| if (!UploadVertices(data, vertices, vertsize)) { |
| return false; |
| } |
| |
| data->state.color_attachment.load_op = SDL_GPU_LOADOP_LOAD; |
| |
| if (renderer->target) { |
| GPU_TextureData *tdata = renderer->target->internal; |
| data->state.color_attachment.texture = tdata->texture; |
| } else { |
| data->state.color_attachment.texture = data->backbuffer.texture; |
| } |
| |
| if (!data->state.color_attachment.texture) { |
| return SDL_SetError("Render target texture is NULL"); |
| } |
| |
| while (cmd) { |
| switch (cmd->command) { |
| case SDL_RENDERCMD_SETDRAWCOLOR: |
| { |
| data->state.draw_color = GetDrawCmdColor(renderer, cmd); |
| break; |
| } |
| |
| case SDL_RENDERCMD_SETVIEWPORT: |
| { |
| SDL_Rect *viewport = &cmd->data.viewport.rect; |
| data->state.viewport.x = viewport->x; |
| data->state.viewport.y = viewport->y; |
| data->state.viewport.w = viewport->w; |
| data->state.viewport.h = viewport->h; |
| break; |
| } |
| |
| case SDL_RENDERCMD_SETCLIPRECT: |
| { |
| const SDL_Rect *rect = &cmd->data.cliprect.rect; |
| data->state.scissor.x = (int)data->state.viewport.x + rect->x; |
| data->state.scissor.y = (int)data->state.viewport.y + rect->y; |
| data->state.scissor.w = rect->w; |
| data->state.scissor.h = rect->h; |
| data->state.scissor_enabled = cmd->data.cliprect.enabled; |
| break; |
| } |
| |
| case SDL_RENDERCMD_CLEAR: |
| { |
| data->state.color_attachment.clear_color = GetDrawCmdColor(renderer, cmd); |
| data->state.color_attachment.load_op = SDL_GPU_LOADOP_CLEAR; |
| break; |
| } |
| |
| case SDL_RENDERCMD_FILL_RECTS: // unused |
| break; |
| |
| case SDL_RENDERCMD_COPY: // unused |
| break; |
| |
| case SDL_RENDERCMD_COPY_EX: // unused |
| break; |
| |
| case SDL_RENDERCMD_DRAW_LINES: |
| { |
| Uint32 count = (Uint32)cmd->data.draw.count; |
| Uint32 offset = (Uint32)cmd->data.draw.first; |
| |
| if (count > 2) { |
| // joined lines cannot be grouped |
| Draw(data, cmd, count, offset, SDL_GPU_PRIMITIVETYPE_LINESTRIP); |
| } else { |
| // let's group non joined lines |
| SDL_RenderCommand *finalcmd = cmd; |
| SDL_RenderCommand *nextcmd = cmd->next; |
| SDL_BlendMode thisblend = cmd->data.draw.blend; |
| |
| while (nextcmd) { |
| const SDL_RenderCommandType nextcmdtype = nextcmd->command; |
| if (nextcmdtype != SDL_RENDERCMD_DRAW_LINES) { |
| break; // can't go any further on this draw call, different render command up next. |
| } else if (nextcmd->data.draw.count != 2) { |
| break; // can't go any further on this draw call, those are joined lines |
| } else if (nextcmd->data.draw.blend != thisblend) { |
| break; // can't go any further on this draw call, different blendmode copy up next. |
| } else { |
| finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command. |
| count += (Uint32)nextcmd->data.draw.count; |
| } |
| nextcmd = nextcmd->next; |
| } |
| |
| Draw(data, cmd, count, offset, SDL_GPU_PRIMITIVETYPE_LINELIST); |
| cmd = finalcmd; // skip any copy commands we just combined in here. |
| } |
| break; |
| } |
| |
| case SDL_RENDERCMD_DRAW_POINTS: |
| case SDL_RENDERCMD_GEOMETRY: |
| { |
| /* as long as we have the same copy command in a row, with the |
| same texture, we can combine them all into a single draw call. */ |
| SDL_Texture *thistexture = cmd->data.draw.texture; |
| SDL_BlendMode thisblend = cmd->data.draw.blend; |
| const SDL_RenderCommandType thiscmdtype = cmd->command; |
| SDL_RenderCommand *finalcmd = cmd; |
| SDL_RenderCommand *nextcmd = cmd->next; |
| Uint32 count = (Uint32)cmd->data.draw.count; |
| Uint32 offset = (Uint32)cmd->data.draw.first; |
| |
| while (nextcmd) { |
| const SDL_RenderCommandType nextcmdtype = nextcmd->command; |
| if (nextcmdtype != thiscmdtype) { |
| break; // can't go any further on this draw call, different render command up next. |
| } else if (nextcmd->data.draw.texture != thistexture || nextcmd->data.draw.blend != thisblend) { |
| // FIXME should we check address mode too? |
| break; // can't go any further on this draw call, different texture/blendmode copy up next. |
| } else { |
| finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command. |
| count += (Uint32)nextcmd->data.draw.count; |
| } |
| nextcmd = nextcmd->next; |
| } |
| |
| SDL_GPUPrimitiveType prim = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; // SDL_RENDERCMD_GEOMETRY |
| if (thiscmdtype == SDL_RENDERCMD_DRAW_POINTS) { |
| prim = SDL_GPU_PRIMITIVETYPE_POINTLIST; |
| } |
| |
| Draw(data, cmd, count, offset, prim); |
| |
| cmd = finalcmd; // skip any copy commands we just combined in here. |
| break; |
| } |
| |
| case SDL_RENDERCMD_NO_OP: |
| break; |
| } |
| |
| cmd = cmd->next; |
| } |
| |
| if (data->state.color_attachment.load_op == SDL_GPU_LOADOP_CLEAR) { |
| RestartRenderPass(data); |
| } |
| |
| if (data->state.render_pass) { |
| SDL_EndGPURenderPass(data->state.render_pass); |
| data->state.render_pass = NULL; |
| } |
| |
| return true; |
| } |
| |
| static SDL_Surface *GPU_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect) |
| { |
| GPU_RenderData *data = (GPU_RenderData *)renderer->internal; |
| SDL_GPUTexture *gpu_tex; |
| SDL_PixelFormat pixfmt; |
| |
| if (data->state.render_target) { |
| SDL_Texture *texture = data->state.render_target; |
| GPU_TextureData *texdata = texture->internal; |
| gpu_tex = texdata->texture; |
| pixfmt = texture->format; |
| } else { |
| gpu_tex = data->backbuffer.texture; |
| pixfmt = TexFormatToPixFormat(data->backbuffer.format); |
| |
| if (pixfmt == SDL_PIXELFORMAT_UNKNOWN) { |
| SDL_SetError("Unsupported backbuffer format"); |
| return NULL; |
| } |
| } |
| |
| Uint32 bpp = SDL_BYTESPERPIXEL(pixfmt); |
| size_t row_size, image_size; |
| |
| if (!SDL_size_mul_check_overflow(rect->w, bpp, &row_size) || |
| !SDL_size_mul_check_overflow(rect->h, row_size, &image_size)) { |
| SDL_SetError("read size overflow"); |
| return NULL; |
| } |
| |
| SDL_Surface *surface = SDL_CreateSurface(rect->w, rect->h, pixfmt); |
| |
| if (!surface) { |
| return NULL; |
| } |
| |
| SDL_GPUTransferBufferCreateInfo tbci; |
| SDL_zero(tbci); |
| tbci.size = (Uint32)image_size; |
| tbci.usage = SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD; |
| |
| SDL_GPUTransferBuffer *tbuf = SDL_CreateGPUTransferBuffer(data->device, &tbci); |
| |
| if (!tbuf) { |
| return NULL; |
| } |
| |
| SDL_GPUCopyPass *pass = SDL_BeginGPUCopyPass(data->state.command_buffer); |
| |
| SDL_GPUTextureRegion src; |
| SDL_zero(src); |
| src.texture = gpu_tex; |
| src.x = rect->x; |
| src.y = rect->y; |
| src.w = rect->w; |
| src.h = rect->h; |
| src.d = 1; |
| |
| SDL_GPUTextureTransferInfo dst; |
| SDL_zero(dst); |
| dst.transfer_buffer = tbuf; |
| dst.rows_per_layer = rect->h; |
| dst.pixels_per_row = rect->w; |
| |
| SDL_DownloadFromGPUTexture(pass, &src, &dst); |
| SDL_EndGPUCopyPass(pass); |
| |
| SDL_GPUFence *fence = SDL_SubmitGPUCommandBufferAndAcquireFence(data->state.command_buffer); |
| SDL_WaitForGPUFences(data->device, true, &fence, 1); |
| SDL_ReleaseGPUFence(data->device, fence); |
| data->state.command_buffer = SDL_AcquireGPUCommandBuffer(data->device); |
| |
| void *mapped_tbuf = SDL_MapGPUTransferBuffer(data->device, tbuf, false); |
| |
| if ((size_t)surface->pitch == row_size) { |
| SDL_memcpy(surface->pixels, mapped_tbuf, image_size); |
| } else { |
| Uint8 *input = mapped_tbuf; |
| Uint8 *output = surface->pixels; |
| |
| for (int row = 0; row < rect->h; ++row) { |
| SDL_memcpy(output, input, row_size); |
| output += surface->pitch; |
| input += row_size; |
| } |
| } |
| |
| SDL_UnmapGPUTransferBuffer(data->device, tbuf); |
| SDL_ReleaseGPUTransferBuffer(data->device, tbuf); |
| |
| return surface; |
| } |
| |
| static bool CreateBackbuffer(GPU_RenderData *data, Uint32 w, Uint32 h, SDL_GPUTextureFormat fmt) |
| { |
| SDL_GPUTextureCreateInfo tci; |
| SDL_zero(tci); |
| tci.width = w; |
| tci.height = h; |
| tci.format = fmt; |
| tci.layer_count_or_depth = 1; |
| tci.num_levels = 1; |
| tci.sample_count = SDL_GPU_SAMPLECOUNT_1; |
| tci.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; |
| |
| data->backbuffer.texture = SDL_CreateGPUTexture(data->device, &tci); |
| data->backbuffer.width = w; |
| data->backbuffer.height = h; |
| data->backbuffer.format = fmt; |
| |
| if (!data->backbuffer.texture) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool GPU_RenderPresent(SDL_Renderer *renderer) |
| { |
| GPU_RenderData *data = (GPU_RenderData *)renderer->internal; |
| |
| SDL_GPUTexture *swapchain; |
| Uint32 swapchain_texture_width, swapchain_texture_height; |
| bool result = SDL_WaitAndAcquireGPUSwapchainTexture(data->state.command_buffer, renderer->window, &swapchain, &swapchain_texture_width, &swapchain_texture_height); |
| |
| if (!result) { |
| SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to acquire swapchain texture: %s", SDL_GetError()); |
| } |
| |
| if (swapchain != NULL) { |
| SDL_GPUBlitInfo blit_info; |
| SDL_zero(blit_info); |
| |
| blit_info.source.texture = data->backbuffer.texture; |
| blit_info.source.w = data->backbuffer.width; |
| blit_info.source.h = data->backbuffer.height; |
| blit_info.destination.texture = swapchain; |
| blit_info.destination.w = swapchain_texture_width; |
| blit_info.destination.h = swapchain_texture_height; |
| blit_info.load_op = SDL_GPU_LOADOP_DONT_CARE; |
| blit_info.filter = SDL_GPU_FILTER_LINEAR; |
| |
| SDL_BlitGPUTexture(data->state.command_buffer, &blit_info); |
| |
| SDL_SubmitGPUCommandBuffer(data->state.command_buffer); |
| |
| if (swapchain_texture_width != data->backbuffer.width || swapchain_texture_height != data->backbuffer.height) { |
| SDL_ReleaseGPUTexture(data->device, data->backbuffer.texture); |
| CreateBackbuffer(data, swapchain_texture_width, swapchain_texture_height, SDL_GetGPUSwapchainTextureFormat(data->device, renderer->window)); |
| } |
| } else { |
| SDL_SubmitGPUCommandBuffer(data->state.command_buffer); |
| } |
| |
| data->state.command_buffer = SDL_AcquireGPUCommandBuffer(data->device); |
| |
| return true; |
| } |
| |
| static void GPU_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture) |
| { |
| GPU_RenderData *renderdata = (GPU_RenderData *)renderer->internal; |
| GPU_TextureData *data = (GPU_TextureData *)texture->internal; |
| |
| if (renderdata->state.render_target == texture) { |
| renderdata->state.render_target = NULL; |
| } |
| |
| if (!data) { |
| return; |
| } |
| |
| SDL_ReleaseGPUTexture(renderdata->device, data->texture); |
| SDL_free(data->pixels); |
| SDL_free(data); |
| texture->internal = NULL; |
| } |
| |
| static void GPU_DestroyRenderer(SDL_Renderer *renderer) |
| { |
| GPU_RenderData *data = (GPU_RenderData *)renderer->internal; |
| |
| if (!data) { |
| return; |
| } |
| |
| if (data->state.command_buffer) { |
| SDL_SubmitGPUCommandBuffer(data->state.command_buffer); |
| data->state.command_buffer = NULL; |
| } |
| |
| for (Uint32 i = 0; i < sizeof(data->samplers) / sizeof(SDL_GPUSampler *); ++i) { |
| SDL_ReleaseGPUSampler(data->device, ((SDL_GPUSampler **)data->samplers)[i]); |
| } |
| |
| if (data->backbuffer.texture) { |
| SDL_ReleaseGPUTexture(data->device, data->backbuffer.texture); |
| } |
| |
| if (renderer->window) { |
| SDL_ReleaseWindowFromGPUDevice(data->device, renderer->window); |
| } |
| |
| ReleaseVertexBuffer(data); |
| GPU_DestroyPipelineCache(&data->pipeline_cache); |
| GPU_ReleaseShaders(&data->shaders, data->device); |
| SDL_DestroyGPUDevice(data->device); |
| |
| SDL_free(data); |
| } |
| |
| static bool ChoosePresentMode(SDL_GPUDevice *device, SDL_Window *window, const int vsync, SDL_GPUPresentMode *out_mode) |
| { |
| SDL_GPUPresentMode mode; |
| |
| switch (vsync) { |
| case 0: |
| mode = SDL_GPU_PRESENTMODE_MAILBOX; |
| |
| if (!SDL_WindowSupportsGPUPresentMode(device, window, mode)) { |
| mode = SDL_GPU_PRESENTMODE_IMMEDIATE; |
| |
| if (!SDL_WindowSupportsGPUPresentMode(device, window, mode)) { |
| mode = SDL_GPU_PRESENTMODE_VSYNC; |
| } |
| } |
| |
| // FIXME should we return an error if both mailbox and immediate fail? |
| break; |
| |
| case 1: |
| mode = SDL_GPU_PRESENTMODE_VSYNC; |
| break; |
| |
| default: |
| return SDL_Unsupported(); |
| } |
| |
| *out_mode = mode; |
| return true; |
| } |
| |
| static bool GPU_SetVSync(SDL_Renderer *renderer, const int vsync) |
| { |
| GPU_RenderData *data = (GPU_RenderData *)renderer->internal; |
| SDL_GPUPresentMode mode = SDL_GPU_PRESENTMODE_VSYNC; |
| |
| if (!ChoosePresentMode(data->device, renderer->window, vsync, &mode)) { |
| return false; |
| } |
| |
| if (mode != data->swapchain.present_mode) { |
| // XXX returns bool instead of SDL-style error code |
| if (SDL_SetGPUSwapchainParameters(data->device, renderer->window, data->swapchain.composition, mode)) { |
| data->swapchain.present_mode = mode; |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool InitSamplers(GPU_RenderData *data) |
| { |
| struct |
| { |
| struct |
| { |
| SDL_TextureAddressMode address_mode; |
| SDL_ScaleMode scale_mode; |
| } sdl; |
| struct |
| { |
| SDL_GPUSamplerAddressMode address_mode; |
| SDL_GPUFilter filter; |
| SDL_GPUSamplerMipmapMode mipmap_mode; |
| Uint32 anisotropy; |
| } gpu; |
| } configs[] = { |
| { |
| { SDL_TEXTURE_ADDRESS_CLAMP, SDL_SCALEMODE_NEAREST }, |
| { SDL_GPU_SAMPLERADDRESSMODE_REPEAT, SDL_GPU_FILTER_NEAREST, SDL_GPU_SAMPLERMIPMAPMODE_NEAREST, 0 }, |
| }, |
| { |
| { SDL_TEXTURE_ADDRESS_CLAMP, SDL_SCALEMODE_LINEAR }, |
| { SDL_GPU_SAMPLERADDRESSMODE_REPEAT, SDL_GPU_FILTER_LINEAR, SDL_GPU_SAMPLERMIPMAPMODE_LINEAR, 0 }, |
| }, |
| { |
| { SDL_TEXTURE_ADDRESS_WRAP, SDL_SCALEMODE_NEAREST }, |
| { SDL_GPU_SAMPLERADDRESSMODE_REPEAT, SDL_GPU_FILTER_NEAREST, SDL_GPU_SAMPLERMIPMAPMODE_NEAREST, 0 }, |
| }, |
| { |
| { SDL_TEXTURE_ADDRESS_WRAP, SDL_SCALEMODE_LINEAR }, |
| { SDL_GPU_SAMPLERADDRESSMODE_REPEAT, SDL_GPU_FILTER_LINEAR, SDL_GPU_SAMPLERMIPMAPMODE_LINEAR, 0 }, |
| }, |
| }; |
| |
| for (Uint32 i = 0; i < SDL_arraysize(configs); ++i) { |
| SDL_GPUSamplerCreateInfo sci; |
| SDL_zero(sci); |
| sci.max_anisotropy = configs[i].gpu.anisotropy; |
| sci.enable_anisotropy = configs[i].gpu.anisotropy > 0; |
| sci.address_mode_u = sci.address_mode_v = sci.address_mode_w = configs[i].gpu.address_mode; |
| sci.min_filter = sci.mag_filter = configs[i].gpu.filter; |
| sci.mipmap_mode = configs[i].gpu.mipmap_mode; |
| |
| SDL_GPUSampler *sampler = SDL_CreateGPUSampler(data->device, &sci); |
| |
| if (sampler == NULL) { |
| return false; |
| } |
| |
| *SamplerPointer(data, configs[i].sdl.address_mode, configs[i].sdl.scale_mode) = sampler; |
| } |
| |
| return true; |
| } |
| |
| static bool GPU_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_PropertiesID create_props) |
| { |
| GPU_RenderData *data = NULL; |
| |
| SDL_SetupRendererColorspace(renderer, create_props); |
| |
| if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) { |
| // TODO |
| return SDL_SetError("Unsupported output colorspace"); |
| } |
| |
| data = (GPU_RenderData *)SDL_calloc(1, sizeof(*data)); |
| if (!data) { |
| return false; |
| } |
| |
| renderer->SupportsBlendMode = GPU_SupportsBlendMode; |
| renderer->CreateTexture = GPU_CreateTexture; |
| renderer->UpdateTexture = GPU_UpdateTexture; |
| renderer->LockTexture = GPU_LockTexture; |
| renderer->UnlockTexture = GPU_UnlockTexture; |
| renderer->SetTextureScaleMode = GPU_SetTextureScaleMode; |
| renderer->SetRenderTarget = GPU_SetRenderTarget; |
| renderer->QueueSetViewport = GPU_QueueNoOp; |
| renderer->QueueSetDrawColor = GPU_QueueNoOp; |
| renderer->QueueDrawPoints = GPU_QueueDrawPoints; |
| renderer->QueueDrawLines = GPU_QueueDrawPoints; // lines and points queue vertices the same way. |
| renderer->QueueGeometry = GPU_QueueGeometry; |
| renderer->InvalidateCachedState = GPU_InvalidateCachedState; |
| renderer->RunCommandQueue = GPU_RunCommandQueue; |
| renderer->RenderReadPixels = GPU_RenderReadPixels; |
| renderer->RenderPresent = GPU_RenderPresent; |
| renderer->DestroyTexture = GPU_DestroyTexture; |
| renderer->DestroyRenderer = GPU_DestroyRenderer; |
| renderer->SetVSync = GPU_SetVSync; |
| renderer->internal = data; |
| renderer->window = window; |
| renderer->name = GPU_RenderDriver.name; |
| |
| bool debug = SDL_GetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, false); |
| bool lowpower = SDL_GetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, false); |
| |
| // Prefer environment variables/hints if they exist, otherwise defer to properties |
| debug = SDL_GetHintBoolean(SDL_HINT_RENDER_GPU_DEBUG, debug); |
| lowpower = SDL_GetHintBoolean(SDL_HINT_RENDER_GPU_LOW_POWER, lowpower); |
| |
| SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, debug); |
| SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, lowpower); |
| |
| GPU_FillSupportedShaderFormats(create_props); |
| data->device = SDL_CreateGPUDeviceWithProperties(create_props); |
| |
| if (!data->device) { |
| return false; |
| } |
| |
| if (!GPU_InitShaders(&data->shaders, data->device)) { |
| return false; |
| } |
| |
| if (!GPU_InitPipelineCache(&data->pipeline_cache, data->device)) { |
| return false; |
| } |
| |
| // XXX what's a good initial size? |
| if (!InitVertexBuffer(data, 1 << 16)) { |
| return false; |
| } |
| |
| if (!InitSamplers(data)) { |
| return false; |
| } |
| |
| if (!SDL_ClaimWindowForGPUDevice(data->device, window)) { |
| return false; |
| } |
| |
| data->swapchain.composition = SDL_GPU_SWAPCHAINCOMPOSITION_SDR; |
| data->swapchain.present_mode = SDL_GPU_PRESENTMODE_VSYNC; |
| |
| int vsync = (int)SDL_GetNumberProperty(create_props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 0); |
| ChoosePresentMode(data->device, window, vsync, &data->swapchain.present_mode); |
| |
| SDL_SetGPUSwapchainParameters(data->device, window, data->swapchain.composition, data->swapchain.present_mode); |
| |
| SDL_SetGPUAllowedFramesInFlight(data->device, 1); |
| |
| SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA32); |
| SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRA32); |
| SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBX32); |
| SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRX32); |
| |
| SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 16384); |
| |
| data->state.draw_color.r = 1.0f; |
| data->state.draw_color.g = 1.0f; |
| data->state.draw_color.b = 1.0f; |
| data->state.draw_color.a = 1.0f; |
| data->state.viewport.min_depth = 0; |
| data->state.viewport.max_depth = 1; |
| data->state.command_buffer = SDL_AcquireGPUCommandBuffer(data->device); |
| |
| int w, h; |
| SDL_GetWindowSizeInPixels(window, &w, &h); |
| |
| if (!CreateBackbuffer(data, w, h, SDL_GetGPUSwapchainTextureFormat(data->device, window))) { |
| return false; |
| } |
| |
| SDL_SetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_GPU_DEVICE_POINTER, data->device); |
| |
| return true; |
| } |
| |
| SDL_RenderDriver GPU_RenderDriver = { |
| GPU_CreateRenderer, "gpu" |
| }; |
| |
| #endif // SDL_VIDEO_RENDER_GPU |