Содержание блога
В этой пошаговой инструкции создадим с нуля веб-приложение, которое выводит текст в окне браузера. Запустим на Android на локальном сервере. Загрузим Release на бесплатный хостинг Vercel из консоли парой консольных команд: "vercel login" и "vercel". Финальный результат инструкции в браузере: запустить демку в один клик. Исходники финальной демки: finish-hello-ttf-wasm-sdl3-c.zip
Если у вас не установлены Emscripten SDK и CMake, то установите их по следующей инструкции и выполните "hello world" инструкцию там же: Установка Emscripten SDK (emsdk) и CMake
- Скачайте стартовый пример: start-hello-ttf-wasm-sdl3-c.zip. Этот пример мы создали в инструкции по установке: Установка Emscripten SDK - обязательно пойдите "hello world" инструкцию там же
- Откройте этот пример в каком-нибудь редакторе кода. Например, в Notepad++. Мне больше всего нравится редактор кода Sublime Text 4: https://www.sublimetext.com/download. ST4 почти такой же лёгкий, как Notepad++ и гораздо удобнее. Добавьте путь с ST4 в Path и вы сможете запускать ST4 из консоли, открывая проект в текущей папке, командой: subl . (точка означает, что нужно открыть текущую папку в ST4)

- Скачайте архив SDL3-devel-3.4.0-wasm.zip - это SDL3-библиотека, которая собрана из исходников для Wasm-проектов с помощью emsdk 4.0.15. У вас должна быть установлена и активирована именно эта версия emsdk
- Скачайте архив SDL3_ttf-devel-3.2.2-wasm.zip - эта библиотека для SDL3-проектов для работы с TTF-шрифтами, которая собрана для Wasm-проектов с помощью emsdk 4.0.15
- Скачайте архив freetype-2.13.3-wasm.zip - библиотека FreeType необходима для работы SDL3_ttf
- Извлеките архивы в текущую папку и перенесите три извлечённые папки на какой-нибудь локальный диск, наприме, можете создать папку "libs" на системном диске C и скопировать эти папки в папку "libs"
- Откройте файл CMakeLists.txt и добавьте код для подключения библиотек SDL3, SDL3_ttf и FreeType (проверьте пути в этом файле - правильные ли они на вашем компьютере):
| Code | 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
| cmake_minimum_required(VERSION 3.21)
project(start-hello-ttf-wasm-sdl3-c)
# Задаем название будущего приложения (в Windows это был бы app.exe, а в вебе будет app.js / app.wasm)
add_executable(app)
# Устанавливаем стандарт C
set(CMAKE_C_STANDARD 11)
# Подсказываем CMake, где искать конфигурационные файлы библиотек
set(SDL3_DIR "C:/libs/SDL3-devel-3.4.0-wasm/lib/cmake/SDL3")
set(SDL3_ttf_DIR "C:/libs/SDL3_ttf-devel-3.2.2-wasm/lib/cmake/SDL3_ttf")
set(FREETYPE_INCLUDE_DIRS "C:/libs/freetype-2.13.3-wasm/include")
set(FREETYPE_LIBRARY "C:/libs/freetype-2.13.3-wasm/lib/libfreetype.a")
# Загружаем настройки пакетов (параметры компиляции и пути к заголовкам)
find_package(SDL3 REQUIRED)
find_package(SDL3_ttf REQUIRED)
# Привязываем библиотеки к нашему приложению (настройка линковки и путей include)
target_link_libraries(app PRIVATE SDL3_ttf::SDL3_ttf SDL3::SDL3)
# Добавляем исходный код к проекту
target_sources(app
PRIVATE
src/main.c
) |
|
- Пример пока пустой (в main.c только базовый код вывода "hello world" в консоль), но проверьте работоспособность. В инструкции по установке Emscripten SDK мы запускали этот пример командами:
| Bash | 1
2
3
| config-web
build-web
http-server -c-1 |
|
- Локальный сервер выведет адреса, которые скопировать в адресную строку браузера, например, 127.0.0.1:8080 (либо можно ввести localhost:8080)
- Перейдите в браузер и введите адрес: localhost:8080 и нажмите Enter
- Вы увидите результат работы программы в консоли браузера, если нажмёте: Ctrl+Shift+J в Chrome и Edge, либо Ctrl+Shift+K в FireFox
- Запустите в корне папки проекта вторую консоль, чтобы дальше можно было переконфигурировать и собирать проект в этой консоли, а в первой консоли пусть запущен локальные сервер
- Откройте файл src/main.c и добавим код с базовыми четырьмя функциями SDL3. Этот код создаёт холст для рисования размеров 400 на 400 пикселя, заливает его тёмно-серым цветом и выводит в консоль версию SDL3:
| 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
74
75
76
77
78
79
| #define SDL_MAIN_USE_CALLBACKS 1 // Use the callbacks instead of main()
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <stdio.h>
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
// This function runs once at startup
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
if (!SDL_Init(SDL_INIT_VIDEO))
{
SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
const char *title = "Example";
if (!SDL_CreateWindowAndRenderer(title, 400, 400, 0, &window, &renderer))
{
SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
SDL_SetRenderVSync(renderer, 1);
// --- Print SDL versions ---
printf("Compiled SDL3 version: %d.%d.%d\n",
SDL_MAJOR_VERSION,
SDL_MINOR_VERSION,
SDL_MICRO_VERSION);
// Get the version of the SDL library linked at runtime
int v = SDL_GetVersion();
printf("Linked SDL3 version: %d.%d.%d\n", SDL_VERSIONNUM_MAJOR(v),
SDL_VERSIONNUM_MINOR(v), SDL_VERSIONNUM_MICRO(v));
return SDL_APP_CONTINUE;
}
// This function runs when a new event (mouse input, keypresses, etc) occurs
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
if (event->type == SDL_EVENT_QUIT)
{
return SDL_APP_SUCCESS; // End the program, reporting success to the OS
}
return SDL_APP_CONTINUE;
}
// This function runs once per frame, and is the heart of the program
SDL_AppResult SDL_AppIterate(void *appstate)
{
// Clear the screen
SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255);
SDL_RenderClear(renderer);
// Update the screen
SDL_RenderPresent(renderer);
return SDL_APP_CONTINUE;
}
// This function runs once at shutdown
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
if (renderer)
{
SDL_DestroyRenderer(renderer);
renderer = NULL;
}
if (window)
{
SDL_DestroyWindow(window);
window = NULL;
}
SDL_Quit();
} |
|
- Откройте в редакторе кода файл "public/index.html" и добавьте элемент <canvas> и удалите элемент <title> (потому что Title задаются в функции SDL_CreateWindowAndRenderer - в первом аргументе этой функции):
index.html
| PHP/HTML | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <!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> |
|
- Введите команды конфигурирования и сборки:
- Перейдите в браузер и введите адрес: localhost:8080 и нажмите Enter
- Вы увидите холст размером 400 на 400 пикселей тёрмно-серого цвета. Если откроете консоль браузера, то увидите вывод версии SDL3:
| Code | 1
2
| Compiled SDL3 version: 3.4.0
Linked SDL3 version: 3.4.0 |
|
- Скачайте бесплатный шрифт TTF: LiberationSans-Regular.zip (файл был взят по ссылке)
- В корне проекта создайте папку "assets", а в внутри этой папки создайте папку "fonts"
- Перенесите извлечённый файл LiberationSans-Regular.ttf в папку "assets/fonts"
- Откройте файл CMakeLists.txt и скопируйте код для загрузки TTF файла в конец файла CMakeLists.txt:
| Code | 1
2
3
4
| if (EMSCRIPTEN)
target_link_options("app" PRIVATE "SHELL:--embed-file ${CMAKE_CURRENT_SOURCE_DIR}/assets/fonts/LiberationSans-Regular.ttf@/assets/fonts/LiberationSans-Regular.ttf")
set_property(TARGET "app" APPEND PROPERTY LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/assets/fonts/LiberationSans-Regular.ttf")
endif() |
|
- Примечание. Таким образом можно загружать любые ассеты: картинки, звуки, JSON-файлы и т.д. - просто копируйте код выше и меняйте пути
- Замените весь код main.c на следующий код, который выводит текст "Привет из WebAssembly!" зелёного цвета размера 28 на холст:
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
| #define SDL_MAIN_USE_CALLBACKS 1 // Use the callbacks instead of main()
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <stdio.h>
// We will use this renderer to draw into this window every frame
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_Texture *texture = NULL;
// This function runs once at startup
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
if (!SDL_Init(SDL_INIT_VIDEO))
{
SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
// Initialize the TTF library
if (!TTF_Init())
{
SDL_Log("Couldn't initialize TTF: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
const char *title = "English Text (SDL3 / C)";
if (!SDL_CreateWindowAndRenderer(title, 350, 350, 0, &window, &renderer))
{
SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
SDL_SetRenderVSync(renderer, 1);
// --- Print SDL versions ---
printf("Compiled SDL3 version: %d.%d.%d\n",
SDL_MAJOR_VERSION,
SDL_MINOR_VERSION,
SDL_MICRO_VERSION);
// Get the version of the SDL library linked at runtime
int v = SDL_GetVersion();
printf("Linked SDL3 version: %d.%d.%d\n", SDL_VERSIONNUM_MAJOR(v),
SDL_VERSIONNUM_MINOR(v), SDL_VERSIONNUM_MICRO(v));
// --- SDL3_ttf version ---
v = TTF_Version();
int major = SDL_VERSIONNUM_MAJOR(v);
int minor = SDL_VERSIONNUM_MINOR(v);
int micro = SDL_VERSIONNUM_MICRO(v);
printf("SDL3_ttf version: %d.%d.%d\n", major, minor, micro);
TTF_Font *font = TTF_OpenFont("assets/fonts/LiberationSans-Regular.ttf", 28);
if (!font)
{
SDL_Log("TTF_OpenFont failed: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
SDL_Color textColor = (SDL_Color) { 0, 255, 0 };
SDL_Surface *surface = TTF_RenderText_Blended(font, "Привет из WebAssembly!", 0, textColor);
texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_DestroySurface(surface);
return SDL_APP_CONTINUE;
}
// This function runs when a new event (mouse input, keypresses, etc) occurs
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
if (event->type == SDL_EVENT_QUIT)
{
return SDL_APP_SUCCESS; // End the program, reporting success to the OS
}
return SDL_APP_CONTINUE;
}
// This function runs once per frame, and is the heart of the program
SDL_AppResult SDL_AppIterate(void *appstate)
{
// Set the position where you want to draw the text
float x = 10.f;
float y = 10.f;
// Create a rectangle to hold the texture dimensions
SDL_FRect rect;
SDL_GetTextureSize(texture, &rect.w, &rect.h);
rect.x = x;
rect.y = y;
// SDL_RenderTexture(renderer, texture, NULL, NULL);
// Clear the screen
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
// Draw the text
SDL_RenderTexture(renderer, texture, NULL, &rect);
// Update the screen
SDL_RenderPresent(renderer);
return SDL_APP_CONTINUE;
}
// This function runs once at shutdown
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
if (texture)
{
SDL_DestroyTexture(texture);
texture = NULL;
}
if (renderer)
{
SDL_DestroyRenderer(renderer);
renderer = NULL;
}
if (window)
{
SDL_DestroyWindow(window);
window = NULL;
}
TTF_Quit();
SDL_Quit();
} |
|
- Примечание. Ещё код выше выводит в консоль версии SDL3 и SDL3_ttf:
| Code | 1
2
3
| Compiled SDL3 version: 3.4.0
Linked SDL3 version: 3.4.0
SDL3_ttf version: 3.2.2 |
|
- Не смотря на то, что были изменения в файле CMakeLists.txt конфигурировать проект командой config-web не нужно. Просто введите команду для сборки:
- Обновите страницу браузера и вы увидите результат вывода текста в окне браузера:

Запуск веб-приложения на Android на локальном сервере
- Обратите внимание, что локальный сервер http-server выводит адреса, например, у меня:
| Code | 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
Загрузка веб-приложения на бесплатный хостинг Vercel из консоли
|