From 4db0d16f1520724cac50806342af586736a3d347 Mon Sep 17 00:00:00 2001
From: Jaan Soulier <[EMAIL REDACTED]>
Date: Sun, 11 Jan 2026 17:38:49 -0500
Subject: [PATCH] Add helpers for loading GPU textures (closes #546)
---
CMakeLists.txt | 2 +
examples/showgpuimage.c | 168 +++++++++++++++++++++++++++++++++
include/SDL3_image/SDL_image.h | 135 ++++++++++++++++++++++++--
src/IMG_gpu.c | 141 +++++++++++++++++++++++++++
src/SDL_image.exports | 3 +
src/SDL_image.sym | 3 +
6 files changed, 446 insertions(+), 6 deletions(-)
create mode 100644 examples/showgpuimage.c
create mode 100644 src/IMG_gpu.c
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 66bc3153..d79756c2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -270,6 +270,7 @@ add_library(${sdl3_image_target_name}
src/IMG_avif.c
src/IMG_bmp.c
src/IMG_gif.c
+ src/IMG_gpu.c
src/IMG_jpg.c
src/IMG_jxl.c
src/IMG_lbm.c
@@ -1330,6 +1331,7 @@ if(SDLIMAGE_SAMPLES)
add_sdl_image_example_executable(showanim examples/showanim.c)
add_sdl_image_example_executable(showimage examples/showimage.c)
add_sdl_image_example_executable(showclipboard examples/showclipboard.c)
+ add_sdl_image_example_executable(showgpuimage examples/showgpuimage.c)
endif()
if(SDLIMAGE_TESTS)
diff --git a/examples/showgpuimage.c b/examples/showgpuimage.c
new file mode 100644
index 00000000..f01c8e24
--- /dev/null
+++ b/examples/showgpuimage.c
@@ -0,0 +1,168 @@
+/*
+ showgpuimage: A test application for the SDL image loading library.
+ Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_main.h>
+#include <SDL3_image/SDL_image.h>
+
+static SDL_Window *window;
+static SDL_GPUDevice *device;
+static SDL_GPUTexture *texture;
+static Uint32 texture_width;
+static Uint32 texture_height;
+
+static const char *get_file_path(const char *file)
+{
+ static char path[4096];
+
+ if (*file != '/' && !SDL_GetPathInfo(file, NULL)) {
+ SDL_snprintf(path, sizeof(path), "%s%s", SDL_GetBasePath(), file);
+ if (SDL_GetPathInfo(path, NULL)) {
+ return path;
+ }
+ }
+ return file;
+}
+
+static bool load_image(SDL_GPUCommandBuffer *command_buffer, const char *path)
+{
+ SDL_ReleaseGPUTexture(device, texture);
+ texture = NULL;
+
+ SDL_GPUCopyPass *copy_pass = SDL_BeginGPUCopyPass(command_buffer);
+ if (!copy_pass) {
+ SDL_Log("SDL_BeginGPUCopyPass() failed: %s", SDL_GetError());
+ return false;
+ }
+ texture = IMG_LoadGPUTexture(device, copy_pass, get_file_path(path), &texture_width, &texture_height);
+ if (!texture) {
+ SDL_Log("IMG_LoadGPUTexture() failed: %s", SDL_GetError());
+ }
+ SDL_EndGPUCopyPass(copy_pass);
+
+ return texture != NULL;
+}
+
+int main(int argc, char *argv[])
+{
+ int result = 0;
+ bool quit = false;
+ SDL_GPUCommandBuffer *command_buffer = NULL;
+ SDL_Event event = {0};
+ SDL_GPUTexture *swapchain_texture = NULL;
+ Uint32 swapchain_width = 0;
+ Uint32 swapchain_height = 0;
+ SDL_GPUBlitInfo blit_info = {0};
+
+ if (!SDL_Init(SDL_INIT_VIDEO)) {
+ SDL_Log("SDL_Init(SDL_INIT_VIDEO) failed: %s", SDL_GetError());
+ result = 2;
+ goto done;
+ }
+
+ window = SDL_CreateWindow("", 960, 720, SDL_WINDOW_RESIZABLE);
+ if (!window) {
+ SDL_Log("SDL_CreateWindow() failed: %s", SDL_GetError());
+ result = 2;
+ goto done;
+ }
+
+ device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_MSL, true, NULL);
+ if (!window) {
+ SDL_Log("SDL_CreateGPUDevice() failed: %s", SDL_GetError());
+ result = 2;
+ goto done;
+ }
+
+ if (!SDL_ClaimWindowForGPUDevice(device, window)) {
+ SDL_Log("SDL_ClaimWindowForGPUDevice() failed: %s", SDL_GetError());
+ result = 2;
+ goto done;
+ }
+
+ if (argc > 1) {
+ SDL_GPUCommandBuffer *command_buffer = SDL_AcquireGPUCommandBuffer(device);
+ if (!command_buffer) {
+ SDL_Log("SDL_AcquireGPUCommandBuffer() failed: %s", SDL_GetError());
+ result = 2;
+ goto done;
+ }
+ if (!load_image(command_buffer, argv[1])) {
+ result = 2;
+ goto done;
+ }
+ SDL_SubmitGPUCommandBuffer(command_buffer);
+ }
+
+ while (!quit) {
+ command_buffer = SDL_AcquireGPUCommandBuffer(device);
+ if (!command_buffer) {
+ SDL_Log("SDL_AcquireGPUCommandBuffer() failed: %s", SDL_GetError());
+ continue;
+ }
+
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_EVENT_QUIT:
+ quit = true;
+ break;
+ case SDL_EVENT_DROP_FILE:
+ load_image(command_buffer, event.drop.data);
+ break;
+ }
+ }
+ if (quit) {
+ break;
+ }
+
+ if (!SDL_WaitAndAcquireGPUSwapchainTexture(command_buffer, window, &swapchain_texture, &swapchain_width, &swapchain_height)) {
+ SDL_Log("SDL_WaitAndAcquireGPUSwapchainTexture() failed: %s", SDL_GetError());
+ SDL_CancelGPUCommandBuffer(command_buffer);
+ continue;
+ }
+
+ if (!swapchain_texture || !swapchain_width || !swapchain_height) {
+ // Not an error. Happens on minimize
+ SDL_CancelGPUCommandBuffer(command_buffer);
+ continue;
+ }
+
+ if (texture) {
+ blit_info.source.texture = texture;
+ blit_info.source.w = texture_width;
+ blit_info.source.h = texture_height;
+ blit_info.destination.texture = swapchain_texture;
+ blit_info.destination.w = swapchain_width;
+ blit_info.destination.h = swapchain_height;
+ SDL_BlitGPUTexture(command_buffer, &blit_info);
+ }
+
+ SDL_SubmitGPUCommandBuffer(command_buffer);
+ }
+
+done:
+ SDL_ReleaseGPUTexture(device, texture);
+ SDL_ReleaseWindowFromGPUDevice(device, window);
+ SDL_DestroyGPUDevice(device);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+ return result;
+}
diff --git a/include/SDL3_image/SDL_image.h b/include/SDL3_image/SDL_image.h
index 55517e66..334d05ea 100644
--- a/include/SDL3_image/SDL_image.h
+++ b/include/SDL3_image/SDL_image.h
@@ -229,7 +229,7 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL IMG_Load_IO(SDL_IOStream *src, bool cl
extern SDL_DECLSPEC SDL_Surface * SDLCALL IMG_LoadTyped_IO(SDL_IOStream *src, bool closeio, const char *type);
/**
- * Load an image from a filesystem path into a GPU texture.
+ * Load an image from a filesystem path into a texture.
*
* An SDL_Texture represents an image in GPU memory, usable by SDL's 2D Render
* API. This can be significantly more efficient than using a CPU-bound
@@ -252,7 +252,7 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL IMG_LoadTyped_IO(SDL_IOStream *src, bo
* When done with the returned texture, the app should dispose of it with a
* call to SDL_DestroyTexture().
*
- * \param renderer the SDL_Renderer to use to create the GPU texture.
+ * \param renderer the SDL_Renderer to use to create the texture.
* \param file a path on the filesystem to load an image from.
* \returns a new texture, or NULL on error.
*
@@ -264,7 +264,7 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL IMG_LoadTyped_IO(SDL_IOStream *src, bo
extern SDL_DECLSPEC SDL_Texture * SDLCALL IMG_LoadTexture(SDL_Renderer *renderer, const char *file);
/**
- * Load an image from an SDL data source into a GPU texture.
+ * Load an image from an SDL data source into a texture.
*
* An SDL_Texture represents an image in GPU memory, usable by SDL's 2D Render
* API. This can be significantly more efficient than using a CPU-bound
@@ -296,7 +296,7 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL IMG_LoadTexture(SDL_Renderer *renderer
* When done with the returned texture, the app should dispose of it with a
* call to SDL_DestroyTexture().
*
- * \param renderer the SDL_Renderer to use to create the GPU texture.
+ * \param renderer the SDL_Renderer to use to create the texture.
* \param src an SDL_IOStream that data will be read from.
* \param closeio true to close/free the SDL_IOStream before returning, false
* to leave it open.
@@ -310,7 +310,7 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL IMG_LoadTexture(SDL_Renderer *renderer
extern SDL_DECLSPEC SDL_Texture * SDLCALL IMG_LoadTexture_IO(SDL_Renderer *renderer, SDL_IOStream *src, bool closeio);
/**
- * Load an image from an SDL data source into a GPU texture.
+ * Load an image from an SDL data source into a texture.
*
* An SDL_Texture represents an image in GPU memory, usable by SDL's 2D Render
* API. This can be significantly more efficient than using a CPU-bound
@@ -348,7 +348,7 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL IMG_LoadTexture_IO(SDL_Renderer *rende
* When done with the returned texture, the app should dispose of it with a
* call to SDL_DestroyTexture().
*
- * \param renderer the SDL_Renderer to use to create the GPU texture.
+ * \param renderer the SDL_Renderer to use to create the texture.
* \param src an SDL_IOStream that data will be read from.
* \param closeio true to close/free the SDL_IOStream before returning, false
* to leave it open.
@@ -363,6 +363,129 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL IMG_LoadTexture_IO(SDL_Renderer *rende
*/
extern SDL_DECLSPEC SDL_Texture * SDLCALL IMG_LoadTextureTyped_IO(SDL_Renderer *renderer, SDL_IOStream *src, bool closeio, const char *type);
+/**
+ * Load an image from a filesystem path into a GPU texture.
+ *
+ * An SDL_GPUTexture represents an image in GPU memory, usable by SDL's GPU
+ * API. Regardless of the source format of the image, this function will create a
+ * GPU texture with the format SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM with no mip
+ * levels. It can be bound as a sampled texture from a graphics or compute pipeline
+ * and as a a readonly storage texture in a compute pipeline.
+ *
+ * There is a separate function to read files from an SDL_IOStream, if you
+ * need an i/o abstraction to provide data from anywhere instead of a simple
+ * filesystem read; that function is IMG_LoadGPUTexture_IO().
+ *
+ * When done with the returned texture, the app should dispose of it with a
+ * call to SDL_ReleaseGPUTexture().
+ *
+ * \param device the SDL_GPUDevice to use to create the GPU texture.
+ * \param copy_pass the SDL_GPUCopyPass to use to upload the loaded image to
+ * the GPU texture.
+ * \param file a path on the filesystem to load an image from.
+ * \param width a pointer filled in with the width of the GPU texture. may be NULL.
+ * \param height a pointer filled in with the width of the GPU texture. may be NULL.
+ * \returns a new GPU texture, or NULL on error.
+ *
+ * \since This function is available since SDL_image 3.4.0.
+ *
+ * \sa IMG_LoadGPUTextureTyped_IO
+ * \sa IMG_LoadGPUTexture_IO
+ */
+extern SDL_DECLSPEC SDL_GPUTexture * SDLCALL IMG_LoadGPUTexture(SDL_GPUDevice *device, SDL_GPUCopyPass *copy_pass, const char *file, int *width, int *height);
+
+/**
+ * Load an image from an SDL data source into a GPU texture.
+ *
+ * An SDL_GPUTexture represents an image in GPU memory, usable by SDL's GPU
+ * API. Regardless of the source format of the image, this function will create a
+ * GPU texture with the format SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM with no mip
+ * levels. It can be bound as a sampled texture from a graphics or compute pipeline
+ * and as a a readonly storage texture in a compute pipeline.
+ *
+ * If `closeio` is true, `src` will be closed before returning, whether this
+ * function succeeds or not. SDL_image reads everything it needs from `src`
+ * during this call in any case.
+ *
+ * There is a separate function to read files from disk without having to deal
+ * with SDL_IOStream: `IMG_LoadGPUTexture(device, copy_pass, "filename.jpg", width, height)
+ * will call this function and manage those details for you, determining the file type
+ * from the filename's extension.
+ *
+ * There is also IMG_LoadGPUTextureTyped_IO(), which is equivalent to this
+ * function except a file extension (like "BMP", "JPG", etc) can be specified,
+ * in case SDL_image cannot autodetect the file format.
+ *
+ * When done with the returned texture, the app should dispose of it with a
+ * call to SDL_ReleaseGPUTexture().
+ *
+ * \param device the SDL_GPUDevice to use to create the GPU texture.
+ * \param copy_pass the SDL_GPUCopyPass to use to upload the loaded image to
+ * the GPU texture.
+ * \param src an SDL_IOStream that data will be read from.
+ * \param closeio true to close/free the SDL_IOStream before returning, false
+ * to leave it open.
+ * \param width a pointer filled in with the width of the GPU texture. may be NULL.
+ * \param height a pointer filled in with the width of the GPU texture. may be NULL.
+ * \returns a new GPU texture, or NULL on error.
+ *
+ * \since This function is available since SDL_image 3.4.0.
+ *
+ * \sa IMG_LoadGPUTexture
+ * \sa IMG_LoadGPUTextureTyped_IO
+ */
+extern SDL_DECLSPEC SDL_GPUTexture * SDLCALL IMG_LoadGPUTexture_IO(SDL_GPUDevice *device, SDL_GPUCopyPass *copy_pass, SDL_IOStream *src, bool closeio, int *width, int *height);
+
+/**
+ * Load an image from an SDL data source into a GPU texture.
+ *
+ * An SDL_GPUTexture represents an image in GPU memory, usable by SDL's GPU
+ * API. Regardless of the source format of the image, this function will create a
+ * GPU texture with the format SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM with no mip
+ * levels. It can be bound as a sampled texture from a graphics or compute pipeline
+ * and as a a readonly storage texture in a compute pipeline.
+ *
+ * If `closeio` is true, `src` will be closed before returning, whether this
+ * function succeeds or not. SDL_image reads everything it needs from `src`
+ * during this call in any case.
+ *
+ * Even though this function accepts a file type, SDL_image may still try
+ * other decoders that are capable of detecting file type from the contents of
+ * the image data, but may rely on the caller-provided type string for formats
+ * that it cannot autodetect. If `type` is NULL, SDL_image will rely solely on
+ * its ability to guess the format.
+ *
+ * There is a separate function to read files from disk without having to deal
+ * with SDL_IOStream: `IMG_LoadGPUTexture(device, copy_pass, "filename.jpg", width, height)
+ * will call this function and manage those details for you, determining the file type
+ * from the filename's extension.
+ *
+ * There is also IMG_LoadGPUTexture_IO(), which is equivalent to this function
+ * except that it will rely on SDL_image to determine what type of data it is
+ * loading, much like passing a NULL for type.
+ *
+ * When done with the returned texture, the app should dispose of it with a
+ * call to SDL_ReleaseGPUTexture().
+ *
+ * \param device the SDL_GPUDevice to use to create the GPU texture.
+ * \param copy_pass the SDL_GPUCopyPass to use to upload the loaded image to
+ * the GPU texture.
+ * \param src an SDL_IOStream that data will be read from.
+ * \param closeio true to close/free the SDL_IOStream before returning, false
+ * to leave it open.
+ * \param type a filename extension that represent this data ("BMP", "GIF",
+ * "PNG", etc).
+ * \param width a pointer filled in with the width of the GPU texture. may be NULL.
+ * \param height a pointer filled in with the width of the GPU texture. may be NULL.
+ * \returns a new GPU texture, or NULL on error.
+ *
+ * \since This function is available since SDL_image 3.4.0.
+ *
+ * \sa IMG_LoadGPUTexture
+ * \sa IMG_LoadGPUTexture_IO
+ */
+extern SDL_DECLSPEC SDL_GPUTexture * SDLCALL IMG_LoadGPUTextureTyped_IO(SDL_GPUDevice *device, SDL_GPUCopyPass *copy_pass, SDL_IOStream *src, bool closeio, const char *type, int *width, int *height);
+
/**
* Get the image currently in the clipboard.
*
diff --git a/src/IMG_gpu.c b/src/IMG_gpu.c
new file mode 100644
index 00000000..e002b0fd
--- /dev/null
+++ b/src/IMG_gpu.c
@@ -0,0 +1,141 @@
+/*
+ SDL_image: An example image loading library for use with SDL
+ Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include <SDL3_image/SDL_image.h>
+
+static SDL_GPUTexture * LoadGPUTexture(SDL_GPUDevice *device, SDL_GPUCopyPass *copy_pass, SDL_Surface *surface, Uint32 *width, Uint32 *height)
+{
+ if (width) {
+ *width = 0;
+ }
+ if (height) {
+ *height = 0;
+ }
+ if (!surface) {
+ return NULL;
+ }
+
+ if (surface->format != SDL_PIXELFORMAT_RGBA32) {
+ SDL_Surface *old_surface = surface;
+ surface = SDL_ConvertSurface(old_surface, SDL_PIXELFORMAT_RGBA32);
+ SDL_DestroySurface(old_surface);
+ if (!surface) {
+ return NULL;
+ }
+ }
+
+ SDL_GPUTextureCreateInfo texture_create_info = {0};
+ texture_create_info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
+ texture_create_info.type = SDL_GPU_TEXTURETYPE_2D;
+ texture_create_info.layer_count_or_depth = 1;
+ texture_create_info.num_levels = 1;
+ texture_create_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ;
+ texture_create_info.width = surface->w;
+ texture_create_info.height = surface->h;
+ SDL_GPUTexture *texture = SDL_CreateGPUTexture(device, &texture_create_info);
+ if (!texture) {
+ SDL_DestroySurface(surface);
+ return NULL;
+ }
+
+ SDL_GPUTransferBufferCreateInfo transfer_buffer_create_info = {0};
+ transfer_buffer_create_info.size = surface->w * surface->h * 4;
+ transfer_buffer_create_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
+ SDL_GPUTransferBuffer *transfer_buffer = SDL_CreateGPUTransferBuffer(device, &transfer_buffer_create_info);
+ if (!transfer_buffer) {
+ SDL_DestroySurface(surface);
+ SDL_ReleaseGPUTexture(device, texture);
+ return NULL;
+ }
+
+ Uint8 *dst = SDL_MapGPUTransferBuffer(device, transfer_buffer, 0);
+ if (!dst) {
+ SDL_DestroySurface(surface);
+ SDL_ReleaseGPUTexture(device, texture);
+ SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
+ return NULL;
+ }
+ const Uint8 *src = surface->pixels;
+ const int row_bytes = surface->w * 4;
+ if (row_bytes == surface->pitch) {
+ SDL_memcpy(dst, src, row_bytes * surface->h);
+ }
+ else {
+ for (int y = 0; y < surface->h; y++) {
+ SDL_memcpy(dst + y * row_bytes, src + y * surface->pitch, row_bytes);
+ }
+ }
+ SDL_UnmapGPUTransferBuffer(device, transfer_buffer);
+
+ SDL_GPUTextureTransferInfo texture_transfer_info = {0};
+ SDL_GPUTextureRegion texture_region = {0};
+ texture_transfer_info.transfer_buffer = transfer_buffer;
+ texture_region.texture = texture;
+ texture_region.w = surface->w;
+ texture_region.h = surface->h;
+ texture_region.d = 1;
+ SDL_UploadToGPUTexture(copy_pass, &texture_transfer_info, &texture_region, 0);
+
+ if (width) {
+ *width = surface->w;
+ }
+ if (height) {
+ *height = surface->h;
+ }
+
+ SDL_DestroySurface(surface);
+ SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
+
+ return texture;
+}
+
+SDL_GPUTexture * IMG_LoadGPUTexture(SDL_GPUDevice *device, SDL_GPUCopyPass *copy_pass, const char *file, int *width, int *height)
+{
+ if (!device) {
+ SDL_InvalidParamError("device");
+ return NULL;
+ }
+ if (!copy_pass) {
+ SDL_InvalidParamError("copy_pass");
+ return NULL;
+ }
+
+ return LoadGPUTexture(device, copy_pass, IMG_Load(file), width, height);
+}
+
+SDL_GPUTexture * IMG_LoadGPUTexture_IO(SDL_GPUDevice *device, SDL_GPUCopyPass *copy_pass, SDL_IOStream *src, bool closeio, int *width, int *height)
+{
+ return IMG_LoadGPUTextureTyped_IO(device, copy_pass, src, closeio, NULL, width, height);
+}
+
+SDL_GPUTexture * IMG_LoadGPUTextureTyped_IO(SDL_GPUDevice *device, SDL_GPUCopyPass *copy_pass, SDL_IOStream *src, bool closeio, const char *type, int *width, int *height)
+{
+ if (!device) {
+ SDL_InvalidParamError("device");
+ return NULL;
+ }
+ if (!copy_pass) {
+ SDL_InvalidParamError("copy_pass");
+ return NULL;
+ }
+
+ return LoadGPUTexture(device, copy_pass, IMG_LoadTyped_IO(src, closeio, type), width, height);
+}
diff --git a/src/SDL_image.exports b/src/SDL_image.exports
index da3336a4..ec45da5c 100644
--- a/src/SDL_image.exports
+++ b/src/SDL_image.exports
@@ -98,4 +98,7 @@ _IMG_SaveAPNGAnimation_IO
_IMG_SaveAVIFAnimation_IO
_IMG_SaveGIFAnimation_IO
_IMG_SaveWEBPAnimation_IO
+_IMG_LoadGPUTexture
+_IMG_LoadGPUTexture_IO
+_IMG_LoadGPUTextureTyped_IO
# extra symbols go here (don't modify this line)
diff --git a/src/SDL_image.sym b/src/SDL_image.sym
index b6215801..251e8c67 100644
--- a/src/SDL_image.sym
+++ b/src/SDL_image.sym
@@ -99,6 +99,9 @@ SDL3_image_0.0.0 {
IMG_SaveAVIFAnimation_IO;
IMG_SaveGIFAnimation_IO;
IMG_SaveWEBPAnimation_IO;
+ IMG_LoadGPUTexture;
+ IMG_LoadGPUTexture_IO;
+ IMG_LoadGPUTextureTyped_IO;
# extra symbols go here (don't modify this line)
local: *;
};