SDL3 & OpenGL: Texture Loading with SDL3_image SDL3 и OpenGL: Загрузка текстур с помощью SDL3_image

A guide to loading image files into GPU memory as modern OpenGL textures within the SDL3 callback system. Руководство по загрузке файлов изображений в память GPU как текстур OpenGL в современной системе обратных вызовов SDL3.

2. Obtain SDL3_image 3.4.4 2. Получение SDL3_image 3.4.4

3. Add SDL3 to Environment Variables (Path) 3. Добавление SDL3 в переменные среды (Path)

To ensure your applications can find the SDL3.dll file at runtime, you should add the following path to the Path variable in your User variables section: Чтобы приложения могли находить SDL3.dll файл при запуске, добавьте следующий путь в переменную Path в разделе Переменные среды пользователя:

C:\libs\SDL3_image-3.4.4-win32-x64

4. Project Structure and Assets 4. Структура проекта и ресурсы

Before creating the files, download the required asset: Перед созданием файлов скачайте необходимый ресурс:

  • Download the PNG file with transparent background: right-arrow.zip Скачайте PNG-файл с прозрачным фоном: right-arrow.zip
  • Note: This is a free file taken from this link. Примечание. Это бесплатный файл, который был взят по ссылке.

C Project: Create an empty folder named texture-sdl3-opengl-mingw-c and set up the following hierarchy by creating new CMakeLists.txt and main.c files:

Проект на C: Создайте пустую папку с именем texture-sdl3-opengl-mingw-c и подготовьте следующую иерархию, создав файлы CMakeLists.txt и main.c:

texture-sdl3-opengl-mingw-c/
├── CMakeLists.txt
├── assets/images/
│   └── right-arrow.png
└── src/
    └── main.c

C++ Project: Create an empty folder named texture-sdl3-opengl-mingw-cpp and set up the following hierarchy by creating new CMakeLists.txt and main.cpp files:

Проект на C++: Создайте пустую папку с именем texture-sdl3-opengl-mingw-cpp и подготовьте следующую иерархию, создав файлы CMakeLists.txt и main.cpp:

texture-sdl3-opengl-mingw-cpp/
├── CMakeLists.txt
├── assets/images/
│   └── right-arrow.png
└── src/
    └── main.cpp

5. CMake Configuration 5. Конфигурация CMake

C Project: Copy and paste the following code into the CMakeLists.txt file:

Проект на C: Скопируйте и вставьте следующее содержимое в файл CMakeLists.txt:

set(CMAKE_BUILD_TYPE "Debug")
cmake_minimum_required(VERSION 3.21)
project(texture-sdl3-opengl-mingw-c)

set(SDL3_DIR "C:/libs/SDL3-devel-3.4.8-mingw/lib/cmake/SDL3")
set(SDL3_image_DIR "C:/libs/SDL3_image-devel-3.4.4-mingw/lib/cmake/SDL3_image")

find_package(SDL3 REQUIRED)
find_package(SDL3_image REQUIRED)

add_executable(app)

target_include_directories(app PRIVATE "C:/libs/glad-0.1.36/opengl-3.3/include")

target_sources(app
PRIVATE
    src/main.c
    "C:/libs/glad-0.1.36/opengl-3.3/src/glad.c"
)

# Copy the assets folder to the dist folder
if(EXISTS "${CMAKE_SOURCE_DIR}/assets")
    add_custom_command(TARGET app POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_directory
                "${CMAKE_SOURCE_DIR}/assets"
                "$<TARGET_FILE_DIR:app>/assets"
        COMMENT "Copying assets directory"
    )
endif()

target_link_libraries(app PRIVATE SDL3_image::SDL3_image SDL3::SDL3)
target_link_options(app PRIVATE -mconsole)

C++ Project: Copy and paste the following code into the CMakeLists.txt file:

Проект на C++: Скопируйте и вставьте следующее содержимое в файл CMakeLists.txt:

set(CMAKE_BUILD_TYPE "Debug")
cmake_minimum_required(VERSION 3.21)
project(texture-sdl3-opengl-mingw-cpp)

set(SDL3_DIR "C:/libs/SDL3-devel-3.4.8-mingw/lib/cmake/SDL3")
set(SDL3_image_DIR "C:/libs/SDL3_image-devel-3.4.4-mingw/lib/cmake/SDL3_image")

find_package(SDL3 REQUIRED)
find_package(SDL3_image REQUIRED)

add_executable(app)

target_include_directories(app PRIVATE "C:/libs/glad-0.1.36/opengl-3.3/include")

target_sources(app
PRIVATE
    src/main.cpp
    "C:/libs/glad-0.1.36/opengl-3.3/src/glad.c"
)

# Copy the assets folder to the dist folder
if(EXISTS "${CMAKE_SOURCE_DIR}/assets")
    add_custom_command(TARGET app POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_directory
                "${CMAKE_SOURCE_DIR}/assets"
                "$<TARGET_FILE_DIR:app>/assets"
        COMMENT "Copying assets directory"
    )
endif()

target_link_libraries(app PRIVATE SDL3_image::SDL3_image SDL3::SDL3)
target_link_options(app PRIVATE -mconsole)

6. Source Code (OpenGL 3.3 Texture Mapping) 6. Исходный код (Текстурирование в OpenGL 3.3)

Copy and paste the following code into the src/main.c (or src/main.cpp) file:

Скопируйте и вставьте следующее содержимое в файл src/main.c (или src/main.cpp):

src/main.c (or src/main.cpp)

#define SDL_MAIN_USE_CALLBACKS 1

#include <glad/glad.h>

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_image/SDL_image.h>

static SDL_Window *window = NULL;
static SDL_GLContext glContext = NULL;
static GLuint shaderProgram;
static GLuint VAO, VBO, EBO;
static GLuint textureID;

const char *vertexShaderSource =
    "#version 330 core\n"
    "layout (location = 0) in vec3 aPosition;\n"
    "layout (location = 1) in vec2 aTexCoord;\n"
    "out vec2 vTexCoord;\n"
    "void main() {\n"
    "   gl_Position = vec4(aPosition, 1.0);\n"
    "   vTexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);\n"
    "}";

const char *fragmentShaderSource =
    "#version 330 core\n"
    "precision mediump float;\n"
    "out vec4 FragColor;\n"
    "in vec2 vTexCoord;\n"
    "uniform sampler2D ourTexture;\n"
    "void main() {\n"
    "   FragColor = texture(ourTexture, vTexCoord);\n"
    "}";

SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
    if (!SDL_Init(SDL_INIT_VIDEO))
        return SDL_APP_FAILURE;

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);

    window = SDL_CreateWindow("SDL3_image OpenGL", 380, 380, SDL_WINDOW_OPENGL);
    if (!window)
        return SDL_APP_FAILURE;

    glContext = SDL_GL_CreateContext(window);

    // Load standard Desktop GLAD mapping
    if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
    {
        return SDL_APP_FAILURE;
    }

    SDL_GL_SetSwapInterval(1);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Setup Geometry
    float vertices[] = {
        0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
        0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
        -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
        -0.5f, 0.5f, 0.0f, 0.0f, 1.0f
    };
    unsigned int indices[] = { 0, 1, 3, 1, 2, 3 };

    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    glBindVertexArray(0);

    // Simple Shader Compilation
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);

    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    // Load Image file via SDL3_image directly into memory surface
    const char *texturePath = "assets/images/right-arrow.png";
    SDL_Surface *surface = IMG_Load(texturePath);
    if (!surface)
    {
        SDL_Log("Image loading failed: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    // Convert to standardized ABGR8888 (Matches GL_RGBA memory layout perfectly)
    SDL_Surface *optimizedSurface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ABGR8888);
    SDL_DestroySurface(surface); // Free the unoptimized source copy immediately

    if (!optimizedSurface)
    {
        SDL_Log("Surface conversion failed: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    // Convert pixels to an OpenGL Texture Object
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID);

    // Set wrapping/filtering options
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // Upload using strict GL_RGBA formatting (Since it is safely converted)
    glTexImage2D(
        GL_TEXTURE_2D,
        0,
        GL_RGBA,
        optimizedSurface->w,
        optimizedSurface->h,
        0,
        GL_RGBA,
        GL_UNSIGNED_BYTE,
        optimizedSurface->pixels);
    glGenerateMipmap(GL_TEXTURE_2D);

    // Free optimized surface copy from CPU RAM
    SDL_DestroySurface(optimizedSurface);

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
    if (event->type == SDL_EVENT_QUIT)
        return SDL_APP_SUCCESS;
    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void *appstate)
{
    glClearColor(0.392f, 0.392f, 0.392f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(shaderProgram);
    glBindTexture(GL_TEXTURE_2D, textureID);
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);

    SDL_GL_SwapWindow(window);
    return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
    glDeleteProgram(shaderProgram);
    SDL_GL_DestroyContext(glContext);
    SDL_DestroyWindow(window);
}

7. Opening the Project in IDEs 7. Открытие проекта в IDE

Open the CMakeLists.txt file in CLion or Qt Creator. CMake will handle the rest. Откройте файл CMakeLists.txt в CLion или Qt Creator. CMake позаботится об остальном.

8. Automation Scripts (.bat) 8. Скрипты автоматизации (.bat)

You can open the project folder in Sublime Text 4 (or Notepad++). Create the following .bat scripts in the project root directory to automate the configuration, building, and running of your application:

Вы можете открыть папку проекта в Sublime Text 4 (или Notepad++). Создайте следующие .bat скрипты в корневой директории проекта для автоматизации конфигурации, сборки и запуска вашего приложения:

1. config-exe.bat
cmake -G "MinGW Makefiles" -S . -B dist/exe
2. build-exe.bat
cd dist\exe
cmake --build .
cd ..\..
3. run-exe.bat
dist\exe\app

To build and launch the application, run these scripts in the terminal in the following order:

Чтобы собрать и запустить приложение, выполните эти скрипты в терминале в следующем порядке:

config-exe
build-exe
run-exe

9. Source Code & Downloads 9. Исходный код и загрузки

You can download the complete configured project as a ZIP archive or explore the source code directly on GitHub: Вы можете скачать готовую настроенную сборку проекта в виде ZIP-архива или изучить исходный код прямо на GitHub:


Support My Work Поддержать проект

If these tutorials helped you, consider buying me a coffee! Если эти туториалы вам помогли, вы можете поддержать автора.

Sberbank (Russia only) Сбербанк (только для РФ)

Sberbank SBP QR Code

Direct transfer via phone number (Russia only) Перевод по номеру телефона (только для РФ)

+7 (917) 212-29-59

USDT TRC20

USDT TRC20 QR Code

Support via Cryptocurrency Поддержка криптовалютой

TMtY1YifNf6FKvgeFmqKGQR4NStKr3csGp