/* * * Singe 3 * Copyright (C) 2006-2026 Scott Duensing * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * */ // Backend selection and dispatch. // // One indirect call per entry point. That is nothing beside the work each one goes on to do, and it // buys runtime selection: the same binary runs on a machine with Vulkan and on one with GLES only. #include "render.h" #include "util.h" static const RenderBackendT *_backend = NULL; static SDL_Renderer *_renderer = NULL; static RenderApiT _api = RENDER_NONE; RenderApiT renderApi(void) { return _api; } const char *renderApiName(void) { switch (_api) { case RENDER_GPU: return "SDL_GPU"; case RENDER_GLES: return "OpenGL ES"; default: return "none"; } } // GLES shares one context with SDL_Renderer, which caches its own drawing state. SDL_FlushRenderer // both flushes the queue and invalidates that cache (SDL_render.c), which is what makes handing the // context over safe rather than merely lucky -- see PLAN.md section 56, phase 0. Under SDL_GPU // there is no shared state and this costs a comparison. void renderBegin(void) { if ((_api == RENDER_GLES) && (_renderer != NULL)) { SDL_FlushRenderer(_renderer); } } // Nothing to undo under SDL_GPU; it owns no state SDL_Renderer can see. GLES shares one context, // so it puts back the bindings SDL does not track for itself. void renderEnd(void) { if (_api == RENDER_GLES) { renderGlesRestore(); } } void renderSelect(RenderApiT api, const RenderBackendT *backend, SDL_Renderer *renderer) { _api = backend != NULL ? api : RENDER_NONE; _backend = backend; _renderer = renderer; utilTrace("Render: %s", renderApiName()); } SDL_GPUTexture *renderTextureFor(SDL_Texture *texture) { if (texture == NULL) { return NULL; } if (_api == RENDER_GLES) { float width = 0.0f; float height = 0.0f; SDL_GetTextureSize(texture, &width, &height); return renderGlesTextureWrap((Uint32)SDL_GetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_NUMBER, 0), (int32_t)width, (int32_t)height); } return (SDL_GPUTexture *)SDL_GetPointerProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_GPU_TEXTURE_POINTER, NULL); } SDL_Texture *renderWrapTexture(SDL_Renderer *renderer, SDL_GPUTexture *texture, SDL_PixelFormat format, int32_t width, int32_t height) { SDL_PropertiesID props; SDL_Texture *result; if ((renderer == NULL) || (texture == NULL)) { return NULL; } props = SDL_CreateProperties(); if (_api == RENDER_GLES) { SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER, (Sint64)renderGlesTextureName(texture)); // Core GLES has no BGRA storage, so a caller asking for BGRA32 -- which the scene does, as // its way of naming the swapchain's layout -- really has an RGBA8 texture. Saying otherwise // leaves SDL drawing nothing at all, which is how this was found. if ((format == SDL_PIXELFORMAT_BGRA32) || (format == SDL_PIXELFORMAT_BGRX32)) { format = SDL_PIXELFORMAT_RGBA32; } } else { SDL_SetPointerProperty(props, SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_POINTER, texture); } SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, format); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STATIC); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, width); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, height); result = SDL_CreateTextureWithProperties(renderer, props); SDL_DestroyProperties(props); return result; } // Every run of rgpu* drawing sits between acquiring a command buffer and submitting it, so that is // where the context is taken and given back. Bracketing here rather than at the callers means // scene.c and guiRender.cpp need to know nothing about it. SDL_GPUCommandBuffer *rgpuAcquireCommandBuffer(SDL_GPUDevice *device) { renderBegin(); return _backend->acquireCommandBuffer(device); } SDL_GPUCopyPass *rgpuBeginCopyPass(SDL_GPUCommandBuffer *commandBuffer) { return _backend->beginCopyPass(commandBuffer); } SDL_GPURenderPass *rgpuBeginRenderPass(SDL_GPUCommandBuffer *commandBuffer, const SDL_GPUColorTargetInfo *colorTargetInfos, Uint32 numColorTargets, const SDL_GPUDepthStencilTargetInfo *depthStencilTargetInfo) { return _backend->beginRenderPass(commandBuffer, colorTargetInfos, numColorTargets, depthStencilTargetInfo); } void rgpuBindFragmentSamplers(SDL_GPURenderPass *renderPass, Uint32 firstSlot, const SDL_GPUTextureSamplerBinding *textureSamplerBindings, Uint32 numBindings) { _backend->bindFragmentSamplers(renderPass, firstSlot, textureSamplerBindings, numBindings); } void rgpuBindGraphicsPipeline(SDL_GPURenderPass *renderPass, SDL_GPUGraphicsPipeline *graphicsPipeline) { _backend->bindGraphicsPipeline(renderPass, graphicsPipeline); } void rgpuBindIndexBuffer(SDL_GPURenderPass *renderPass, const SDL_GPUBufferBinding *binding, SDL_GPUIndexElementSize indexElementSize) { _backend->bindIndexBuffer(renderPass, binding, indexElementSize); } void rgpuBindVertexBuffers(SDL_GPURenderPass *renderPass, Uint32 firstSlot, const SDL_GPUBufferBinding *bindings, Uint32 numBindings) { _backend->bindVertexBuffers(renderPass, firstSlot, bindings, numBindings); } void rgpuBindVertexStorageBuffers(SDL_GPURenderPass *renderPass, Uint32 firstSlot, SDL_GPUBuffer *const *storageBuffers, Uint32 numBindings) { _backend->bindVertexStorageBuffers(renderPass, firstSlot, storageBuffers, numBindings); } void rgpuBlitTexture(SDL_GPUCommandBuffer *commandBuffer, const SDL_GPUBlitInfo *info) { _backend->blitTexture(commandBuffer, info); } bool rgpuCancelCommandBuffer(SDL_GPUCommandBuffer *commandBuffer) { bool ok = _backend->cancelCommandBuffer(commandBuffer); renderEnd(); return ok; } void rgpuCopyTextureToTexture(SDL_GPUCopyPass *copyPass, const SDL_GPUTextureLocation *source, const SDL_GPUTextureLocation *destination, Uint32 w, Uint32 h, Uint32 d, bool cycle) { _backend->copyTextureToTexture(copyPass, source, destination, w, h, d, cycle); } SDL_GPUBuffer *rgpuCreateBuffer(SDL_GPUDevice *device, const SDL_GPUBufferCreateInfo *createinfo) { return _backend->createBuffer(device, createinfo); } SDL_GPUDevice *rgpuCreateDevice(SDL_GPUShaderFormat formatFlags, bool debugMode, const char *name) { return _backend->createDevice(formatFlags, debugMode, name); } SDL_GPUGraphicsPipeline *rgpuCreateGraphicsPipeline(SDL_GPUDevice *device, const SDL_GPUGraphicsPipelineCreateInfo *createinfo) { return _backend->createGraphicsPipeline(device, createinfo); } SDL_GPUSampler *rgpuCreateSampler(SDL_GPUDevice *device, const SDL_GPUSamplerCreateInfo *createinfo) { return _backend->createSampler(device, createinfo); } SDL_GPUShader *rgpuCreateShader(SDL_GPUDevice *device, const SDL_GPUShaderCreateInfo *createinfo) { return _backend->createShader(device, createinfo); } SDL_GPUTexture *rgpuCreateTexture(SDL_GPUDevice *device, const SDL_GPUTextureCreateInfo *createinfo) { return _backend->createTexture(device, createinfo); } SDL_GPUTransferBuffer *rgpuCreateTransferBuffer(SDL_GPUDevice *device, const SDL_GPUTransferBufferCreateInfo *createinfo) { return _backend->createTransferBuffer(device, createinfo); } void rgpuDestroyDevice(SDL_GPUDevice *device) { _backend->destroyDevice(device); } void rgpuDrawIndexedPrimitives(SDL_GPURenderPass *renderPass, Uint32 numIndices, Uint32 numInstances, Uint32 firstIndex, Sint32 vertexOffset, Uint32 firstInstance) { _backend->drawIndexedPrimitives(renderPass, numIndices, numInstances, firstIndex, vertexOffset, firstInstance); } void rgpuDrawPrimitives(SDL_GPURenderPass *renderPass, Uint32 numVertices, Uint32 numInstances, Uint32 firstVertex, Uint32 firstInstance) { _backend->drawPrimitives(renderPass, numVertices, numInstances, firstVertex, firstInstance); } void rgpuEndCopyPass(SDL_GPUCopyPass *copyPass) { _backend->endCopyPass(copyPass); } void rgpuEndRenderPass(SDL_GPURenderPass *renderPass) { _backend->endRenderPass(renderPass); } void rgpuGenerateMipmapsForTexture(SDL_GPUCommandBuffer *commandBuffer, SDL_GPUTexture *texture) { _backend->generateMipmapsForTexture(commandBuffer, texture); } const char *rgpuGetDeviceDriver(SDL_GPUDevice *device) { return _backend->getDeviceDriver(device); } SDL_GPUShaderFormat rgpuGetShaderFormats(SDL_GPUDevice *device) { return _backend->getShaderFormats(device); } SDL_GPUTextureFormat rgpuGetSwapchainTextureFormat(SDL_GPUDevice *device, SDL_Window *window) { return _backend->getSwapchainTextureFormat(device, window); } SDL_GPUTextureFormat rgpuGetTextureFormatFromPixelFormat(SDL_PixelFormat format) { return _backend->getTextureFormatFromPixelFormat(format); } void *rgpuMapTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer, bool cycle) { return _backend->mapTransferBuffer(device, transferBuffer, cycle); } void rgpuPushFragmentUniformData(SDL_GPUCommandBuffer *commandBuffer, Uint32 slotIndex, const void *data, Uint32 length) { _backend->pushFragmentUniformData(commandBuffer, slotIndex, data, length); } void rgpuPushVertexUniformData(SDL_GPUCommandBuffer *commandBuffer, Uint32 slotIndex, const void *data, Uint32 length) { _backend->pushVertexUniformData(commandBuffer, slotIndex, data, length); } void rgpuReleaseBuffer(SDL_GPUDevice *device, SDL_GPUBuffer *buffer) { _backend->releaseBuffer(device, buffer); } void rgpuReleaseGraphicsPipeline(SDL_GPUDevice *device, SDL_GPUGraphicsPipeline *graphicsPipeline) { _backend->releaseGraphicsPipeline(device, graphicsPipeline); } void rgpuReleaseSampler(SDL_GPUDevice *device, SDL_GPUSampler *sampler) { _backend->releaseSampler(device, sampler); } void rgpuReleaseShader(SDL_GPUDevice *device, SDL_GPUShader *shader) { _backend->releaseShader(device, shader); } void rgpuReleaseTexture(SDL_GPUDevice *device, SDL_GPUTexture *texture) { _backend->releaseTexture(device, texture); } void rgpuReleaseTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer) { _backend->releaseTransferBuffer(device, transferBuffer); } void rgpuSetScissor(SDL_GPURenderPass *renderPass, const SDL_Rect *scissor) { _backend->setScissor(renderPass, scissor); } void rgpuSetStencilReference(SDL_GPURenderPass *renderPass, Uint8 reference) { _backend->setStencilReference(renderPass, reference); } bool rgpuSubmitCommandBuffer(SDL_GPUCommandBuffer *commandBuffer) { bool ok = _backend->submitCommandBuffer(commandBuffer); renderEnd(); return ok; } bool rgpuTextureSupportsFormat(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUTextureType type, SDL_GPUTextureUsageFlags usage) { // A format of our own naming (ETC2) is the GLES backend's alone; SDL's function would index // its tables past the end with it. if ((format >= RGPU_TEXTUREFORMAT_PRIVATE_FIRST) && (_api != RENDER_GLES)) { return false; } return _backend->textureSupportsFormat(device, format, type, usage); } bool rgpuTextureSupportsSampleCount(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUSampleCount sampleCount) { return _backend->textureSupportsSampleCount(device, format, sampleCount); } void rgpuUnmapTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer) { _backend->unmapTransferBuffer(device, transferBuffer); } void rgpuUploadToBuffer(SDL_GPUCopyPass *copyPass, const SDL_GPUTransferBufferLocation *source, const SDL_GPUBufferRegion *destination, bool cycle) { _backend->uploadToBuffer(copyPass, source, destination, cycle); } void rgpuUploadToTexture(SDL_GPUCopyPass *copyPass, const SDL_GPUTextureTransferInfo *source, const SDL_GPUTextureRegion *destination, bool cycle) { _backend->uploadToTexture(copyPass, source, destination, cycle); }