Форум программистов, компьютерный форум, киберфорум
8Observer8
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  

SDL3 для Web (WebAssembly): Сборка C/C++ проекта из консоли в WebAssembly (Wasm)

Запись от 8Observer8 размещена 30.01.2026 в 23:45. Обновил(-а) 8Observer8 01.04.2026 в 14:01
Показов 3519 Комментарии 1

Содержание блога

Финальный код: finish-hello-wasm-sdl3-c.zip

Название: 693b6f07-c865-4613-88b4-b0e812a904bf.png
Просмотров: 1294

Размер: 2.0 Кб

QR-код для запуска на смартфонах:

Название: 010516df-4372-49e1-8b0a-ffcb04494b76.png
Просмотров: 1294

Размер: 4.6 Кб

Демка в браузере

Если вы откроете примеры для начинающих на официальном репозитории SDL3 в папке: examples (кстати, они все на Си), то вы увидите, что все примеры используют следующие четыре обязательные функции, а привычная функция main() отсутствует:
  • SDL_AppInit (Initialization) - эта функция срабатывает один раз самой первой.
  • SDL_AppEvent (Event Handler) - эта функция срабатывает каждый раз, когда происходит какое-то событие, например, нажатие клавиши, движение мышью, клик мышью, изменение размеров окна и т.д.
  • SDL_AppIterate (Main Loop) - эта функция срабатывает множество раз в секунду. Здесь можно рассчитать dt (delta time) и сделать анимацию, скорость которой не будет зависеть от числа кадров в секунду, который поддерживает монитор.
  • SDL_AppQuit (Clean up) - эта функция сработает один раз перед закрытием приложения. Здесь нужно располагать код для освобождения ресурсов.

Эти функции позволяют собирать один и тот же код для Android, Desktop и WebAssembly без лишних #ifdef и дублирования логики - например, без специфичного для браузера цикла emscripten_set_main_loop при сборке через Emscripten. То есть кодовая база на SDL3-callbacks будет одинаковая для сборки в .exe, .apk, .wasm и т.д.

Рассмотрим пример 01-clear в файле clear.c. Я попросил Gemini 3 перевести комментарии на русский, не меняя ничего в коде, даже отступы:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
 * Этот пример кода создает окно и рендерер SDL, а затем очищает
 * окно разным цветом в каждом кадре, так что вы фактически получите окно,
 * цвет которого плавно меняется.
 *
 * Этот код является общественным достоянием. Не стесняйтесь использовать его в любых целях!
 */
 
#define SDL_MAIN_USE_CALLBACKS 1  /* использовать функции обратного вызова вместо main() */
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
 
/* Мы будем использовать этот рендерер для отрисовки в этом окне в каждом кадре. */
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
 
/* Эта функция запускается один раз при запуске. */
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
    SDL_SetAppMetadata("Example Renderer Clear", "1.0", "com.example.renderer-clear");
 
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    if (!SDL_CreateWindowAndRenderer("examples/renderer/clear", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) {
        SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
    SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX);
 
    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)
{
    const double now = ((double)SDL_GetTicks()) / 1000.0;  /* преобразуем миллисекунды в секунды. */
    /* выбираем цвет для кадра, который будем рисовать. Трюк с синусоидой заставляет цвета плавно сменять друг друга. */
    const float red = (float) (0.5 + 0.5 * SDL_sin(now));
    const float green = (float) (0.5 + 0.5 * SDL_sin(now + SDL_PI_D * 2 / 3));
    const float blue = (float) (0.5 + 0.5 * SDL_sin(now + SDL_PI_D * 4 / 3));
    SDL_SetRenderDrawColorFloat(renderer, red, green, blue, SDL_ALPHA_OPAQUE_FLOAT);  /* новый цвет, полная непрозрачность. */
 
    /* очищаем окно цветом отрисовки. */
    SDL_RenderClear(renderer);
 
    /* выводим свежеочищенный рендер на экран. */
    SDL_RenderPresent(renderer);
 
    return SDL_APP_CONTINUE;  /* продолжаем выполнение программы! */
}
 
/* Эта функция запускается один раз при завершении работы. */
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
    /* SDL сама очистит окно/рендерер за нас. */
}
Далее мы по шагам рассмотрим, как собрать этот пример в WASM, запустить в браузере локально и загрузить на бесплатный хостинг Vercel из командной строки.

Код стартового примера



  • Скачайте стартовый пример: start-hello-wasm-sdl3-c.zip
  • Код стартового примера:

    main.c

    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    #include <stdio.h>
    #include <emscripten.h>
     
    // Функция, которая будет вызываться браузером каждую итерацию (фрейм)
    void loop()
    {
        // Здесь обычно обрабатывается ввод пользователя или обновление кадра
    }
     
    int main()
    {
        printf("Привет из WebAssembly!\n");
     
        // Устанавливает главный цикл приложения. 
        // 0 — использовать стандартную частоту кадров браузера.
        // 1 — имитировать бесконечный цикл (предотвращает выход из main).
        emscripten_set_main_loop(loop, 0, 1); 
     
        return 0;
    }
    CMakeLists.txt

    Bash
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    cmake_minimum_required(VERSION 3.21)
    project(start-hello-wasm-sdl3-c)
     
    # Устанавливаем стандарт C (обязательно перед add_executable)
    set(CMAKE_C_STANDARD 17)
    set(CMAKE_C_STANDARD_REQUIRED ON)
     
    # Задаем название будущего приложения (в Windows это был бы app.exe, а
    # в вебе будет app.js / app.wasm)
    add_executable(app)
     
    # Добавляем исполняемый файл в сборку
    target_sources(app
    PRIVATE
        src/main.c
    )
    index.html

    PHP/HTML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    <!DOCTYPE html>
     
    <html>
     
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Hello Wasm</title>
    </head>
     
    <body>
        <script async src="./js/app.js"></script>
    </body>
  • Эта программа выводит строку "Привет из WebAssembly!" в консоль браузера. Функция main() не должна завершаться в браузере, поэтому используется бесконечный цикл emscripten_set_main_loop. Но мы не будет использовать эту функцию дальше. Мы будет использовать SDL3-callbacks, то есть 4 функции вместо main(), которые и на Desktop и в браузере
  • Примечание. При сборке в Release стартового примера выше у меня получилось приложение весом всего лишь 15.8 KB:
    • index.html - 245 bytes
    • app.js - 13.9 KB
    • app.wasm - 1.68 KB
    • Итог - 15.8 KB
    Название: 6f45f4bc-ddf5-43eb-88fe-9a9ef02fa610.png
Просмотров: 16

Размер: 2.9 Кб
  • На SDL3 будет около в районе 1 МБ, что тоже мало по современным меркам
  • Сделает сборку стартового примера по пошаговой инструкции ниже

Сборка стартового примера



  • Проверим работоспособность стартового примера, который мы скачали выше
  • Установите emsdk 4.0.15 по пошаговой инструкции: Установка Emscripten SDK (emsdk) и CMake для сборки C и C++ приложений в Wasm
  • После выполнения инструкции выше у вас будут установлены: CMake, emsdk, Node.js и http-server
  • Откройте CMD (консоль) в папке стартового примера
  • Введите следующие команды по очереди (это команды: конфигурирования, сборки и запуска локального сервера)
    Bash
    1
    2
    3
    
    config-web
    build-web
    http-server -c-1
  • Перейдите в браузер и введите адрес: localhost:8080 и нажмите Enter
  • Вы увидите результат работы программы в консоли браузера, если нажмёте: Ctrl+Shift+J в Chrome и Edge, либо Ctrl+Shift+K в FireFox
  • Запустите в корне папки проекта вторую консоль, чтобы дальше можно было переконфигурировать и собирать проект в этой консоли, а в первой консоли пусть запущен локальные сервер


Настройка инструментов



  • Скачайте архив SDL3-devel-3.4.2-wasm.zip
  • Примечание. Эту библиотеку я собрал из исходников с помощью Emscripten SDK 4.0.15 по инструкции: SDL3 для Web (WebAssembly): Сборка библиотек: SDL3, Box2D, FreeType, SDL3_ttf, SDL3_mixer и SDL3_image из исходников с помощью CMake и Emscripten
  • Создайте на каком-нибудь локальном диске папку "libs", например, на диске C и разархивируйте скаченный архив в папку "libs":
    Нажмите на изображение для увеличения
Название: 931653e4-26ae-43ee-9938-f38e599e9330.png
Просмотров: 20
Размер:	3.0 Кб
ID:	11817
  • Откройте скаченный пример в каком-нибудь редакторе кода, например, Notepad++, а лучше в Sublime Text 4: https://www.sublimetext.com/download. Если вы добавите путь к Sublime Text 4 (ST4) в PATH, то сможете запускать ST4 из консоли командой subl . и тогда ST4 откроет текущую папку
  • Откройте файл CMakeLists.txt и скопируйте в него следующие настройки и не забудьте заменить путь к SDL3 на свой, если у вас путь отличается:

    CMakeLists.txt

    Bash
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    cmake_minimum_required(VERSION 3.21)
    project(start-hello-wasm-sdl3-c)
     
    # Устанавливаем стандарт C (обязательно перед add_executable)
    set(CMAKE_C_STANDARD 17)
    set(CMAKE_C_STANDARD_REQUIRED ON)
     
    # Задаем название будущего приложения (в Windows это был бы app.exe, а
    # в вебе будет app.js / app.wasm)
    add_executable(app)
     
    # Подсказываем CMake, где искать конфигурационные файлы библиотеки SDL3
    set(SDL3_DIR "C:/libs/SDL3-devel-3.4.2-wasm/lib/cmake/SDL3")
     
    # Загружаем настройки пакета SDL3 (параметры компиляции и пути к заголовкам)
    find_package(SDL3 REQUIRED)
     
    # Привязываем SDL3 к нашему приложению (настройка линковки и путей include)
    target_link_libraries(app PRIVATE SDL3::SDL3)
     
    # Добавляем исполняемый файл в сборку
    target_sources(app
    PRIVATE
        src/main.c
    )
  • Заменим этот код на код из официального примера, который был показан в начале этого сообщения (пример 01-clear), а в самом начале этого сообщение мы перевели комментарии на русский с помощью Gemini
  • Добавьте в код строку SDL_SetRenderVSync(renderer, 1); после создания окна:

    main.c

    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    
    /*
     * Этот пример кода создает окно и рендерер SDL, а затем очищает
     * окно разным цветом в каждом кадре, так что вы фактически получите окно,
     * цвет которого плавно меняется.
     *
     * Этот код является общественным достоянием. Не стесняйтесь использовать его в любых целях!
     */
     
    #define SDL_MAIN_USE_CALLBACKS 1 /* использовать функции обратного вызова вместо main() */
    #include <SDL3/SDL.h>
    #include <SDL3/SDL_main.h>
     
    /* Мы будем использовать этот рендерер для отрисовки в этом окне в каждом кадре. */
    static SDL_Window *window = NULL;
    static SDL_Renderer *renderer = NULL;
     
    /* Эта функция запускается один раз при запуске. */
    SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
    {
        SDL_SetAppMetadata("Example Renderer Clear", "1.0", "com.example.renderer-clear");
     
        if (!SDL_Init(SDL_INIT_VIDEO))
        {
            SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
            return SDL_APP_FAILURE;
        }
     
        if (!SDL_CreateWindowAndRenderer("examples/renderer/clear", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer))
        {
            SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
            return SDL_APP_FAILURE;
        }
        SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX);
     
        SDL_SetRenderVSync(renderer, 1);
     
        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)
    {
        const double now = ((double)SDL_GetTicks()) / 1000.0; /* преобразуем миллисекунды в секунды. */
        /* выбираем цвет для кадра, который будем рисовать. Трюк с синусоидой заставляет цвета плавно сменять друг друга. */
        const float red = (float)(0.5 + 0.5 * SDL_sin(now));
        const float green = (float)(0.5 + 0.5 * SDL_sin(now + SDL_PI_D * 2 / 3));
        const float blue = (float)(0.5 + 0.5 * SDL_sin(now + SDL_PI_D * 4 / 3));
        SDL_SetRenderDrawColorFloat(renderer, red, green, blue, SDL_ALPHA_OPAQUE_FLOAT); /* новый цвет, полная непрозрачность. */
     
        /* очищаем окно цветом отрисовки. */
        SDL_RenderClear(renderer);
     
        /* выводим свежеочищенный рендер на экран. */
        SDL_RenderPresent(renderer);
     
        return SDL_APP_CONTINUE; /* продолжаем выполнение программы! */
    }
     
    /* Эта функция запускается один раз при завершении работы. */
    void SDL_AppQuit(void *appstate, SDL_AppResult result)
    {
        /* SDL сама очистит окно/рендерер за нас. */
    }
  • Примечание. Вы можете отформатировать одой командой из консоли, то есть автоматически расставить отступы с помощью пошаговой инструкции: Консольные команды для форматирования исходного кода на C, C++, C#, Java, JavaScript, HTML и CSS. Сортировка пакетов на Python
  • Откройте в редакторе кода файл "public/index.html" и замените его содержимое на следующее:

    index.html

    PHP/HTML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    <!DOCTYPE html>
     
    <html>
     
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
     
    <body>
        <canvas id="canvas"></canvas>
     
        <script async src="./js/app.js"></script>
    </body>
     
    </html>
  • Введите команды конфигурирования и сборки (на самом деле можно команду build-web, а конфигурирование произойдёт по автомату, потому что CMake автоматически проверяет не был ли изменён файл CMakeLists.txt)
    Bash
    1
    2
    
    config-web
    build-web
  • Перейдите в браузер и введите адрес: localhost:8080 и нажмите Enter
  • Обратите внимание, что локальный сервер http-server выводит адреса, например, у меня:
    Bash
    1
    2
    3
    4
    5
    
    Available on:
      http://100.116.245.193:8080
      http://192.168.56.1:8080
      http://127.0.0.1:8080
    Hit CTRL-C to stop the server
  • Вы можете взять телефон, который подключён к Wi-Fi и ввести в его браузере адрес 192.168.56.1:8080 и приложение запустится на Android
  • Загрузите созданное веб-приложение (папку public на бесплатный хостинг Vercel по следующей инструкции

Сборка в Release и вес SDL3 веб-приложения



При сборке в релиз нужно в файле config-web.bat заменить Debug на Release]:

config-web.bat

Bash
1
emcmake cmake -S . -B dist -DCMAKE_BUILD_TYPE=Release
После этого нужно ввести команду config-web. Собирается медленнее секунд на 10 секунд, чем в Debug, в зависимости от мощности компьютера. В результате у меня получилось приложение весом 982 KB (папка public). Папку public мы будет отгружать на бесплатный хостинг в следующей инструкции

Загрузка веб-приложения на бесплатный хостинг Vercel



  • Зарегистрируйтесь в Vercel: https://vercel.com/
    Установите Vercel глобально командой:
    Bash
    1
    
    npm i -g vercel
  • В корне проекта создайте файл с именем "vercel.json" со следующим содержимым:

    vercel.json

    JSON
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    {
      "headers": [
        {
          "source": "/(.*)",
          "headers": [
            { "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" },
            { "key": "Cross-Origin-Opener-Policy", "value": "same-origin" }
          ]
        }
      ]
    }
  • Примечание. Этот файл нужен для загрузки файла app.wasm
  • В консоли из корня проекта выполните команду для входа в Vercel:
    Bash
    1
    
    vercel login
  • В консоли вы увидите сообщение: "Press [ENTER] to open the browser" ("Нажмите [ENTER], чтобы перейти в браузер")
  • Нажмите Enter и вы будете перенаправлены в браузер, где нужно будет нажать кнопку "Allow" ("Позволяю")
  • Наберите команду для первой отгрузки приложения на хостинг:
    Bash
    1
    
    vercel
  • В консоли будут задаваться вопросы и на все эти вопросы Enter
  • Вы будет выдан адрес вашего приложения:
    Code
    1
    
    Aliased: https://finish-hello-wasm-sdl3-c.vercel.app
  • Примечание. К сожалению, иногда Vercel часто становится недоступным в РФ. Как альтернатива, можете загрузить папку public на бесплатный хостинг GitHub Pages. Я загрузил финальный результат на GitHub Pages -> демка в браузере

Создание QR-кода для быстрого запуска на смартфонах



Мы собрали приложение и загрузили его на бесплатный хостинг. У нас есть ссылка на приложение. Используем эту ссылку, чтобы создать QR-код

  • Наберите в поисковике интернета: qr code generator
  • Одна из первых ссылок будет указывать на ресурс, на который нужно перейти: https://www.qr-code-generator.com/
  • В поле ввода скопируйте ссылку на приложение. QR-код появится справа
  • Внизу выберите какой-нибудь способ отображение QR-кода. Нарпимер:
    Название: 010516df-4372-49e1-8b0a-ffcb04494b76.png
Просмотров: 1294

Размер: 4.6 Кб
Вложения
Тип файла: zip start-hello-wasm-sdl3-c.zip (3.2 Кб, 14 просмотров)
Тип файла: zip SDL3-devel-3.4.2-wasm.zip (1.22 Мб, 17 просмотров)
Тип файла: zip finish-hello-wasm-sdl3-c.zip (4.5 Кб, 30 просмотров)
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 1
Комментарии
  1. Старый комментарий
    Обновил c SDL 3.4.0 до SDL 3.4.2
    Запись от 8Observer8 размещена 31.03.2026 в 01:10 8Observer8 вне форума
    Обновил(-а) 8Observer8 31.03.2026 в 04:57
 
Новые блоги и статьи
Отчёт о спецтехнике находящейся в ремонте
Maks 20.04.2026
Отчёт из решения ниже размещен в конфигурации КА2. Задача: отобразить спецтехнику, которая на данный момент находится в ремонте. Есть нетиповой документ "Заявка на ремонт спецтехники" который. . .
Памятка для бота и "визитка" для читателей "Semantic Universe Layer (Слой семантической вселенной)"
Hrethgir 19.04.2026
Сгенерировано для краткого описания по случаю сборки и компиляции скелета серверного приложения. И пусть после этого скажут, что статьи сгенерированные AI - туфта и не интересно. И это не реклама -. . .
Запрет удаления строк ТЧ документа при определенном условии
Maks 19.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа "Аккумуляторы", разработанного в конфигурации КА2. У данного документа есть ТЧ, в которой в зависимости от прав доступа. . .
Модель заражения группы наркоманов
alhaos 17.04.2026
Условия задачи сформулированы тут Суть: - Группа наркоманов из 10 человек. - Только один инфицирован ВИЧ. - Колются одной иглой. - Колются раз в день. - Колются последовательно через. . .
Мысли в слух. Про "навсегда".
kumehtar 16.04.2026
Подумалось тут, что наверное очень глупо использовать во всяких своих установках понятие "навсегда". Это очень сильное понятие, и я только начинаю понимать край его смысла, не смотря на то что давно. . .
My Business CRM
MaGz GoLd 16.04.2026
Всем привет, недавно возникла потребность создать CRM, для личных нужд. Собственно программа предоставляет из себя базу данных клиентов, в которой можно фиксировать звонки, стадии сделки, а также. . .
Знаешь почему 90% людей редко бывают счастливыми?
kumehtar 14.04.2026
Потому что они ждут. Ждут выходных, ждут отпуска, ждут удачного момента. . . а удачный момент так и не приходит.
Фиксация колонок в отчете СКД
Maks 14.04.2026
Фиксация колонок в СКД отчета типа Таблица. Задача: зафиксировать три левых колонки в отчете. Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка) / / . . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru