diff --git a/CHANGELOG b/CHANGELOG index c75bf0c36..bf0b44099 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1245,6 +1245,27 @@ SINGE 2.10 New Features ------------ +- Texture samplers now walk the whole mipmap chain. SDL hands max_lod + straight to Vulkan's maxLod and Direct3D 12's MaxLOD, and the scene + and GUI left it at the zero a memset gives, which clamps every lookup + to mip level 0. Mipmapping was therefore off on every backend: + distant surfaces aliased, the 8x anisotropy did far less than it + looked, and the sky cube could not pick a level by roughness. + +- An OpenGL ES 3.1 backend for the 3D scene and the GUI. SDL_GPU has + Vulkan, Direct3D 12 and Metal and no OpenGL of any kind, which left the + cheap Linux handhelds and the Raspberry Pi with 2D only: their Mali and + VideoCore parts have a mature GLES driver and either no Vulkan or an + immature one. The subset of SDL_GPU the scene and the GUI use is now + behind one table of function pointers with two implementations, so both + files stay the single description of what Singe draws, and the backend is + chosen at run time -- one aarch64 binary serves a board with Vulkan and one + without. The ES shaders come from the same HLSL through SPIRV-Cross, and + the GL entry points are loaded through SDL rather than linked, so a machine + with no GLES driver simply keeps its 2D renderer. The Raspberry Pi 4 + remains the minimum for 3D: the Pi 3 has only OpenGL ES 2.0, which cannot + express the scene's skinning or morph targets. + - spriteDraw() now has two more forms. In addition to being able to draw regular sprites and stretched sprites it can now draw both using the sprite's center as the anchor instead of the upper left. This is highly diff --git a/CMakeLists.txt b/CMakeLists.txt index c71e75aea..b7018fb3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -327,6 +327,12 @@ set(SINGE_SOURCE src/midiIo.h thirdparty/tinysoundfont/tsf.h thirdparty/tinysoundfont/tml.h + src/render.c + src/render.h + src/renderGles.c + src/renderGlesApi.h + src/renderGlesLoad.c + src/renderGpu.c src/scene.c src/scene.h src/singe.c @@ -755,10 +761,19 @@ if(NOT SINGE_SHADERCROSS) message(FATAL_ERROR "SDL_shadercross is needed to compile the scene shaders; build through the superbuild (cmake --preset ...) or set SINGE_SHADERCROSS.") endif() endif() +# SPIRV-Cross turns the same SPIR-V into GLSL ES for the GLES backend (PLAN.md section 56). Unlike +# shadercross this one is not fatal when missing: the header then carries null ES strings, the GLES +# backend declines to start, and every other platform builds exactly as before. +if(NOT SINGE_SPIRV_CROSS) + find_program(SINGE_SPIRV_CROSS spirv-cross) +endif() +if(NOT SINGE_SPIRV_CROSS) + message(WARNING "spirv-cross was not found; this build will have no OpenGL ES shaders and so no GLES backend.") +endif() set(shaderHeader ${CMAKE_BINARY_DIR}/generated/shaders/sceneShaders.h) add_custom_command( OUTPUT ${shaderHeader} - COMMAND ${CMAKE_COMMAND} -DSHADERCROSS=${SINGE_SHADERCROSS} -DSOURCE=${CMAKE_SOURCE_DIR}/src/shaders/scene.hlsl -DOUTPUT=${shaderHeader} -P ${CMAKE_SOURCE_DIR}/cmake/shaderHeader.cmake + COMMAND ${CMAKE_COMMAND} -DSHADERCROSS=${SINGE_SHADERCROSS} -DSPIRVCROSS=${SINGE_SPIRV_CROSS} -DSOURCE=${CMAKE_SOURCE_DIR}/src/shaders/scene.hlsl -DOUTPUT=${shaderHeader} -P ${CMAKE_SOURCE_DIR}/cmake/shaderHeader.cmake DEPENDS ${CMAKE_SOURCE_DIR}/src/shaders/scene.hlsl ${CMAKE_SOURCE_DIR}/src/sceneShared.h ${CMAKE_SOURCE_DIR}/cmake/shaderHeader.cmake COMMENT "Compiling the scene shaders" ) @@ -769,7 +784,7 @@ set(guiShaderHeader ${CMAKE_BINARY_DIR}/generated/shaders/guiShaders.h) set(guiShaderEntries "guiVertex:vertex;guiFragmentColor:fragment;guiFragmentTexture:fragment;guiFragmentFilter:fragment;guiFragmentBlur:fragment;guiFragmentColorMatrix:fragment;guiFragmentGradient:fragment") add_custom_command( OUTPUT ${guiShaderHeader} - COMMAND ${CMAKE_COMMAND} -DSHADERCROSS=${SINGE_SHADERCROSS} -DSOURCE=${CMAKE_SOURCE_DIR}/src/shaders/gui.hlsl -DOUTPUT=${guiShaderHeader} "-DENTRIES=${guiShaderEntries}" -DPREFIX=guiShader -DTYPE=GuiShaderT -P ${CMAKE_SOURCE_DIR}/cmake/shaderHeader.cmake + COMMAND ${CMAKE_COMMAND} -DSHADERCROSS=${SINGE_SHADERCROSS} -DSPIRVCROSS=${SINGE_SPIRV_CROSS} -DSOURCE=${CMAKE_SOURCE_DIR}/src/shaders/gui.hlsl -DOUTPUT=${guiShaderHeader} "-DENTRIES=${guiShaderEntries}" -DPREFIX=guiShader -DTYPE=GuiShaderT -P ${CMAKE_SOURCE_DIR}/cmake/shaderHeader.cmake DEPENDS ${CMAKE_SOURCE_DIR}/src/shaders/gui.hlsl ${CMAKE_SOURCE_DIR}/cmake/shaderHeader.cmake COMMENT "Compiling the GUI shaders" VERBATIM diff --git a/INSTALL b/INSTALL index eadbb3a74..2d6278eb1 100644 --- a/INSTALL +++ b/INSTALL @@ -122,7 +122,8 @@ newer, the 64-bit Raspberry Pi OS among them. It is not Pi specific: the decoder it builds talks to any V4L2 memory-to-memory device, so one binary also serves Amlogic, Exynos, Qualcomm and other boards whose kernel offers one, and it carries Rockchip's own decoders besides. 3D games need a Pi 4 -or later; the Pi 3 has no Vulkan driver and plays 2D games only. The build +or later, which has OpenGL ES 3.1 (and Vulkan); the Pi 3 has only OpenGL +ES 2.0 and plays 2D games only. The build uses zig. The platform headers and libraries it links against are Debian bookworm arm64 packages listed in cmake/zig/arm64Packages.cmake, fetched from snapshot.debian.org and unpacked with dpkg-deb into diff --git a/cmake/hostTools.cmake b/cmake/hostTools.cmake index a6075998f..63579950a 100644 --- a/cmake/hostTools.cmake +++ b/cmake/hostTools.cmake @@ -58,9 +58,13 @@ endfunction() # SDL3 for the host, shared, with nothing SDL_shadercross's tool does not use. singeHostProject(hostSDL3 ${SB_THIRDPARTY}/SDL3 "" "-DSDL_SHARED=ON;-DSDL_STATIC=OFF;-DSDL_TESTS=OFF;-DSDL_EXAMPLES=OFF;-DSDL_AUDIO=OFF;-DSDL_VIDEO=OFF;-DSDL_GPU=OFF;-DSDL_RENDER=OFF;-DSDL_CAMERA=OFF;-DSDL_JOYSTICK=OFF;-DSDL_HAPTIC=OFF;-DSDL_HIDAPI=OFF;-DSDL_POWER=OFF;-DSDL_SENSOR=OFF;-DSDL_DIALOG=OFF;-DSDL_UNIX_CONSOLE_BUILD=ON") -# SPIRV-Cross as the shared C library SDL_shadercross looks for. -singeHostProject(spirvCross ${SB_THIRDPARTY}/SPIRV-Cross "" "-DSPIRV_CROSS_SHARED=ON;-DSPIRV_CROSS_STATIC=OFF;-DSPIRV_CROSS_CLI=OFF;-DSPIRV_CROSS_ENABLE_TESTS=OFF") +# SPIRV-Cross as the shared C library SDL_shadercross looks for, and as the command line tool that +# turns SPIR-V into GLSL ES for the GLES backend -- SDL_shadercross emits DXBC, DXIL, MSL, SPIRV, +# HLSL and JSON and no GLSL of any kind, so the ES shaders come from this one (PLAN.md section 56). +# The CLI needs the static library as well as the shared one. +singeHostProject(spirvCross ${SB_THIRDPARTY}/SPIRV-Cross "" "-DSPIRV_CROSS_SHARED=ON;-DSPIRV_CROSS_STATIC=ON;-DSPIRV_CROSS_CLI=ON;-DSPIRV_CROSS_ENABLE_TESTS=OFF") # SDL_shadercross, unvendored: SPIRV-Cross and SDL3 from the host prefix, DXC from its download. singeHostProject(shadercross ${SB_THIRDPARTY}/SDL_shadercross "hostSDL3;spirvCross" "-DSDLSHADERCROSS_VENDORED=OFF;-DSDLSHADERCROSS_DXC=ON;-DSDLSHADERCROSS_SHARED=OFF;-DSDLSHADERCROSS_STATIC=ON;-DSDLSHADERCROSS_CLI=ON;-DSDLSHADERCROSS_INSTALL=ON;-DSDLSHADERCROSS_INSTALL_CPACK=OFF;-DCMAKE_PREFIX_PATH=${hostPrefix}|${hostDxc};-DSDL3_DIR=${hostPrefix}/lib/cmake/SDL3") set(SINGE_SHADERCROSS ${hostPrefix}/bin/shadercross) +set(SINGE_SPIRV_CROSS ${hostPrefix}/bin/spirv-cross) diff --git a/cmake/shaderHeader.cmake b/cmake/shaderHeader.cmake index 54df094ad..86ad9b9e4 100644 --- a/cmake/shaderHeader.cmake +++ b/cmake/shaderHeader.cmake @@ -1,7 +1,11 @@ -# Compiles src/shaders/scene.hlsl (which includes src/sceneShared.h) into a C header of SPIR-V, DXIL and MSL blobs with -# SDL_shadercross. Run by the build as -# cmake -DSHADERCROSS= -DSOURCE= -DOUTPUT= -P shaderHeader.cmake +# Compiles src/shaders/scene.hlsl (which includes src/sceneShared.h) into a C header of SPIR-V, DXIL, MSL and GLSL ES +# with SDL_shadercross and SPIRV-Cross. Run by the build as +# cmake -DSHADERCROSS= -DSPIRVCROSS= -DSOURCE= -DOUTPUT= -P shaderHeader.cmake # so the header is generated into the build tree like the icon and the other embedded files. +# +# SDL_shadercross has no GLSL destination (DXBC, DXIL, MSL, SPIRV, HLSL, JSON), so the ES form is a second step over the +# SPIR-V it already produced: one pipeline, four outputs. SPIRVCROSS may be empty, and then the ES strings are null and +# the GLES backend refuses to start -- which is what a build without the host tool should do rather than fail. # Invoked in script mode: -DSHADERCROSS -DSOURCE= -DOUTPUT=
-DENTRIES= # -DPREFIX= -DTYPE=. ENTRIES, PREFIX and TYPE default to the scene's. @@ -19,6 +23,28 @@ string(TOUPPER "${PREFIX}" guard) string(REPLACE "SHADER" "_SHADERS_H" guard "${guard}") set(entries ${ENTRIES}) set(formats SPIRV DXIL MSL) + +# GLSL ES 3.10 for the GLES backend. Three switches matter and each is load bearing: +# --es --version 310 ES 3.1, the floor the scene needs; its storage buffers are ES 3.1 only. +# --combined-samplers-inherit-bindings +# HLSL's separate Texture2D and SamplerState have no ES equivalent, so SPIRV-Cross folds +# each pair into one sampler and keeps the HLSL t-register as the binding, which is the +# slot the engine already binds by. +# --remove-unused-variables Without it every entry point declares every cbuffer and both storage buffers in the file: +# fragmentMain came out with twelve blocks instead of two, and a fragment shader that +# merely declares an unused SSBO can fail to link where MAX_FRAGMENT_SHADER_STORAGE_BLOCKS +# is zero, which ES 3.1 permits. +# --flip-vert-y SDL_GPU puts the framebuffer origin at the top left, as Vulkan, D3D12 and Metal do, and +# GL puts it at the bottom left. Without this every render to a texture arrives upside +# down -- the GUI came out mirrored the first time this ran. Inverting gl_Position.y is +# the same fix as a negative viewport height, and it also reverses triangle winding, which +# renderGles.c undoes by flipping the front face it sets. +# --fixup-clipspace HLSL puts clip-space Z in [0, w] and GL in [-w, w], and GL then stores (z/w + 1) / 2. +# Without the rewrite a depth written as d lands in the buffer as (d + 1) / 2, while the +# shader's own shadow comparison still computes d -- so everything in the near half of a +# light's range read as lit. Sponza showed it as a 2.6x too bright mid-shadow band with +# correct highlights and correct deep shadow, which is what a half-range comparison does. +set(esslFlags --es --version 310 --combined-samplers-inherit-bindings --remove-unused-variables --flip-vert-y --fixup-clipspace) get_filename_component(sourceDir ${SOURCE} DIRECTORY) get_filename_component(includeDir ${sourceDir} DIRECTORY) get_filename_component(outputDir ${OUTPUT} DIRECTORY) @@ -26,10 +52,10 @@ set(work ${outputDir}/work) file(MAKE_DIRECTORY ${work}) set(header "// Generated from ${sourceName} by cmake/shaderHeader.cmake with SDL_shadercross; do not edit.\n") -string(APPEND header "// SPIR-V for Vulkan, DXIL for Direct3D 12, MSL for Metal, one set per entry point.\n\n") -string(APPEND header "#ifndef ${guard}\n#define ${guard}\n\n#include \n\n") +string(APPEND header "// SPIR-V for Vulkan, DXIL for Direct3D 12, MSL for Metal, GLSL ES for OpenGL ES, one set per entry point.\n\n") +string(APPEND header "#ifndef ${guard}\n#define ${guard}\n\n#include \n#include \n\n") string(REGEX REPLACE "T$" "S" structName "${TYPE}") -string(APPEND header "typedef struct ${structName} {\n\tconst char *entryPoint;\n\tconst unsigned char *spirv;\n\tsize_t spirvSize;\n\tconst unsigned char *dxil;\n\tsize_t dxilSize;\n\tconst unsigned char *msl;\n\tsize_t mslSize;\n} ${TYPE};\n\n") +string(APPEND header "typedef struct ${structName} {\n\tconst char *entryPoint;\n\tconst unsigned char *spirv;\n\tsize_t spirvSize;\n\tconst unsigned char *dxil;\n\tsize_t dxilSize;\n\tconst unsigned char *msl;\n\tsize_t mslSize;\n\tconst char *essl; // GLSL ES 3.10 source, or NULL when the host tool was absent\n} ${TYPE};\n\n") foreach(entry IN LISTS entries) string(REPLACE ":" ";" parts ${entry}) list(GET parts 0 name) @@ -45,10 +71,31 @@ foreach(entry IN LISTS entries) string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," bytes "${hex}") string(APPEND header "static const unsigned char _${name}${format}[] = {\n\t${bytes}\n};\n\n") endforeach() + # The ES form, cross compiled from the SPIR-V above. Emitted as a C string rather than bytes because that is what + # glShaderSource takes; the escaping is only quotes and backslashes, neither of which GLSL ES has a use for. + set(esslSymbol "NULL") + if(SPIRVCROSS) + set(essl ${work}/${name}.essl) + execute_process(COMMAND ${SPIRVCROSS} ${esslFlags} ${work}/${name}.spirv --output ${essl} RESULT_VARIABLE code OUTPUT_VARIABLE out ERROR_VARIABLE err) + if(NOT code EQUAL 0) + message(FATAL_ERROR "Shader ${name} (GLSL ES) failed to cross compile:\n${out}\n${err}") + endif() + file(READ ${essl} esslText) + # SPIRV-Cross defaults an ES fragment shader to "precision mediump float", and a struct + # declared inside a uniform block carries no qualifier of its own, so Light's members inherit + # mediump -- fp16 on a real tiler. The scene's own values do not fit that: a camera or a + # light tens of units from the origin loses whole units of position. Ask for highp instead. + string(REPLACE "precision mediump float;" "precision highp float;" esslText "${esslText}") + string(REPLACE "\\" "\\\\" esslText "${esslText}") + string(REPLACE "\"" "\\\"" esslText "${esslText}") + string(REPLACE "\n" "\\n\"\n\t\"" esslText "${esslText}") + string(APPEND header "static const char _${name}ESSL[] =\n\t\"${esslText}\";\n\n") + set(esslSymbol "_${name}ESSL") + endif() string(SUBSTRING ${name} 0 1 initial) string(SUBSTRING ${name} 1 -1 rest) string(TOUPPER ${initial} initial) - string(APPEND header "static const ${TYPE} ${PREFIX}${initial}${rest} = { \"${name}\", _${name}SPIRV, sizeof(_${name}SPIRV), _${name}DXIL, sizeof(_${name}DXIL), _${name}MSL, sizeof(_${name}MSL) };\n\n") + string(APPEND header "static const ${TYPE} ${PREFIX}${initial}${rest} = { \"${name}\", _${name}SPIRV, sizeof(_${name}SPIRV), _${name}DXIL, sizeof(_${name}DXIL), _${name}MSL, sizeof(_${name}MSL), ${esslSymbol} };\n\n") endforeach() string(APPEND header "#endif\n") file(WRITE ${OUTPUT} "${header}") diff --git a/docs/Manual.adoc b/docs/Manual.adoc index 336690102..4d5cfd577 100644 --- a/docs/Manual.adoc +++ b/docs/Manual.adoc @@ -840,10 +840,13 @@ do not allow you to use stereo, you can use Virtual Audio Cable to fix this: *Does 3D work on a Raspberry Pi?* -On a Raspberry Pi 4 or later, yes: they have the Vulkan driver the 3D scene -needs. The Pi 3 and earlier do not, so they run 2D games (2D physics included) -exactly as before and refuse the first 3D call. The Pi 4 is the minimum for -any game that uses the 3D Scenes chapter. +On a Raspberry Pi 4 or later, yes. They have both a Vulkan driver and OpenGL +ES 3.1, and Singe uses whichever it finds. The Pi 3 has only OpenGL ES 2.0, +which cannot express the scene's skinning or morph targets, so it runs 2D games +(2D physics included) exactly as before and refuses the first 3D call. The Pi 4 +is the minimum for any game that uses the 3D Scenes chapter. The same applies to +the cheap handhelds: a Mali-G31 or G610 has the ES 3.1 the scene needs, whether +or not it has Vulkan. *Why does my audio stutter on the Raspberry Pi?* @@ -2557,11 +2560,15 @@ end ==== Performance and Requirements The scene needs a GPU that speaks Vulkan (Linux), Direct3D 12 (Windows 10 -and later) or Metal (macOS). The Raspberry Pi 4 is the minimum Pi for 3D: -it and every later model have a Vulkan driver, the Pi 3 and earlier do not. -On a machine without a suitable GPU, 2D games run exactly as before (2D -physics included) and the first 3D call ends the game with an error naming -the problem. +and later), Metal (macOS) or OpenGL ES 3.1. The ES backend is what brings in +the cheap Linux handhelds and the Raspberry Pi, whose Mali and VideoCore parts +have a mature GLES driver and either no Vulkan at all or an immature one; it is +chosen automatically when none of the other three is available, and it draws +the same picture from the same shaders. The Raspberry Pi 4 remains the minimum +Pi for 3D: the Pi 3 has only OpenGL ES 2.0, which cannot express the scene's +skinning or morph targets. On a machine without any of them, 2D games run +exactly as before (2D physics included) and the first 3D call ends the game +with an error naming the problem. Meshes outside the camera's view are skipped, and copies of the same mesh with the same material draw as one instanced call, so a forest of one tree diff --git a/src/gui.cpp b/src/gui.cpp index 439d1772c..288aabadc 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -39,6 +39,7 @@ #include #include "gui.h" #include "guiRender.h" +#include "render.h" extern "C" { #include "util.h" #include "vfs.h" @@ -459,10 +460,10 @@ static void _releaseContext(GuiContextT *context) { SDL_DestroyTexture(context->wrap); } if (context->target != nullptr) { - SDL_ReleaseGPUTexture(_device, context->target); + rgpuReleaseTexture(_device, context->target); } if (context->stencil != nullptr) { - SDL_ReleaseGPUTexture(_device, context->stencil); + rgpuReleaseTexture(_device, context->stencil); } _render->ReleaseLayers((uint32_t)context->width, (uint32_t)context->height); memset(context, 0, sizeof(GuiContextT)); @@ -478,12 +479,12 @@ static void _renderContext(GuiContextT *context, SDL_GPUCommandBuffer *commands) target.load_op = SDL_GPU_LOADOP_CLEAR; target.store_op = SDL_GPU_STOREOP_STORE; target.clear_color = { 0.0f, 0.0f, 0.0f, 0.0f }; - pass = SDL_BeginGPURenderPass(commands, &target, 1, nullptr); + pass = rgpuBeginRenderPass(commands, &target, 1, nullptr); if (pass == nullptr) { utilTrace("Gui: %s", SDL_GetError()); return; } - SDL_EndGPURenderPass(pass); + rgpuEndRenderPass(pass); _render->BeginFrame(commands, context->target, context->stencil, (uint32_t)context->width, (uint32_t)context->height); context->context->Render(); _render->EndFrame(); @@ -666,7 +667,7 @@ bool guiInit(SDL_GPUDevice *device, SDL_Renderer *renderer, SDL_Window *window) utilTrace("Gui: no GPU device; the GUI is unavailable"); return false; } - _targetFormat = SDL_GetGPUSwapchainTextureFormat(device, window); + _targetFormat = rgpuGetSwapchainTextureFormat(device, window); _wrapFormat = _pixelFormatOf(_targetFormat); if (_wrapFormat == SDL_PIXELFORMAT_UNKNOWN) { utilTrace("Gui: unsupported swapchain format %d; the GUI is unavailable", (int32_t)_targetFormat); @@ -838,7 +839,6 @@ bool guiMouseWheel(int32_t gui, float delta) { int32_t guiNew(int32_t width, int32_t height) { GuiContextT *context = nullptr; SDL_GPUTextureCreateInfo info = {}; - SDL_PropertiesID props = 0; int32_t slot = GUI_NO_HANDLE; int32_t i = 0; @@ -870,7 +870,7 @@ int32_t guiNew(int32_t width, int32_t height) { info.layer_count_or_depth = 1; info.num_levels = 1; info.sample_count = SDL_GPU_SAMPLECOUNT_1; - context->target = SDL_CreateGPUTexture(_device, &info); + context->target = rgpuCreateTexture(_device, &info); if (context->target == nullptr) { _setError("%s", SDL_GetError()); _releaseContext(context); @@ -881,21 +881,14 @@ int32_t guiNew(int32_t width, int32_t height) { if (_render->StencilFormat() != SDL_GPU_TEXTUREFORMAT_INVALID) { info.format = _render->StencilFormat(); info.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET; - context->stencil = SDL_CreateGPUTexture(_device, &info); + context->stencil = rgpuCreateTexture(_device, &info); if (context->stencil == nullptr) { _setError("%s", SDL_GetError()); _releaseContext(context); return GUI_NO_HANDLE; } } - props = SDL_CreateProperties(); - SDL_SetPointerProperty(props, SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_POINTER, context->target); - SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, _wrapFormat); - 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); - context->wrap = SDL_CreateTextureWithProperties(_renderer, props); - SDL_DestroyProperties(props); + context->wrap = renderWrapTexture(_renderer, context->target, (SDL_PixelFormat)_wrapFormat, width, height); if (context->wrap == nullptr) { _setError("%s", SDL_GetError()); _releaseContext(context); @@ -1024,7 +1017,7 @@ void guiUpdate(double seconds) { } } _freeDeadListeners(); - commands = SDL_AcquireGPUCommandBuffer(_device); + commands = rgpuAcquireCommandBuffer(_device); if (commands == nullptr) { utilTrace("Gui: %s", SDL_GetError()); return; @@ -1036,7 +1029,7 @@ void guiUpdate(double seconds) { } } _render->GetStats(&_stats); - SDL_SubmitGPUCommandBuffer(commands); + rgpuSubmitCommandBuffer(commands); } diff --git a/src/guiRender.cpp b/src/guiRender.cpp index fd6050f17..eafb32150 100644 --- a/src/guiRender.cpp +++ b/src/guiRender.cpp @@ -69,6 +69,7 @@ extern "C" { #include "decode.h" } #include "guiRender.h" +#include "render.h" #include "shaders/guiShaders.h" #define BYTES_PER_PIXEL 4 // RGBA8, as RmlUi hands textures over and as the layers are @@ -82,6 +83,7 @@ extern "C" { #define UNIFORM_TRANSFORM 0 #define UNIFORM_TRANSLATE 1 #define PASS_UNIFORMS 1 // The filter, blur and colour matrix shaders' one block each +#define GUI_SAMPLER_MAX_LOD 1000.0f // "No clamp": walk the whole mipmap chain #define FILTER_SAMPLERS 2 // guiFragmentFilter: the source and the mask image #define SCREEN_QUAD_VERTICES 4 #define SCREEN_QUAD_INDICES 6 @@ -185,7 +187,7 @@ static void _blurWeights(float sigma, float *weights) { // A shader from the generated blobs, in whichever format the device takes (as the scene does). static SDL_GPUShader *_createShader(SDL_GPUDevice *device, const GuiShaderT *shader, SDL_GPUShaderStage stage, uint32_t samplers, uint32_t uniforms) { SDL_GPUShaderCreateInfo info; - SDL_GPUShaderFormat formats = SDL_GetGPUShaderFormats(device); + SDL_GPUShaderFormat formats = rgpuGetShaderFormats(device); SDL_GPUShader *result = nullptr; memset(&info, 0, sizeof(info)); @@ -201,15 +203,19 @@ static SDL_GPUShader *_createShader(SDL_GPUDevice *device, const GuiShaderT *sha info.code = shader->msl; info.code_size = shader->mslSize; info.format = SDL_GPU_SHADERFORMAT_MSL; + } else if (formats & RGPU_SHADERFORMAT_ESSL) { + info.code = (const Uint8 *)shader->essl; + info.code_size = shader->essl != nullptr ? SDL_strlen(shader->essl) : 0; + info.format = RGPU_SHADERFORMAT_ESSL; } else { - Rml::Log::Message(Rml::Log::LT_ERROR, "the GPU device accepts none of SPIR-V, DXIL or MSL"); + Rml::Log::Message(Rml::Log::LT_ERROR, "the device accepts none of SPIR-V, DXIL, MSL or GLSL ES"); return nullptr; } info.entrypoint = shader->entryPoint; info.stage = stage; info.num_samplers = samplers; info.num_uniform_buffers = uniforms; - result = SDL_CreateGPUShader(device, &info); + result = rgpuCreateShader(device, &info); if (result == nullptr) { Rml::Log::Message(Rml::Log::LT_ERROR, "shader %s: %s", shader->entryPoint, SDL_GetError()); } @@ -341,7 +347,7 @@ void GuiRenderT::ReleaseShaderCommandT::Run(GuiRenderT &render) { void GuiRenderT::ReleaseTextureCommandT::Run(GuiRenderT &render) { - SDL_ReleaseGPUTexture(render.device, reinterpret_cast(handle)); + rgpuReleaseTexture(render.device, reinterpret_cast(handle)); } @@ -644,11 +650,11 @@ void GuiRenderT::EndFrame() { } commands.clear(); if (copyPass != nullptr) { - SDL_EndGPUCopyPass(copyPass); + rgpuEndCopyPass(copyPass); copyPass = nullptr; } if (renderPass != nullptr) { - SDL_EndGPURenderPass(renderPass); + rgpuEndRenderPass(renderPass); renderPass = nullptr; } // The stack is balanced by contract; anything left is returned to the pool. @@ -674,19 +680,19 @@ Rml::TextureHandle GuiRenderT::GenerateTexture(Rml::Span source transferInfo.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; transferInfo.size = size; - transfer = SDL_CreateGPUTransferBuffer(device, &transferInfo); + transfer = rgpuCreateTransferBuffer(device, &transferInfo); if (transfer == nullptr) { Rml::Log::Message(Rml::Log::LT_ERROR, "failed to create transfer buffer: %s", SDL_GetError()); return 0; } - destination = SDL_MapGPUTransferBuffer(device, transfer, false); + destination = rgpuMapTransferBuffer(device, transfer, false); if (destination == nullptr) { Rml::Log::Message(Rml::Log::LT_ERROR, "failed to map transfer buffer: %s", SDL_GetError()); - SDL_ReleaseGPUTransferBuffer(device, transfer); + rgpuReleaseTransferBuffer(device, transfer); return 0; } memcpy(destination, source.data(), size); - SDL_UnmapGPUTransferBuffer(device, transfer); + rgpuUnmapTransferBuffer(device, transfer); textureInfo.type = SDL_GPU_TEXTURETYPE_2D; textureInfo.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER; textureInfo.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; @@ -694,10 +700,10 @@ Rml::TextureHandle GuiRenderT::GenerateTexture(Rml::Span source textureInfo.height = (Uint32)sourceDimensions.y; textureInfo.layer_count_or_depth = 1; textureInfo.num_levels = 1; - texture = SDL_CreateGPUTexture(device, &textureInfo); + texture = rgpuCreateTexture(device, &textureInfo); if (texture == nullptr) { Rml::Log::Message(Rml::Log::LT_ERROR, "failed to create texture: %s", SDL_GetError()); - SDL_ReleaseGPUTransferBuffer(device, transfer); + rgpuReleaseTransferBuffer(device, transfer); return 0; } upload.transfer_buffer = transfer; @@ -705,25 +711,25 @@ Rml::TextureHandle GuiRenderT::GenerateTexture(Rml::Span source region.w = (Uint32)sourceDimensions.x; region.h = (Uint32)sourceDimensions.y; region.d = 1; - uploadBuffer = SDL_AcquireGPUCommandBuffer(device); + uploadBuffer = rgpuAcquireCommandBuffer(device); if (uploadBuffer == nullptr) { Rml::Log::Message(Rml::Log::LT_ERROR, "failed to acquire command buffer: %s", SDL_GetError()); - SDL_ReleaseGPUTransferBuffer(device, transfer); - SDL_ReleaseGPUTexture(device, texture); + rgpuReleaseTransferBuffer(device, transfer); + rgpuReleaseTexture(device, texture); return 0; } - pass = SDL_BeginGPUCopyPass(uploadBuffer); + pass = rgpuBeginCopyPass(uploadBuffer); if (pass == nullptr) { Rml::Log::Message(Rml::Log::LT_ERROR, "failed to begin copy pass: %s", SDL_GetError()); - SDL_ReleaseGPUTransferBuffer(device, transfer); - SDL_ReleaseGPUTexture(device, texture); - SDL_CancelGPUCommandBuffer(uploadBuffer); + rgpuReleaseTransferBuffer(device, transfer); + rgpuReleaseTexture(device, texture); + rgpuCancelCommandBuffer(uploadBuffer); return 0; } - SDL_UploadToGPUTexture(pass, &upload, ®ion, false); - SDL_ReleaseGPUTransferBuffer(device, transfer); - SDL_EndGPUCopyPass(pass); - SDL_SubmitGPUCommandBuffer(uploadBuffer); + rgpuUploadToTexture(pass, &upload, ®ion, false); + rgpuReleaseTransferBuffer(device, transfer); + rgpuEndCopyPass(pass); + rgpuSubmitCommandBuffer(uploadBuffer); return reinterpret_cast(texture); } @@ -747,7 +753,7 @@ GuiRenderT::GuiRenderT(SDL_GPUDevice *gpuDevice, SDL_Window *gpuWindow) { linearSampler = nullptr; pointSampler = nullptr; linearClampSampler = nullptr; - targetFormat = SDL_GetGPUSwapchainTextureFormat(device, window); + targetFormat = rgpuGetSwapchainTextureFormat(device, window); stencilFormat = SDL_GPU_TEXTUREFORMAT_INVALID; screenQuad = nullptr; recordDepth = 0; @@ -771,27 +777,30 @@ GuiRenderT::GuiRenderT(SDL_GPUDevice *gpuDevice, SDL_Window *gpuWindow) { memset(&stats, 0, sizeof(stats)); memset(pipelines, 0, sizeof(pipelines)); for (i = 0; i < SDL_arraysize(_stencilFormats); i++) { - if (SDL_GPUTextureSupportsFormat(device, _stencilFormats[i], SDL_GPU_TEXTURETYPE_2D, SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) { + if (rgpuTextureSupportsFormat(device, _stencilFormats[i], SDL_GPU_TEXTURETYPE_2D, SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) { stencilFormat = _stencilFormats[i]; break; } } createPipelines(); + // See scene.c: a zero max_lod clamps every lookup to mip level 0, because SDL passes it through + // to Vulkan and D3D12 unchanged. + info.max_lod = GUI_SAMPLER_MAX_LOD; info.min_filter = SDL_GPU_FILTER_LINEAR; info.mag_filter = SDL_GPU_FILTER_LINEAR; info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR; info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_REPEAT; info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_REPEAT; info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_REPEAT; - linearSampler = SDL_CreateGPUSampler(device, &info); + linearSampler = rgpuCreateSampler(device, &info); info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; - linearClampSampler = SDL_CreateGPUSampler(device, &info); + linearClampSampler = rgpuCreateSampler(device, &info); info.min_filter = SDL_GPU_FILTER_NEAREST; info.mag_filter = SDL_GPU_FILTER_NEAREST; info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST; - pointSampler = SDL_CreateGPUSampler(device, &info); + pointSampler = rgpuCreateSampler(device, &info); if ((linearSampler == nullptr) || (pointSampler == nullptr) || (linearClampSampler == nullptr)) { Rml::Log::Message(Rml::Log::LT_ERROR, "failed to create sampler: %s", SDL_GetError()); } @@ -904,7 +913,7 @@ void GuiRenderT::ReleaseLayers(uint32_t width, uint32_t height) { TargetT *pooled = targets[i].get(); if (!pooled->inUse && (pooled->width == width) && (pooled->height == height)) { - SDL_ReleaseGPUTexture(device, pooled->texture); + rgpuReleaseTexture(device, pooled->texture); } else { targets[kept] = std::move(targets[i]); kept++; @@ -983,7 +992,7 @@ Rml::TextureHandle GuiRenderT::SaveLayerAsTexture() { info.height = (Uint32)region.h; info.layer_count_or_depth = 1; info.num_levels = 1; - texture = SDL_CreateGPUTexture(device, &info); + texture = rgpuCreateTexture(device, &info); if (texture == nullptr) { Rml::Log::Message(Rml::Log::LT_ERROR, "failed to create layer texture: %s", SDL_GetError()); return 0; @@ -1019,22 +1028,22 @@ void GuiRenderT::Shutdown() { releaseGeometry(screenQuad); screenQuad = nullptr; for (Rml::UniquePtr &buffer : buffers) { - SDL_ReleaseGPUTransferBuffer(device, buffer->transfer); - SDL_ReleaseGPUBuffer(device, buffer->buffer); + rgpuReleaseTransferBuffer(device, buffer->transfer); + rgpuReleaseBuffer(device, buffer->buffer); } buffers.clear(); for (Rml::UniquePtr &pooled : targets) { - SDL_ReleaseGPUTexture(device, pooled->texture); + rgpuReleaseTexture(device, pooled->texture); } targets.clear(); - SDL_ReleaseGPUSampler(device, linearSampler); - SDL_ReleaseGPUSampler(device, pointSampler); - SDL_ReleaseGPUSampler(device, linearClampSampler); + rgpuReleaseSampler(device, linearSampler); + rgpuReleaseSampler(device, pointSampler); + rgpuReleaseSampler(device, linearClampSampler); linearSampler = nullptr; pointSampler = nullptr; linearClampSampler = nullptr; for (i = 0; i < GUI_PIPELINE_COUNT; i++) { - SDL_ReleaseGPUGraphicsPipeline(device, pipelines[i]); + rgpuReleaseGraphicsPipeline(device, pipelines[i]); pipelines[i] = nullptr; } } @@ -1066,7 +1075,7 @@ GuiRenderT::TargetT *GuiRenderT::acquireTarget() { info.layer_count_or_depth = 1; info.num_levels = 1; made = Rml::MakeUnique(); - made->texture = SDL_CreateGPUTexture(device, &info); + made->texture = rgpuCreateTexture(device, &info); made->width = targetWidth; made->height = targetHeight; made->inUse = true; @@ -1084,10 +1093,10 @@ bool GuiRenderT::beginCopyPass() { return true; } if (renderPass != nullptr) { - SDL_EndGPURenderPass(renderPass); + rgpuEndRenderPass(renderPass); renderPass = nullptr; } - copyPass = SDL_BeginGPUCopyPass(commandBuffer); + copyPass = rgpuBeginCopyPass(commandBuffer); if (copyPass == nullptr) { Rml::Log::Message(Rml::Log::LT_ERROR, "failed to begin copy pass: %s", SDL_GetError()); return false; @@ -1107,7 +1116,7 @@ bool GuiRenderT::beginRenderPass() { return true; } if (copyPass != nullptr) { - SDL_EndGPUCopyPass(copyPass); + rgpuEndCopyPass(copyPass); copyPass = nullptr; } colorInfo.texture = target; @@ -1122,7 +1131,7 @@ bool GuiRenderT::beginRenderPass() { stencilInfo.stencil_store_op = SDL_GPU_STOREOP_STORE; stencilInfo.clear_stencil = MASK_OUTSIDE; } - renderPass = SDL_BeginGPURenderPass(commandBuffer, &colorInfo, 1, (stencil != nullptr) ? &stencilInfo : nullptr); + renderPass = rgpuBeginRenderPass(commandBuffer, &colorInfo, 1, (stencil != nullptr) ? &stencilInfo : nullptr); if (renderPass == nullptr) { Rml::Log::Message(Rml::Log::LT_ERROR, "failed to begin render pass: %s", SDL_GetError()); return false; @@ -1144,11 +1153,11 @@ void GuiRenderT::blitTarget(SDL_GPUTexture *source, SDL_Rect from, SDL_GPUTextur return; } if (copyPass != nullptr) { - SDL_EndGPUCopyPass(copyPass); + rgpuEndCopyPass(copyPass); copyPass = nullptr; } if (renderPass != nullptr) { - SDL_EndGPURenderPass(renderPass); + rgpuEndRenderPass(renderPass); renderPass = nullptr; } info.source.texture = source; @@ -1163,7 +1172,7 @@ void GuiRenderT::blitTarget(SDL_GPUTexture *source, SDL_Rect from, SDL_GPUTextur info.destination.h = (Uint32)to.h; info.load_op = SDL_GPU_LOADOP_LOAD; info.filter = SDL_GPU_FILTER_LINEAR; - SDL_BlitGPUTexture(commandBuffer, &info); + rgpuBlitTexture(commandBuffer, &info); stats.filterPasses++; } @@ -1240,7 +1249,7 @@ void GuiRenderT::copyTarget(SDL_GPUTexture *source, SDL_Rect region, SDL_GPUText from.x = (Uint32)x; from.y = (Uint32)y; to.texture = destination; - SDL_CopyGPUTextureToTexture(copyPass, &from, &to, (Uint32)w, (Uint32)h, 1, false); + rgpuCopyTextureToTexture(copyPass, &from, &to, (Uint32)w, (Uint32)h, 1, false); } @@ -1269,9 +1278,9 @@ void GuiRenderT::createPipelines() { } if (!complete) { for (i = 0; i < GUI_FRAGMENT_COUNT; i++) { - SDL_ReleaseGPUShader(device, fragments[i]); + rgpuReleaseShader(device, fragments[i]); } - SDL_ReleaseGPUShader(device, vertexShader); + rgpuReleaseShader(device, vertexShader); return; } attributes[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2; @@ -1320,15 +1329,15 @@ void GuiRenderT::createPipelines() { info.depth_stencil_state.front_stencil_state = stencilOp; info.depth_stencil_state.back_stencil_state = stencilOp; info.fragment_shader = fragments[desc->fragment]; - pipelines[i] = SDL_CreateGPUGraphicsPipeline(device, &info); + pipelines[i] = rgpuCreateGraphicsPipeline(device, &info); if (pipelines[i] == nullptr) { Rml::Log::Message(Rml::Log::LT_ERROR, "failed to create pipeline %d: %s", i, SDL_GetError()); } } for (i = 0; i < GUI_FRAGMENT_COUNT; i++) { - SDL_ReleaseGPUShader(device, fragments[i]); + rgpuReleaseShader(device, fragments[i]); } - SDL_ReleaseGPUShader(device, vertexShader); + rgpuReleaseShader(device, vertexShader); } @@ -1344,28 +1353,28 @@ void GuiRenderT::drawGeometry(GeometryT *geometry, GuiPipelineE pipeline, SDL_GP if ((geometry == nullptr) || (pipelines[pipeline] == nullptr) || (textureCount > FILTER_SAMPLERS) || !beginRenderPass()) { return; } - SDL_BindGPUGraphicsPipeline(renderPass, pipelines[pipeline]); + rgpuBindGraphicsPipeline(renderPass, pipelines[pipeline]); if (textureCount > 0) { for (i = 0; i < textureCount; i++) { bindings[i].texture = textures[i]; bindings[i].sampler = sampler; } - SDL_BindGPUFragmentSamplers(renderPass, 0, bindings, textureCount); + rgpuBindFragmentSamplers(renderPass, 0, bindings, textureCount); } vertices.buffer = geometry->vertices->buffer; indices.buffer = geometry->indices->buffer; - SDL_BindGPUVertexBuffers(renderPass, 0, &vertices, 1); - SDL_BindGPUIndexBuffer(renderPass, &indices, SDL_GPU_INDEXELEMENTSIZE_32BIT); - SDL_SetGPUScissor(renderPass, &scissor); + rgpuBindVertexBuffers(renderPass, 0, &vertices, 1); + rgpuBindIndexBuffer(renderPass, &indices, SDL_GPU_INDEXELEMENTSIZE_32BIT); + rgpuSetScissor(renderPass, &scissor); if (stencil != nullptr) { - SDL_SetGPUStencilReference(renderPass, reference); + rgpuSetStencilReference(renderPass, reference); } - SDL_PushGPUVertexUniformData(commandBuffer, UNIFORM_TRANSFORM, &matrix, sizeof(matrix)); - SDL_PushGPUVertexUniformData(commandBuffer, UNIFORM_TRANSLATE, &translation, sizeof(translation)); + rgpuPushVertexUniformData(commandBuffer, UNIFORM_TRANSFORM, &matrix, sizeof(matrix)); + rgpuPushVertexUniformData(commandBuffer, UNIFORM_TRANSLATE, &translation, sizeof(translation)); if (uniforms != nullptr) { - SDL_PushGPUFragmentUniformData(commandBuffer, 0, uniforms, uniformSize); + rgpuPushFragmentUniformData(commandBuffer, 0, uniforms, uniformSize); } - SDL_DrawGPUIndexedPrimitives(renderPass, (Uint32)geometry->indexCount, 1, 0, 0, 0); + rgpuDrawIndexedPrimitives(renderPass, (Uint32)geometry->indexCount, 1, 0, 0, 0); stats.drawCalls++; if (_pipelineDescs[pipeline].mask) { stats.maskWrites++; @@ -1492,15 +1501,15 @@ GuiRenderT::BufferT *GuiRenderT::requestBuffer(int32_t capacity, SDL_GPUBufferUs transferInfo.size = (Uint32)capacity; bufferInfo.usage = usage; bufferInfo.size = (Uint32)capacity; - made->transfer = SDL_CreateGPUTransferBuffer(device, &transferInfo); - made->buffer = SDL_CreateGPUBuffer(device, &bufferInfo); + made->transfer = rgpuCreateTransferBuffer(device, &transferInfo); + made->buffer = rgpuCreateBuffer(device, &bufferInfo); made->usage = usage; made->capacity = capacity; made->inUse = false; if ((made->transfer == nullptr) || (made->buffer == nullptr)) { Rml::Log::Message(Rml::Log::LT_ERROR, "failed to create buffer(s): %s", SDL_GetError()); - SDL_ReleaseGPUTransferBuffer(device, made->transfer); - SDL_ReleaseGPUBuffer(device, made->buffer); + rgpuReleaseTransferBuffer(device, made->transfer); + rgpuReleaseBuffer(device, made->buffer); return nullptr; } return buffers.insert(first, std::move(made))->get(); @@ -1511,7 +1520,7 @@ GuiRenderT::BufferT *GuiRenderT::requestBuffer(int32_t capacity, SDL_GPUBufferUs // draw begins one on the new target. void GuiRenderT::setTarget(SDL_GPUTexture *texture, bool clear) { if ((renderPass != nullptr) && ((texture != target) || clear)) { - SDL_EndGPURenderPass(renderPass); + rgpuEndRenderPass(renderPass); renderPass = nullptr; } target = texture; @@ -1538,31 +1547,31 @@ GuiRenderT::GeometryT *GuiRenderT::uploadGeometry(const void *vertexData, uint32 delete geometry; return nullptr; } - vertexStage = SDL_MapGPUTransferBuffer(device, geometry->vertices->transfer, true); - indexStage = SDL_MapGPUTransferBuffer(device, geometry->indices->transfer, true); + vertexStage = rgpuMapTransferBuffer(device, geometry->vertices->transfer, true); + indexStage = rgpuMapTransferBuffer(device, geometry->indices->transfer, true); if ((vertexStage == nullptr) || (indexStage == nullptr)) { Rml::Log::Message(Rml::Log::LT_ERROR, "failed to map transfer buffer(s): %s", SDL_GetError()); if (vertexStage != nullptr) { - SDL_UnmapGPUTransferBuffer(device, geometry->vertices->transfer); + rgpuUnmapTransferBuffer(device, geometry->vertices->transfer); } if (indexStage != nullptr) { - SDL_UnmapGPUTransferBuffer(device, geometry->indices->transfer); + rgpuUnmapTransferBuffer(device, geometry->indices->transfer); } delete geometry; return nullptr; } memcpy(vertexStage, vertexData, vertexSize); memcpy(indexStage, indexData, indexSize); - SDL_UnmapGPUTransferBuffer(device, geometry->vertices->transfer); - SDL_UnmapGPUTransferBuffer(device, geometry->indices->transfer); + rgpuUnmapTransferBuffer(device, geometry->vertices->transfer); + rgpuUnmapTransferBuffer(device, geometry->indices->transfer); location.transfer_buffer = geometry->vertices->transfer; region.buffer = geometry->vertices->buffer; region.size = vertexSize; - SDL_UploadToGPUBuffer(copyPass, &location, ®ion, false); + rgpuUploadToBuffer(copyPass, &location, ®ion, false); location.transfer_buffer = geometry->indices->transfer; region.buffer = geometry->indices->buffer; region.size = indexSize; - SDL_UploadToGPUBuffer(copyPass, &location, ®ion, false); + rgpuUploadToBuffer(copyPass, &location, ®ion, false); geometry->indexCount = (int32_t)(indexSize / sizeof(int32_t)); geometry->vertices->inUse = true; geometry->indices->inUse = true; diff --git a/src/main.c b/src/main.c index 50e249854..d477c55d8 100644 --- a/src/main.c +++ b/src/main.c @@ -64,6 +64,7 @@ #include "singe.h" #include "pack.h" #include "vfs.h" +#include "render.h" #include "../thirdparty/ffmpeg/libavformat/avformat.h" #include "embedded.h" @@ -1249,11 +1250,11 @@ static void _launcher(const char *exeName, ConfigT *conf) { _mainTrace(conf, "Creating renderer"); device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_MSL, false, NULL); if (device == NULL) { - _mainTrace(conf, "No GPU device (%s); 3D is unavailable", SDL_GetError()); + _mainTrace(conf, "No SDL_GPU device (%s); trying OpenGL ES", SDL_GetError()); } else { renderer = SDL_CreateGPURenderer(device, window); if (renderer == NULL) { - _mainTrace(conf, "GPU renderer failed (%s); 3D is unavailable", SDL_GetError()); + _mainTrace(conf, "GPU renderer failed (%s); trying OpenGL ES", SDL_GetError()); SDL_DestroyGPUDevice(device); device = NULL; } @@ -1265,6 +1266,22 @@ static void _launcher(const char *exeName, ConfigT *conf) { } } _mainTrace(conf, "Renderer: %s", SDL_GetRendererName(renderer)); + // Tell the render layer which backend the scene and the GUI will be calling through. SDL_GPU + // when the platform has it; otherwise OpenGL ES through the context SDL_Renderer just made, + // which is the only one a Pi or a Mali handheld offers. Neither leaves 2D untouched and the + // scene and the GUI reporting themselves unavailable, as they already did on such a machine. + if (device != NULL) { + renderSelect(RENDER_GPU, &renderGpuBackend, renderer); + } else if (renderGlesStart()) { + renderSelect(RENDER_GLES, &renderGlesBackend, renderer); + device = rgpuCreateDevice(RGPU_SHADERFORMAT_ESSL, false, NULL); + if (device == NULL) { + renderSelect(RENDER_NONE, NULL, renderer); + } + } else { + renderSelect(RENDER_NONE, NULL, renderer); + } + _mainTrace(conf, "Render backend: %s", renderApiName()); // Clear screen with black SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); @@ -1308,7 +1325,7 @@ static void _launcher(const char *exeName, ConfigT *conf) { _mainTrace(conf, "Destroying renderer"); SDL_DestroyRenderer(renderer); if (device != NULL) { - SDL_DestroyGPUDevice(device); + rgpuDestroyDevice(device); } _mainTrace(conf, "Destroying window"); SDL_DestroyWindow(window); @@ -1761,7 +1778,7 @@ static void _traceHeader(const ConfigT *conf, SDL_Renderer *renderer, SDL_GPUDev utilTrace("Command: %s", (_commandLine != NULL) ? _commandLine : ""); utilTrace("OS: %s", os); utilTrace("CPU: %s", cpu); - utilTrace("Renderer: %s%s%s", SDL_GetRendererName(renderer), (device != NULL) ? ", GPU driver " : " (no GPU device; 3D is unavailable)", (device != NULL) ? SDL_GetGPUDeviceDriver(device) : ""); + utilTrace("Renderer: %s%s%s", SDL_GetRendererName(renderer), (device != NULL) ? ", 3D through " : " (no 3D and no GUI on this machine)", (device != NULL) ? rgpuGetDeviceDriver(device) : ""); utilTrace("Decoder: %s", videoGetDecoderDescription()); utilTrace("Audio: %s", audio); utilTrace("SoundFont: %s", midiSoundfont()); diff --git a/src/render.c b/src/render.c new file mode 100644 index 000000000..b7de22a2b --- /dev/null +++ b/src/render.c @@ -0,0 +1,356 @@ +/* + * + * 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(); + } +} + + +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; +} + + +void renderSelect(RenderApiT api, const RenderBackendT *backend, SDL_Renderer *renderer) { + _api = backend != NULL ? api : RENDER_NONE; + _backend = backend; + _renderer = renderer; + utilTrace("Render: %s", renderApiName()); +} + + + +// 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) { + 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); +} diff --git a/src/render.h b/src/render.h new file mode 100644 index 000000000..ce5748600 --- /dev/null +++ b/src/render.h @@ -0,0 +1,193 @@ +/* + * + * 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. + * + */ + +#ifndef RENDER_H +#define RENDER_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// The rendering backend, behind one table of function pointers. +// +// SDL_GPU has Vulkan, Direct3D 12 and Metal and no OpenGL of any kind, which leaves the cheap +// handhelds and the Pi with 2D only: they have GLES and nothing else. Rather than grow a second +// renderer beside scene.c and guiRender.cpp -- two descriptions of the same picture, drifting apart +// -- the subset of SDL_GPU those files actually use is virtualised here and implemented twice. It +// is 45 entry points, and nothing exotic is in it: no compute, no indirect draws, no manual +// barriers. See PLAN.md section 56. +// +// The types are NOT reinvented. SDL3 declares SDL_GPUTextureCreateInfo and the rest whether or not +// a backend exists on this platform, so both backends share SDL's structures and enumerations and +// only the functions are dispatched. That holds this layer to names alone. +// +// Selection is at run time, not build time: one linux-aarch64 binary ships to an RK3588 that may +// have Vulkan and to a Mali-G31 that certainly does not. + +// GLSL ES source, as a shader "format". SDL_GPUShaderFormat is a bit field and SDL uses bits 0 to 5 +// (PRIVATE, SPIRV, DXBC, DXIL, MSL, METALLIB), so this takes one well clear of them. A shader's +// code pointer is then the ES source string rather than a blob, and its size is ignored. +#define RGPU_SHADERFORMAT_ESSL (1u << 16) + + +typedef struct RenderBackendS { + SDL_GPUCommandBuffer * (*acquireCommandBuffer)(SDL_GPUDevice *device); + SDL_GPUCopyPass * (*beginCopyPass)(SDL_GPUCommandBuffer *commandBuffer); + SDL_GPURenderPass * (*beginRenderPass)(SDL_GPUCommandBuffer *commandBuffer, const SDL_GPUColorTargetInfo *colorTargetInfos, Uint32 numColorTargets, const SDL_GPUDepthStencilTargetInfo *depthStencilTargetInfo); + void (*bindFragmentSamplers)(SDL_GPURenderPass *renderPass, Uint32 firstSlot, const SDL_GPUTextureSamplerBinding *textureSamplerBindings, Uint32 numBindings); + void (*bindGraphicsPipeline)(SDL_GPURenderPass *renderPass, SDL_GPUGraphicsPipeline *graphicsPipeline); + void (*bindIndexBuffer)(SDL_GPURenderPass *renderPass, const SDL_GPUBufferBinding *binding, SDL_GPUIndexElementSize indexElementSize); + void (*bindVertexBuffers)(SDL_GPURenderPass *renderPass, Uint32 firstSlot, const SDL_GPUBufferBinding *bindings, Uint32 numBindings); + void (*bindVertexStorageBuffers)(SDL_GPURenderPass *renderPass, Uint32 firstSlot, SDL_GPUBuffer *const *storageBuffers, Uint32 numBindings); + void (*blitTexture)(SDL_GPUCommandBuffer *commandBuffer, const SDL_GPUBlitInfo *info); + bool (*cancelCommandBuffer)(SDL_GPUCommandBuffer *commandBuffer); + void (*copyTextureToTexture)(SDL_GPUCopyPass *copyPass, const SDL_GPUTextureLocation *source, const SDL_GPUTextureLocation *destination, Uint32 w, Uint32 h, Uint32 d, bool cycle); + SDL_GPUBuffer * (*createBuffer)(SDL_GPUDevice *device, const SDL_GPUBufferCreateInfo *createinfo); + SDL_GPUDevice * (*createDevice)(SDL_GPUShaderFormat formatFlags, bool debugMode, const char *name); + SDL_GPUGraphicsPipeline *(*createGraphicsPipeline)(SDL_GPUDevice *device, const SDL_GPUGraphicsPipelineCreateInfo *createinfo); + SDL_GPUSampler * (*createSampler)(SDL_GPUDevice *device, const SDL_GPUSamplerCreateInfo *createinfo); + SDL_GPUShader * (*createShader)(SDL_GPUDevice *device, const SDL_GPUShaderCreateInfo *createinfo); + SDL_GPUTexture * (*createTexture)(SDL_GPUDevice *device, const SDL_GPUTextureCreateInfo *createinfo); + SDL_GPUTransferBuffer * (*createTransferBuffer)(SDL_GPUDevice *device, const SDL_GPUTransferBufferCreateInfo *createinfo); + void (*destroyDevice)(SDL_GPUDevice *device); + void (*drawIndexedPrimitives)(SDL_GPURenderPass *renderPass, Uint32 numIndices, Uint32 numInstances, Uint32 firstIndex, Sint32 vertexOffset, Uint32 firstInstance); + void (*drawPrimitives)(SDL_GPURenderPass *renderPass, Uint32 numVertices, Uint32 numInstances, Uint32 firstVertex, Uint32 firstInstance); + void (*endCopyPass)(SDL_GPUCopyPass *copyPass); + void (*endRenderPass)(SDL_GPURenderPass *renderPass); + void (*generateMipmapsForTexture)(SDL_GPUCommandBuffer *commandBuffer, SDL_GPUTexture *texture); + const char * (*getDeviceDriver)(SDL_GPUDevice *device); + SDL_GPUShaderFormat (*getShaderFormats)(SDL_GPUDevice *device); + SDL_GPUTextureFormat (*getSwapchainTextureFormat)(SDL_GPUDevice *device, SDL_Window *window); + SDL_GPUTextureFormat (*getTextureFormatFromPixelFormat)(SDL_PixelFormat format); + void * (*mapTransferBuffer)(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer, bool cycle); + void (*pushFragmentUniformData)(SDL_GPUCommandBuffer *commandBuffer, Uint32 slotIndex, const void *data, Uint32 length); + void (*pushVertexUniformData)(SDL_GPUCommandBuffer *commandBuffer, Uint32 slotIndex, const void *data, Uint32 length); + void (*releaseBuffer)(SDL_GPUDevice *device, SDL_GPUBuffer *buffer); + void (*releaseGraphicsPipeline)(SDL_GPUDevice *device, SDL_GPUGraphicsPipeline *graphicsPipeline); + void (*releaseSampler)(SDL_GPUDevice *device, SDL_GPUSampler *sampler); + void (*releaseShader)(SDL_GPUDevice *device, SDL_GPUShader *shader); + void (*releaseTexture)(SDL_GPUDevice *device, SDL_GPUTexture *texture); + void (*releaseTransferBuffer)(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer); + void (*setScissor)(SDL_GPURenderPass *renderPass, const SDL_Rect *scissor); + void (*setStencilReference)(SDL_GPURenderPass *renderPass, Uint8 reference); + bool (*submitCommandBuffer)(SDL_GPUCommandBuffer *commandBuffer); + bool (*textureSupportsFormat)(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUTextureType type, SDL_GPUTextureUsageFlags usage); + bool (*textureSupportsSampleCount)(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUSampleCount sampleCount); + void (*unmapTransferBuffer)(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer); + void (*uploadToBuffer)(SDL_GPUCopyPass *copyPass, const SDL_GPUTransferBufferLocation *source, const SDL_GPUBufferRegion *destination, bool cycle); + void (*uploadToTexture)(SDL_GPUCopyPass *copyPass, const SDL_GPUTextureTransferInfo *source, const SDL_GPUTextureRegion *destination, bool cycle); +} RenderBackendT; + + +// Which backend is in use. RENDER_NONE means 3D and the GUI are unavailable and the engine draws +// 2D through SDL_Renderer alone, which is what a machine with no usable driver gets. +typedef enum RenderApiE { + RENDER_NONE = 0, + RENDER_GPU, // SDL_GPU: Vulkan, Direct3D 12 or Metal + RENDER_GLES // OpenGL ES 3.1, or 2.0 with the GUI only +} RenderApiT; + + +extern const RenderBackendT renderGlesBackend; +extern const RenderBackendT renderGpuBackend; + + +// The engine side. renderBegin and renderEnd bracket every frame's rgpu* calls: under SDL_GPU +// they do nothing, and under GLES they hand SDL_Renderer's context over and give it back. +RenderApiT renderApi(void); +const char *renderApiName(void); +void renderBegin(void); +void renderEnd(void); +void renderSelect(RenderApiT api, const RenderBackendT *backend, SDL_Renderer *renderer); + +// The composite seam. The scene and the GUI do not draw to the swapchain; each renders into its +// own texture and hands it to SDL_Renderer to draw as an ordinary layer. Under SDL_GPU that is +// SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_POINTER and under GLES it is the twin taking a GLuint, +// SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER, so the choice lives here rather than at each of +// the three call sites. renderTextureFor goes the other way, for a texture SDL made. +SDL_Texture *renderWrapTexture(SDL_Renderer *renderer, SDL_GPUTexture *texture, SDL_PixelFormat format, int32_t width, int32_t height); +SDL_GPUTexture *renderTextureFor(SDL_Texture *texture); + +// The GLES backend's own two. renderGlesStart looks at the context SDL_Renderer already made and +// says whether it is one this backend can use; renderGlesRestore puts that context back as found. +void renderGlesRestore(void); +bool renderGlesStart(void); +Uint32 renderGlesTextureName(SDL_GPUTexture *texture); +SDL_GPUTexture *renderGlesTextureWrap(Uint32 name, int32_t width, int32_t height); + +SDL_GPUCommandBuffer * rgpuAcquireCommandBuffer(SDL_GPUDevice *device); +SDL_GPUCopyPass * rgpuBeginCopyPass(SDL_GPUCommandBuffer *commandBuffer); +SDL_GPURenderPass * rgpuBeginRenderPass(SDL_GPUCommandBuffer *commandBuffer, const SDL_GPUColorTargetInfo *colorTargetInfos, Uint32 numColorTargets, const SDL_GPUDepthStencilTargetInfo *depthStencilTargetInfo); +void rgpuBindFragmentSamplers(SDL_GPURenderPass *renderPass, Uint32 firstSlot, const SDL_GPUTextureSamplerBinding *textureSamplerBindings, Uint32 numBindings); +void rgpuBindGraphicsPipeline(SDL_GPURenderPass *renderPass, SDL_GPUGraphicsPipeline *graphicsPipeline); +void rgpuBindIndexBuffer(SDL_GPURenderPass *renderPass, const SDL_GPUBufferBinding *binding, SDL_GPUIndexElementSize indexElementSize); +void rgpuBindVertexBuffers(SDL_GPURenderPass *renderPass, Uint32 firstSlot, const SDL_GPUBufferBinding *bindings, Uint32 numBindings); +void rgpuBindVertexStorageBuffers(SDL_GPURenderPass *renderPass, Uint32 firstSlot, SDL_GPUBuffer *const *storageBuffers, Uint32 numBindings); +void rgpuBlitTexture(SDL_GPUCommandBuffer *commandBuffer, const SDL_GPUBlitInfo *info); +bool rgpuCancelCommandBuffer(SDL_GPUCommandBuffer *commandBuffer); +void rgpuCopyTextureToTexture(SDL_GPUCopyPass *copyPass, const SDL_GPUTextureLocation *source, const SDL_GPUTextureLocation *destination, Uint32 w, Uint32 h, Uint32 d, bool cycle); +SDL_GPUBuffer * rgpuCreateBuffer(SDL_GPUDevice *device, const SDL_GPUBufferCreateInfo *createinfo); +SDL_GPUDevice * rgpuCreateDevice(SDL_GPUShaderFormat formatFlags, bool debugMode, const char *name); +SDL_GPUGraphicsPipeline *rgpuCreateGraphicsPipeline(SDL_GPUDevice *device, const SDL_GPUGraphicsPipelineCreateInfo *createinfo); +SDL_GPUSampler * rgpuCreateSampler(SDL_GPUDevice *device, const SDL_GPUSamplerCreateInfo *createinfo); +SDL_GPUShader * rgpuCreateShader(SDL_GPUDevice *device, const SDL_GPUShaderCreateInfo *createinfo); +SDL_GPUTexture * rgpuCreateTexture(SDL_GPUDevice *device, const SDL_GPUTextureCreateInfo *createinfo); +SDL_GPUTransferBuffer * rgpuCreateTransferBuffer(SDL_GPUDevice *device, const SDL_GPUTransferBufferCreateInfo *createinfo); +void rgpuDestroyDevice(SDL_GPUDevice *device); +void rgpuDrawIndexedPrimitives(SDL_GPURenderPass *renderPass, Uint32 numIndices, Uint32 numInstances, Uint32 firstIndex, Sint32 vertexOffset, Uint32 firstInstance); +void rgpuDrawPrimitives(SDL_GPURenderPass *renderPass, Uint32 numVertices, Uint32 numInstances, Uint32 firstVertex, Uint32 firstInstance); +void rgpuEndCopyPass(SDL_GPUCopyPass *copyPass); +void rgpuEndRenderPass(SDL_GPURenderPass *renderPass); +void rgpuGenerateMipmapsForTexture(SDL_GPUCommandBuffer *commandBuffer, SDL_GPUTexture *texture); +const char * rgpuGetDeviceDriver(SDL_GPUDevice *device); +SDL_GPUShaderFormat rgpuGetShaderFormats(SDL_GPUDevice *device); +SDL_GPUTextureFormat rgpuGetSwapchainTextureFormat(SDL_GPUDevice *device, SDL_Window *window); +SDL_GPUTextureFormat rgpuGetTextureFormatFromPixelFormat(SDL_PixelFormat format); +void * rgpuMapTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer, bool cycle); +void rgpuPushFragmentUniformData(SDL_GPUCommandBuffer *commandBuffer, Uint32 slotIndex, const void *data, Uint32 length); +void rgpuPushVertexUniformData(SDL_GPUCommandBuffer *commandBuffer, Uint32 slotIndex, const void *data, Uint32 length); +void rgpuReleaseBuffer(SDL_GPUDevice *device, SDL_GPUBuffer *buffer); +void rgpuReleaseGraphicsPipeline(SDL_GPUDevice *device, SDL_GPUGraphicsPipeline *graphicsPipeline); +void rgpuReleaseSampler(SDL_GPUDevice *device, SDL_GPUSampler *sampler); +void rgpuReleaseShader(SDL_GPUDevice *device, SDL_GPUShader *shader); +void rgpuReleaseTexture(SDL_GPUDevice *device, SDL_GPUTexture *texture); +void rgpuReleaseTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer); +void rgpuSetScissor(SDL_GPURenderPass *renderPass, const SDL_Rect *scissor); +void rgpuSetStencilReference(SDL_GPURenderPass *renderPass, Uint8 reference); +bool rgpuSubmitCommandBuffer(SDL_GPUCommandBuffer *commandBuffer); +bool rgpuTextureSupportsFormat(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUTextureType type, SDL_GPUTextureUsageFlags usage); +bool rgpuTextureSupportsSampleCount(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUSampleCount sampleCount); +void rgpuUnmapTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer); +void rgpuUploadToBuffer(SDL_GPUCopyPass *copyPass, const SDL_GPUTransferBufferLocation *source, const SDL_GPUBufferRegion *destination, bool cycle); +void rgpuUploadToTexture(SDL_GPUCopyPass *copyPass, const SDL_GPUTextureTransferInfo *source, const SDL_GPUTextureRegion *destination, bool cycle); + + +#ifdef __cplusplus +} +#endif + +#endif // RENDER_H diff --git a/src/renderGles.c b/src/renderGles.c new file mode 100644 index 000000000..7d3e26a74 --- /dev/null +++ b/src/renderGles.c @@ -0,0 +1,1675 @@ +/* + * + * 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. + * + */ + +// The OpenGL ES backend: SDL_GPU's shape, drawn with GLES 3.1. +// +// SDL_GPU has Vulkan, Direct3D 12 and Metal, which is nothing at all on a Pi or a Mali handheld. +// This file implements the 45 entry points scene.c and guiRender.cpp actually use, so those two +// files stay the single description of what Singe draws. PLAN.md section 56 has the design. +// +// Three things shape the code: +// +// 1. SDL_GPU records into a command buffer and submits; GLES executes as it is called. Singe +// acquires, records and submits inside one frame and never reorders, so a command buffer here +// is bookkeeping and the drawing happens where it is written. Submitting is a flush. +// +// 2. It shares one GL context with SDL_Renderer, which caches its own drawing state. render.c +// calls SDL_FlushRenderer first, which both flushes and invalidates that cache, and this file +// leaves the context as it found it. The phase 0 spike measured the alternative -- a second +// shared context -- at eleven times the cost on a software driver, so there is only one context. +// +// 3. HLSL gives each stage its own uniform slots (space1 for vertex, space3 for fragment) and GLES +// has one namespace for the whole program, so the two collide at binding 0. Fragment blocks are +// moved up by GLES_FRAGMENT_UNIFORM_BASE when a pipeline links. + +#include +#include +#include +#include +#include "renderGlesApi.h" +#include "render.h" +#include "util.h" + +#define GLES_FRAGMENT_UNIFORM_BASE 4 // Vertex uniform blocks take 0..3; fragment blocks start here +#define GLES_MAX_ATTRIBUTES 16 +#define GLES_MAX_COLOR_TARGETS 4 +#define GLES_MAX_SAMPLERS 16 +#define GLES_MAX_VERTEX_BUFFERS 4 +#define GLES_UNIFORM_RING_BYTES (4 * 1024 * 1024) +#define GLES_FRAMEBUFFER_CACHE 32 +#define GLES_LOG_BYTES 1024 // A driver's compile or link log, which is the only clue it gives +#define GLES_NAME_BYTES 128 // A uniform block's name as SPIRV-Cross emits it + +// A texture's storage. A multisampled colour target is a renderbuffer rather than a texture: the +// scene only ever resolves it and never samples it, and multisampled textures are ES 3.1 with an +// extension where renderbuffers are core. +typedef struct GlesTextureS { + GLuint name; + GLuint renderbuffer; + GLenum target; + SDL_GPUTextureFormat format; + uint32_t width; + uint32_t height; + uint32_t layers; + uint32_t levels; + uint32_t samples; + bool borrowed; // SDL owns this name; releasing the wrapper must not delete it +} GlesTextureT; + +typedef struct GlesBufferS { + GLuint name; + GLenum target; +} GlesBufferT; + +// A transfer buffer is plain memory. GLES has no persistent mapping worth relying on here, and the +// uploads that follow are glBufferSubData and glTexSubImage2D, which take a pointer anyway. +typedef struct GlesTransferS { + uint8_t *data; + uint32_t size; +} GlesTransferT; + +// The source is kept because a pipeline needs the fragment shader's text when it links, to move that +// stage's uniform blocks clear of the vertex stage's. The resource counts SDL_GPU passes in are not +// kept: the ES shaders carry their own layout bindings, so the counts would be a second, weaker +// description of the same thing. +typedef struct GlesShaderS { + GLuint name; + GLenum stage; + char *source; +} GlesShaderT; + +typedef struct GlesPipelineS { + GLuint program; + GLint baseInstance; // SPIRV_Cross_BaseInstance, or -1 when the shader has none + GLuint vertexArray; + SDL_GPUVertexAttribute attributes[GLES_MAX_ATTRIBUTES]; + uint32_t attributeCount; + uint32_t strides[GLES_MAX_VERTEX_BUFFERS]; + SDL_GPURasterizerState rasterizer; + SDL_GPUDepthStencilState depthStencil; + SDL_GPUColorTargetBlendState blend; + bool hasColorTarget; + SDL_GPUPrimitiveType primitive; +} GlesPipelineT; + +typedef struct GlesFramebufferS { + GLuint name; + GlesTextureT *colour[GLES_MAX_COLOR_TARGETS]; + uint32_t colourLayer[GLES_MAX_COLOR_TARGETS]; + uint32_t colourLevel[GLES_MAX_COLOR_TARGETS]; + uint32_t colourCount; + GlesTextureT *depth; + uint32_t depthLayer; + uint32_t depthLevel; +} GlesFramebufferT; + +typedef struct GlesRenderPassS { + GlesPipelineT *pipeline; + GlesBufferT *vertexBuffer[GLES_MAX_VERTEX_BUFFERS]; + uint32_t vertexOffset[GLES_MAX_VERTEX_BUFFERS]; + uint32_t vertexCount; + bool vertexDirty; + SDL_GPUColorTargetInfo colour[GLES_MAX_COLOR_TARGETS]; // Kept so the pass can honour store_op when it ends + uint32_t colourCount; + GLuint framebuffer; + GLenum indexType; + uint32_t width; + uint32_t height; +} GlesRenderPassT; + +typedef struct GlesCommandBufferS { + GlesRenderPassT renderPass; + bool issued; // Read when a buffer is cancelled, to be sure nothing had been drawn +} GlesCommandBufferT; + +// The device. Cast to and from SDL_GPUDevice *, which is opaque, so the engine never learns the +// difference. +typedef struct GlesDeviceS { + GlesCommandBufferT commands; + GlesFramebufferT framebuffers[GLES_FRAMEBUFFER_CACHE]; + uint32_t framebufferCount; + GLuint scratchDraw; + GLuint scratchRead; + GLuint uniformRing; + uint32_t uniformOffset; + int32_t uniformAlignment; + GLfloat maxAnisotropy; // 0 when the driver has no GL_EXT_texture_filter_anisotropic + bool uniformWrapped; + bool ready; +} GlesDeviceT; + +static GlesDeviceT _device; + +static void _applyVertexLayout(GlesRenderPassT *pass); +static void _bindTextureUnit(uint32_t unit, GlesTextureT *texture, SDL_GPUSampler *sampler); +static GLenum _addressMode(SDL_GPUSamplerAddressMode mode); +static GLenum _blendFactor(SDL_GPUBlendFactor factor); +static GLenum _blendOp(SDL_GPUBlendOp op); +static GLenum _compare(SDL_GPUCompareOp op); +static void _formatToGl(SDL_GPUTextureFormat format, GLenum *internalFormat, GLenum *layout, GLenum *type); +static GLuint _framebufferFor(const SDL_GPUColorTargetInfo *colour, uint32_t colourCount, const SDL_GPUDepthStencilTargetInfo *depth, uint32_t *width, uint32_t *height); +static SDL_GPUCommandBuffer * _glesAcquireCommandBuffer(SDL_GPUDevice *device); +static SDL_GPUCopyPass * _glesBeginCopyPass(SDL_GPUCommandBuffer *commandBuffer); +static SDL_GPURenderPass * _glesBeginRenderPass(SDL_GPUCommandBuffer *commandBuffer, const SDL_GPUColorTargetInfo *colorTargetInfos, Uint32 numColorTargets, const SDL_GPUDepthStencilTargetInfo *depthStencilTargetInfo); +static void _glesBindFragmentSamplers(SDL_GPURenderPass *renderPass, Uint32 firstSlot, const SDL_GPUTextureSamplerBinding *textureSamplerBindings, Uint32 numBindings); +static void _glesBindGraphicsPipeline(SDL_GPURenderPass *renderPass, SDL_GPUGraphicsPipeline *graphicsPipeline); +static void _glesBindIndexBuffer(SDL_GPURenderPass *renderPass, const SDL_GPUBufferBinding *binding, SDL_GPUIndexElementSize indexElementSize); +static void _glesBindVertexBuffers(SDL_GPURenderPass *renderPass, Uint32 firstSlot, const SDL_GPUBufferBinding *bindings, Uint32 numBindings); +static void _glesBindVertexStorageBuffers(SDL_GPURenderPass *renderPass, Uint32 firstSlot, SDL_GPUBuffer *const *storageBuffers, Uint32 numBindings); +static void _glesBlitTexture(SDL_GPUCommandBuffer *commandBuffer, const SDL_GPUBlitInfo *info); +static bool _glesCancelCommandBuffer(SDL_GPUCommandBuffer *commandBuffer); +static void _glesCopyTextureToTexture(SDL_GPUCopyPass *copyPass, const SDL_GPUTextureLocation *source, const SDL_GPUTextureLocation *destination, Uint32 w, Uint32 h, Uint32 d, bool cycle); +static SDL_GPUBuffer * _glesCreateBuffer(SDL_GPUDevice *device, const SDL_GPUBufferCreateInfo *createinfo); +static SDL_GPUDevice * _glesCreateDevice(SDL_GPUShaderFormat formatFlags, bool debugMode, const char *name); +static SDL_GPUGraphicsPipeline *_glesCreateGraphicsPipeline(SDL_GPUDevice *device, const SDL_GPUGraphicsPipelineCreateInfo *createinfo); +static SDL_GPUSampler * _glesCreateSampler(SDL_GPUDevice *device, const SDL_GPUSamplerCreateInfo *createinfo); +static SDL_GPUShader * _glesCreateShader(SDL_GPUDevice *device, const SDL_GPUShaderCreateInfo *createinfo); +static SDL_GPUTexture * _glesCreateTexture(SDL_GPUDevice *device, const SDL_GPUTextureCreateInfo *createinfo); +static SDL_GPUTransferBuffer * _glesCreateTransferBuffer(SDL_GPUDevice *device, const SDL_GPUTransferBufferCreateInfo *createinfo); +static void _glesDestroyDevice(SDL_GPUDevice *device); +static void _glesDrawIndexedPrimitives(SDL_GPURenderPass *renderPass, Uint32 numIndices, Uint32 numInstances, Uint32 firstIndex, Sint32 vertexOffset, Uint32 firstInstance); +static void _glesDrawPrimitives(SDL_GPURenderPass *renderPass, Uint32 numVertices, Uint32 numInstances, Uint32 firstVertex, Uint32 firstInstance); +static void _glesEndCopyPass(SDL_GPUCopyPass *copyPass); +static void _glesEndRenderPass(SDL_GPURenderPass *renderPass); +static void _glesGenerateMipmapsForTexture(SDL_GPUCommandBuffer *commandBuffer, SDL_GPUTexture *texture); +static const char * _glesGetDeviceDriver(SDL_GPUDevice *device); +static SDL_GPUShaderFormat _glesGetShaderFormats(SDL_GPUDevice *device); +static SDL_GPUTextureFormat _glesGetSwapchainTextureFormat(SDL_GPUDevice *device, SDL_Window *window); +static SDL_GPUTextureFormat _glesGetTextureFormatFromPixelFormat(SDL_PixelFormat format); +static void * _glesMapTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer, bool cycle); +static void _glesPushFragmentUniformData(SDL_GPUCommandBuffer *commandBuffer, Uint32 slotIndex, const void *data, Uint32 length); +static void _glesPushUniformData(uint32_t binding, const void *data, uint32_t length); +static void _glesPushVertexUniformData(SDL_GPUCommandBuffer *commandBuffer, Uint32 slotIndex, const void *data, Uint32 length); +static void _glesReleaseBuffer(SDL_GPUDevice *device, SDL_GPUBuffer *buffer); +static void _glesReleaseGraphicsPipeline(SDL_GPUDevice *device, SDL_GPUGraphicsPipeline *graphicsPipeline); +static void _glesReleaseSampler(SDL_GPUDevice *device, SDL_GPUSampler *sampler); +static void _glesReleaseShader(SDL_GPUDevice *device, SDL_GPUShader *shader); +static void _glesReleaseTexture(SDL_GPUDevice *device, SDL_GPUTexture *texture); +static void _glesReleaseTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer); +static void _glesSetScissor(SDL_GPURenderPass *renderPass, const SDL_Rect *scissor); +static void _glesSetStencilReference(SDL_GPURenderPass *renderPass, Uint8 reference); +static bool _glesSubmitCommandBuffer(SDL_GPUCommandBuffer *commandBuffer); +static bool _glesTextureSupportsFormat(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUTextureType type, SDL_GPUTextureUsageFlags usage); +static bool _glesTextureSupportsSampleCount(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUSampleCount sampleCount); +static void _glesUnmapTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer); +static void _glesUploadToBuffer(SDL_GPUCopyPass *copyPass, const SDL_GPUTransferBufferLocation *source, const SDL_GPUBufferRegion *destination, bool cycle); +static void _glesUploadToTexture(SDL_GPUCopyPass *copyPass, const SDL_GPUTextureTransferInfo *source, const SDL_GPUTextureRegion *destination, bool cycle); +static GLenum _primitive(SDL_GPUPrimitiveType type); +static GLuint _scratchFramebuffer(GLenum binding, GlesTextureT *texture, uint32_t level); +static void _remapFragmentBlocks(GLuint program, const char *fragmentSource); +static void _setPipelineState(GlesPipelineT *pipeline); +static GLenum _stencilOp(SDL_GPUStencilOp op); +static void _vertexFormat(SDL_GPUVertexElementFormat format, GLint *size, GLenum *type, GLboolean *normalized); + +void renderGlesRestore(void); +bool renderGlesStart(void); + + +static void _bindTextureUnit(uint32_t unit, GlesTextureT *texture, SDL_GPUSampler *sampler) { + GLuint name = (GLuint)(uintptr_t)sampler; + + glActiveTexture(GL_TEXTURE0 + unit); + if (texture != NULL) { + glBindTexture(texture->target, texture->name); + } + // A sampler object carries the filtering and wrapping; binding it to the unit leaves the + // texture's own parameters alone, which is what SDL_GPU's separate sampler means. + glBindSampler(unit, name); +} + + +static GLenum _addressMode(SDL_GPUSamplerAddressMode mode) { + switch (mode) { + case SDL_GPU_SAMPLERADDRESSMODE_REPEAT: return GL_REPEAT; + case SDL_GPU_SAMPLERADDRESSMODE_MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; + default: return GL_CLAMP_TO_EDGE; + } +} + + +static GLenum _blendFactor(SDL_GPUBlendFactor factor) { + switch (factor) { + case SDL_GPU_BLENDFACTOR_ZERO: return GL_ZERO; + case SDL_GPU_BLENDFACTOR_ONE: return GL_ONE; + case SDL_GPU_BLENDFACTOR_SRC_COLOR: return GL_SRC_COLOR; + case SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return GL_ONE_MINUS_SRC_COLOR; + case SDL_GPU_BLENDFACTOR_DST_COLOR: return GL_DST_COLOR; + case SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_COLOR: return GL_ONE_MINUS_DST_COLOR; + case SDL_GPU_BLENDFACTOR_SRC_ALPHA: return GL_SRC_ALPHA; + case SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return GL_ONE_MINUS_SRC_ALPHA; + case SDL_GPU_BLENDFACTOR_DST_ALPHA: return GL_DST_ALPHA; + case SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return GL_ONE_MINUS_DST_ALPHA; + case SDL_GPU_BLENDFACTOR_CONSTANT_COLOR: return GL_CONSTANT_COLOR; + case SDL_GPU_BLENDFACTOR_ONE_MINUS_CONSTANT_COLOR: return GL_ONE_MINUS_CONSTANT_COLOR; + case SDL_GPU_BLENDFACTOR_SRC_ALPHA_SATURATE: return GL_SRC_ALPHA_SATURATE; + default: return GL_ONE; + } +} + + +static GLenum _blendOp(SDL_GPUBlendOp op) { + switch (op) { + case SDL_GPU_BLENDOP_SUBTRACT: return GL_FUNC_SUBTRACT; + case SDL_GPU_BLENDOP_REVERSE_SUBTRACT: return GL_FUNC_REVERSE_SUBTRACT; + case SDL_GPU_BLENDOP_MIN: return GL_MIN; + case SDL_GPU_BLENDOP_MAX: return GL_MAX; + default: return GL_FUNC_ADD; + } +} + + +static GLenum _compare(SDL_GPUCompareOp op) { + switch (op) { + case SDL_GPU_COMPAREOP_NEVER: return GL_NEVER; + case SDL_GPU_COMPAREOP_LESS: return GL_LESS; + case SDL_GPU_COMPAREOP_EQUAL: return GL_EQUAL; + case SDL_GPU_COMPAREOP_LESS_OR_EQUAL: return GL_LEQUAL; + case SDL_GPU_COMPAREOP_GREATER: return GL_GREATER; + case SDL_GPU_COMPAREOP_NOT_EQUAL: return GL_NOTEQUAL; + case SDL_GPU_COMPAREOP_GREATER_OR_EQUAL: return GL_GEQUAL; + default: return GL_ALWAYS; + } +} + + +// The formats the engine actually asks for. Anything else reports zero, and rgpuTextureSupportsFormat +// turns that into a plain no so the caller falls back the way it already does on a weak device. +static void _formatToGl(SDL_GPUTextureFormat format, GLenum *internalFormat, GLenum *layout, GLenum *type) { + *internalFormat = 0; + *layout = GL_RGBA; + *type = GL_UNSIGNED_BYTE; + switch (format) { + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM: + *internalFormat = GL_RGBA8; + break; + case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM: + // No BGRA storage in core GLES; the swizzle happens when the pixels are uploaded. + *internalFormat = GL_RGBA8; + break; + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB: + *internalFormat = GL_SRGB8_ALPHA8; + break; + case SDL_GPU_TEXTUREFORMAT_R8_UNORM: + *internalFormat = GL_R8; + *layout = GL_RED; + break; + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT: + *internalFormat = GL_RGBA16F; + *type = GL_HALF_FLOAT; + break; + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT: + *internalFormat = GL_RGBA32F; + *type = GL_FLOAT; + break; + case SDL_GPU_TEXTUREFORMAT_D16_UNORM: + *internalFormat = GL_DEPTH_COMPONENT16; + *layout = GL_DEPTH_COMPONENT; + *type = GL_UNSIGNED_SHORT; + break; + case SDL_GPU_TEXTUREFORMAT_D24_UNORM: + *internalFormat = GL_DEPTH_COMPONENT24; + *layout = GL_DEPTH_COMPONENT; + *type = GL_UNSIGNED_INT; + break; + case SDL_GPU_TEXTUREFORMAT_D32_FLOAT: + *internalFormat = GL_DEPTH_COMPONENT32F; + *layout = GL_DEPTH_COMPONENT; + *type = GL_FLOAT; + break; + case SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT: + *internalFormat = GL_DEPTH24_STENCIL8; + *layout = GL_DEPTH_STENCIL; + *type = GL_UNSIGNED_INT_24_8; + break; + case SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT: + *internalFormat = GL_DEPTH32F_STENCIL8; + *layout = GL_DEPTH_STENCIL; + *type = GL_FLOAT_32_UNSIGNED_INT_24_8_REV; + break; + default: + break; + } +} + + +// One framebuffer per set of attachments, kept because a render pass is entered many times a frame +// with the same targets and creating an FBO per pass is the classic way to make a tiler crawl. +static GLuint _framebufferFor(const SDL_GPUColorTargetInfo *colour, uint32_t colourCount, const SDL_GPUDepthStencilTargetInfo *depth, uint32_t *width, uint32_t *height) { + GlesFramebufferT *slot; + GlesTextureT *depthTexture = depth != NULL ? (GlesTextureT *)depth->texture : NULL; + GLenum buffers[GLES_MAX_COLOR_TARGETS]; + uint32_t i; + uint32_t index; + + *width = 0; + *height = 0; + for (index = 0; index < _device.framebufferCount; index++) { + bool match = true; + + slot = &_device.framebuffers[index]; + if ((slot->colourCount != colourCount) || (slot->depth != depthTexture)) { + continue; + } + // The layer matters as much as the texture: the scene's shadow maps are one array with a + // layer per light, and matching on the texture alone gave every light the same framebuffer, + // so every shadow was rendered into layer 0 and every other layer read as fully occluded. + if ((depthTexture != NULL) && ((slot->depthLayer != depth->layer) || (slot->depthLevel != depth->mip_level))) { + continue; + } + for (i = 0; i < colourCount; i++) { + if ((slot->colour[i] != (GlesTextureT *)colour[i].texture) || (slot->colourLayer[i] != colour[i].layer_or_depth_plane) || (slot->colourLevel[i] != colour[i].mip_level)) { + match = false; + break; + } + } + if (match) { + glBindFramebuffer(GL_FRAMEBUFFER, slot->name); + if (colourCount > 0) { + *width = ((GlesTextureT *)colour[0].texture)->width >> colour[0].mip_level; + *height = ((GlesTextureT *)colour[0].texture)->height >> colour[0].mip_level; + } else if (depthTexture != NULL) { + *width = depthTexture->width; + *height = depthTexture->height; + } + return slot->name; + } + } + if (_device.framebufferCount >= GLES_FRAMEBUFFER_CACHE) { + // The cache is a fixed size on purpose; overflowing it means a target set is being rebuilt + // every frame, which is a bug worth hearing about rather than hiding behind a bigger array. + utilTrace("Gles: framebuffer cache full"); + _device.framebufferCount = 0; + } + slot = &_device.framebuffers[_device.framebufferCount]; + memset(slot, 0, sizeof(*slot)); + glGenFramebuffers(1, &slot->name); + glBindFramebuffer(GL_FRAMEBUFFER, slot->name); + slot->colourCount = colourCount; + slot->depth = depthTexture; + for (i = 0; i < colourCount; i++) { + GlesTextureT *texture = (GlesTextureT *)colour[i].texture; + + slot->colour[i] = texture; + slot->colourLayer[i] = colour[i].layer_or_depth_plane; + slot->colourLevel[i] = colour[i].mip_level; + buffers[i] = GL_COLOR_ATTACHMENT0 + i; + if (texture->renderbuffer != 0) { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_RENDERBUFFER, texture->renderbuffer); + } else if (texture->target == GL_TEXTURE_2D) { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, texture->name, (GLint)colour[i].mip_level); + } else { + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, texture->name, (GLint)colour[i].mip_level, (GLint)colour[i].layer_or_depth_plane); + } + *width = texture->width >> colour[i].mip_level; + *height = texture->height >> colour[i].mip_level; + } + if (depthTexture != NULL) { + GLenum attachment = (depthTexture->format == SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT) || (depthTexture->format == SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT) ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT; + + slot->depthLayer = depth->layer; + slot->depthLevel = depth->mip_level; + if (depthTexture->renderbuffer != 0) { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, depthTexture->renderbuffer); + } else if (depthTexture->target == GL_TEXTURE_2D) { + glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, depthTexture->name, (GLint)depth->mip_level); + } else { + glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, depthTexture->name, (GLint)depth->mip_level, (GLint)depth->layer); + } + if (colourCount == 0) { + *width = depthTexture->width; + *height = depthTexture->height; + } + } + if (colourCount > 0) { + glDrawBuffers((GLsizei)colourCount, buffers); + } else { + // A depth-only pass: the scene's shadow and prepass pipelines have no colour target at all. + GLenum none = GL_NONE; + + glDrawBuffers(1, &none); + glReadBuffer(GL_NONE); + } + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + utilTrace("Gles: incomplete framebuffer (%u colour, %s depth)", colourCount, depthTexture != NULL ? "with" : "no"); + } + _device.framebufferCount++; + return slot->name; +} + + +static SDL_GPUCommandBuffer *_glesAcquireCommandBuffer(SDL_GPUDevice *device) { + (void)device; + // The context is shared with SDL_Renderer, and render.c has just handed it over with + // SDL_FlushRenderer. Anything SDL flagged is SDL's; draining here keeps the render pass probe + // honest, and without it SDL's first-frame errors were reported as ours and chased for an hour. + while (glGetError() != GL_NO_ERROR) { + } + // Orphan the uniform ring rather than writing over ranges the GPU may still be reading. Handing + // the driver a fresh allocation is the cheap GLES idiom for per-frame data; reusing the storage + // stalls at best and corrupts a draw's uniforms at worst. + if (_device.uniformRing != 0) { + glBindBuffer(GL_UNIFORM_BUFFER, _device.uniformRing); + glBufferData(GL_UNIFORM_BUFFER, GLES_UNIFORM_RING_BYTES, NULL, GL_STREAM_DRAW); + _device.uniformOffset = 0; + _device.uniformWrapped = false; + } + _device.commands.issued = false; + return (SDL_GPUCommandBuffer *)&_device.commands; +} + + +// GLES copies as it is told to, so a copy pass carries no state of its own; the command buffer is +// handle enough, and the uploads take their targets as arguments. +static SDL_GPUCopyPass *_glesBeginCopyPass(SDL_GPUCommandBuffer *commandBuffer) { + return (SDL_GPUCopyPass *)commandBuffer; +} + + +static SDL_GPURenderPass *_glesBeginRenderPass(SDL_GPUCommandBuffer *commandBuffer, const SDL_GPUColorTargetInfo *colorTargetInfos, Uint32 numColorTargets, const SDL_GPUDepthStencilTargetInfo *depthStencilTargetInfo) { + GlesCommandBufferT *commands = (GlesCommandBufferT *)commandBuffer; + GlesRenderPassT *pass = &commands->renderPass; + GLbitfield clear = 0; + uint32_t i; + + pass->framebuffer = _framebufferFor(colorTargetInfos, numColorTargets, depthStencilTargetInfo, &pass->width, &pass->height); + pass->pipeline = NULL; + pass->vertexCount = 0; + pass->vertexDirty = false; + memset(pass->vertexBuffer, 0, sizeof(pass->vertexBuffer)); + pass->colourCount = numColorTargets < GLES_MAX_COLOR_TARGETS ? numColorTargets : GLES_MAX_COLOR_TARGETS; + for (i = 0; i < pass->colourCount; i++) { + pass->colour[i] = colorTargetInfos[i]; + } + glViewport(0, 0, (GLsizei)pass->width, (GLsizei)pass->height); + glDisable(GL_SCISSOR_TEST); + // A clear has to be able to write, whatever the last pipeline left behind. + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDepthMask(GL_TRUE); + for (i = 0; i < numColorTargets; i++) { + if (colorTargetInfos[i].load_op == SDL_GPU_LOADOP_CLEAR) { + glClearBufferfv(GL_COLOR, (GLint)i, &colorTargetInfos[i].clear_color.r); + } + } + if (depthStencilTargetInfo != NULL) { + if (depthStencilTargetInfo->load_op == SDL_GPU_LOADOP_CLEAR) { + clear |= GL_DEPTH_BUFFER_BIT; + glClearDepthf(depthStencilTargetInfo->clear_depth); + } + if (depthStencilTargetInfo->stencil_load_op == SDL_GPU_LOADOP_CLEAR) { + clear |= GL_STENCIL_BUFFER_BIT; + glStencilMask(0xFF); + glClearStencil(depthStencilTargetInfo->clear_stencil); + } + if (clear != 0) { + glClear(clear); + } + } + return (SDL_GPURenderPass *)pass; +} + + +static void _glesBindFragmentSamplers(SDL_GPURenderPass *renderPass, Uint32 firstSlot, const SDL_GPUTextureSamplerBinding *textureSamplerBindings, Uint32 numBindings) { + uint32_t i; + + (void)renderPass; + for (i = 0; i < numBindings; i++) { + _bindTextureUnit(firstSlot + i, (GlesTextureT *)textureSamplerBindings[i].texture, textureSamplerBindings[i].sampler); + } +} + + +static void _glesBindGraphicsPipeline(SDL_GPURenderPass *renderPass, SDL_GPUGraphicsPipeline *graphicsPipeline) { + GlesRenderPassT *pass = (GlesRenderPassT *)renderPass; + GlesPipelineT *pipeline = (GlesPipelineT *)graphicsPipeline; + + if (pass->pipeline != pipeline) { + pass->vertexDirty = true; + } + pass->pipeline = pipeline; + glUseProgram(pipeline->program); + glBindVertexArray(pipeline->vertexArray); + _setPipelineState(pipeline); +} + + +static void _glesBindIndexBuffer(SDL_GPURenderPass *renderPass, const SDL_GPUBufferBinding *binding, SDL_GPUIndexElementSize indexElementSize) { + GlesRenderPassT *pass = (GlesRenderPassT *)renderPass; + GlesBufferT *buffer = (GlesBufferT *)binding->buffer; + + pass->indexType = indexElementSize == SDL_GPU_INDEXELEMENTSIZE_16BIT ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT; + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->name); +} + + +// Records the buffers only. SDL_GPU lets a pass bind its vertex buffers and its pipeline in either +// order, and the scene uses both: meshes bind the pipeline first, particles bind the buffer first +// and the pipeline inside the run loop. GLES has no vertex-buffer binding point of its own -- the +// attribute pointers carry the buffer -- so the layout needs both halves and is applied at the draw, +// by _applyVertexLayout. Applying it here instead silently drew nothing whenever the buffer came +// first, which is why the 3D particles were missing while every mesh was fine. +static void _glesBindVertexBuffers(SDL_GPURenderPass *renderPass, Uint32 firstSlot, const SDL_GPUBufferBinding *bindings, Uint32 numBindings) { + GlesRenderPassT *pass = (GlesRenderPassT *)renderPass; + uint32_t i; + + for (i = 0; i < numBindings; i++) { + uint32_t slot = firstSlot + i; + + if (slot < GLES_MAX_VERTEX_BUFFERS) { + pass->vertexBuffer[slot] = (GlesBufferT *)bindings[i].buffer; + pass->vertexOffset[slot] = bindings[i].offset; + if (slot >= pass->vertexCount) { + pass->vertexCount = slot + 1; + } + } + } + pass->vertexDirty = true; +} + + +static void _applyVertexLayout(GlesRenderPassT *pass) { + GlesPipelineT *pipeline = pass->pipeline; + uint32_t i; + uint32_t a; + + if ((pipeline == NULL) || !pass->vertexDirty) { + return; + } + pass->vertexDirty = false; + for (i = 0; i < pass->vertexCount; i++) { + GlesBufferT *buffer = pass->vertexBuffer[i]; + uint32_t slot = i; + + if (buffer == NULL) { + continue; + } + glBindBuffer(GL_ARRAY_BUFFER, buffer->name); + for (a = 0; a < pipeline->attributeCount; a++) { + const SDL_GPUVertexAttribute *attribute = &pipeline->attributes[a]; + GLboolean normalized; + GLenum type; + GLint size; + + if (attribute->buffer_slot != slot) { + continue; + } + _vertexFormat(attribute->format, &size, &type, &normalized); + glEnableVertexAttribArray(attribute->location); + // An integer attribute must be fed as an integer: the scene's joint indices are uvec4 in + // the shader, and handing them over as floats is a link-time type mismatch, not a value + // one, so the draw is rejected rather than merely wrong. + if (((type == GL_UNSIGNED_BYTE) && (normalized == GL_FALSE)) || (type == GL_INT) || (type == GL_UNSIGNED_INT)) { + glVertexAttribIPointer(attribute->location, size, type, (GLsizei)pipeline->strides[slot], (const void *)(uintptr_t)(pass->vertexOffset[slot] + attribute->offset)); + } else { + glVertexAttribPointer(attribute->location, size, type, normalized, (GLsizei)pipeline->strides[slot], (const void *)(uintptr_t)(pass->vertexOffset[slot] + attribute->offset)); + } + } + } +} + + +static void _glesBindVertexStorageBuffers(SDL_GPURenderPass *renderPass, Uint32 firstSlot, SDL_GPUBuffer *const *storageBuffers, Uint32 numBindings) { + uint32_t i; + + (void)renderPass; + for (i = 0; i < numBindings; i++) { + GlesBufferT *buffer = (GlesBufferT *)storageBuffers[i]; + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, firstSlot + i, buffer != NULL ? buffer->name : 0); + } +} + + +static void _glesBlitTexture(SDL_GPUCommandBuffer *commandBuffer, const SDL_GPUBlitInfo *info) { + GlesTextureT *source = (GlesTextureT *)info->source.texture; + GlesTextureT *destination = (GlesTextureT *)info->destination.texture; + (void)commandBuffer; + _scratchFramebuffer(GL_READ_FRAMEBUFFER, source, info->source.mip_level); + _scratchFramebuffer(GL_DRAW_FRAMEBUFFER, destination, info->destination.mip_level); + glDisable(GL_SCISSOR_TEST); + glBlitFramebuffer((GLint)info->source.x, (GLint)info->source.y, (GLint)(info->source.x + info->source.w), (GLint)(info->source.y + info->source.h), + (GLint)info->destination.x, (GLint)info->destination.y, (GLint)(info->destination.x + info->destination.w), (GLint)(info->destination.y + info->destination.h), + GL_COLOR_BUFFER_BIT, info->filter == SDL_GPU_FILTER_NEAREST ? GL_NEAREST : GL_LINEAR); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + + +// The engine cancels only when a copy pass failed to begin, so nothing has been issued (see +// guiRender.cpp). Work already executed could not be taken back, and none ever has been. +static bool _glesCancelCommandBuffer(SDL_GPUCommandBuffer *commandBuffer) { + GlesCommandBufferT *commands = (GlesCommandBufferT *)commandBuffer; + + if (commands->issued) { + utilTrace("Gles: a command buffer with work in it was cancelled"); + } + return true; +} + + +static void _glesCopyTextureToTexture(SDL_GPUCopyPass *copyPass, const SDL_GPUTextureLocation *source, const SDL_GPUTextureLocation *destination, Uint32 w, Uint32 h, Uint32 d, bool cycle) { + GlesTextureT *from = (GlesTextureT *)source->texture; + GlesTextureT *to = (GlesTextureT *)destination->texture; + (void)copyPass; + (void)d; + (void)cycle; + _scratchFramebuffer(GL_READ_FRAMEBUFFER, from, source->mip_level); + glBindTexture(GL_TEXTURE_2D, to->name); + glCopyTexSubImage2D(GL_TEXTURE_2D, (GLint)destination->mip_level, (GLint)destination->x, (GLint)destination->y, (GLint)source->x, (GLint)source->y, (GLsizei)w, (GLsizei)h); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); +} + + +static SDL_GPUBuffer *_glesCreateBuffer(SDL_GPUDevice *device, const SDL_GPUBufferCreateInfo *createinfo) { + GlesBufferT *buffer = calloc(1, sizeof(GlesBufferT)); + + (void)device; + if (buffer == NULL) { + return NULL; + } + buffer->target = (createinfo->usage & SDL_GPU_BUFFERUSAGE_INDEX) != 0 ? GL_ELEMENT_ARRAY_BUFFER : ((createinfo->usage & SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ) != 0 ? GL_SHADER_STORAGE_BUFFER : GL_ARRAY_BUFFER); + glGenBuffers(1, &buffer->name); + glBindBuffer(buffer->target, buffer->name); + glBufferData(buffer->target, (GLsizeiptr)createinfo->size, NULL, GL_DYNAMIC_DRAW); + return (SDL_GPUBuffer *)buffer; +} + + +static SDL_GPUDevice *_glesCreateDevice(SDL_GPUShaderFormat formatFlags, bool debugMode, const char *name) { + (void)formatFlags; + (void)debugMode; + (void)name; + return _device.ready ? (SDL_GPUDevice *)&_device : NULL; +} + + +static SDL_GPUGraphicsPipeline *_glesCreateGraphicsPipeline(SDL_GPUDevice *device, const SDL_GPUGraphicsPipelineCreateInfo *createinfo) { + GlesShaderT *fragment = (GlesShaderT *)createinfo->fragment_shader; + GlesShaderT *vertex = (GlesShaderT *)createinfo->vertex_shader; + GlesPipelineT *pipeline; + GLint linked = 0; + uint32_t i; + + (void)device; + if ((vertex == NULL) || (fragment == NULL)) { + return NULL; + } + pipeline = calloc(1, sizeof(GlesPipelineT)); + if (pipeline == NULL) { + return NULL; + } + pipeline->program = glCreateProgram(); + glAttachShader(pipeline->program, vertex->name); + glAttachShader(pipeline->program, fragment->name); + glLinkProgram(pipeline->program); + glGetProgramiv(pipeline->program, GL_LINK_STATUS, &linked); + if (!linked) { + char log[GLES_LOG_BYTES] = { 0 }; + + glGetProgramInfoLog(pipeline->program, sizeof(log) - 1, NULL, log); + utilTrace("Gles: pipeline link failed: %s", log); + glDeleteProgram(pipeline->program); + free(pipeline); + return NULL; + } + _remapFragmentBlocks(pipeline->program, fragment->source); + // GLES has no gl_BaseInstance, so SPIRV-Cross emits a plain uniform for it and expects the + // application to set it before every draw. Leaving it alone is not harmless: the scene indexes + // its instance-matrix storage buffer by gl_InstanceID + this, so an unset value reads another + // draw's matrices -- which is how the ground picked up a *billboard* flag belonging to something + // else and started turning to face whichever camera was rendering. + pipeline->baseInstance = glGetUniformLocation(pipeline->program, "SPIRV_Cross_BaseInstance"); + pipeline->primitive = createinfo->primitive_type; + pipeline->rasterizer = createinfo->rasterizer_state; + pipeline->depthStencil = createinfo->depth_stencil_state; + pipeline->hasColorTarget = createinfo->target_info.num_color_targets > 0; + if (pipeline->hasColorTarget) { + pipeline->blend = createinfo->target_info.color_target_descriptions[0].blend_state; + } + pipeline->attributeCount = createinfo->vertex_input_state.num_vertex_attributes; + if (pipeline->attributeCount > GLES_MAX_ATTRIBUTES) { + pipeline->attributeCount = GLES_MAX_ATTRIBUTES; + } + for (i = 0; i < pipeline->attributeCount; i++) { + pipeline->attributes[i] = createinfo->vertex_input_state.vertex_attributes[i]; + } + for (i = 0; i < createinfo->vertex_input_state.num_vertex_buffers; i++) { + uint32_t slot = createinfo->vertex_input_state.vertex_buffer_descriptions[i].slot; + + if (slot < GLES_MAX_VERTEX_BUFFERS) { + pipeline->strides[slot] = createinfo->vertex_input_state.vertex_buffer_descriptions[i].pitch; + } + } + // Its own vertex array, so the attribute state a draw sets up cannot leak into SDL_Renderer's. + glGenVertexArrays(1, &pipeline->vertexArray); + return (SDL_GPUGraphicsPipeline *)pipeline; +} + + +static SDL_GPUSampler *_glesCreateSampler(SDL_GPUDevice *device, const SDL_GPUSamplerCreateInfo *createinfo) { + GLuint name = 0; + GLenum minification; + + (void)device; + glGenSamplers(1, &name); + if (createinfo->min_filter == SDL_GPU_FILTER_NEAREST) { + minification = createinfo->mipmap_mode == SDL_GPU_SAMPLERMIPMAPMODE_LINEAR ? GL_NEAREST_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST; + } else { + minification = createinfo->mipmap_mode == SDL_GPU_SAMPLERMIPMAPMODE_LINEAR ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR_MIPMAP_NEAREST; + } + glSamplerParameteri(name, GL_TEXTURE_MIN_FILTER, (GLint)minification); + glSamplerParameteri(name, GL_TEXTURE_MAG_FILTER, createinfo->mag_filter == SDL_GPU_FILTER_NEAREST ? GL_NEAREST : GL_LINEAR); + glSamplerParameteri(name, GL_TEXTURE_WRAP_S, (GLint)_addressMode(createinfo->address_mode_u)); + glSamplerParameteri(name, GL_TEXTURE_WRAP_T, (GLint)_addressMode(createinfo->address_mode_v)); + glSamplerParameteri(name, GL_TEXTURE_WRAP_R, (GLint)_addressMode(createinfo->address_mode_w)); + // The LOD clamps are not decoration. SDL_GPU hands max_lod straight to Vulkan's maxLod and + // D3D12's MaxLOD, so a sampler left at zero by a memset samples mip level 0 and nothing else. + // Ignoring them here left GLES walking the whole mip chain while the other backends did not, + // which showed up as Sponza looking softer and read, wrongly, as a lighting fault. + glSamplerParameterf(name, GL_TEXTURE_MIN_LOD, createinfo->min_lod); + glSamplerParameterf(name, GL_TEXTURE_MAX_LOD, createinfo->max_lod); + // mip_lod_bias has no sampler equivalent in GLES -- desktop GL's GL_TEXTURE_LOD_BIAS is not in + // the ES profile, where a bias can only be given per fetch in the shader. Nothing asks for one. + if (createinfo->mip_lod_bias != 0.0f) { + utilTrace("Gles: a sampler asked for a mip LOD bias, which GLES cannot express"); + } + // Anisotropy is an extension in GLES rather than core state, and skipping it is not cosmetic: + // Sponza's floors and walls are seen at grazing angles, where trilinear alone blurs away the + // detail that normal mapping puts there. Leaving this out cost about a third of the + // high-frequency detail on masonry and looked, wrongly, like a lighting fault. + if (createinfo->enable_anisotropy && (_device.maxAnisotropy > 1.0f)) { + GLfloat wanted = (GLfloat)createinfo->max_anisotropy; + + if (wanted > _device.maxAnisotropy) { + wanted = _device.maxAnisotropy; + } + glSamplerParameterf(name, GL_TEXTURE_MAX_ANISOTROPY_EXT, wanted); + } + if (createinfo->enable_compare) { + glSamplerParameteri(name, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + glSamplerParameteri(name, GL_TEXTURE_COMPARE_FUNC, (GLint)_compare(createinfo->compare_op)); + } + return (SDL_GPUSampler *)(uintptr_t)name; +} + + +static SDL_GPUShader *_glesCreateShader(SDL_GPUDevice *device, const SDL_GPUShaderCreateInfo *createinfo) { + GlesShaderT *shader; + const char *source = (const char *)createinfo->code; + GLint ok = 0; + + (void)device; + if ((source == NULL) || (createinfo->format != RGPU_SHADERFORMAT_ESSL)) { + utilTrace("Gles: shader %s has no GLSL ES form", createinfo->entrypoint != NULL ? createinfo->entrypoint : "?"); + return NULL; + } + shader = calloc(1, sizeof(GlesShaderT)); + if (shader == NULL) { + return NULL; + } + shader->stage = createinfo->stage == SDL_GPU_SHADERSTAGE_VERTEX ? GL_VERTEX_SHADER : GL_FRAGMENT_SHADER; + shader->source = SDL_strdup(source); + shader->name = glCreateShader(shader->stage); + glShaderSource(shader->name, 1, &source, NULL); + glCompileShader(shader->name); + glGetShaderiv(shader->name, GL_COMPILE_STATUS, &ok); + if (!ok) { + char log[GLES_LOG_BYTES] = { 0 }; + + glGetShaderInfoLog(shader->name, sizeof(log) - 1, NULL, log); + utilTrace("Gles: shader %s: %s", createinfo->entrypoint != NULL ? createinfo->entrypoint : "?", log); + glDeleteShader(shader->name); + SDL_free(shader->source); + free(shader); + return NULL; + } + return (SDL_GPUShader *)shader; +} + + +static SDL_GPUTexture *_glesCreateTexture(SDL_GPUDevice *device, const SDL_GPUTextureCreateInfo *createinfo) { + GlesTextureT *texture; + GLenum internalFormat; + GLenum layout; + GLenum type; + + (void)device; + _formatToGl(createinfo->format, &internalFormat, &layout, &type); + if (internalFormat == 0) { + utilTrace("Gles: texture format %d is not offered", (int32_t)createinfo->format); + return NULL; + } + texture = calloc(1, sizeof(GlesTextureT)); + if (texture == NULL) { + return NULL; + } + texture->format = createinfo->format; + texture->width = createinfo->width; + texture->height = createinfo->height; + texture->layers = createinfo->layer_count_or_depth; + texture->levels = createinfo->num_levels > 0 ? createinfo->num_levels : 1; + texture->samples = createinfo->sample_count == SDL_GPU_SAMPLECOUNT_1 ? 1 : (createinfo->sample_count == SDL_GPU_SAMPLECOUNT_2 ? 2 : (createinfo->sample_count == SDL_GPU_SAMPLECOUNT_4 ? 4 : 8)); + if (texture->samples > 1) { + // Multisampled targets are renderbuffers here: the engine resolves them and never samples + // them, and a renderbuffer is core where a multisampled texture is not. + texture->target = GL_RENDERBUFFER; + glGenRenderbuffers(1, &texture->renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, texture->renderbuffer); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, (GLsizei)texture->samples, internalFormat, (GLsizei)texture->width, (GLsizei)texture->height); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + return (SDL_GPUTexture *)texture; + } + switch (createinfo->type) { + case SDL_GPU_TEXTURETYPE_CUBE: + texture->target = GL_TEXTURE_CUBE_MAP; + break; + case SDL_GPU_TEXTURETYPE_2D_ARRAY: + case SDL_GPU_TEXTURETYPE_CUBE_ARRAY: + texture->target = GL_TEXTURE_2D_ARRAY; + break; + case SDL_GPU_TEXTURETYPE_3D: + texture->target = GL_TEXTURE_3D; + break; + default: + texture->target = GL_TEXTURE_2D; + break; + } + glGenTextures(1, &texture->name); + glBindTexture(texture->target, texture->name); + if ((texture->target == GL_TEXTURE_2D) || (texture->target == GL_TEXTURE_CUBE_MAP)) { + glTexStorage2D(texture->target, (GLsizei)texture->levels, internalFormat, (GLsizei)texture->width, (GLsizei)texture->height); + } else { + glTexStorage3D(texture->target, (GLsizei)texture->levels, internalFormat, (GLsizei)texture->width, (GLsizei)texture->height, (GLsizei)texture->layers); + } + // A default the engine can rely on; the sampler object overrides all of this per draw. + glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, texture->levels > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR); + glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + return (SDL_GPUTexture *)texture; +} + + +static SDL_GPUTransferBuffer *_glesCreateTransferBuffer(SDL_GPUDevice *device, const SDL_GPUTransferBufferCreateInfo *createinfo) { + GlesTransferT *transfer = calloc(1, sizeof(GlesTransferT)); + + (void)device; + if (transfer == NULL) { + return NULL; + } + transfer->size = createinfo->size; + transfer->data = calloc(1, createinfo->size); + if (transfer->data == NULL) { + free(transfer); + return NULL; + } + return (SDL_GPUTransferBuffer *)transfer; +} + + +static void _glesDestroyDevice(SDL_GPUDevice *device) { + uint32_t i; + + (void)device; + for (i = 0; i < _device.framebufferCount; i++) { + glDeleteFramebuffers(1, &_device.framebuffers[i].name); + } + _device.framebufferCount = 0; + if (_device.scratchRead != 0) { + glDeleteFramebuffers(1, &_device.scratchRead); + _device.scratchRead = 0; + } + if (_device.scratchDraw != 0) { + glDeleteFramebuffers(1, &_device.scratchDraw); + _device.scratchDraw = 0; + } + if (_device.uniformRing != 0) { + glDeleteBuffers(1, &_device.uniformRing); + _device.uniformRing = 0; + } + _device.ready = false; +} + + +static void _glesDrawIndexedPrimitives(SDL_GPURenderPass *renderPass, Uint32 numIndices, Uint32 numInstances, Uint32 firstIndex, Sint32 vertexOffset, Uint32 firstInstance) { + GlesRenderPassT *pass = (GlesRenderPassT *)renderPass; + size_t width = pass->indexType == GL_UNSIGNED_SHORT ? sizeof(uint16_t) : sizeof(uint32_t); + + (void)vertexOffset; + if (pass->pipeline == NULL) { + return; + } + if (pass->pipeline->baseInstance >= 0) { + glUniform1i(pass->pipeline->baseInstance, (GLint)firstInstance); + } + _applyVertexLayout(pass); + _device.commands.issued = true; + glDrawElementsInstanced(_primitive(pass->pipeline->primitive), (GLsizei)numIndices, pass->indexType, (const void *)(uintptr_t)((size_t)firstIndex * width), (GLsizei)numInstances); +} + + +static void _glesDrawPrimitives(SDL_GPURenderPass *renderPass, Uint32 numVertices, Uint32 numInstances, Uint32 firstVertex, Uint32 firstInstance) { + GlesRenderPassT *pass = (GlesRenderPassT *)renderPass; + + if (pass->pipeline == NULL) { + return; + } + if (pass->pipeline->baseInstance >= 0) { + glUniform1i(pass->pipeline->baseInstance, (GLint)firstInstance); + } + _applyVertexLayout(pass); + _device.commands.issued = true; + glDrawArraysInstanced(_primitive(pass->pipeline->primitive), (GLint)firstVertex, (GLsizei)numVertices, (GLsizei)numInstances); +} + + +static void _glesEndCopyPass(SDL_GPUCopyPass *copyPass) { + (void)copyPass; +} + + +// Ending a pass has real work in it: SDL_GPU resolves a multisampled colour target into its +// resolve_texture as part of store_op, and GLES has no such notion, so the resolve is a blit from +// this pass's framebuffer into one holding the resolve target. Leaving it out was not visible as an +// error -- the scene simply rendered into a multisampled renderbuffer that nothing ever read, and +// the post pass tonemapped whatever undefined memory its single-sample source happened to hold. +static void _glesEndRenderPass(SDL_GPURenderPass *renderPass) { + GlesRenderPassT *pass = (GlesRenderPassT *)renderPass; + GLenum error; + uint32_t i; + + for (i = 0; i < pass->colourCount; i++) { + GlesTextureT *resolve = (GlesTextureT *)pass->colour[i].resolve_texture; + + if (((pass->colour[i].store_op != SDL_GPU_STOREOP_RESOLVE) && (pass->colour[i].store_op != SDL_GPU_STOREOP_RESOLVE_AND_STORE)) || (resolve == NULL)) { + continue; + } + _scratchFramebuffer(GL_DRAW_FRAMEBUFFER, resolve, pass->colour[i].resolve_mip_level); + glBindFramebuffer(GL_READ_FRAMEBUFFER, pass->framebuffer); + glReadBuffer(GL_COLOR_ATTACHMENT0 + i); + glDisable(GL_SCISSOR_TEST); + // A multisample resolve must not scale and must not filter, which is why this is NEAREST over + // matching rectangles rather than the general blit above. + glBlitFramebuffer(0, 0, (GLint)pass->width, (GLint)pass->height, 0, 0, (GLint)pass->width, (GLint)pass->height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + } + error = glGetError(); + // One probe a pass. GLES reports asynchronously and has no debug callback in core 3.1, so this + // is the only place a mistake announces itself; without it a bad pass simply draws nothing. + if (error != GL_NO_ERROR) { + utilTrace("Gles: error 0x%04X in a render pass", (uint32_t)error); + } + pass->pipeline = NULL; + pass->colourCount = 0; + glDisable(GL_SCISSOR_TEST); + glBindVertexArray(0); +} + + +static void _glesGenerateMipmapsForTexture(SDL_GPUCommandBuffer *commandBuffer, SDL_GPUTexture *texture) { + GlesTextureT *target = (GlesTextureT *)texture; + + (void)commandBuffer; + if (target->renderbuffer != 0) { + return; + } + glBindTexture(target->target, target->name); + glGenerateMipmap(target->target); +} + + +static const char *_glesGetDeviceDriver(SDL_GPUDevice *device) { + (void)device; + return "opengles"; +} + + +static SDL_GPUShaderFormat _glesGetShaderFormats(SDL_GPUDevice *device) { + (void)device; + return RGPU_SHADERFORMAT_ESSL; +} + + +static SDL_GPUTextureFormat _glesGetSwapchainTextureFormat(SDL_GPUDevice *device, SDL_Window *window) { + (void)device; + (void)window; + return SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; +} + + +static SDL_GPUTextureFormat _glesGetTextureFormatFromPixelFormat(SDL_PixelFormat format) { + // The engine asks this for its composite target, which it creates as BGRA32. Core GLES has no + // BGRA storage, so the texture is RGBA8 and the channel order is settled when pixels are handed + // over; reporting RGBA here keeps that one truth in one place. + switch (format) { + case SDL_PIXELFORMAT_BGRA32: + case SDL_PIXELFORMAT_RGBA32: + return SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; + default: + return SDL_GPU_TEXTUREFORMAT_INVALID; + } +} + + +static void *_glesMapTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer, bool cycle) { + (void)device; + (void)cycle; + return ((GlesTransferT *)transferBuffer)->data; +} + + +static void _glesPushFragmentUniformData(SDL_GPUCommandBuffer *commandBuffer, Uint32 slotIndex, const void *data, Uint32 length) { + (void)commandBuffer; + _glesPushUniformData(GLES_FRAGMENT_UNIFORM_BASE + slotIndex, data, length); +} + + +// SDL_GPU's push uniforms are transient; GLES uniform blocks are objects. One ring buffer stands in +// for both: a slice per push, bound by range. Vertex blocks keep their declared binding and +// fragment blocks were moved up when the pipeline linked, so the two stages cannot collide. +static void _glesPushUniformData(uint32_t binding, const void *data, uint32_t length) { + uint32_t aligned = (uint32_t)_device.uniformAlignment; + uint32_t size = (length + aligned - 1) / aligned * aligned; + + if (_device.uniformRing == 0) { + return; + } + if (_device.uniformOffset + size > GLES_UNIFORM_RING_BYTES) { + // Wrapping inside one frame means overwriting uniforms a pending draw still points at. The + // ring is sized so this does not happen; if it ever does, the frame is wrong and saying so + // once is better than a silently mis-lit scene. + if (!_device.uniformWrapped) { + utilTrace("Gles: the uniform ring wrapped within a frame; draws before the wrap may be wrong"); + _device.uniformWrapped = true; + } + _device.uniformOffset = 0; + } + glBindBuffer(GL_UNIFORM_BUFFER, _device.uniformRing); + glBufferSubData(GL_UNIFORM_BUFFER, (GLintptr)_device.uniformOffset, (GLsizeiptr)length, data); + glBindBufferRange(GL_UNIFORM_BUFFER, binding, _device.uniformRing, (GLintptr)_device.uniformOffset, (GLsizeiptr)size); + _device.uniformOffset += size; +} + + +static void _glesPushVertexUniformData(SDL_GPUCommandBuffer *commandBuffer, Uint32 slotIndex, const void *data, Uint32 length) { + (void)commandBuffer; + _glesPushUniformData(slotIndex, data, length); +} + + +static void _glesReleaseBuffer(SDL_GPUDevice *device, SDL_GPUBuffer *buffer) { + GlesBufferT *target = (GlesBufferT *)buffer; + + (void)device; + if (target == NULL) { + return; + } + glDeleteBuffers(1, &target->name); + free(target); +} + + +static void _glesReleaseGraphicsPipeline(SDL_GPUDevice *device, SDL_GPUGraphicsPipeline *graphicsPipeline) { + GlesPipelineT *pipeline = (GlesPipelineT *)graphicsPipeline; + + (void)device; + if (pipeline == NULL) { + return; + } + glDeleteVertexArrays(1, &pipeline->vertexArray); + glDeleteProgram(pipeline->program); + free(pipeline); +} + + +static void _glesReleaseSampler(SDL_GPUDevice *device, SDL_GPUSampler *sampler) { + GLuint name = (GLuint)(uintptr_t)sampler; + + (void)device; + if (name != 0) { + glDeleteSamplers(1, &name); + } +} + + +static void _glesReleaseShader(SDL_GPUDevice *device, SDL_GPUShader *shader) { + GlesShaderT *target = (GlesShaderT *)shader; + + (void)device; + if (target == NULL) { + return; + } + glDeleteShader(target->name); + SDL_free(target->source); + free(target); +} + + +static void _glesReleaseTexture(SDL_GPUDevice *device, SDL_GPUTexture *texture) { + GlesTextureT *target = (GlesTextureT *)texture; + uint32_t i; + + (void)device; + if (target == NULL) { + return; + } + // A framebuffer holding this texture has to go with it, or the next pass with the same targets + // finds a cached name pointing at freed storage. + for (i = 0; i < _device.framebufferCount; i++) { + GlesFramebufferT *slot = &_device.framebuffers[i]; + bool holds = slot->depth == target; + uint32_t c; + + for (c = 0; c < slot->colourCount; c++) { + if (slot->colour[c] == target) { + holds = true; + } + } + if (holds) { + glDeleteFramebuffers(1, &slot->name); + _device.framebuffers[i] = _device.framebuffers[_device.framebufferCount - 1]; + _device.framebufferCount--; + i--; + } + } + if (target->renderbuffer != 0) { + glDeleteRenderbuffers(1, &target->renderbuffer); + } + if ((target->name != 0) && !target->borrowed) { + glDeleteTextures(1, &target->name); + } + free(target); +} + + +static void _glesReleaseTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer) { + GlesTransferT *transfer = (GlesTransferT *)transferBuffer; + + (void)device; + if (transfer == NULL) { + return; + } + free(transfer->data); + free(transfer); +} + + +static void _glesSetScissor(SDL_GPURenderPass *renderPass, const SDL_Rect *scissor) { + GlesRenderPassT *pass = (GlesRenderPassT *)renderPass; + + if ((scissor->w <= 0) || (scissor->h <= 0)) { + glDisable(GL_SCISSOR_TEST); + return; + } + glEnable(GL_SCISSOR_TEST); + // No Y flip here, which looks wrong and is not. The shaders already carry --flip-vert-y, so the + // top of the picture lands at framebuffer row 0 and SDL_GPU's top-left scissor is measured from + // the same edge GL measures from. Flipping it as well emptied the GUI's scrolling list, which + // was scissored to a rectangle that had moved off its own content. + glScissor(scissor->x, scissor->y, scissor->w, scissor->h); + (void)pass; +} + + +static void _glesSetStencilReference(SDL_GPURenderPass *renderPass, Uint8 reference) { + GlesRenderPassT *pass = (GlesRenderPassT *)renderPass; + + if ((pass->pipeline == NULL) || !pass->pipeline->depthStencil.enable_stencil_test) { + return; + } + glStencilFuncSeparate(GL_FRONT, _compare(pass->pipeline->depthStencil.front_stencil_state.compare_op), reference, pass->pipeline->depthStencil.compare_mask); + glStencilFuncSeparate(GL_BACK, _compare(pass->pipeline->depthStencil.back_stencil_state.compare_op), reference, pass->pipeline->depthStencil.compare_mask); +} + + +static bool _glesSubmitCommandBuffer(SDL_GPUCommandBuffer *commandBuffer) { + (void)commandBuffer; + glFlush(); + return true; +} + + +static bool _glesTextureSupportsFormat(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUTextureType type, SDL_GPUTextureUsageFlags usage) { + GLenum internalFormat; + GLenum layout; + GLenum glType; + + (void)device; + (void)type; + (void)usage; + _formatToGl(format, &internalFormat, &layout, &glType); + return internalFormat != 0; +} + + +static bool _glesTextureSupportsSampleCount(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUSampleCount sampleCount) { + GLenum internalFormat; + GLenum layout; + GLenum type; + GLint samples = 0; + + (void)device; + _formatToGl(format, &internalFormat, &layout, &type); + if (internalFormat == 0) { + return false; + } + if (sampleCount == SDL_GPU_SAMPLECOUNT_1) { + return true; + } + glGetInternalformativ(GL_RENDERBUFFER, internalFormat, GL_SAMPLES, 1, &samples); + return samples >= (sampleCount == SDL_GPU_SAMPLECOUNT_2 ? 2 : (sampleCount == SDL_GPU_SAMPLECOUNT_4 ? 4 : 8)); +} + + +static void _glesUnmapTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transferBuffer) { + (void)device; + (void)transferBuffer; +} + + +static void _glesUploadToBuffer(SDL_GPUCopyPass *copyPass, const SDL_GPUTransferBufferLocation *source, const SDL_GPUBufferRegion *destination, bool cycle) { + GlesTransferT *transfer = (GlesTransferT *)source->transfer_buffer; + GlesBufferT *buffer = (GlesBufferT *)destination->buffer; + + (void)copyPass; + (void)cycle; + glBindBuffer(buffer->target, buffer->name); + glBufferSubData(buffer->target, (GLintptr)destination->offset, (GLsizeiptr)destination->size, transfer->data + source->offset); +} + + +static void _glesUploadToTexture(SDL_GPUCopyPass *copyPass, const SDL_GPUTextureTransferInfo *source, const SDL_GPUTextureRegion *destination, bool cycle) { + GlesTransferT *transfer = (GlesTransferT *)source->transfer_buffer; + GlesTextureT *texture = (GlesTextureT *)destination->texture; + GLenum internalFormat; + GLenum layout; + GLenum type; + + (void)copyPass; + (void)cycle; + _formatToGl(texture->format, &internalFormat, &layout, &type); + glBindTexture(texture->target, texture->name); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + if (texture->target == GL_TEXTURE_2D) { + glTexSubImage2D(GL_TEXTURE_2D, (GLint)destination->mip_level, (GLint)destination->x, (GLint)destination->y, (GLsizei)destination->w, (GLsizei)destination->h, layout, type, transfer->data + source->offset); + } else if (texture->target == GL_TEXTURE_CUBE_MAP) { + glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + destination->layer, (GLint)destination->mip_level, (GLint)destination->x, (GLint)destination->y, (GLsizei)destination->w, (GLsizei)destination->h, layout, type, transfer->data + source->offset); + } else { + glTexSubImage3D(texture->target, (GLint)destination->mip_level, (GLint)destination->x, (GLint)destination->y, (GLint)destination->layer, (GLsizei)destination->w, (GLsizei)destination->h, 1, layout, type, transfer->data + source->offset); + } +} + + +static GLenum _primitive(SDL_GPUPrimitiveType type) { + switch (type) { + case SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP: return GL_TRIANGLE_STRIP; + case SDL_GPU_PRIMITIVETYPE_LINELIST: return GL_LINES; + case SDL_GPU_PRIMITIVETYPE_LINESTRIP: return GL_LINE_STRIP; + case SDL_GPU_PRIMITIVETYPE_POINTLIST: return GL_POINTS; + default: return GL_TRIANGLES; + } +} + + +// HLSL space1 (vertex) and space3 (fragment) both start their uniform blocks at binding 0, and a GLES +// program has one binding namespace, so the two would read the same buffer. The fragment shader's +// blocks are moved up. The names SPIRV-Cross emits are stable ("type_FragmentUniforms"), and the +// source scanned here is the source this build generated, so this reads what it wrote. +static void _remapFragmentBlocks(GLuint program, const char *fragmentSource) { + const char *at = fragmentSource; + + if (fragmentSource == NULL) { + return; + } + while ((at = strstr(at, "std140) uniform ")) != NULL) { + const char *binding = at; + GLuint index; + char name[GLES_NAME_BYTES]; + uint32_t length = 0; + int32_t slot = 0; + + at += strlen("std140) uniform "); + while ((at[length] != '\0') && (at[length] != '\n') && (at[length] != ' ') && (length < sizeof(name) - 1)) { + name[length] = at[length]; + length++; + } + name[length] = '\0'; + // Walk back to this declaration's "binding = N" to learn the slot it was compiled with. + while ((binding > fragmentSource) && (strncmp(binding, "binding = ", strlen("binding = ")) != 0)) { + binding--; + } + if (strncmp(binding, "binding = ", strlen("binding = ")) == 0) { + slot = atoi(binding + strlen("binding = ")); + } + index = glGetUniformBlockIndex(program, name); + if (index != GL_INVALID_INDEX) { + glUniformBlockBinding(program, index, (GLuint)(GLES_FRAGMENT_UNIFORM_BASE + slot)); + } + } +} + + +// Blits, texture-to-texture copies and multisample resolves all want a framebuffer wrapped round one +// texture for the length of one call. Making and destroying an FBO each time is the usual way to +// make a tiler miserable, so two are kept -- one to read through, one to draw into -- and reattached. +static GLuint _scratchFramebuffer(GLenum binding, GlesTextureT *texture, uint32_t level) { + GLuint *name = binding == GL_READ_FRAMEBUFFER ? &_device.scratchRead : &_device.scratchDraw; + + if (*name == 0) { + glGenFramebuffers(1, name); + } + glBindFramebuffer(binding, *name); + glFramebufferTexture2D(binding, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->name, (GLint)level); + return *name; +} + + +static void _setPipelineState(GlesPipelineT *pipeline) { + if (pipeline->blend.enable_blend) { + glEnable(GL_BLEND); + glBlendFuncSeparate(_blendFactor(pipeline->blend.src_color_blendfactor), _blendFactor(pipeline->blend.dst_color_blendfactor), _blendFactor(pipeline->blend.src_alpha_blendfactor), _blendFactor(pipeline->blend.dst_alpha_blendfactor)); + glBlendEquationSeparate(_blendOp(pipeline->blend.color_blend_op), _blendOp(pipeline->blend.alpha_blend_op)); + } else { + glDisable(GL_BLEND); + } + if (pipeline->blend.enable_color_write_mask) { + glColorMask((pipeline->blend.color_write_mask & SDL_GPU_COLORCOMPONENT_R) != 0, (pipeline->blend.color_write_mask & SDL_GPU_COLORCOMPONENT_G) != 0, (pipeline->blend.color_write_mask & SDL_GPU_COLORCOMPONENT_B) != 0, (pipeline->blend.color_write_mask & SDL_GPU_COLORCOMPONENT_A) != 0); + } else { + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + } + if (pipeline->depthStencil.enable_depth_test) { + glEnable(GL_DEPTH_TEST); + glDepthFunc(_compare(pipeline->depthStencil.compare_op)); + } else { + glDisable(GL_DEPTH_TEST); + } + glDepthMask(pipeline->depthStencil.enable_depth_write ? GL_TRUE : GL_FALSE); + if (pipeline->depthStencil.enable_stencil_test) { + glEnable(GL_STENCIL_TEST); + glStencilMaskSeparate(GL_FRONT_AND_BACK, pipeline->depthStencil.write_mask); + glStencilFuncSeparate(GL_FRONT, _compare(pipeline->depthStencil.front_stencil_state.compare_op), 0, pipeline->depthStencil.compare_mask); + glStencilOpSeparate(GL_FRONT, _stencilOp(pipeline->depthStencil.front_stencil_state.fail_op), _stencilOp(pipeline->depthStencil.front_stencil_state.depth_fail_op), _stencilOp(pipeline->depthStencil.front_stencil_state.pass_op)); + glStencilFuncSeparate(GL_BACK, _compare(pipeline->depthStencil.back_stencil_state.compare_op), 0, pipeline->depthStencil.compare_mask); + glStencilOpSeparate(GL_BACK, _stencilOp(pipeline->depthStencil.back_stencil_state.fail_op), _stencilOp(pipeline->depthStencil.back_stencil_state.depth_fail_op), _stencilOp(pipeline->depthStencil.back_stencil_state.pass_op)); + } else { + glDisable(GL_STENCIL_TEST); + } + switch (pipeline->rasterizer.cull_mode) { + case SDL_GPU_CULLMODE_FRONT: + glEnable(GL_CULL_FACE); + glCullFace(GL_FRONT); + break; + case SDL_GPU_CULLMODE_BACK: + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + break; + default: + glDisable(GL_CULL_FACE); + break; + } + // The shaders were cross compiled with --flip-vert-y to put the framebuffer origin where SDL_GPU + // expects it, and that reverses the winding a triangle arrives with. The front face is swapped + // here so the pipeline's own culling still means what it says. + glFrontFace(pipeline->rasterizer.front_face == SDL_GPU_FRONTFACE_CLOCKWISE ? GL_CCW : GL_CW); + if (pipeline->rasterizer.enable_depth_bias) { + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(pipeline->rasterizer.depth_bias_slope_factor, pipeline->rasterizer.depth_bias_constant_factor); + } else { + glDisable(GL_POLYGON_OFFSET_FILL); + } +} + + +static GLenum _stencilOp(SDL_GPUStencilOp op) { + switch (op) { + case SDL_GPU_STENCILOP_ZERO: return GL_ZERO; + case SDL_GPU_STENCILOP_REPLACE: return GL_REPLACE; + case SDL_GPU_STENCILOP_INCREMENT_AND_CLAMP: return GL_INCR; + case SDL_GPU_STENCILOP_DECREMENT_AND_CLAMP: return GL_DECR; + case SDL_GPU_STENCILOP_INVERT: return GL_INVERT; + case SDL_GPU_STENCILOP_INCREMENT_AND_WRAP: return GL_INCR_WRAP; + case SDL_GPU_STENCILOP_DECREMENT_AND_WRAP: return GL_DECR_WRAP; + default: return GL_KEEP; + } +} + + +static void _vertexFormat(SDL_GPUVertexElementFormat format, GLint *size, GLenum *type, GLboolean *normalized) { + *size = 4; + *type = GL_FLOAT; + *normalized = GL_FALSE; + switch (format) { + case SDL_GPU_VERTEXELEMENTFORMAT_FLOAT: + *size = 1; + break; + case SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2: + *size = 2; + break; + case SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3: + *size = 3; + break; + case SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4: + *size = 4; + break; + case SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4: + *size = 4; + *type = GL_UNSIGNED_BYTE; + break; + case SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4_NORM: + *size = 4; + *type = GL_UNSIGNED_BYTE; + *normalized = GL_TRUE; + break; + case SDL_GPU_VERTEXELEMENTFORMAT_INT: + *size = 1; + *type = GL_INT; + break; + case SDL_GPU_VERTEXELEMENTFORMAT_UINT: + *size = 1; + *type = GL_UNSIGNED_INT; + break; + default: + break; + } +} + + +// Leave the context as it was found. render.c calls this after every frame's drawing: SDL's own +// cached state was invalidated by SDL_FlushRenderer on the way in, but the bindings below are +// container state SDL does not track, so they are put back by hand. +void renderGlesRestore(void) { + uint32_t unit; + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + glUseProgram(0); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_CULL_FACE); + glDepthMask(GL_TRUE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + // Every unit, not just the first. A sampler object left bound to a unit overrides the texture's + // own parameters, so one left behind changes how SDL_Renderer filters everything it draws there. + for (unit = 0; unit < GLES_MAX_SAMPLERS; unit++) { + glBindSampler(unit, 0); + } + glActiveTexture(GL_TEXTURE0); + // Uploads here set the alignment to 1 for tightly packed rows; SDL's default is 4 and its own + // uploads assume it. + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); +} + + +// Stand the backend up inside the context SDL_Renderer already made. Fails, rather than half +// starts, when the context is not GLES 3.1 or the uniform ring cannot be built. +bool renderGlesStart(void) { + const char *version; + GLint major = 0; + GLint minor = 0; + + memset(&_device, 0, sizeof(_device)); + if (SDL_GL_GetCurrentContext() == NULL) { + utilTrace("Gles: no GL context; the renderer is not an OpenGL one"); + return false; + } + // Nothing below may touch GL until the table is filled; an initialiser on the declaration above + // ran glGetString through a null pointer and took the engine with it. + if (!renderGlesLoad()) { + return false; + } + version = (const char *)glGetString(GL_VERSION); + // It has to be ES, not desktop GL. On a desktop the 2D fallback renderer is usually the "opengl" + // one, whose context would pass the version test below and then reject every "#version 310 es" + // shader. The shaders are the ES ones or nothing, so the context must match them. + if ((version == NULL) || (strncmp(version, "OpenGL ES", strlen("OpenGL ES")) != 0)) { + utilTrace("Gles: the context is %s, not OpenGL ES", version != NULL ? version : "unknown"); + return false; + } + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + if ((major < 3) || ((major == 3) && (minor < 1))) { + utilTrace("Gles: %s is below the OpenGL ES 3.1 the scene needs", version); + return false; + } + { + const char *extensions = (const char *)glGetString(GL_EXTENSIONS); + + if ((extensions != NULL) && (strstr(extensions, "GL_EXT_texture_filter_anisotropic") != NULL)) { + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &_device.maxAnisotropy); + } + utilTrace("Gles: anisotropy %s", _device.maxAnisotropy > 1.0f ? "available" : "not offered"); + } + glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &_device.uniformAlignment); + if (_device.uniformAlignment <= 0) { + _device.uniformAlignment = 256; + } + glGenBuffers(1, &_device.uniformRing); + glBindBuffer(GL_UNIFORM_BUFFER, _device.uniformRing); + glBufferData(GL_UNIFORM_BUFFER, GLES_UNIFORM_RING_BYTES, NULL, GL_STREAM_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + while (glGetError() != GL_NO_ERROR) { + } + _device.ready = true; + utilTrace("Render: OpenGL ES ready (%s)", version); + return true; +} + + +// The composite seam, GLES side: the GL name behind one of our textures, and a texture wrapped +// round a name SDL made. render.c uses these to hand pictures to SDL_Renderer and back. +Uint32 renderGlesTextureName(SDL_GPUTexture *texture) { + GlesTextureT *target = (GlesTextureT *)texture; + + return target != NULL ? target->name : 0; +} + + +SDL_GPUTexture *renderGlesTextureWrap(Uint32 name, int32_t width, int32_t height) { + GlesTextureT *texture; + + if (name == 0) { + return NULL; + } + texture = calloc(1, sizeof(GlesTextureT)); + if (texture == NULL) { + return NULL; + } + texture->name = name; + texture->target = GL_TEXTURE_2D; + texture->format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; + texture->width = (uint32_t)width; + texture->height = (uint32_t)height; + texture->layers = 1; + texture->levels = 1; + // Borrowed, not owned. SDL made this name and SDL will delete it; releasing the wrapper must + // free only the wrapper. Deleting it here handed the name back to GL, which reissued it for a + // texture of another type, and the next bind of it failed with INVALID_OPERATION. + texture->borrowed = true; + // SDL made this one with a single level and no mipmap chain. The scene's material sampler asks + // for GL_LINEAR_MIPMAP_LINEAR, and a texture whose chain does not reach the last level is + // *incomplete*; GLES samples an incomplete texture as (0, 0, 0, 1) rather than complaining, so + // the video on the arcade cabinet's screen was simply black while every texture the engine made + // itself -- built with glTexStorage2D, which allocates the whole chain -- was fine. + // + // Confirmed rather than assumed: forcing a non-mipmap min filter on the sampler instead fixes + // it identically (scene8 1.61 -> 0.36 either way). Capping the level count is the one to keep, + // because the sampler is shared with textures that do want their mipmaps, and SDL only ever + // uses level 0 of this one. + glBindTexture(GL_TEXTURE_2D, name); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + return (SDL_GPUTexture *)texture; +} + + +const RenderBackendT renderGlesBackend = { + .acquireCommandBuffer = _glesAcquireCommandBuffer, + .beginCopyPass = _glesBeginCopyPass, + .beginRenderPass = _glesBeginRenderPass, + .bindFragmentSamplers = _glesBindFragmentSamplers, + .bindGraphicsPipeline = _glesBindGraphicsPipeline, + .bindIndexBuffer = _glesBindIndexBuffer, + .bindVertexBuffers = _glesBindVertexBuffers, + .bindVertexStorageBuffers = _glesBindVertexStorageBuffers, + .blitTexture = _glesBlitTexture, + .cancelCommandBuffer = _glesCancelCommandBuffer, + .copyTextureToTexture = _glesCopyTextureToTexture, + .createBuffer = _glesCreateBuffer, + .createDevice = _glesCreateDevice, + .createGraphicsPipeline = _glesCreateGraphicsPipeline, + .createSampler = _glesCreateSampler, + .createShader = _glesCreateShader, + .createTexture = _glesCreateTexture, + .createTransferBuffer = _glesCreateTransferBuffer, + .destroyDevice = _glesDestroyDevice, + .drawIndexedPrimitives = _glesDrawIndexedPrimitives, + .drawPrimitives = _glesDrawPrimitives, + .endCopyPass = _glesEndCopyPass, + .endRenderPass = _glesEndRenderPass, + .generateMipmapsForTexture = _glesGenerateMipmapsForTexture, + .getDeviceDriver = _glesGetDeviceDriver, + .getShaderFormats = _glesGetShaderFormats, + .getSwapchainTextureFormat = _glesGetSwapchainTextureFormat, + .getTextureFormatFromPixelFormat = _glesGetTextureFormatFromPixelFormat, + .mapTransferBuffer = _glesMapTransferBuffer, + .pushFragmentUniformData = _glesPushFragmentUniformData, + .pushVertexUniformData = _glesPushVertexUniformData, + .releaseBuffer = _glesReleaseBuffer, + .releaseGraphicsPipeline = _glesReleaseGraphicsPipeline, + .releaseSampler = _glesReleaseSampler, + .releaseShader = _glesReleaseShader, + .releaseTexture = _glesReleaseTexture, + .releaseTransferBuffer = _glesReleaseTransferBuffer, + .setScissor = _glesSetScissor, + .setStencilReference = _glesSetStencilReference, + .submitCommandBuffer = _glesSubmitCommandBuffer, + .textureSupportsFormat = _glesTextureSupportsFormat, + .textureSupportsSampleCount = _glesTextureSupportsSampleCount, + .unmapTransferBuffer = _glesUnmapTransferBuffer, + .uploadToBuffer = _glesUploadToBuffer, + .uploadToTexture = _glesUploadToTexture, +}; diff --git a/src/renderGlesApi.h b/src/renderGlesApi.h new file mode 100644 index 000000000..6d2a189ae --- /dev/null +++ b/src/renderGlesApi.h @@ -0,0 +1,486 @@ +/* + * + * 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. + * + */ + +#ifndef RENDER_GLES_API_H +#define RENDER_GLES_API_H + +// The OpenGL ES entry points renderGles.c uses: declared here, loaded through SDL, linked never. +// +// Nothing in this file includes a GL header. Linking -lGLESv2 would break the Windows and macOS +// builds, and merely *including* breaks them too -- neither platform ships it, and +// the first cross build after this backend landed failed on all three non-Linux targets for exactly +// that reason. Since every function is fetched at run time through SDL_GL_GetProcAddress anyway, +// the types and enumerants below are all the declaration that is needed, and a machine with no GLES +// driver simply fails renderGlesStart and keeps its 2D renderer. +// +// The lists are exactly what renderGles.c uses, so anything it gains that is not here fails to +// compile or link rather than crashing on a null pointer. + +#include +#include +#include +#include + + +// The Khronos types, in the one spelling every ES implementation agrees on. +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef signed char GLbyte; +typedef short GLshort; +typedef int GLint; +typedef int GLsizei; +typedef unsigned char GLubyte; +typedef unsigned short GLushort; +typedef unsigned int GLuint; +typedef float GLfloat; +typedef char GLchar; +typedef void GLvoid; +typedef intptr_t GLintptr; +typedef intptr_t GLsizeiptr; + +#define GL_APIENTRYP * + + +// The enumerants used, and only those. +#define GL_ALWAYS 0x0207 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_BACK 0x0405 +#define GL_BLEND 0x0BE2 +#define GL_CCW 0x0901 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_COLOR 0x1800 +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_COMPARE_REF_TO_TEXTURE 0x884E +#define GL_COMPILE_STATUS 0x8B81 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_CULL_FACE 0x0B44 +#define GL_CW 0x0900 +#define GL_DECR 0x1E03 +#define GL_DECR_WRAP 0x8508 +#define GL_DEPTH24_STENCIL8 0x88F0 +#define GL_DEPTH32F_STENCIL8 0x8CAD +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32F 0x8CAC +#define GL_DEPTH_STENCIL 0x84F9 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GL_DEPTH_TEST 0x0B71 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_DST_ALPHA 0x0304 +#define GL_DST_COLOR 0x0306 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_EQUAL 0x0202 +#define GL_FALSE 0 +#define GL_FLOAT 0x1406 +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRONT 0x0404 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_FUNC_ADD 0x8006 +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_FUNC_SUBTRACT 0x800A +#define GL_GEQUAL 0x0206 +#define GL_GREATER 0x0204 +#define GL_HALF_FLOAT 0x140B +#define GL_INCR 0x1E02 +#define GL_INCR_WRAP 0x8507 +#define GL_INT 0x1404 +#define GL_INVALID_INDEX 0xFFFFFFFFu +#define GL_INVERT 0x150A +#define GL_KEEP 0x1E00 +#define GL_LEQUAL 0x0203 +#define GL_LESS 0x0201 +#define GL_LINEAR 0x2601 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_LINES 0x0001 +#define GL_LINE_STRIP 0x0003 +#define GL_LINK_STATUS 0x8B82 +#define GL_MAJOR_VERSION 0x821B +#define GL_MAX 0x8008 +#define GL_MIN 0x8007 +#define GL_MINOR_VERSION 0x821C +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_NEAREST 0x2600 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_NEVER 0x0200 +#define GL_NONE 0 +#define GL_NOTEQUAL 0x0205 +#define GL_NO_ERROR 0 +#define GL_ONE 1 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_POINTS 0x0000 +#define GL_POLYGON_OFFSET_FILL 0x8037 +#define GL_R8 0x8229 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_RED 0x1903 +#define GL_RENDERBUFFER 0x8D41 +#define GL_REPEAT 0x2901 +#define GL_REPLACE 0x1E01 +#define GL_RGBA 0x1908 +#define GL_RGBA16F 0x881A +#define GL_RGBA32F 0x8814 +#define GL_RGBA8 0x8058 +#define GL_SAMPLES 0x80A9 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_SHADER_STORAGE_BUFFER 0x90D2 +#define GL_SRC_ALPHA 0x0302 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_SRC_COLOR 0x0300 +#define GL_SRGB8_ALPHA8 0x8C43 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_STENCIL_TEST 0x0B90 +#define GL_STREAM_DRAW 0x88E0 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_TEXTURE_3D 0x806F +#define GL_EXTENSIONS 0x1F03 +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRUE 1 +#define GL_UNIFORM_BUFFER 0x8A11 +#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_UNSIGNED_INT 0x1405 +#define GL_UNSIGNED_INT_24_8 0x84FA +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_VERSION 0x1F02 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_ZERO 0 + + +typedef void (GL_APIENTRYP GlesActiveTextureFn)(GLenum texture); +typedef void (GL_APIENTRYP GlesAttachShaderFn)(GLuint program, GLuint shader); +typedef void (GL_APIENTRYP GlesBindBufferFn)(GLenum target, GLuint buffer); +typedef void (GL_APIENTRYP GlesBindBufferBaseFn)(GLenum target, GLuint index, GLuint buffer); +typedef void (GL_APIENTRYP GlesBindBufferRangeFn)(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (GL_APIENTRYP GlesBindFramebufferFn)(GLenum target, GLuint framebuffer); +typedef void (GL_APIENTRYP GlesBindRenderbufferFn)(GLenum target, GLuint renderbuffer); +typedef void (GL_APIENTRYP GlesBindSamplerFn)(GLuint unit, GLuint sampler); +typedef void (GL_APIENTRYP GlesBindTextureFn)(GLenum target, GLuint texture); +typedef void (GL_APIENTRYP GlesBindVertexArrayFn)(GLuint array); +typedef void (GL_APIENTRYP GlesBlendEquationSeparateFn)(GLenum modeRGB, GLenum modeAlpha); +typedef void (GL_APIENTRYP GlesBlendFuncSeparateFn)(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (GL_APIENTRYP GlesBlitFramebufferFn)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef void (GL_APIENTRYP GlesBufferDataFn)(GLenum target, GLsizeiptr size, const void *data, GLenum usage); +typedef void (GL_APIENTRYP GlesBufferSubDataFn)(GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +typedef GLenum (GL_APIENTRYP GlesCheckFramebufferStatusFn)(GLenum target); +typedef void (GL_APIENTRYP GlesClearFn)(GLbitfield mask); +typedef void (GL_APIENTRYP GlesClearBufferfvFn)(GLenum buffer, GLint drawbuffer, const GLfloat *value); +typedef void (GL_APIENTRYP GlesClearDepthfFn)(GLfloat d); +typedef void (GL_APIENTRYP GlesClearStencilFn)(GLint s); +typedef void (GL_APIENTRYP GlesColorMaskFn)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +typedef void (GL_APIENTRYP GlesCompileShaderFn)(GLuint shader); +typedef void (GL_APIENTRYP GlesCopyTexSubImage2DFn)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef GLuint (GL_APIENTRYP GlesCreateProgramFn)(void); +typedef GLuint (GL_APIENTRYP GlesCreateShaderFn)(GLenum type); +typedef void (GL_APIENTRYP GlesCullFaceFn)(GLenum mode); +typedef void (GL_APIENTRYP GlesDeleteBuffersFn)(GLsizei n, const GLuint *buffers); +typedef void (GL_APIENTRYP GlesDeleteFramebuffersFn)(GLsizei n, const GLuint *framebuffers); +typedef void (GL_APIENTRYP GlesDeleteProgramFn)(GLuint program); +typedef void (GL_APIENTRYP GlesDeleteRenderbuffersFn)(GLsizei n, const GLuint *renderbuffers); +typedef void (GL_APIENTRYP GlesDeleteSamplersFn)(GLsizei count, const GLuint *samplers); +typedef void (GL_APIENTRYP GlesDeleteShaderFn)(GLuint shader); +typedef void (GL_APIENTRYP GlesDeleteTexturesFn)(GLsizei n, const GLuint *textures); +typedef void (GL_APIENTRYP GlesDeleteVertexArraysFn)(GLsizei n, const GLuint *arrays); +typedef void (GL_APIENTRYP GlesDepthFuncFn)(GLenum func); +typedef void (GL_APIENTRYP GlesDepthMaskFn)(GLboolean flag); +typedef void (GL_APIENTRYP GlesDisableFn)(GLenum cap); +typedef void (GL_APIENTRYP GlesDrawArraysInstancedFn)(GLenum mode, GLint first, GLsizei count, GLsizei instancecount); +typedef void (GL_APIENTRYP GlesDrawBuffersFn)(GLsizei n, const GLenum *bufs); +typedef void (GL_APIENTRYP GlesDrawElementsInstancedFn)(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount); +typedef void (GL_APIENTRYP GlesEnableFn)(GLenum cap); +typedef void (GL_APIENTRYP GlesEnableVertexAttribArrayFn)(GLuint index); +typedef void (GL_APIENTRYP GlesFlushFn)(void); +typedef void (GL_APIENTRYP GlesFramebufferRenderbufferFn)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (GL_APIENTRYP GlesFramebufferTexture2DFn)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (GL_APIENTRYP GlesFramebufferTextureLayerFn)(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (GL_APIENTRYP GlesFrontFaceFn)(GLenum mode); +typedef void (GL_APIENTRYP GlesGenBuffersFn)(GLsizei n, GLuint *buffers); +typedef void (GL_APIENTRYP GlesGenFramebuffersFn)(GLsizei n, GLuint *framebuffers); +typedef void (GL_APIENTRYP GlesGenRenderbuffersFn)(GLsizei n, GLuint *renderbuffers); +typedef void (GL_APIENTRYP GlesGenSamplersFn)(GLsizei count, GLuint *samplers); +typedef void (GL_APIENTRYP GlesGenTexturesFn)(GLsizei n, GLuint *textures); +typedef void (GL_APIENTRYP GlesGenVertexArraysFn)(GLsizei n, GLuint *arrays); +typedef void (GL_APIENTRYP GlesGenerateMipmapFn)(GLenum target); +typedef GLenum (GL_APIENTRYP GlesGetErrorFn)(void); +typedef void (GL_APIENTRYP GlesGetIntegervFn)(GLenum pname, GLint *data); +typedef void (GL_APIENTRYP GlesGetInternalformativFn)(GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint *params); +typedef void (GL_APIENTRYP GlesGetProgramInfoLogFn)(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (GL_APIENTRYP GlesGetProgramivFn)(GLuint program, GLenum pname, GLint *params); +typedef void (GL_APIENTRYP GlesGetShaderInfoLogFn)(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (GL_APIENTRYP GlesGetShaderivFn)(GLuint shader, GLenum pname, GLint *params); +typedef const GLubyte * (GL_APIENTRYP GlesGetStringFn)(GLenum name); +typedef GLuint (GL_APIENTRYP GlesGetUniformBlockIndexFn)(GLuint program, const GLchar *uniformBlockName); +typedef void (GL_APIENTRYP GlesLinkProgramFn)(GLuint program); +typedef void (GL_APIENTRYP GlesPixelStoreiFn)(GLenum pname, GLint param); +typedef void (GL_APIENTRYP GlesPolygonOffsetFn)(GLfloat factor, GLfloat units); +typedef void (GL_APIENTRYP GlesReadBufferFn)(GLenum src); +typedef void (GL_APIENTRYP GlesRenderbufferStorageMultisampleFn)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP GlesGetFloatvFn)(GLenum pname, GLfloat *data); +typedef GLint (GL_APIENTRYP GlesGetUniformLocationFn)(GLuint program, const GLchar *name); +typedef void (GL_APIENTRYP GlesUniform1iFn)(GLint location, GLint v0); +typedef void (GL_APIENTRYP GlesSamplerParameterfFn)(GLuint sampler, GLenum pname, GLfloat param); +typedef void (GL_APIENTRYP GlesSamplerParameteriFn)(GLuint sampler, GLenum pname, GLint param); +typedef void (GL_APIENTRYP GlesScissorFn)(GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP GlesShaderSourceFn)(GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +typedef void (GL_APIENTRYP GlesStencilFuncSeparateFn)(GLenum face, GLenum func, GLint ref, GLuint mask); +typedef void (GL_APIENTRYP GlesStencilMaskFn)(GLuint mask); +typedef void (GL_APIENTRYP GlesStencilMaskSeparateFn)(GLenum face, GLuint mask); +typedef void (GL_APIENTRYP GlesStencilOpSeparateFn)(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (GL_APIENTRYP GlesTexParameteriFn)(GLenum target, GLenum pname, GLint param); +typedef void (GL_APIENTRYP GlesTexStorage2DFn)(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (GL_APIENTRYP GlesTexStorage3DFn)(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +typedef void (GL_APIENTRYP GlesTexSubImage2DFn)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (GL_APIENTRYP GlesTexSubImage3DFn)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +typedef void (GL_APIENTRYP GlesUniformBlockBindingFn)(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +typedef void (GL_APIENTRYP GlesUseProgramFn)(GLuint program); +typedef void (GL_APIENTRYP GlesValidateProgramFn)(GLuint program); +typedef void (GL_APIENTRYP GlesVertexAttribIPointerFn)(GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (GL_APIENTRYP GlesVertexAttribPointerFn)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +typedef void (GL_APIENTRYP GlesViewportFn)(GLint x, GLint y, GLsizei width, GLsizei height); + + +typedef struct GlesApiS { + GlesActiveTextureFn activeTexture; + GlesAttachShaderFn attachShader; + GlesBindBufferFn bindBuffer; + GlesBindBufferBaseFn bindBufferBase; + GlesBindBufferRangeFn bindBufferRange; + GlesBindFramebufferFn bindFramebuffer; + GlesBindRenderbufferFn bindRenderbuffer; + GlesBindSamplerFn bindSampler; + GlesBindTextureFn bindTexture; + GlesBindVertexArrayFn bindVertexArray; + GlesBlendEquationSeparateFn blendEquationSeparate; + GlesBlendFuncSeparateFn blendFuncSeparate; + GlesBlitFramebufferFn blitFramebuffer; + GlesBufferDataFn bufferData; + GlesBufferSubDataFn bufferSubData; + GlesCheckFramebufferStatusFn checkFramebufferStatus; + GlesClearFn clear; + GlesClearBufferfvFn clearBufferfv; + GlesClearDepthfFn clearDepthf; + GlesClearStencilFn clearStencil; + GlesColorMaskFn colorMask; + GlesCompileShaderFn compileShader; + GlesCopyTexSubImage2DFn copyTexSubImage2D; + GlesCreateProgramFn createProgram; + GlesCreateShaderFn createShader; + GlesCullFaceFn cullFace; + GlesDeleteBuffersFn deleteBuffers; + GlesDeleteFramebuffersFn deleteFramebuffers; + GlesDeleteProgramFn deleteProgram; + GlesDeleteRenderbuffersFn deleteRenderbuffers; + GlesDeleteSamplersFn deleteSamplers; + GlesDeleteShaderFn deleteShader; + GlesDeleteTexturesFn deleteTextures; + GlesDeleteVertexArraysFn deleteVertexArrays; + GlesDepthFuncFn depthFunc; + GlesDepthMaskFn depthMask; + GlesDisableFn disable; + GlesDrawArraysInstancedFn drawArraysInstanced; + GlesDrawBuffersFn drawBuffers; + GlesDrawElementsInstancedFn drawElementsInstanced; + GlesEnableFn enable; + GlesEnableVertexAttribArrayFn enableVertexAttribArray; + GlesFlushFn flush; + GlesFramebufferRenderbufferFn framebufferRenderbuffer; + GlesFramebufferTexture2DFn framebufferTexture2D; + GlesFramebufferTextureLayerFn framebufferTextureLayer; + GlesFrontFaceFn frontFace; + GlesGenBuffersFn genBuffers; + GlesGenFramebuffersFn genFramebuffers; + GlesGenRenderbuffersFn genRenderbuffers; + GlesGenSamplersFn genSamplers; + GlesGenTexturesFn genTextures; + GlesGenVertexArraysFn genVertexArrays; + GlesGenerateMipmapFn generateMipmap; + GlesGetErrorFn getError; + GlesGetIntegervFn getIntegerv; + GlesGetInternalformativFn getInternalformativ; + GlesGetProgramInfoLogFn getProgramInfoLog; + GlesGetProgramivFn getProgramiv; + GlesGetShaderInfoLogFn getShaderInfoLog; + GlesGetShaderivFn getShaderiv; + GlesGetStringFn getString; + GlesGetUniformBlockIndexFn getUniformBlockIndex; + GlesLinkProgramFn linkProgram; + GlesPixelStoreiFn pixelStorei; + GlesPolygonOffsetFn polygonOffset; + GlesReadBufferFn readBuffer; + GlesRenderbufferStorageMultisampleFn renderbufferStorageMultisample; + GlesGetFloatvFn getFloatv; + GlesGetUniformLocationFn getUniformLocation; + GlesUniform1iFn uniform1i; + GlesSamplerParameterfFn samplerParameterf; + GlesSamplerParameteriFn samplerParameteri; + GlesScissorFn scissor; + GlesShaderSourceFn shaderSource; + GlesStencilFuncSeparateFn stencilFuncSeparate; + GlesStencilMaskFn stencilMask; + GlesStencilMaskSeparateFn stencilMaskSeparate; + GlesStencilOpSeparateFn stencilOpSeparate; + GlesTexParameteriFn texParameteri; + GlesTexStorage2DFn texStorage2D; + GlesTexStorage3DFn texStorage3D; + GlesTexSubImage2DFn texSubImage2D; + GlesTexSubImage3DFn texSubImage3D; + GlesUniformBlockBindingFn uniformBlockBinding; + GlesUseProgramFn useProgram; + GlesValidateProgramFn validateProgram; + GlesVertexAttribIPointerFn vertexAttribIPointer; + GlesVertexAttribPointerFn vertexAttribPointer; + GlesViewportFn viewport; +} GlesApiT; + + +extern GlesApiT _glesApi; + + +bool renderGlesLoad(void); + + +// Every call in renderGles.c goes through the table. +#define glActiveTexture _glesApi.activeTexture +#define glAttachShader _glesApi.attachShader +#define glBindBuffer _glesApi.bindBuffer +#define glBindBufferBase _glesApi.bindBufferBase +#define glBindBufferRange _glesApi.bindBufferRange +#define glBindFramebuffer _glesApi.bindFramebuffer +#define glBindRenderbuffer _glesApi.bindRenderbuffer +#define glBindSampler _glesApi.bindSampler +#define glBindTexture _glesApi.bindTexture +#define glBindVertexArray _glesApi.bindVertexArray +#define glBlendEquationSeparate _glesApi.blendEquationSeparate +#define glBlendFuncSeparate _glesApi.blendFuncSeparate +#define glBlitFramebuffer _glesApi.blitFramebuffer +#define glBufferData _glesApi.bufferData +#define glBufferSubData _glesApi.bufferSubData +#define glCheckFramebufferStatus _glesApi.checkFramebufferStatus +#define glClear _glesApi.clear +#define glClearBufferfv _glesApi.clearBufferfv +#define glClearDepthf _glesApi.clearDepthf +#define glClearStencil _glesApi.clearStencil +#define glColorMask _glesApi.colorMask +#define glCompileShader _glesApi.compileShader +#define glCopyTexSubImage2D _glesApi.copyTexSubImage2D +#define glCreateProgram _glesApi.createProgram +#define glCreateShader _glesApi.createShader +#define glCullFace _glesApi.cullFace +#define glDeleteBuffers _glesApi.deleteBuffers +#define glDeleteFramebuffers _glesApi.deleteFramebuffers +#define glDeleteProgram _glesApi.deleteProgram +#define glDeleteRenderbuffers _glesApi.deleteRenderbuffers +#define glDeleteSamplers _glesApi.deleteSamplers +#define glDeleteShader _glesApi.deleteShader +#define glDeleteTextures _glesApi.deleteTextures +#define glDeleteVertexArrays _glesApi.deleteVertexArrays +#define glDepthFunc _glesApi.depthFunc +#define glDepthMask _glesApi.depthMask +#define glDisable _glesApi.disable +#define glDrawArraysInstanced _glesApi.drawArraysInstanced +#define glDrawBuffers _glesApi.drawBuffers +#define glDrawElementsInstanced _glesApi.drawElementsInstanced +#define glEnable _glesApi.enable +#define glEnableVertexAttribArray _glesApi.enableVertexAttribArray +#define glFlush _glesApi.flush +#define glFramebufferRenderbuffer _glesApi.framebufferRenderbuffer +#define glFramebufferTexture2D _glesApi.framebufferTexture2D +#define glFramebufferTextureLayer _glesApi.framebufferTextureLayer +#define glFrontFace _glesApi.frontFace +#define glGenBuffers _glesApi.genBuffers +#define glGenFramebuffers _glesApi.genFramebuffers +#define glGenRenderbuffers _glesApi.genRenderbuffers +#define glGenSamplers _glesApi.genSamplers +#define glGenTextures _glesApi.genTextures +#define glGenVertexArrays _glesApi.genVertexArrays +#define glGenerateMipmap _glesApi.generateMipmap +#define glGetError _glesApi.getError +#define glGetIntegerv _glesApi.getIntegerv +#define glGetInternalformativ _glesApi.getInternalformativ +#define glGetProgramInfoLog _glesApi.getProgramInfoLog +#define glGetProgramiv _glesApi.getProgramiv +#define glGetShaderInfoLog _glesApi.getShaderInfoLog +#define glGetShaderiv _glesApi.getShaderiv +#define glGetString _glesApi.getString +#define glGetUniformBlockIndex _glesApi.getUniformBlockIndex +#define glLinkProgram _glesApi.linkProgram +#define glPixelStorei _glesApi.pixelStorei +#define glPolygonOffset _glesApi.polygonOffset +#define glReadBuffer _glesApi.readBuffer +#define glRenderbufferStorageMultisample _glesApi.renderbufferStorageMultisample +#define glGetFloatv _glesApi.getFloatv +#define glGetUniformLocation _glesApi.getUniformLocation +#define glUniform1i _glesApi.uniform1i +#define glSamplerParameterf _glesApi.samplerParameterf +#define glSamplerParameteri _glesApi.samplerParameteri +#define glScissor _glesApi.scissor +#define glShaderSource _glesApi.shaderSource +#define glStencilFuncSeparate _glesApi.stencilFuncSeparate +#define glStencilMask _glesApi.stencilMask +#define glStencilMaskSeparate _glesApi.stencilMaskSeparate +#define glStencilOpSeparate _glesApi.stencilOpSeparate +#define glTexParameteri _glesApi.texParameteri +#define glTexStorage2D _glesApi.texStorage2D +#define glTexStorage3D _glesApi.texStorage3D +#define glTexSubImage2D _glesApi.texSubImage2D +#define glTexSubImage3D _glesApi.texSubImage3D +#define glUniformBlockBinding _glesApi.uniformBlockBinding +#define glUseProgram _glesApi.useProgram +#define glValidateProgram _glesApi.validateProgram +#define glVertexAttribIPointer _glesApi.vertexAttribIPointer +#define glVertexAttribPointer _glesApi.vertexAttribPointer +#define glViewport _glesApi.viewport + + +#endif // RENDER_GLES_API_H diff --git a/src/renderGlesLoad.c b/src/renderGlesLoad.c new file mode 100644 index 000000000..e07528498 --- /dev/null +++ b/src/renderGlesLoad.c @@ -0,0 +1,230 @@ +/* + * + * 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. + * + */ + +// Loading the GLES entry points. See renderGlesApi.h for why they are fetched rather than linked. + +#include "renderGlesApi.h" +#include "util.h" + + +GlesApiT _glesApi; + + +// Fails on the first missing entry point rather than half loading: a null here would be a crash +// later, inside a draw call, with nothing to say which function was absent. +bool renderGlesLoad(void) { + _glesApi.activeTexture = (GlesActiveTextureFn)SDL_GL_GetProcAddress("glActiveTexture"); + _glesApi.attachShader = (GlesAttachShaderFn)SDL_GL_GetProcAddress("glAttachShader"); + _glesApi.bindBuffer = (GlesBindBufferFn)SDL_GL_GetProcAddress("glBindBuffer"); + _glesApi.bindBufferBase = (GlesBindBufferBaseFn)SDL_GL_GetProcAddress("glBindBufferBase"); + _glesApi.bindBufferRange = (GlesBindBufferRangeFn)SDL_GL_GetProcAddress("glBindBufferRange"); + _glesApi.bindFramebuffer = (GlesBindFramebufferFn)SDL_GL_GetProcAddress("glBindFramebuffer"); + _glesApi.bindRenderbuffer = (GlesBindRenderbufferFn)SDL_GL_GetProcAddress("glBindRenderbuffer"); + _glesApi.bindSampler = (GlesBindSamplerFn)SDL_GL_GetProcAddress("glBindSampler"); + _glesApi.bindTexture = (GlesBindTextureFn)SDL_GL_GetProcAddress("glBindTexture"); + _glesApi.bindVertexArray = (GlesBindVertexArrayFn)SDL_GL_GetProcAddress("glBindVertexArray"); + _glesApi.blendEquationSeparate = (GlesBlendEquationSeparateFn)SDL_GL_GetProcAddress("glBlendEquationSeparate"); + _glesApi.blendFuncSeparate = (GlesBlendFuncSeparateFn)SDL_GL_GetProcAddress("glBlendFuncSeparate"); + _glesApi.blitFramebuffer = (GlesBlitFramebufferFn)SDL_GL_GetProcAddress("glBlitFramebuffer"); + _glesApi.bufferData = (GlesBufferDataFn)SDL_GL_GetProcAddress("glBufferData"); + _glesApi.bufferSubData = (GlesBufferSubDataFn)SDL_GL_GetProcAddress("glBufferSubData"); + _glesApi.checkFramebufferStatus = (GlesCheckFramebufferStatusFn)SDL_GL_GetProcAddress("glCheckFramebufferStatus"); + _glesApi.clear = (GlesClearFn)SDL_GL_GetProcAddress("glClear"); + _glesApi.clearBufferfv = (GlesClearBufferfvFn)SDL_GL_GetProcAddress("glClearBufferfv"); + _glesApi.clearDepthf = (GlesClearDepthfFn)SDL_GL_GetProcAddress("glClearDepthf"); + _glesApi.clearStencil = (GlesClearStencilFn)SDL_GL_GetProcAddress("glClearStencil"); + _glesApi.colorMask = (GlesColorMaskFn)SDL_GL_GetProcAddress("glColorMask"); + _glesApi.compileShader = (GlesCompileShaderFn)SDL_GL_GetProcAddress("glCompileShader"); + _glesApi.copyTexSubImage2D = (GlesCopyTexSubImage2DFn)SDL_GL_GetProcAddress("glCopyTexSubImage2D"); + _glesApi.createProgram = (GlesCreateProgramFn)SDL_GL_GetProcAddress("glCreateProgram"); + _glesApi.createShader = (GlesCreateShaderFn)SDL_GL_GetProcAddress("glCreateShader"); + _glesApi.cullFace = (GlesCullFaceFn)SDL_GL_GetProcAddress("glCullFace"); + _glesApi.deleteBuffers = (GlesDeleteBuffersFn)SDL_GL_GetProcAddress("glDeleteBuffers"); + _glesApi.deleteFramebuffers = (GlesDeleteFramebuffersFn)SDL_GL_GetProcAddress("glDeleteFramebuffers"); + _glesApi.deleteProgram = (GlesDeleteProgramFn)SDL_GL_GetProcAddress("glDeleteProgram"); + _glesApi.deleteRenderbuffers = (GlesDeleteRenderbuffersFn)SDL_GL_GetProcAddress("glDeleteRenderbuffers"); + _glesApi.deleteSamplers = (GlesDeleteSamplersFn)SDL_GL_GetProcAddress("glDeleteSamplers"); + _glesApi.deleteShader = (GlesDeleteShaderFn)SDL_GL_GetProcAddress("glDeleteShader"); + _glesApi.deleteTextures = (GlesDeleteTexturesFn)SDL_GL_GetProcAddress("glDeleteTextures"); + _glesApi.deleteVertexArrays = (GlesDeleteVertexArraysFn)SDL_GL_GetProcAddress("glDeleteVertexArrays"); + _glesApi.depthFunc = (GlesDepthFuncFn)SDL_GL_GetProcAddress("glDepthFunc"); + _glesApi.depthMask = (GlesDepthMaskFn)SDL_GL_GetProcAddress("glDepthMask"); + _glesApi.disable = (GlesDisableFn)SDL_GL_GetProcAddress("glDisable"); + _glesApi.drawArraysInstanced = (GlesDrawArraysInstancedFn)SDL_GL_GetProcAddress("glDrawArraysInstanced"); + _glesApi.drawBuffers = (GlesDrawBuffersFn)SDL_GL_GetProcAddress("glDrawBuffers"); + _glesApi.drawElementsInstanced = (GlesDrawElementsInstancedFn)SDL_GL_GetProcAddress("glDrawElementsInstanced"); + _glesApi.enable = (GlesEnableFn)SDL_GL_GetProcAddress("glEnable"); + _glesApi.enableVertexAttribArray = (GlesEnableVertexAttribArrayFn)SDL_GL_GetProcAddress("glEnableVertexAttribArray"); + _glesApi.flush = (GlesFlushFn)SDL_GL_GetProcAddress("glFlush"); + _glesApi.framebufferRenderbuffer = (GlesFramebufferRenderbufferFn)SDL_GL_GetProcAddress("glFramebufferRenderbuffer"); + _glesApi.framebufferTexture2D = (GlesFramebufferTexture2DFn)SDL_GL_GetProcAddress("glFramebufferTexture2D"); + _glesApi.framebufferTextureLayer = (GlesFramebufferTextureLayerFn)SDL_GL_GetProcAddress("glFramebufferTextureLayer"); + _glesApi.frontFace = (GlesFrontFaceFn)SDL_GL_GetProcAddress("glFrontFace"); + _glesApi.genBuffers = (GlesGenBuffersFn)SDL_GL_GetProcAddress("glGenBuffers"); + _glesApi.genFramebuffers = (GlesGenFramebuffersFn)SDL_GL_GetProcAddress("glGenFramebuffers"); + _glesApi.genRenderbuffers = (GlesGenRenderbuffersFn)SDL_GL_GetProcAddress("glGenRenderbuffers"); + _glesApi.genSamplers = (GlesGenSamplersFn)SDL_GL_GetProcAddress("glGenSamplers"); + _glesApi.genTextures = (GlesGenTexturesFn)SDL_GL_GetProcAddress("glGenTextures"); + _glesApi.genVertexArrays = (GlesGenVertexArraysFn)SDL_GL_GetProcAddress("glGenVertexArrays"); + _glesApi.generateMipmap = (GlesGenerateMipmapFn)SDL_GL_GetProcAddress("glGenerateMipmap"); + _glesApi.getError = (GlesGetErrorFn)SDL_GL_GetProcAddress("glGetError"); + _glesApi.getIntegerv = (GlesGetIntegervFn)SDL_GL_GetProcAddress("glGetIntegerv"); + _glesApi.getInternalformativ = (GlesGetInternalformativFn)SDL_GL_GetProcAddress("glGetInternalformativ"); + _glesApi.getProgramInfoLog = (GlesGetProgramInfoLogFn)SDL_GL_GetProcAddress("glGetProgramInfoLog"); + _glesApi.getProgramiv = (GlesGetProgramivFn)SDL_GL_GetProcAddress("glGetProgramiv"); + _glesApi.getShaderInfoLog = (GlesGetShaderInfoLogFn)SDL_GL_GetProcAddress("glGetShaderInfoLog"); + _glesApi.getShaderiv = (GlesGetShaderivFn)SDL_GL_GetProcAddress("glGetShaderiv"); + _glesApi.getString = (GlesGetStringFn)SDL_GL_GetProcAddress("glGetString"); + _glesApi.getUniformBlockIndex = (GlesGetUniformBlockIndexFn)SDL_GL_GetProcAddress("glGetUniformBlockIndex"); + _glesApi.linkProgram = (GlesLinkProgramFn)SDL_GL_GetProcAddress("glLinkProgram"); + _glesApi.pixelStorei = (GlesPixelStoreiFn)SDL_GL_GetProcAddress("glPixelStorei"); + _glesApi.polygonOffset = (GlesPolygonOffsetFn)SDL_GL_GetProcAddress("glPolygonOffset"); + _glesApi.readBuffer = (GlesReadBufferFn)SDL_GL_GetProcAddress("glReadBuffer"); + _glesApi.renderbufferStorageMultisample = (GlesRenderbufferStorageMultisampleFn)SDL_GL_GetProcAddress("glRenderbufferStorageMultisample"); + _glesApi.getFloatv = (GlesGetFloatvFn)SDL_GL_GetProcAddress("glGetFloatv"); + _glesApi.getUniformLocation = (GlesGetUniformLocationFn)SDL_GL_GetProcAddress("glGetUniformLocation"); + _glesApi.uniform1i = (GlesUniform1iFn)SDL_GL_GetProcAddress("glUniform1i"); + _glesApi.samplerParameterf = (GlesSamplerParameterfFn)SDL_GL_GetProcAddress("glSamplerParameterf"); + _glesApi.samplerParameteri = (GlesSamplerParameteriFn)SDL_GL_GetProcAddress("glSamplerParameteri"); + _glesApi.scissor = (GlesScissorFn)SDL_GL_GetProcAddress("glScissor"); + _glesApi.shaderSource = (GlesShaderSourceFn)SDL_GL_GetProcAddress("glShaderSource"); + _glesApi.stencilFuncSeparate = (GlesStencilFuncSeparateFn)SDL_GL_GetProcAddress("glStencilFuncSeparate"); + _glesApi.stencilMask = (GlesStencilMaskFn)SDL_GL_GetProcAddress("glStencilMask"); + _glesApi.stencilMaskSeparate = (GlesStencilMaskSeparateFn)SDL_GL_GetProcAddress("glStencilMaskSeparate"); + _glesApi.stencilOpSeparate = (GlesStencilOpSeparateFn)SDL_GL_GetProcAddress("glStencilOpSeparate"); + _glesApi.texParameteri = (GlesTexParameteriFn)SDL_GL_GetProcAddress("glTexParameteri"); + _glesApi.texStorage2D = (GlesTexStorage2DFn)SDL_GL_GetProcAddress("glTexStorage2D"); + _glesApi.texStorage3D = (GlesTexStorage3DFn)SDL_GL_GetProcAddress("glTexStorage3D"); + _glesApi.texSubImage2D = (GlesTexSubImage2DFn)SDL_GL_GetProcAddress("glTexSubImage2D"); + _glesApi.texSubImage3D = (GlesTexSubImage3DFn)SDL_GL_GetProcAddress("glTexSubImage3D"); + _glesApi.uniformBlockBinding = (GlesUniformBlockBindingFn)SDL_GL_GetProcAddress("glUniformBlockBinding"); + _glesApi.useProgram = (GlesUseProgramFn)SDL_GL_GetProcAddress("glUseProgram"); + _glesApi.validateProgram = (GlesValidateProgramFn)SDL_GL_GetProcAddress("glValidateProgram"); + _glesApi.vertexAttribIPointer = (GlesVertexAttribIPointerFn)SDL_GL_GetProcAddress("glVertexAttribIPointer"); + _glesApi.vertexAttribPointer = (GlesVertexAttribPointerFn)SDL_GL_GetProcAddress("glVertexAttribPointer"); + _glesApi.viewport = (GlesViewportFn)SDL_GL_GetProcAddress("glViewport"); + + { + const void *const *slot = (const void *const *)&_glesApi; + const char *const names[] = { + "glActiveTexture", + "glAttachShader", + "glBindBuffer", + "glBindBufferBase", + "glBindBufferRange", + "glBindFramebuffer", + "glBindRenderbuffer", + "glBindSampler", + "glBindTexture", + "glBindVertexArray", + "glBlendEquationSeparate", + "glBlendFuncSeparate", + "glBlitFramebuffer", + "glBufferData", + "glBufferSubData", + "glCheckFramebufferStatus", + "glClear", + "glClearBufferfv", + "glClearDepthf", + "glClearStencil", + "glColorMask", + "glCompileShader", + "glCopyTexSubImage2D", + "glCreateProgram", + "glCreateShader", + "glCullFace", + "glDeleteBuffers", + "glDeleteFramebuffers", + "glDeleteProgram", + "glDeleteRenderbuffers", + "glDeleteSamplers", + "glDeleteShader", + "glDeleteTextures", + "glDeleteVertexArrays", + "glDepthFunc", + "glDepthMask", + "glDisable", + "glDrawArraysInstanced", + "glDrawBuffers", + "glDrawElementsInstanced", + "glEnable", + "glEnableVertexAttribArray", + "glFlush", + "glFramebufferRenderbuffer", + "glFramebufferTexture2D", + "glFramebufferTextureLayer", + "glFrontFace", + "glGenBuffers", + "glGenFramebuffers", + "glGenRenderbuffers", + "glGenSamplers", + "glGenTextures", + "glGenVertexArrays", + "glGenerateMipmap", + "glGetError", + "glGetIntegerv", + "glGetInternalformativ", + "glGetProgramInfoLog", + "glGetProgramiv", + "glGetShaderInfoLog", + "glGetShaderiv", + "glGetString", + "glGetUniformBlockIndex", + "glLinkProgram", + "glPixelStorei", + "glPolygonOffset", + "glReadBuffer", + "glRenderbufferStorageMultisample", + "glGetFloatv", + "glGetUniformLocation", + "glUniform1i", + "glSamplerParameterf", + "glSamplerParameteri", + "glScissor", + "glShaderSource", + "glStencilFuncSeparate", + "glStencilMask", + "glStencilMaskSeparate", + "glStencilOpSeparate", + "glTexParameteri", + "glTexStorage2D", + "glTexStorage3D", + "glTexSubImage2D", + "glTexSubImage3D", + "glUniformBlockBinding", + "glUseProgram", + "glValidateProgram", + "glVertexAttribIPointer", + "glVertexAttribPointer", + "glViewport", + }; + size_t i; + + for (i = 0; i < sizeof(names) / sizeof(names[0]); i++) { + if (slot[i] == NULL) { + utilTrace("Gles: %s is missing from this driver", names[i]); + return false; + } + } + } + return true; +} diff --git a/src/renderGpu.c b/src/renderGpu.c new file mode 100644 index 000000000..5e9a059cf --- /dev/null +++ b/src/renderGpu.c @@ -0,0 +1,78 @@ +/* + * + * 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. + * + */ + +// The SDL_GPU backend: a table, not an implementation. +// +// Every entry in RenderBackendT carries SDL_GPU's own signature, so this backend is SDL's functions +// taken by address. Nothing is forwarded by hand, so there is no wrapper here to fall out of step +// with the header and nothing to get wrong. renderGles.c is where the work is. + +#include "render.h" + + +const RenderBackendT renderGpuBackend = { + .acquireCommandBuffer = SDL_AcquireGPUCommandBuffer, + .beginCopyPass = SDL_BeginGPUCopyPass, + .beginRenderPass = SDL_BeginGPURenderPass, + .bindFragmentSamplers = SDL_BindGPUFragmentSamplers, + .bindGraphicsPipeline = SDL_BindGPUGraphicsPipeline, + .bindIndexBuffer = SDL_BindGPUIndexBuffer, + .bindVertexBuffers = SDL_BindGPUVertexBuffers, + .bindVertexStorageBuffers = SDL_BindGPUVertexStorageBuffers, + .blitTexture = SDL_BlitGPUTexture, + .cancelCommandBuffer = SDL_CancelGPUCommandBuffer, + .copyTextureToTexture = SDL_CopyGPUTextureToTexture, + .createBuffer = SDL_CreateGPUBuffer, + .createDevice = SDL_CreateGPUDevice, + .createGraphicsPipeline = SDL_CreateGPUGraphicsPipeline, + .createSampler = SDL_CreateGPUSampler, + .createShader = SDL_CreateGPUShader, + .createTexture = SDL_CreateGPUTexture, + .createTransferBuffer = SDL_CreateGPUTransferBuffer, + .destroyDevice = SDL_DestroyGPUDevice, + .drawIndexedPrimitives = SDL_DrawGPUIndexedPrimitives, + .drawPrimitives = SDL_DrawGPUPrimitives, + .endCopyPass = SDL_EndGPUCopyPass, + .endRenderPass = SDL_EndGPURenderPass, + .generateMipmapsForTexture = SDL_GenerateMipmapsForGPUTexture, + .getDeviceDriver = SDL_GetGPUDeviceDriver, + .getShaderFormats = SDL_GetGPUShaderFormats, + .getSwapchainTextureFormat = SDL_GetGPUSwapchainTextureFormat, + .getTextureFormatFromPixelFormat = SDL_GetGPUTextureFormatFromPixelFormat, + .mapTransferBuffer = SDL_MapGPUTransferBuffer, + .pushFragmentUniformData = SDL_PushGPUFragmentUniformData, + .pushVertexUniformData = SDL_PushGPUVertexUniformData, + .releaseBuffer = SDL_ReleaseGPUBuffer, + .releaseGraphicsPipeline = SDL_ReleaseGPUGraphicsPipeline, + .releaseSampler = SDL_ReleaseGPUSampler, + .releaseShader = SDL_ReleaseGPUShader, + .releaseTexture = SDL_ReleaseGPUTexture, + .releaseTransferBuffer = SDL_ReleaseGPUTransferBuffer, + .setScissor = SDL_SetGPUScissor, + .setStencilReference = SDL_SetGPUStencilReference, + .submitCommandBuffer = SDL_SubmitGPUCommandBuffer, + .textureSupportsFormat = SDL_GPUTextureSupportsFormat, + .textureSupportsSampleCount = SDL_GPUTextureSupportsSampleCount, + .unmapTransferBuffer = SDL_UnmapGPUTransferBuffer, + .uploadToBuffer = SDL_UploadToGPUBuffer, + .uploadToTexture = SDL_UploadToGPUTexture, +}; diff --git a/src/scene.c b/src/scene.c index 3f60f0b4d..990f9f653 100644 --- a/src/scene.c +++ b/src/scene.c @@ -36,6 +36,7 @@ #include #include #include "util.h" +#include "render.h" #include "scene.h" #include "shaders/sceneShaders.h" #include "particles.h" @@ -44,6 +45,7 @@ #define COLOUR_MAX 255.0f #define MAX_ANISOTROPY 8.0f // Texture samples along a grazing surface +#define SAMPLER_MAX_LOD 1000.0f // "No clamp", SDL's own spelling of it: walk the whole mipmap chain #define POST_VERTICES 3 // One triangle covers the screen #define BLOOM_LEVELS 5 // Half-size chain for the glow #define BLOOM_LEVELS_MIN 2 // Fewer and the up pass has nothing to add (tiny targets) @@ -1036,16 +1038,16 @@ static bool _createBloomPipelines(void) { info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1; info.target_info.color_target_descriptions = &colour; info.target_info.num_color_targets = 1; - _scene.bloomDownPipeline = SDL_CreateGPUGraphicsPipeline(_scene.device, &info); + _scene.bloomDownPipeline = rgpuCreateGraphicsPipeline(_scene.device, &info); if (_scene.bloomDownPipeline == NULL) { utilTrace("Scene: bloom pipeline: %s", SDL_GetError()); return false; } info.fragment_shader = _scene.bloomUpFragment; - _scene.bloomUpPipeline = SDL_CreateGPUGraphicsPipeline(_scene.device, &info); + _scene.bloomUpPipeline = rgpuCreateGraphicsPipeline(_scene.device, &info); if (_scene.bloomUpPipeline == NULL) { utilTrace("Scene: bloom pipeline: %s", SDL_GetError()); - SDL_ReleaseGPUGraphicsPipeline(_scene.device, _scene.bloomDownPipeline); + rgpuReleaseGraphicsPipeline(_scene.device, _scene.bloomDownPipeline); _scene.bloomDownPipeline = NULL; return false; } @@ -1080,8 +1082,8 @@ static bool _createBloomTargets(int32_t width, int32_t height) { } info.width = (Uint32)w; info.height = (Uint32)h; - _scene.bloomDown[level] = SDL_CreateGPUTexture(_scene.device, &info); - _scene.bloomUp[level] = SDL_CreateGPUTexture(_scene.device, &info); + _scene.bloomDown[level] = rgpuCreateTexture(_scene.device, &info); + _scene.bloomUp[level] = rgpuCreateTexture(_scene.device, &info); if ((_scene.bloomDown[level] == NULL) || (_scene.bloomUp[level] == NULL)) { utilTrace("Scene: bloom targets: %s", SDL_GetError()); _destroyBloomTargets(); @@ -1135,7 +1137,7 @@ static bool _createLinePipeline(int32_t sampleSet) { info.target_info.num_color_targets = 1; info.target_info.depth_stencil_format = _scene.depthFormat; info.target_info.has_depth_stencil_target = true; - _scene.linePipeline[sampleSet] = SDL_CreateGPUGraphicsPipeline(_scene.device, &info); + _scene.linePipeline[sampleSet] = rgpuCreateGraphicsPipeline(_scene.device, &info); if (_scene.linePipeline[sampleSet] == NULL) { utilTrace("Scene: line pipeline: %s", SDL_GetError()); return false; @@ -1193,7 +1195,7 @@ static bool _createParticlePipeline(int32_t sampleSet, int32_t blend) { info.target_info.num_color_targets = 1; info.target_info.depth_stencil_format = _scene.depthFormat; info.target_info.has_depth_stencil_target = true; - _scene.particlePipelines[sampleSet][blend] = SDL_CreateGPUGraphicsPipeline(_scene.device, &info); + _scene.particlePipelines[sampleSet][blend] = rgpuCreateGraphicsPipeline(_scene.device, &info); if (_scene.particlePipelines[sampleSet][blend] == NULL) { utilTrace("Scene: particle pipeline %d: %s", blend, SDL_GetError()); return false; @@ -1234,7 +1236,7 @@ static bool _createPipeline(int32_t sampleSet, int32_t variant) { info.target_info.num_color_targets = 1; info.target_info.depth_stencil_format = _scene.depthFormat; info.target_info.has_depth_stencil_target = true; - _scene.pipelines[sampleSet][variant] = SDL_CreateGPUGraphicsPipeline(_scene.device, &info); + _scene.pipelines[sampleSet][variant] = rgpuCreateGraphicsPipeline(_scene.device, &info); if (_scene.pipelines[sampleSet][variant] == NULL) { utilTrace("Scene: pipeline %d: %s", variant, SDL_GetError()); return false; @@ -1250,7 +1252,7 @@ static bool _createPostPipeline(void) { memset(&info, 0, sizeof(info)); memset(&colour, 0, sizeof(colour)); - colour.format = SDL_GetGPUTextureFormatFromPixelFormat(SDL_PIXELFORMAT_BGRA32); + colour.format = rgpuGetTextureFormatFromPixelFormat(SDL_PIXELFORMAT_BGRA32); info.vertex_shader = _scene.postVertex; info.fragment_shader = _scene.postFragment; info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; @@ -1260,7 +1262,7 @@ static bool _createPostPipeline(void) { info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1; info.target_info.color_target_descriptions = &colour; info.target_info.num_color_targets = 1; - _scene.postPipeline = SDL_CreateGPUGraphicsPipeline(_scene.device, &info); + _scene.postPipeline = rgpuCreateGraphicsPipeline(_scene.device, &info); if (_scene.postPipeline == NULL) { utilTrace("Scene: post pipeline: %s", SDL_GetError()); return false; @@ -1272,7 +1274,7 @@ static bool _createPostPipeline(void) { // Picks the blob for the format the device accepts. static SDL_GPUShader *_createShader(const SceneShaderT *shader, SDL_GPUShaderStage stage, uint32_t samplers, uint32_t uniforms, uint32_t storageBuffers) { SDL_GPUShaderCreateInfo info; - SDL_GPUShaderFormat formats = SDL_GetGPUShaderFormats(_scene.device); + SDL_GPUShaderFormat formats = rgpuGetShaderFormats(_scene.device); SDL_GPUShader *result; memset(&info, 0, sizeof(info)); @@ -1288,8 +1290,12 @@ static SDL_GPUShader *_createShader(const SceneShaderT *shader, SDL_GPUShaderSta info.code = shader->msl; info.code_size = shader->mslSize; info.format = SDL_GPU_SHADERFORMAT_MSL; + } else if (formats & RGPU_SHADERFORMAT_ESSL) { + info.code = (const Uint8 *)shader->essl; + info.code_size = shader->essl != NULL ? SDL_strlen(shader->essl) : 0; + info.format = RGPU_SHADERFORMAT_ESSL; } else { - utilTrace("Scene: the GPU device accepts none of SPIR-V, DXIL or MSL."); + utilTrace("Scene: the device accepts none of SPIR-V, DXIL, MSL or GLSL ES."); return NULL; } info.entrypoint = shader->entryPoint; @@ -1297,7 +1303,7 @@ static SDL_GPUShader *_createShader(const SceneShaderT *shader, SDL_GPUShaderSta info.num_samplers = samplers; info.num_uniform_buffers = uniforms; info.num_storage_buffers = storageBuffers; - result = SDL_CreateGPUShader(_scene.device, &info); + result = rgpuCreateShader(_scene.device, &info); if (result == NULL) { utilTrace("Scene: shader %s: %s", shader->entryPoint, SDL_GetError()); } @@ -1339,7 +1345,7 @@ static SDL_GPUTexture *_createShadowArray(SDL_GPUTextureType type, int32_t layer info.layer_count_or_depth = (Uint32)layers; info.num_levels = 1; info.sample_count = SDL_GPU_SAMPLECOUNT_1; - texture = SDL_CreateGPUTexture(_scene.device, &info); + texture = rgpuCreateTexture(_scene.device, &info); if (texture == NULL) { utilTrace("Scene: shadow map: %s", SDL_GetError()); } @@ -1351,7 +1357,7 @@ static SDL_GPUTexture *_createShadowArray(SDL_GPUTextureType type, int32_t layer static bool _createShadowMaps(int32_t layers) { if (layers > _scene.shadowMapLayers) { if (_scene.shadowMaps != NULL) { - SDL_ReleaseGPUTexture(_scene.device, _scene.shadowMaps); + rgpuReleaseTexture(_scene.device, _scene.shadowMaps); } _scene.shadowMaps = _createShadowArray(SDL_GPU_TEXTURETYPE_2D_ARRAY, layers, _scene.shadowSize); _scene.shadowMapsVersion++; @@ -1390,7 +1396,7 @@ static bool _createShadowPipeline(int32_t variant) { info.target_info.num_color_targets = 0; info.target_info.depth_stencil_format = _scene.shadowFormat; info.target_info.has_depth_stencil_target = true; - _scene.shadowPipelines[variant] = SDL_CreateGPUGraphicsPipeline(_scene.device, &info); + _scene.shadowPipelines[variant] = rgpuCreateGraphicsPipeline(_scene.device, &info); if (_scene.shadowPipelines[variant] == NULL) { utilTrace("Scene: shadow pipeline %d: %s", variant, SDL_GetError()); return false; @@ -1421,7 +1427,7 @@ static bool _createSkyPipeline(int32_t sampleSet) { info.target_info.num_color_targets = 1; info.target_info.depth_stencil_format = _scene.depthFormat; info.target_info.has_depth_stencil_target = true; - _scene.skyPipeline[sampleSet] = SDL_CreateGPUGraphicsPipeline(_scene.device, &info); + _scene.skyPipeline[sampleSet] = rgpuCreateGraphicsPipeline(_scene.device, &info); if (_scene.skyPipeline[sampleSet] == NULL) { utilTrace("Scene: sky pipeline: %s", SDL_GetError()); return false; @@ -1610,7 +1616,7 @@ static void _destroyPipelines(void) { static void _destroyShadowMaps(void) { if (_scene.shadowMaps != NULL) { - SDL_ReleaseGPUTexture(_scene.device, _scene.shadowMaps); + rgpuReleaseTexture(_scene.device, _scene.shadowMaps); _scene.shadowMaps = NULL; } _scene.shadowMapLayers = 0; @@ -1624,19 +1630,19 @@ static void _destroyTargets(void) { _scene.composite = NULL; } if (_scene.output != NULL) { - SDL_ReleaseGPUTexture(_scene.device, _scene.output); + rgpuReleaseTexture(_scene.device, _scene.output); _scene.output = NULL; } if (_scene.colour != NULL) { - SDL_ReleaseGPUTexture(_scene.device, _scene.colour); + rgpuReleaseTexture(_scene.device, _scene.colour); _scene.colour = NULL; } if (_scene.multisampled != NULL) { - SDL_ReleaseGPUTexture(_scene.device, _scene.multisampled); + rgpuReleaseTexture(_scene.device, _scene.multisampled); _scene.multisampled = NULL; } if (_scene.depth != NULL) { - SDL_ReleaseGPUTexture(_scene.device, _scene.depth); + rgpuReleaseTexture(_scene.device, _scene.depth); _scene.depth = NULL; } _releaseTexture(&_scene.softDepth); @@ -1704,12 +1710,12 @@ static void _drawBloom(SDL_GPUCommandBuffer *commands, const CameraFrameT *frame uniforms.params[3] = (level == 0) ? 1.0f : 0.0f; samplers[0].texture = (level == 0) ? frame->colour : _scene.bloomDown[level - 1]; colour.texture = _scene.bloomDown[level]; - pass = SDL_BeginGPURenderPass(commands, &colour, 1, NULL); - SDL_BindGPUGraphicsPipeline(pass, _scene.bloomDownPipeline); - SDL_BindGPUFragmentSamplers(pass, 0, samplers, 1); - SDL_PushGPUFragmentUniformData(commands, 0, &uniforms, sizeof(uniforms)); - SDL_DrawGPUPrimitives(pass, POST_VERTICES, 1, 0, 0); - SDL_EndGPURenderPass(pass); + pass = rgpuBeginRenderPass(commands, &colour, 1, NULL); + rgpuBindGraphicsPipeline(pass, _scene.bloomDownPipeline); + rgpuBindFragmentSamplers(pass, 0, samplers, 1); + rgpuPushFragmentUniformData(commands, 0, &uniforms, sizeof(uniforms)); + rgpuDrawPrimitives(pass, POST_VERTICES, 1, 0, 0); + rgpuEndRenderPass(pass); w = SDL_max(w / 2, 1); h = SDL_max(h / 2, 1); } @@ -1724,12 +1730,12 @@ static void _drawBloom(SDL_GPUCommandBuffer *commands, const CameraFrameT *frame samplers[0].texture = _scene.bloomDown[level]; samplers[1].texture = (level == _scene.bloomLevels - 2) ? _scene.bloomDown[level + 1] : _scene.bloomUp[level + 1]; colour.texture = _scene.bloomUp[level]; - pass = SDL_BeginGPURenderPass(commands, &colour, 1, NULL); - SDL_BindGPUGraphicsPipeline(pass, _scene.bloomUpPipeline); - SDL_BindGPUFragmentSamplers(pass, 0, samplers, 2); - SDL_PushGPUFragmentUniformData(commands, 0, &uniforms, sizeof(uniforms)); - SDL_DrawGPUPrimitives(pass, POST_VERTICES, 1, 0, 0); - SDL_EndGPURenderPass(pass); + pass = rgpuBeginRenderPass(commands, &colour, 1, NULL); + rgpuBindGraphicsPipeline(pass, _scene.bloomUpPipeline); + rgpuBindFragmentSamplers(pass, 0, samplers, 2); + rgpuPushFragmentUniformData(commands, 0, &uniforms, sizeof(uniforms)); + rgpuDrawPrimitives(pass, POST_VERTICES, 1, 0, 0); + rgpuEndRenderPass(pass); } } @@ -1748,10 +1754,10 @@ static void _drawLines(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, uniforms.viewProjection = frame->viewProjection; memset(&binding, 0, sizeof(binding)); binding.buffer = _scene.lineBuffer; - SDL_BindGPUGraphicsPipeline(pass, _scene.linePipeline[frame->sampleSet]); - SDL_PushGPUVertexUniformData(commands, 0, &uniforms, sizeof(uniforms)); - SDL_BindGPUVertexBuffers(pass, 0, &binding, 1); - SDL_DrawGPUPrimitives(pass, (Uint32)_scene.lineVertexCount, 1, 0, 0); + rgpuBindGraphicsPipeline(pass, _scene.linePipeline[frame->sampleSet]); + rgpuPushVertexUniformData(commands, 0, &uniforms, sizeof(uniforms)); + rgpuBindVertexBuffers(pass, 0, &binding, 1); + rgpuDrawPrimitives(pass, (Uint32)_scene.lineVertexCount, 1, 0, 0); } @@ -1821,9 +1827,9 @@ static void _drawList(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, i pipeline = _scene.pipelines[sampleSet][variant]; } if (variant != lastPipeline) { - SDL_BindGPUGraphicsPipeline(pass, pipeline); + rgpuBindGraphicsPipeline(pass, pipeline); if (!shadowPass) { - SDL_PushGPUFragmentUniformData(commands, FRAME_UNIFORMS, fragmentUniforms, sizeof(FragmentUniformsT)); + rgpuPushFragmentUniformData(commands, FRAME_UNIFORMS, fragmentUniforms, sizeof(FragmentUniformsT)); } lastPipeline = variant; lastMesh = NO_HANDLE; @@ -1870,21 +1876,21 @@ static void _drawList(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, i drawUniforms.morphInfo[0] = active; drawUniforms.morphInfo[1] = mesh->vertexCount; } - SDL_PushGPUVertexUniformData(commands, DRAW_UNIFORMS, &drawUniforms, sizeof(drawUniforms)); + rgpuPushVertexUniformData(commands, DRAW_UNIFORMS, &drawUniforms, sizeof(drawUniforms)); // Opaque draws are sorted by mesh, so runs of one mesh keep their buffers bound. if (node->mesh != lastMesh) { storage[0] = (mesh->morphBuffer != NULL) ? mesh->morphBuffer : _scene.noMorphs; storage[1] = _scene.instanceBuffer; - SDL_BindGPUVertexStorageBuffers(pass, 0, storage, 2); + rgpuBindVertexStorageBuffers(pass, 0, storage, 2); memset(&binding, 0, sizeof(binding)); binding.buffer = mesh->vertexBuffer; - SDL_BindGPUVertexBuffers(pass, 0, &binding, 1); + rgpuBindVertexBuffers(pass, 0, &binding, 1); binding.buffer = mesh->indexBuffer; - SDL_BindGPUIndexBuffer(pass, &binding, SDL_GPU_INDEXELEMENTSIZE_32BIT); + rgpuBindIndexBuffer(pass, &binding, SDL_GPU_INDEXELEMENTSIZE_32BIT); lastMesh = node->mesh; } if (_scene.draws[index].skin != NO_HANDLE) { - SDL_PushGPUVertexUniformData(commands, SKIN_UNIFORMS, &_scene.skins[_scene.draws[index].skin], sizeof(SkinUniformsT)); + rgpuPushVertexUniformData(commands, SKIN_UNIFORMS, &_scene.skins[_scene.draws[index].skin], sizeof(SkinUniformsT)); } // A cutout caster binds what the masking depth shader reads and nothing else: the base // texture, the colour whose alpha it multiplies, the cutoff and the tiling. The shader @@ -1904,9 +1910,9 @@ static void _drawList(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, i memset(&cutoutBinding, 0, sizeof(cutoutBinding)); cutoutBinding.texture = (baseTexture != NULL) ? baseTexture : _scene.white; cutoutBinding.sampler = (material->filter == FILTER_NEAREST) ? _scene.nearestSampler : _scene.sampler; - SDL_PushGPUFragmentUniformData(commands, FRAME_UNIFORMS, &unread, sizeof(unread)); - SDL_PushGPUFragmentUniformData(commands, MATERIAL_UNIFORMS, &materialUniforms, sizeof(materialUniforms)); - SDL_BindGPUFragmentSamplers(pass, 0, &cutoutBinding, 1); + rgpuPushFragmentUniformData(commands, FRAME_UNIFORMS, &unread, sizeof(unread)); + rgpuPushFragmentUniformData(commands, MATERIAL_UNIFORMS, &materialUniforms, sizeof(materialUniforms)); + rgpuBindFragmentSamplers(pass, 0, &cutoutBinding, 1); } if (!shadowPass) { baseTexture = _materialTexture(material); @@ -1928,7 +1934,7 @@ static void _drawList(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, i materialUniforms.maps[2] = material->cutoff; materialUniforms.tiling[0] = material->tilingU; materialUniforms.tiling[1] = material->tilingV; - SDL_PushGPUFragmentUniformData(commands, MATERIAL_UNIFORMS, &materialUniforms, sizeof(materialUniforms)); + rgpuPushFragmentUniformData(commands, MATERIAL_UNIFORMS, &materialUniforms, sizeof(materialUniforms)); memset(samplerBindings, 0, sizeof(samplerBindings)); materialSampler = (material->filter == FILTER_NEAREST) ? _scene.nearestSampler : _scene.sampler; samplerBindings[0].texture = (baseTexture != NULL) ? baseTexture : _scene.white; @@ -1945,10 +1951,10 @@ static void _drawList(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, i samplerBindings[5].sampler = materialSampler; samplerBindings[6].texture = (_scene.skyCube != NULL) ? _scene.skyCube : _scene.blackCube; samplerBindings[6].sampler = _scene.skySampler; - SDL_BindGPUFragmentSamplers(pass, 0, samplerBindings, MATERIAL_SAMPLERS); + rgpuBindFragmentSamplers(pass, 0, samplerBindings, MATERIAL_SAMPLERS); _scene.statBatches++; } - SDL_DrawGPUIndexedPrimitives(pass, mesh->indexCount, (Uint32)(end - x), 0, 0, 0); + rgpuDrawIndexedPrimitives(pass, mesh->indexCount, (Uint32)(end - x), 0, 0, 0); } } @@ -1991,7 +1997,7 @@ static void _drawParticles(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pa memcpy(params.forward, uniforms.forward, sizeof(params.forward)); memset(&binding, 0, sizeof(binding)); binding.buffer = _scene.particleBuffer; - SDL_BindGPUVertexBuffers(pass, 0, &binding, 1); + rgpuBindVertexBuffers(pass, 0, &binding, 1); memset(samplers, 0, sizeof(samplers)); samplers[0].sampler = _scene.sampler; samplers[1].texture = (_scene.particleSoft && (frame->softDepth != NULL)) ? frame->softDepth : _scene.depthNone; @@ -2003,18 +2009,18 @@ static void _drawParticles(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pa continue; } if ((int32_t)run->blend != lastBlend) { - SDL_BindGPUGraphicsPipeline(pass, pipelines[run->blend]); - SDL_PushGPUVertexUniformData(commands, 0, &uniforms, sizeof(uniforms)); - SDL_PushGPUFragmentUniformData(commands, FRAME_UNIFORMS, fragmentUniforms, sizeof(FragmentUniformsT)); + rgpuBindGraphicsPipeline(pass, pipelines[run->blend]); + rgpuPushVertexUniformData(commands, 0, &uniforms, sizeof(uniforms)); + rgpuPushFragmentUniformData(commands, FRAME_UNIFORMS, fragmentUniforms, sizeof(FragmentUniformsT)); lastBlend = run->blend; } params.flags[0] = (run->blend == PARTICLE_ADD) ? 1.0f : 0.0f; params.flags[1] = run->lit ? 1.0f : 0.0f; params.flags[2] = (frame->softDepth != NULL) ? run->softness : 0.0f; - SDL_PushGPUFragmentUniformData(commands, 1, ¶ms, sizeof(params)); + rgpuPushFragmentUniformData(commands, 1, ¶ms, sizeof(params)); samplers[0].texture = run->texture; - SDL_BindGPUFragmentSamplers(pass, 0, samplers, 2); - SDL_DrawGPUPrimitives(pass, (Uint32)run->count, 1, (Uint32)run->first, 0); + rgpuBindFragmentSamplers(pass, 0, samplers, 2); + rgpuDrawPrimitives(pass, (Uint32)run->count, 1, (Uint32)run->first, 0); } } @@ -2040,21 +2046,21 @@ static void _drawPost(SDL_GPUCommandBuffer *commands, const CameraFrameT *frame) colour.texture = frame->output; colour.load_op = SDL_GPU_LOADOP_DONT_CARE; colour.store_op = SDL_GPU_STOREOP_STORE; - pass = SDL_BeginGPURenderPass(commands, &colour, 1, NULL); - SDL_BindGPUGraphicsPipeline(pass, _scene.postPipeline); + pass = rgpuBeginRenderPass(commands, &colour, 1, NULL); + rgpuBindGraphicsPipeline(pass, _scene.postPipeline); memset(samplers, 0, sizeof(samplers)); samplers[0].texture = frame->colour; samplers[0].sampler = _scene.postSampler; samplers[1].texture = bloom ? _scene.bloomUp[0] : _scene.black; samplers[1].sampler = _scene.postSampler; - SDL_BindGPUFragmentSamplers(pass, 0, samplers, 2); + rgpuBindFragmentSamplers(pass, 0, samplers, 2); memset(&uniforms, 0, sizeof(uniforms)); uniforms.params[0] = SDL_powf(2.0f, _scene.exposure); uniforms.params[1] = (float)_scene.tonemap; uniforms.params[2] = bloom ? _scene.bloomStrength : 0.0f; - SDL_PushGPUFragmentUniformData(commands, 0, &uniforms, sizeof(uniforms)); - SDL_DrawGPUPrimitives(pass, POST_VERTICES, 1, 0, 0); - SDL_EndGPURenderPass(pass); + rgpuPushFragmentUniformData(commands, 0, &uniforms, sizeof(uniforms)); + rgpuDrawPrimitives(pass, POST_VERTICES, 1, 0, 0); + rgpuEndRenderPass(pass); } @@ -2078,13 +2084,13 @@ static void _drawSky(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, co uniforms.eye[1] = frame->eye.y; uniforms.eye[2] = frame->eye.z; uniforms.params[0] = _scene.skyIntensity; - SDL_BindGPUGraphicsPipeline(pass, _scene.skyPipeline[frame->sampleSet]); + rgpuBindGraphicsPipeline(pass, _scene.skyPipeline[frame->sampleSet]); memset(&sampler, 0, sizeof(sampler)); sampler.texture = _scene.skyCube; sampler.sampler = _scene.skySampler; - SDL_BindGPUFragmentSamplers(pass, 0, &sampler, 1); - SDL_PushGPUFragmentUniformData(commands, 0, &uniforms, sizeof(uniforms)); - SDL_DrawGPUPrimitives(pass, POST_VERTICES, 1, 0, 0); + rgpuBindFragmentSamplers(pass, 0, &sampler, 1); + rgpuPushFragmentUniformData(commands, 0, &uniforms, sizeof(uniforms)); + rgpuDrawPrimitives(pass, POST_VERTICES, 1, 0, 0); } @@ -2417,7 +2423,7 @@ static void _freeMorphs(MeshT *mesh) { int32_t x; if (mesh->morphBuffer != NULL) { - SDL_ReleaseGPUBuffer(_scene.device, mesh->morphBuffer); + rgpuReleaseBuffer(_scene.device, mesh->morphBuffer); mesh->morphBuffer = NULL; } for (x = 0; x < mesh->morphCount; x++) { @@ -2689,11 +2695,11 @@ static bool _hasMorphs(const NodeT *node, const MeshT *mesh) { // The scene renders in linear light with headroom above white: 16-bit float where offered. static SDL_GPUTextureFormat _hdrFormat(void) { - if (SDL_GPUTextureSupportsFormat(_scene.device, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, SDL_GPU_TEXTURETYPE_2D, SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER)) { + if (rgpuTextureSupportsFormat(_scene.device, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, SDL_GPU_TEXTURETYPE_2D, SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER)) { return SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT; } utilTrace("Scene: no 16-bit float render target; highlights will clip."); - return SDL_GetGPUTextureFormatFromPixelFormat(SDL_PIXELFORMAT_BGRA32); + return rgpuGetTextureFormatFromPixelFormat(SDL_PIXELFORMAT_BGRA32); } @@ -2945,7 +2951,7 @@ static ParticleTexturesT *_particleTextures(const EmitterViewT *view) { } if ((entry != NULL) && (entry->version != view->textureVersion)) { for (x = 0; x < entry->count; x++) { - SDL_ReleaseGPUTexture(_scene.device, entry->textures[x]); + rgpuReleaseTexture(_scene.device, entry->textures[x]); } SDL_free(entry->textures); entry->textures = NULL; @@ -2973,7 +2979,7 @@ static ParticleTexturesT *_particleTextures(const EmitterViewT *view) { if (entry->textures[x] == NULL) { while (x > 0) { x--; - SDL_ReleaseGPUTexture(_scene.device, entry->textures[x]); + rgpuReleaseTexture(_scene.device, entry->textures[x]); } SDL_free(entry->textures); entry->textures = NULL; @@ -2991,7 +2997,7 @@ static SDL_GPUTextureFormat _pickDepthFormat(const SDL_GPUTextureFormat *wanted, int32_t x; for (x = 0; x < count; x++) { - if (SDL_GPUTextureSupportsFormat(_scene.device, wanted[x], SDL_GPU_TEXTURETYPE_2D, usage)) { + if (rgpuTextureSupportsFormat(_scene.device, wanted[x], SDL_GPU_TEXTURETYPE_2D, usage)) { return wanted[x]; } } @@ -3092,7 +3098,7 @@ static void _releaseParticleTextures(bool all) { if (all || !emitterValid(entry->id)) { for (f = 0; f < entry->count; f++) { - SDL_ReleaseGPUTexture(_scene.device, entry->textures[f]); + rgpuReleaseTexture(_scene.device, entry->textures[f]); } SDL_free(entry->textures); _scene.particleTextureCount--; @@ -3110,7 +3116,7 @@ static void _releaseParticleTextures(bool all) { static void _releasePipeline(SDL_GPUGraphicsPipeline **pipeline) { if (*pipeline != NULL) { - SDL_ReleaseGPUGraphicsPipeline(_scene.device, *pipeline); + rgpuReleaseGraphicsPipeline(_scene.device, *pipeline); *pipeline = NULL; } } @@ -3131,7 +3137,7 @@ static void _releaseTexture(SDL_GPUTexture **texture) { break; } } - SDL_ReleaseGPUTexture(_scene.device, *texture); + rgpuReleaseTexture(_scene.device, *texture); *texture = NULL; } @@ -3180,9 +3186,9 @@ static void _renderCamera(SDL_GPUCommandBuffer *commands, const CameraFrameT *fr depth.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE; depth.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE; _scene.depthPrepass = true; - pass = SDL_BeginGPURenderPass(commands, NULL, 0, &depth); + pass = rgpuBeginRenderPass(commands, NULL, 0, &depth); _drawList(commands, pass, drawCount, &frame->viewProjection, frame, true, false, culled, NULL, SAMPLE_SET_SINGLE); - SDL_EndGPURenderPass(pass); + rgpuEndRenderPass(pass); _scene.depthPrepass = false; } // The main pass. @@ -3204,12 +3210,12 @@ static void _renderCamera(SDL_GPUCommandBuffer *commands, const CameraFrameT *fr depth.store_op = SDL_GPU_STOREOP_DONT_CARE; depth.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE; depth.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE; - pass = SDL_BeginGPURenderPass(commands, &colour, 1, &depth); + pass = rgpuBeginRenderPass(commands, &colour, 1, &depth); _drawSky(commands, pass, frame); _drawList(commands, pass, drawCount, &frame->viewProjection, frame, false, false, culled, uniforms, frame->sampleSet); _drawParticles(commands, pass, frame, uniforms); _drawLines(commands, pass, frame); - SDL_EndGPURenderPass(pass); + rgpuEndRenderPass(pass); _drawPost(commands, frame); } @@ -3397,15 +3403,15 @@ static bool _stageBegin(StagingT *staging, uint32_t bytes) { memset(&info, 0, sizeof(info)); info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; info.size = bytes; - staging->transfer = SDL_CreateGPUTransferBuffer(_scene.device, &info); + staging->transfer = rgpuCreateTransferBuffer(_scene.device, &info); if (staging->transfer == NULL) { utilTrace("Scene: %s", SDL_GetError()); return false; } - staging->mapped = SDL_MapGPUTransferBuffer(_scene.device, staging->transfer, false); + staging->mapped = rgpuMapTransferBuffer(_scene.device, staging->transfer, false); if (staging->mapped == NULL) { utilTrace("Scene: %s", SDL_GetError()); - SDL_ReleaseGPUTransferBuffer(_scene.device, staging->transfer); + rgpuReleaseTransferBuffer(_scene.device, staging->transfer); staging->transfer = NULL; return false; } @@ -3416,28 +3422,28 @@ static bool _stageBegin(StagingT *staging, uint32_t bytes) { // Unmaps the filled transfer buffer and opens the copy pass (staging->pass) the caller issues its // uploads into. False, with everything released, when no command buffer could be had. static bool _stageCopy(StagingT *staging) { - SDL_UnmapGPUTransferBuffer(_scene.device, staging->transfer); + rgpuUnmapTransferBuffer(_scene.device, staging->transfer); staging->mapped = NULL; - staging->commands = SDL_AcquireGPUCommandBuffer(_scene.device); + staging->commands = rgpuAcquireCommandBuffer(_scene.device); if (staging->commands == NULL) { utilTrace("Scene: %s", SDL_GetError()); - SDL_ReleaseGPUTransferBuffer(_scene.device, staging->transfer); + rgpuReleaseTransferBuffer(_scene.device, staging->transfer); staging->transfer = NULL; return false; } - staging->pass = SDL_BeginGPUCopyPass(staging->commands); + staging->pass = rgpuBeginCopyPass(staging->commands); return true; } // Ends the copy pass, generates the mip chain of mipmaps (when given), submits and releases. static void _stageEnd(StagingT *staging, SDL_GPUTexture *mipmaps) { - SDL_EndGPUCopyPass(staging->pass); + rgpuEndCopyPass(staging->pass); if (mipmaps != NULL) { - SDL_GenerateMipmapsForGPUTexture(staging->commands, mipmaps); + rgpuGenerateMipmapsForTexture(staging->commands, mipmaps); } - SDL_SubmitGPUCommandBuffer(staging->commands); - SDL_ReleaseGPUTransferBuffer(_scene.device, staging->transfer); + rgpuSubmitCommandBuffer(staging->commands); + rgpuReleaseTransferBuffer(_scene.device, staging->transfer); memset(staging, 0, sizeof(*staging)); } @@ -3457,7 +3463,7 @@ static void _stageTexture(const StagingT *staging, uint32_t offset, SDL_GPUTextu region.w = width; region.h = height; region.d = 1; - SDL_UploadToGPUTexture(staging->pass, &source, ®ion, false); + rgpuUploadToTexture(staging->pass, &source, ®ion, false); } @@ -3492,18 +3498,18 @@ static SDL_GPUBuffer *_uploadBuffer(SDL_GPUBufferUsageFlags usage, const void *d memset(&info, 0, sizeof(info)); info.usage = usage; info.size = size; - buffer = SDL_CreateGPUBuffer(_scene.device, &info); + buffer = rgpuCreateBuffer(_scene.device, &info); if (buffer == NULL) { utilTrace("Scene: %s", SDL_GetError()); return NULL; } if (!_stageBegin(&staging, size)) { - SDL_ReleaseGPUBuffer(_scene.device, buffer); + rgpuReleaseBuffer(_scene.device, buffer); return NULL; } memcpy(staging.mapped, data, size); if (!_stageCopy(&staging)) { - SDL_ReleaseGPUBuffer(_scene.device, buffer); + rgpuReleaseBuffer(_scene.device, buffer); return NULL; } memset(&source, 0, sizeof(source)); @@ -3511,7 +3517,7 @@ static SDL_GPUBuffer *_uploadBuffer(SDL_GPUBufferUsageFlags usage, const void *d source.transfer_buffer = staging.transfer; region.buffer = buffer; region.size = size; - SDL_UploadToGPUBuffer(staging.pass, &source, ®ion, false); + rgpuUploadToBuffer(staging.pass, &source, ®ion, false); _stageEnd(&staging, NULL); return buffer; } @@ -3552,13 +3558,13 @@ static SDL_GPUTexture *_uploadCompressed(const Ktx2ImageT *image, bool srgb) { info.layer_count_or_depth = 1; info.num_levels = (Uint32)image->levelCount; info.sample_count = SDL_GPU_SAMPLECOUNT_1; - texture = SDL_CreateGPUTexture(_scene.device, &info); + texture = rgpuCreateTexture(_scene.device, &info); if (texture == NULL) { utilTrace("Scene: compressed texture: %s", SDL_GetError()); return NULL; } if (!_stageBegin(&staging, (uint32_t)total)) { - SDL_ReleaseGPUTexture(_scene.device, texture); + rgpuReleaseTexture(_scene.device, texture); return NULL; } for (level = 0; level < image->levelCount; level++) { @@ -3566,7 +3572,7 @@ static SDL_GPUTexture *_uploadCompressed(const Ktx2ImageT *image, bool srgb) { offset += image->levels[level].size; } if (!_stageCopy(&staging)) { - SDL_ReleaseGPUTexture(_scene.device, texture); + rgpuReleaseTexture(_scene.device, texture); return NULL; } offset = 0; @@ -3599,18 +3605,18 @@ static SDL_GPUTexture *_uploadCube(const uint16_t *pixels, int32_t face) { info.layer_count_or_depth = CUBE_FACES; info.num_levels = levels; info.sample_count = SDL_GPU_SAMPLECOUNT_1; - texture = SDL_CreateGPUTexture(_scene.device, &info); + texture = rgpuCreateTexture(_scene.device, &info); if (texture == NULL) { utilTrace("Scene: cube texture: %s", SDL_GetError()); return NULL; } if (!_stageBegin(&staging, faceBytes * CUBE_FACES)) { - SDL_ReleaseGPUTexture(_scene.device, texture); + rgpuReleaseTexture(_scene.device, texture); return NULL; } memcpy(staging.mapped, pixels, faceBytes * CUBE_FACES); if (!_stageCopy(&staging)) { - SDL_ReleaseGPUTexture(_scene.device, texture); + rgpuReleaseTexture(_scene.device, texture); return NULL; } for (f = 0; f < CUBE_FACES; f++) { @@ -3637,28 +3643,28 @@ static bool _uploadDynamic(SDL_GPUCommandBuffer *commands, SDL_GPUBufferUsageFla } if (bytes > *capacity) { if (*buffer != NULL) { - SDL_ReleaseGPUBuffer(_scene.device, *buffer); + rgpuReleaseBuffer(_scene.device, *buffer); } if (*transfer != NULL) { - SDL_ReleaseGPUTransferBuffer(_scene.device, *transfer); + rgpuReleaseTransferBuffer(_scene.device, *transfer); } *capacity = SDL_max(bytes * 2, DYNAMIC_BUFFER_MIN); memset(&info, 0, sizeof(info)); info.usage = usage; info.size = *capacity; - *buffer = SDL_CreateGPUBuffer(_scene.device, &info); + *buffer = rgpuCreateBuffer(_scene.device, &info); memset(&transferInfo, 0, sizeof(transferInfo)); transferInfo.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; transferInfo.size = *capacity; - *transfer = SDL_CreateGPUTransferBuffer(_scene.device, &transferInfo); + *transfer = rgpuCreateTransferBuffer(_scene.device, &transferInfo); if ((*buffer == NULL) || (*transfer == NULL)) { // Nothing kept, so the next frame tries again rather than mapping a buffer it has not got. utilTrace("Scene: %s buffer: %s", what, SDL_GetError()); if (*buffer != NULL) { - SDL_ReleaseGPUBuffer(_scene.device, *buffer); + rgpuReleaseBuffer(_scene.device, *buffer); } if (*transfer != NULL) { - SDL_ReleaseGPUTransferBuffer(_scene.device, *transfer); + rgpuReleaseTransferBuffer(_scene.device, *transfer); } *buffer = NULL; *transfer = NULL; @@ -3666,21 +3672,21 @@ static bool _uploadDynamic(SDL_GPUCommandBuffer *commands, SDL_GPUBufferUsageFla return false; } } - mapped = SDL_MapGPUTransferBuffer(_scene.device, *transfer, true); + mapped = rgpuMapTransferBuffer(_scene.device, *transfer, true); if (mapped == NULL) { utilTrace("Scene: %s buffer: %s", what, SDL_GetError()); return false; } memcpy(mapped, data, bytes); - SDL_UnmapGPUTransferBuffer(_scene.device, *transfer); - pass = SDL_BeginGPUCopyPass(commands); + rgpuUnmapTransferBuffer(_scene.device, *transfer); + pass = rgpuBeginCopyPass(commands); memset(&source, 0, sizeof(source)); memset(®ion, 0, sizeof(region)); source.transfer_buffer = *transfer; region.buffer = *buffer; region.size = bytes; - SDL_UploadToGPUBuffer(pass, &source, ®ion, true); - SDL_EndGPUCopyPass(pass); + rgpuUploadToBuffer(pass, &source, ®ion, true); + rgpuEndCopyPass(pass); return true; } @@ -3734,13 +3740,13 @@ static SDL_GPUTexture *_uploadTexture(SDL_Surface *image, bool srgb) { info.layer_count_or_depth = 1; info.num_levels = levels; info.sample_count = SDL_GPU_SAMPLECOUNT_1; - texture = SDL_CreateGPUTexture(_scene.device, &info); + texture = rgpuCreateTexture(_scene.device, &info); if ((texture == NULL) && (levels > 1)) { utilTrace("Scene: no mipmaps for a %dx%d texture: %s", rgba->w, rgba->h, SDL_GetError()); levels = 1; info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER; info.num_levels = 1; - texture = SDL_CreateGPUTexture(_scene.device, &info); + texture = rgpuCreateTexture(_scene.device, &info); } if (texture == NULL) { utilTrace("Scene: %s", SDL_GetError()); @@ -3748,7 +3754,7 @@ static SDL_GPUTexture *_uploadTexture(SDL_Surface *image, bool srgb) { return NULL; } if (!_stageBegin(&staging, size)) { - SDL_ReleaseGPUTexture(_scene.device, texture); + rgpuReleaseTexture(_scene.device, texture); SDL_DestroySurface(rgba); return NULL; } @@ -3762,7 +3768,7 @@ static SDL_GPUTexture *_uploadTexture(SDL_Surface *image, bool srgb) { } } if (!_stageCopy(&staging)) { - SDL_ReleaseGPUTexture(_scene.device, texture); + rgpuReleaseTexture(_scene.device, texture); SDL_DestroySurface(rgba); return NULL; } @@ -4246,17 +4252,17 @@ bool meshDelete(int32_t mesh) { return false; } if (_scene.meshes[mesh].vertexBuffer != NULL) { - SDL_ReleaseGPUBuffer(_scene.device, _scene.meshes[mesh].vertexBuffer); + rgpuReleaseBuffer(_scene.device, _scene.meshes[mesh].vertexBuffer); } if (_scene.meshes[mesh].indexBuffer != NULL) { - SDL_ReleaseGPUBuffer(_scene.device, _scene.meshes[mesh].indexBuffer); + rgpuReleaseBuffer(_scene.device, _scene.meshes[mesh].indexBuffer); } SDL_free(_scene.meshes[mesh].positions); SDL_free(_scene.meshes[mesh].heights); SDL_free(_scene.meshes[mesh].indices); SDL_free(_scene.meshes[mesh].vertices); if (_scene.meshes[mesh].transfer != NULL) { - SDL_ReleaseGPUTransferBuffer(_scene.device, _scene.meshes[mesh].transfer); + rgpuReleaseTransferBuffer(_scene.device, _scene.meshes[mesh].transfer); } _freeMorphs(&_scene.meshes[mesh]); memset(&_scene.meshes[mesh], 0, sizeof(MeshT)); @@ -4522,33 +4528,33 @@ bool meshSetPositions(int32_t mesh, const float *positions) { memset(&transferInfo, 0, sizeof(transferInfo)); transferInfo.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; transferInfo.size = size; - m->transfer = SDL_CreateGPUTransferBuffer(_scene.device, &transferInfo); + m->transfer = rgpuCreateTransferBuffer(_scene.device, &transferInfo); if (m->transfer == NULL) { utilTrace("Scene: %s", SDL_GetError()); return false; } } - mapped = SDL_MapGPUTransferBuffer(_scene.device, m->transfer, true); + mapped = rgpuMapTransferBuffer(_scene.device, m->transfer, true); if (mapped == NULL) { utilTrace("Scene: %s", SDL_GetError()); return false; } memcpy(mapped, m->vertices, size); - SDL_UnmapGPUTransferBuffer(_scene.device, m->transfer); - commands = SDL_AcquireGPUCommandBuffer(_scene.device); + rgpuUnmapTransferBuffer(_scene.device, m->transfer); + commands = rgpuAcquireCommandBuffer(_scene.device); if (commands == NULL) { utilTrace("Scene: %s", SDL_GetError()); return false; } - pass = SDL_BeginGPUCopyPass(commands); + pass = rgpuBeginCopyPass(commands); memset(&source, 0, sizeof(source)); memset(®ion, 0, sizeof(region)); source.transfer_buffer = m->transfer; region.buffer = m->vertexBuffer; region.size = size; - SDL_UploadToGPUBuffer(pass, &source, ®ion, true); - SDL_EndGPUCopyPass(pass); - SDL_SubmitGPUCommandBuffer(commands); + rgpuUploadToBuffer(pass, &source, ®ion, true); + rgpuEndCopyPass(pass); + rgpuSubmitCommandBuffer(commands); return true; } @@ -5416,11 +5422,11 @@ bool sceneInit(SDL_GPUDevice *device, SDL_Renderer *renderer) { _scene.hdrFormat = _hdrFormat(); // What KTX2 textures become: the best block format the device has, else plain RGBA. _scene.compressedFormat = KTX2_RGBA; - if (SDL_GPUTextureSupportsFormat(device, SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB, SDL_GPU_TEXTURETYPE_2D, SDL_GPU_TEXTUREUSAGE_SAMPLER)) { + if (rgpuTextureSupportsFormat(device, SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB, SDL_GPU_TEXTURETYPE_2D, SDL_GPU_TEXTUREUSAGE_SAMPLER)) { _scene.compressedFormat = KTX2_BC7; - } else if (SDL_GPUTextureSupportsFormat(device, SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB, SDL_GPU_TEXTURETYPE_2D, SDL_GPU_TEXTUREUSAGE_SAMPLER)) { + } else if (rgpuTextureSupportsFormat(device, SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB, SDL_GPU_TEXTURETYPE_2D, SDL_GPU_TEXTUREUSAGE_SAMPLER)) { _scene.compressedFormat = KTX2_ASTC; - } else if (SDL_GPUTextureSupportsFormat(device, SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB, SDL_GPU_TEXTURETYPE_2D, SDL_GPU_TEXTUREUSAGE_SAMPLER)) { + } else if (rgpuTextureSupportsFormat(device, SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB, SDL_GPU_TEXTURETYPE_2D, SDL_GPU_TEXTUREUSAGE_SAMPLER)) { _scene.compressedFormat = KTX2_BC3; } if (!_createShaders()) { @@ -5430,6 +5436,11 @@ bool sceneInit(SDL_GPUDevice *device, SDL_Renderer *renderer) { // Textures: trilinear with anisotropy (SDL asks the driver for it and drops it where it is not // offered), and a nearest-neighbour sampler for pixel art that still walks the mipmap chain. memset(&samplerInfo, 0, sizeof(samplerInfo)); + // max_lod is not optional. SDL hands it straight to Vulkan's maxLod and D3D12's MaxLOD, so the + // zero a memset leaves clamps every lookup to mip level 0: no mipmapping at all, the anisotropy + // below mostly wasted, and the sky cube unable to pick a level by roughness. Every sampler here + // wants the whole chain, so it is set once for all of them. + samplerInfo.max_lod = SAMPLER_MAX_LOD; samplerInfo.min_filter = SDL_GPU_FILTER_LINEAR; samplerInfo.mag_filter = SDL_GPU_FILTER_LINEAR; samplerInfo.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR; @@ -5438,12 +5449,12 @@ bool sceneInit(SDL_GPUDevice *device, SDL_Renderer *renderer) { samplerInfo.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_REPEAT; samplerInfo.enable_anisotropy = true; samplerInfo.max_anisotropy = MAX_ANISOTROPY; - _scene.sampler = SDL_CreateGPUSampler(device, &samplerInfo); + _scene.sampler = rgpuCreateSampler(device, &samplerInfo); samplerInfo.min_filter = SDL_GPU_FILTER_NEAREST; samplerInfo.mag_filter = SDL_GPU_FILTER_NEAREST; samplerInfo.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST; samplerInfo.enable_anisotropy = false; - _scene.nearestSampler = SDL_CreateGPUSampler(device, &samplerInfo); + _scene.nearestSampler = rgpuCreateSampler(device, &samplerInfo); // The shadow map is compared texel by texel, so no filtering, and clamped so its edge holds. samplerInfo.min_filter = SDL_GPU_FILTER_NEAREST; samplerInfo.mag_filter = SDL_GPU_FILTER_NEAREST; @@ -5451,14 +5462,14 @@ bool sceneInit(SDL_GPUDevice *device, SDL_Renderer *renderer) { samplerInfo.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; samplerInfo.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; samplerInfo.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; - _scene.shadowSampler = SDL_CreateGPUSampler(device, &samplerInfo); + _scene.shadowSampler = rgpuCreateSampler(device, &samplerInfo); // The post pass reads the HDR target texel for texel. samplerInfo.min_filter = SDL_GPU_FILTER_LINEAR; samplerInfo.mag_filter = SDL_GPU_FILTER_LINEAR; - _scene.postSampler = SDL_CreateGPUSampler(device, &samplerInfo); + _scene.postSampler = rgpuCreateSampler(device, &samplerInfo); // The sky cube: trilinear (roughness picks the mip level) and clamped. samplerInfo.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR; - _scene.skySampler = SDL_CreateGPUSampler(device, &samplerInfo); + _scene.skySampler = rgpuCreateSampler(device, &samplerInfo); _scene.white = _solidTexture(255, 255, 255); _scene.flatNormal = _solidTexture(128, 128, 255); _scene.black = _solidTexture(0, 0, 0); @@ -5599,44 +5610,44 @@ void sceneQuit(void) { } _destroyPipelines(); if (_scene.instanceBuffer != NULL) { - SDL_ReleaseGPUBuffer(_scene.device, _scene.instanceBuffer); + rgpuReleaseBuffer(_scene.device, _scene.instanceBuffer); } if (_scene.instanceTransfer != NULL) { - SDL_ReleaseGPUTransferBuffer(_scene.device, _scene.instanceTransfer); + rgpuReleaseTransferBuffer(_scene.device, _scene.instanceTransfer); } if (_scene.particleVertex != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.particleVertex); + rgpuReleaseShader(_scene.device, _scene.particleVertex); } if (_scene.particleFragment != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.particleFragment); + rgpuReleaseShader(_scene.device, _scene.particleFragment); } if (_scene.lineVertex != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.lineVertex); + rgpuReleaseShader(_scene.device, _scene.lineVertex); } if (_scene.lineFragment != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.lineFragment); + rgpuReleaseShader(_scene.device, _scene.lineFragment); } _releasePipeline(&_scene.postPipeline); if (_scene.postVertex != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.postVertex); + rgpuReleaseShader(_scene.device, _scene.postVertex); } if (_scene.postFragment != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.postFragment); + rgpuReleaseShader(_scene.device, _scene.postFragment); } if (_scene.postSampler != NULL) { - SDL_ReleaseGPUSampler(_scene.device, _scene.postSampler); + rgpuReleaseSampler(_scene.device, _scene.postSampler); } if (_scene.skySampler != NULL) { - SDL_ReleaseGPUSampler(_scene.device, _scene.skySampler); + rgpuReleaseSampler(_scene.device, _scene.skySampler); } if (_scene.skyFragment != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.skyFragment); + rgpuReleaseShader(_scene.device, _scene.skyFragment); } if (_scene.bloomDownFragment != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.bloomDownFragment); + rgpuReleaseShader(_scene.device, _scene.bloomDownFragment); } if (_scene.bloomUpFragment != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.bloomUpFragment); + rgpuReleaseShader(_scene.device, _scene.bloomUpFragment); } _releasePipeline(&_scene.bloomDownPipeline); _releasePipeline(&_scene.bloomUpPipeline); @@ -5652,53 +5663,53 @@ void sceneQuit(void) { _releaseTexture(&_scene.skyCube); _releaseTexture(&_scene.blackCube); if (_scene.particleBuffer != NULL) { - SDL_ReleaseGPUBuffer(_scene.device, _scene.particleBuffer); + rgpuReleaseBuffer(_scene.device, _scene.particleBuffer); } if (_scene.particleTransfer != NULL) { - SDL_ReleaseGPUTransferBuffer(_scene.device, _scene.particleTransfer); + rgpuReleaseTransferBuffer(_scene.device, _scene.particleTransfer); } if (_scene.lineBuffer != NULL) { - SDL_ReleaseGPUBuffer(_scene.device, _scene.lineBuffer); + rgpuReleaseBuffer(_scene.device, _scene.lineBuffer); } if (_scene.lineTransfer != NULL) { - SDL_ReleaseGPUTransferBuffer(_scene.device, _scene.lineTransfer); + rgpuReleaseTransferBuffer(_scene.device, _scene.lineTransfer); } _releaseParticleTextures(true); if (_scene.vertexStatic != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.vertexStatic); + rgpuReleaseShader(_scene.device, _scene.vertexStatic); } if (_scene.vertexSkinned != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.vertexSkinned); + rgpuReleaseShader(_scene.device, _scene.vertexSkinned); } if (_scene.fragment != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.fragment); + rgpuReleaseShader(_scene.device, _scene.fragment); } if (_scene.depthCutoutFragment != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.depthCutoutFragment); + rgpuReleaseShader(_scene.device, _scene.depthCutoutFragment); _scene.depthCutoutFragment = NULL; } if (_scene.depthFragment != NULL) { - SDL_ReleaseGPUShader(_scene.device, _scene.depthFragment); + rgpuReleaseShader(_scene.device, _scene.depthFragment); } if (_scene.sampler != NULL) { - SDL_ReleaseGPUSampler(_scene.device, _scene.sampler); + rgpuReleaseSampler(_scene.device, _scene.sampler); } if (_scene.nearestSampler != NULL) { - SDL_ReleaseGPUSampler(_scene.device, _scene.nearestSampler); + rgpuReleaseSampler(_scene.device, _scene.nearestSampler); } if (_scene.shadowSampler != NULL) { - SDL_ReleaseGPUSampler(_scene.device, _scene.shadowSampler); + rgpuReleaseSampler(_scene.device, _scene.shadowSampler); } _destroyShadowMaps(); if (_scene.shadowMapsNone != NULL) { - SDL_ReleaseGPUTexture(_scene.device, _scene.shadowMapsNone); + rgpuReleaseTexture(_scene.device, _scene.shadowMapsNone); } _releaseTexture(&_scene.depthNone); _releaseTexture(&_scene.white); _releaseTexture(&_scene.flatNormal); _releaseTexture(&_scene.black); if (_scene.noMorphs != NULL) { - SDL_ReleaseGPUBuffer(_scene.device, _scene.noMorphs); + rgpuReleaseBuffer(_scene.device, _scene.noMorphs); } _destroyTargets(); } @@ -5813,7 +5824,7 @@ SDL_Texture *sceneRender(void) { if (drawCount > opaqueCount) { memmove(&_scene.draws[opaqueCount], &_scene.draws[blendedStart], sizeof(DrawT) * (size_t)(drawCount - opaqueCount)); } - commands = SDL_AcquireGPUCommandBuffer(_scene.device); + commands = rgpuAcquireCommandBuffer(_scene.device); if (commands == NULL) { utilTrace("Scene: %s", SDL_GetError()); return NULL; @@ -5862,9 +5873,9 @@ SDL_Texture *sceneRender(void) { _cullFace(shadow, face, drawCount, skip); depth.texture = _scene.shadowMaps; depth.layer = (Uint8)(shadow->layer + face); - pass = SDL_BeginGPURenderPass(commands, NULL, 0, &depth); + pass = rgpuBeginRenderPass(commands, NULL, 0, &depth); _drawList(commands, pass, drawCount, &shadow->faces[face], &main, true, true, skip, NULL, SAMPLE_SET_SINGLE); - SDL_EndGPURenderPass(pass); + rgpuEndRenderPass(pass); } } else if (shadow->type == SHADOW_CASCADE) { int32_t k; @@ -5876,17 +5887,17 @@ SDL_Texture *sceneRender(void) { _cullCascade(shadow, k, drawCount, skip); depth.texture = _scene.shadowMaps; depth.layer = (Uint8)(shadow->layer + k); - pass = SDL_BeginGPURenderPass(commands, NULL, 0, &depth); + pass = rgpuBeginRenderPass(commands, NULL, 0, &depth); _drawList(commands, pass, drawCount, &shadow->faces[k], &main, true, false, skip, NULL, SAMPLE_SET_SINGLE); - SDL_EndGPURenderPass(pass); + rgpuEndRenderPass(pass); } } else { fragmentUniforms.shadowMatrix[slot * MAX_CASCADES] = shadow->matrix; depth.texture = _scene.shadowMaps; depth.layer = (Uint8)shadow->layer; - pass = SDL_BeginGPURenderPass(commands, NULL, 0, &depth); + pass = rgpuBeginRenderPass(commands, NULL, 0, &depth); _drawList(commands, pass, drawCount, &shadow->matrix, &main, true, false, NULL, NULL, SAMPLE_SET_SINGLE); - SDL_EndGPURenderPass(pass); + rgpuEndRenderPass(pass); } } } else { @@ -5910,7 +5921,7 @@ SDL_Texture *sceneRender(void) { _renderCamera(commands, &frame, &fragmentUniforms, drawCount); } _renderCamera(commands, &main, &fragmentUniforms, drawCount); - SDL_SubmitGPUCommandBuffer(commands); + rgpuSubmitCommandBuffer(commands); _scene.lineVertexCount = 0; return _scene.composite; } @@ -5919,7 +5930,6 @@ SDL_Texture *sceneRender(void) { // (Re)creates the render targets at the overlay's size. bool sceneResize(int32_t width, int32_t height) { SDL_GPUTextureCreateInfo info; - SDL_PropertiesID props; if (_scene.device == NULL) { return false; @@ -5933,21 +5943,21 @@ bool sceneResize(int32_t width, int32_t height) { // The display texture the post pass writes and the 2D renderer composites. memset(&info, 0, sizeof(info)); info.type = SDL_GPU_TEXTURETYPE_2D; - info.format = SDL_GetGPUTextureFormatFromPixelFormat(SDL_PIXELFORMAT_BGRA32); + info.format = rgpuGetTextureFormatFromPixelFormat(SDL_PIXELFORMAT_BGRA32); info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; info.width = (Uint32)width; info.height = (Uint32)height; info.layer_count_or_depth = 1; info.num_levels = 1; info.sample_count = SDL_GPU_SAMPLECOUNT_1; - _scene.output = SDL_CreateGPUTexture(_scene.device, &info); + _scene.output = rgpuCreateTexture(_scene.device, &info); if (_scene.output == NULL) { utilTrace("Scene: %s", SDL_GetError()); return false; } // The scene itself renders in linear light into the HDR target. info.format = _scene.hdrFormat; - _scene.colour = SDL_CreateGPUTexture(_scene.device, &info); + _scene.colour = rgpuCreateTexture(_scene.device, &info); if (_scene.colour == NULL) { utilTrace("Scene: %s", SDL_GetError()); _destroyTargets(); @@ -5956,10 +5966,10 @@ bool sceneResize(int32_t width, int32_t height) { // 4x multisampling when wanted and offered: a multisampled colour target resolved into colour, // and a multisampled depth target to match. _scene.sampleCount = SDL_GPU_SAMPLECOUNT_1; - if (_scene.antialias && SDL_GPUTextureSupportsSampleCount(_scene.device, info.format, SDL_GPU_SAMPLECOUNT_4) && SDL_GPUTextureSupportsSampleCount(_scene.device, _scene.depthFormat, SDL_GPU_SAMPLECOUNT_4)) { + if (_scene.antialias && rgpuTextureSupportsSampleCount(_scene.device, info.format, SDL_GPU_SAMPLECOUNT_4) && rgpuTextureSupportsSampleCount(_scene.device, _scene.depthFormat, SDL_GPU_SAMPLECOUNT_4)) { info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET; info.sample_count = SDL_GPU_SAMPLECOUNT_4; - _scene.multisampled = SDL_CreateGPUTexture(_scene.device, &info); + _scene.multisampled = rgpuCreateTexture(_scene.device, &info); if (_scene.multisampled != NULL) { _scene.sampleCount = SDL_GPU_SAMPLECOUNT_4; } else { @@ -5970,7 +5980,7 @@ bool sceneResize(int32_t width, int32_t height) { _destroyPipelines(); info.format = _scene.depthFormat; info.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET; - _scene.depth = SDL_CreateGPUTexture(_scene.device, &info); + _scene.depth = rgpuCreateTexture(_scene.device, &info); if (_scene.depth == NULL) { utilTrace("Scene: %s", SDL_GetError()); _destroyTargets(); @@ -5979,14 +5989,7 @@ bool sceneResize(int32_t width, int32_t height) { // The camera's depth for soft particles: single sample in the shadow pipelines' format, so the // depth-only pipelines can fill it. _scene.softDepth = _createShadowArray(SDL_GPU_TEXTURETYPE_2D, 1, 0); - props = SDL_CreateProperties(); - SDL_SetPointerProperty(props, SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_POINTER, _scene.output); - SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, SDL_PIXELFORMAT_BGRA32); - 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); - _scene.composite = SDL_CreateTextureWithProperties(_scene.renderer, props); - SDL_DestroyProperties(props); + _scene.composite = renderWrapTexture(_scene.renderer, _scene.output, SDL_PIXELFORMAT_BGRA32, width, height); if (_scene.composite == NULL) { utilTrace("Scene: %s", SDL_GetError()); _destroyTargets(); @@ -6191,7 +6194,7 @@ void sceneUpdateVideo(SceneVideoSourceFn source) { utilTrace("Scene: video feed: %s", SDL_GetError()); continue; } - feed->gpu = SDL_GetPointerProperty(SDL_GetTextureProperties(feed->target), SDL_PROP_TEXTURE_GPU_TEXTURE_POINTER, NULL); + feed->gpu = renderTextureFor(feed->target); } SDL_SetRenderTarget(_scene.renderer, feed->target); SDL_RenderTexture(_scene.renderer, frame, NULL, NULL); @@ -6303,12 +6306,12 @@ int32_t viewNew(int32_t width, int32_t height) { info.layer_count_or_depth = 1; info.num_levels = 1; info.sample_count = SDL_GPU_SAMPLECOUNT_1; - view->colour = SDL_CreateGPUTexture(_scene.device, &info); - info.format = SDL_GetGPUTextureFormatFromPixelFormat(SDL_PIXELFORMAT_BGRA32); - view->output = SDL_CreateGPUTexture(_scene.device, &info); + view->colour = rgpuCreateTexture(_scene.device, &info); + info.format = rgpuGetTextureFormatFromPixelFormat(SDL_PIXELFORMAT_BGRA32); + view->output = rgpuCreateTexture(_scene.device, &info); info.format = _scene.depthFormat; info.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET; - view->depth = SDL_CreateGPUTexture(_scene.device, &info); + view->depth = rgpuCreateTexture(_scene.device, &info); if ((view->colour == NULL) || (view->output == NULL) || (view->depth == NULL)) { utilTrace("Scene: view: %s", SDL_GetError()); _freeView(view); diff --git a/src/singe.c b/src/singe.c index b19fa563b..04d489661 100644 --- a/src/singe.c +++ b/src/singe.c @@ -71,6 +71,7 @@ LSEC_API int luaopen_ssl_config(lua_State *L); #include "vfs.h" #include "persist.h" #include "scene.h" +#include "render.h" #include "scheduler.h" #include "stats.h" #include "hdr.h" @@ -12646,7 +12647,7 @@ static int32_t apiSingeGetSystemInfo(lua_State *L) { lua_pushstring(L, (os != NULL) ? os : ""); lua_setfield(L, -2, "os"); lua_pushstring(L, (cpu != NULL) ? cpu : ""); lua_setfield(L, -2, "cpu"); lua_pushstring(L, SDL_GetRendererName(_global.renderer)); lua_setfield(L, -2, "renderer"); - lua_pushstring(L, (_global.device != NULL) ? SDL_GetGPUDeviceDriver(_global.device) : "none (3D unavailable)"); lua_setfield(L, -2, "gpu"); + lua_pushstring(L, (_global.device != NULL) ? rgpuGetDeviceDriver(_global.device) : "none (3D unavailable)"); lua_setfield(L, -2, "gpu"); lua_pushstring(L, videoGetDecoderDescription()); lua_setfield(L, -2, "decoder"); lua_pushstring(L, (audio != NULL) ? audio : ""); lua_setfield(L, -2, "audio"); lua_pushstring(L, midiSoundfont()); lua_setfield(L, -2, "soundFont");