Pelles C, raylib и радости пионера
Если скажу, что для полноценной разработки под практически любую Windows вам не нужна ни сама Windows, ни Visual Studio ни еще какие дурацкие поделки известной корпорации — сильно удивитесь?
Ниже вас ждет история о поистине уникальном проекте — настоящей жемчужине инженерной мысли, созданной разумеется талантливым психопатом одиночкой.
Пожалуй, даже о двух таких проектах.
Pelles C
Когда мне впервые рассказали, что на свете существует современная среда разработки под Windows размером в 30 Мегабайт, я разумеется не поверил, решив что это очередная дурацкая байка из интернета.
Но рассказы продолжались, народ в красках описывал что сие даже не просто одна IDE, а целый тулчейн «все в одном»:
оптимизирующий компилятор, ассемблер, линковщик, редактор ресурсов, отладчик, система сборки и даже собственный инсталлятор (!)
И вся эта красота умещается в столь скромные размеры.
Короче автор знатно ох#ел удивился, поскольку сей замечательный проект попирает кирзовыми сапогами все его познания в операционных системах, системном программировании, архитектуре и компьютерах вообще.
Итак, встречайте на арене цирка:
Pelles C — небольшая бесплатная интегрированная среда разработки и компилятор для разработки программ на языке C, работающая в операционных системах Windows и поддерживаемая Пелле Ориниусом.
Цитата выше из русскоязычной статьи, потому как английского оригинала по Pelles C в Википедии внезапно нет. По этому интересному проекту вообще крайне мало материалов в сети, несмотря на всю его уникальность.
Не стоит вскрывать эту тему даже задумываться, на какие жертвы (и каким богам) пошел г-н Пелле ради создания этого инженерного шедевра, лишь скромно замечу, что история проекта началась не вчера:
1987–1988: Pelle Orinius begins writing an assembler and DOS/16-bit Windows tools while working at a consulting firm in Sweden.
Затем, уже в конце 90х автор адаптировал свои наработки под современные реалии Windows-разработки, взяв за основу проект Little C Compiler, на котором студентоту учили писать компиляторы в Принстоне.
Увы, но это практически вся доступная информация о проекте Pelles C.
Подготовка
«Pelles C» поддерживает практически любые версии Windows и отлично работает в Wine, что в очередной раз позволит автору использовать FreeBSD, не оскверняя себя работой непосредственно в Windows.
Разумеется проект, который строго придерживается идей радикального минимализма должен иметь особенный подход к собственному дистрибутиву, вместе с процессом установки.
один единственный файл с лаконичным названием setup.exe скачивается прямо с сайта проекта, с главной страницы.Запускаем инсталлятор с помощью Wine:
wine64 ~/Downloads/setup.exe
Запустится классический инсталлятор, который вы видели неоднократно, если только не вчера купили свой первый компьютер.
Рекомендую дополнительно установить исходники примеров:
Стоит установить Pelles C в более короткий путь, прямо на диске C:\ , чтобы было проще работать c этим каталогом с хоста:
Больше ничего настраивать не надо, сама установка проходит очень быстро и завершается без ошибок даже в Wine.
Теперь переходим в каталог с установленной средой:
cd ~/.wine/drive_c/PellesC/
wine ./Bin/poide.exe
При первом запуске среды, вас попросят запустить генерацию специальной базы, для того чтобы работали подсказки и подстановки в редакторе:
Просто жмем «Build» и ожидаем завершения.
Так выглядит только что запущенная среда «Pelles C», готовая к работе:
Самое время для новых интересных развлечений!
Hello, world
Первым делом стоит познакомиться со средой и проверить работоспособность, для чего соберем классический «Hello, world».
Вызываем меню «File→New→Project», запустится диалог создания нового проекта:
Выбираем «Wizard for Windows application» и вводим название, следующим шагом указываем целевую архитектуру и шаблон:
После нажатия кнопки «Next» будут перечислены все генерируемые файлы выбранного шаблона, а после завершения диалога настройки будет сгенерирован проект и автоматически загружен в среде разработки:
Теперь нажимаем кнопку «Build» на панели инструментов, после чего запустится сборка проекта:
Для запуска собранного приложения нажимаем на панели инструментов кнопку «Run» и наслаждаемся видом.
Так выглядит в действии только что собранный «Hello, world»:
Собранное приложение не имеет зависимостей кроме WinAPI, поэтому запросто запускается даже на Windows 95.
Но разумеется на одном «Hello, world» я не успокоился и ниже расскажу о полноценном проекте с 3D-графикой, который можно разрабатывать с помощью Pelles C.
Raylib
Для раскрытия возможностей Pelles C был нужен какой-то интересный, а главное практичный проект, чтобы дорогие читатели одновременно ох#ели обрадовались и смогли бы реально использовать всю эту машинерию в своей разработке.
Хотя-бы в теории.
Недолго думая, мой выбор пал на геймдев и вскоре «острый глаз индейца Джо» заметил одну интересную библиотеку:
raylib is a simple and easy-to-use library to enjoy videogames programming.
Она, как и многое другое, хорошее и прекрасное для приличного инженера, практически неизвестна отечественным разработчикам.
Допускаю, что причина кроется в столь радикальном описании:
raylib is a programming library to enjoy videogames programming; no fancy interface, no visual helpers, no gui tools or editors... just coding in pure spartan-programmers way. Are you ready to enjoy coding?
Поскольку последние остатки инженерной культуры ныне догрызают нейросети с вайбкодерами, даже публичные рассуждения на тему «enjoy coding» теперь приравниваются к половым девиациям и терроризму.
Но автора это разумеется не смущает.
Hello, raylib
Путем недолгих поисков в сети был обнаружен такой интересный пост, где на чистом английском по шагам объяснялось как подключить raylib в Pelles C не привлекая внимания санитаров для полноценной разработки.
Чтобы итоговое приложение с 3D-графикой можно было собрать, запустить и отлаживать в этой замечательной среде.
К сожалению в означенном посте предлагалось использовать устаревшую версию raylib (5.5) вместо последней, поэтому инструкции пришлось актуализовать.
возьмем один из красивых примеров, поставляемых с Raylib, немного его доработаем лобзиком, затем соберем и запустим с помощью Pelles C.
Получим в итоге классическое Windows-приложение с 3D-графикой, созданное и работающее без продукции Microsoft.
Скачиваем архив с последней на момент создания этой статьи версией 6.2 c официального репозитория на Github, для большей совместимости нужна готовая сборка под Win32:
wget https://github.com/raysan5/raylib/releases/download/6.2/raylib-6.2_win32_msvc16.zip
Создаем на виртуальном диске Wine следующую структуру каталогов:
cd ~/.wine/drive_c mkdir -p dev/3rdparty/raylib
Распаковываем архив с raylib, убрав корневой каталог, чтобы содержимое архива находилось сразу в каталоге dev/3rdparty/raylib:
Теперь запускаем среду Pelles C и создаем новый проект типа «x86 Console program (exe)» с названием «hello-raylib», указав в Location C:\dev.
Собственно «hello-raylib» станет названием подкаталога в C:\dev , внутри которого будут созданы файлы проекта.
Нажимаем кнопку «New» на панели инструментов (она там самая левая) и вводим следующий код:
#include "raylib.h"
int main(void)
{
InitWindow(800, 450, "raylib hello");
SetTargetFPS(60);
while (!WindowShouldClose())
{
BeginDrawing();
ClearBackground(RAYWHITE);
DrawText("Hello, raylib!", 190, 200, 40, BLACK);
EndDrawing();
}
CloseWindow();
return 0;
} Сохраняем как main.c, добавив в проект.
Но прежде чем пробовать собрать и запустить этот код, проект необходимо настроить, иначе вас ожидает предсказуемый облом.
Открываем меню «Project→Project Options», вкладка Compiler и выставляем следующие настройки:
Главное выбрать в поле «C standard» значение C17 и поставить галочку «Enable Microsoft extensions».
Теперь переходим в раздел «Code generation» и выбираем значение поля «Default calling convention» в __cdecl:
Следующим шагом переходим в раздел «General→Folders», где необходимо настроить использование каталогов raylib с заголовочными файлами и библиотеками.
В поле «Type» выбираем «Libraries», затем жмем крайне всратую кнопку «New folder» и указываем каталог c:\dev\3rdparty\raylib\lib.
Затем аналогичным образом выбираем из выпадающего списка «Includes» и добавляем каталог c:\dev\3rdparty\raylib\lib.
Наконец в качестве последнего шага эпопеи с настройкой среды разработки выбираем раздел «Linker», затем вводим в поле «Library and object files» следующую строку:
kernel32.lib advapi32.lib delayimp.lib raylibdll.lib opengl32.lib gdi32.lib winmm.lib user32.lib shell32.lib
Это ни что иное как список библиотек, разделенных пробелами, с которыми линкуется наше тестовое приложение:
После этого финального шага можно пробовать собрать наш «Hello, raylib»:
нажимаем кнопку «Build» на панели инструментов и наблюдаем как сборка успешно завершается.
Сборка завершается да, но только собранное приложение.. не работает.
При попытке запуска (кнопка «Run» на все той же панели инструментов) вас ждет очередное интересное сообщение об ошибке:
Проблема заключается в том, что статичная библиотека raylibdll.lib, которую мы линковали выше на самом деле лишь обертка, которая при запуске самостоятельно подгружает отдельную DLL с движком raylib.
Для того чтобы собранное приложение все же заработало необходимо скопировать raylib.dll из каталога dev/3rdparty/raylib/lib в корневой каталог проекта.
Так это выглядит на моей машине:
После трюка с копированием библиотеки, наш «Hello, raylib» наконец запускается прямо из среды разработки:
Для самых упоротых
Вполне очевидно (нормальным людям), что библиотека для разработки видеоигр предполагает наличие на компьютере аппаратного 3D-ускорения.
Поскольку мы работаем в Wine, по идее должно работать аппаратное ускорение на хосте (где у меня FreeBSD), которое с помощью специальных оберток используется из запущенного Win32-приложения.
Но так как автор — радикальный технолуддит фанат винтажной техники, эта замечательная статья вместе с примерами и тестами создается на машине 2006 года, без дискретной видеокарты.
поддержки аппаратного 3D-ускорения тут нет, не было и уже не будет.
Чтобы все же запускать приложения, использующие raylib в столь упоротом окружении, необходимо выставить две переменные окружения:
export LIBGL_ALWAYS_SOFTWARE=true export GALLIUM_DRIVER=llvmpipe
После чего запускаем в Wine наш Pelles C описанным выше способом:
cd ~/.wine/drive_c/PellesC wine ./Bin/poide.exe
Настройки окружения подхватятся Wine и будут применены для запускаемых в нем приложений.
Демо-проект
Если вы, дорогой читатель, дошли до этого места и до сих пор трезвы не утратили интерес — поздравляю, вы наш клиент. И теперь у вас на руках есть настроенная среда разработки Pelles C, а также работающий пример приложения с 3D-графикой.
Можно наконец заняться настоящим делом.
В официальном репозитории проекта raylib есть много разных интересных примеров, один из которых автор и решил взять в качестве демо — для сборки и запуска с помощью Pelles C.
Пример называется «raylib [core] example — 3d camera first person» и демонстрирует различные режимы работы с камерой. Бонусом идет полноценная 3D-сцена с генерацией простеньких объектов и работа с интерфейсом.
Создаем новый проект, повторяем все шаги настройки, описанные выше для «Hello, raylib», добавляем новый файл main.c с таким кодом:
#include "raylib.h"
#define MAX_COLUMNS 20
int main(void)
{
// Initialization
const int screenWidth = 800;
const int screenHeight = 450;
InitWindow(screenWidth, screenHeight,
"0x08: raylib on Pelles C");
// Define the camera to look into our 3d world (position,
// target, up vector)
Camera camera = { 0 };
// Camera position
camera.position = (Vector3){ 0.0f, 2.0f, 4.0f };
// Camera looking at point
camera.target = (Vector3){ 0.0f, 2.0f, 0.0f };
// Camera up vector (rotation towards target)
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
// Camera field-of-view Y
camera.fovy = 80.0f;
// Camera projection type
camera.projection = CAMERA_PERSPECTIVE;
int cameraMode = CAMERA_ORBITAL;
// Generates some random columns
float heights[MAX_COLUMNS] = { 0 };
Vector3 positions[MAX_COLUMNS] = { 0 };
Color colors[MAX_COLUMNS] = { 0 };
for (int i = 0; i < MAX_COLUMNS; i++)
{
heights[i] = (float)GetRandomValue(1, 12);
positions[i] = (Vector3){ (float)GetRandomValue(-15, 15),
heights[i]/2.0f, (float)GetRandomValue(-15, 15) };
colors[i] = (Color){ GetRandomValue(20, 255),
GetRandomValue(10, 55), 30, 255 };
}
// Limit cursor to relative movement inside the window
DisableCursor();
// Set our game to run at 60 frames-per-second
SetTargetFPS(60);
// Main game loop
// Detect window close button or ESC key
while (!WindowShouldClose())
{
// Update
// Switch camera mode
if (IsKeyPressed(KEY_ONE))
{
cameraMode = CAMERA_FREE;
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Reset roll
}
if (IsKeyPressed(KEY_TWO))
{
cameraMode = CAMERA_FIRST_PERSON;
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Reset roll
}
if (IsKeyPressed(KEY_THREE))
{
cameraMode = CAMERA_THIRD_PERSON;
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Reset roll
}
if (IsKeyPressed(KEY_FOUR))
{
cameraMode = CAMERA_ORBITAL;
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Reset roll
}
// Switch camera projection
if (IsKeyPressed(KEY_P))
{
if (camera.projection == CAMERA_PERSPECTIVE)
{
// Create isometric view
cameraMode = CAMERA_THIRD_PERSON;
// Note: The target distance is related to the render
// distance in the orthographic projection
camera.position = (Vector3){ 0.0f, 2.0f, -100.0f };
camera.target = (Vector3){ 0.0f, 2.0f, 0.0f };
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
camera.projection = CAMERA_ORTHOGRAPHIC;
// near plane width in
// CAMERA_ORTHOGRAPHIC
camera.fovy = 20.0f;
CameraYaw(&camera, -135*DEG2RAD, true);
CameraPitch(&camera, -45*DEG2RAD, true, true, false);
}
else if (camera.projection == CAMERA_ORTHOGRAPHIC)
{
// Reset to default view
cameraMode = CAMERA_THIRD_PERSON;
camera.position = (Vector3){ 0.0f, 2.0f, 10.0f };
camera.target = (Vector3){ 0.0f, 2.0f, 0.0f };
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
camera.projection = CAMERA_PERSPECTIVE;
camera.fovy = 60.0f;
}
}
// Update camera computes movement internally depending
// on the camera mode
// Some default standard keyboard/mouse inputs are hardcoded
// to simplify use
// For advanced camera controls, it's recommended to compute
// camera movement manually
UpdateCamera(&camera, cameraMode);
// Draw
BeginDrawing();
ClearBackground(RAYWHITE);
BeginMode3D(camera);
DrawPlane((Vector3){ 0.0f, 0.0f, 0.0f },
(Vector2){ 32.0f, 32.0f }, LIGHTGRAY); // Draw ground
DrawCube((Vector3){ -16.0f, 2.5f, 0.0f },
1.0f, 5.0f, 32.0f, BLUE); // Draw a blue wall
DrawCube((Vector3){ 16.0f, 2.5f, 0.0f },
1.0f, 5.0f, 32.0f, LIME); // Draw a green wall
DrawCube((Vector3){ 0.0f, 2.5f, 16.0f },
32.0f, 5.0f, 1.0f, GOLD); // Draw a yellow wall
// Draw some cubes around
for (int i = 0; i < MAX_COLUMNS; i++)
{
DrawCube(positions[i], 2.0f, heights[i], 2.0f, colors[i]);
DrawCubeWires(positions[i], 2.0f, heights[i], 2.0f, MAROON);
}
// Draw player cube
if (cameraMode == CAMERA_THIRD_PERSON)
{
DrawCube(camera.target, 0.5f, 0.5f, 0.5f, PURPLE);
DrawCubeWires(camera.target, 0.5f, 0.5f, 0.5f, DARKPURPLE);
}
EndMode3D();
// Draw info boxes
DrawRectangle(5, 5, 330, 100, Fade(SKYBLUE, 0.5f));
DrawRectangleLines(5, 5, 330, 100, BLUE);
DrawText("Camera controls:", 15, 15, 10, BLACK);
DrawText("- Move keys: W, A, S, D, Space, Left-Ctrl",
15, 30, 10, BLACK);
DrawText("- Look around: arrow keys or mouse",
15, 45, 10, BLACK);
DrawText("- Camera mode keys: 1, 2, 3, 4",
15, 60, 10, BLACK);
DrawText("- Zoom keys: num-plus, num-minus or mouse scroll",
15, 75, 10, BLACK);
DrawText("- Camera projection key: P", 15, 90, 10, BLACK);
DrawRectangle(600, 5, 195, 100, Fade(SKYBLUE, 0.5f));
DrawRectangleLines(600, 5, 195, 100, BLUE);
DrawText("Camera status:", 610, 15, 10, BLACK);
DrawText(TextFormat("- Mode: %s",
(cameraMode == CAMERA_FREE) ? "FREE" :
(cameraMode == CAMERA_FIRST_PERSON) ? "FIRST_PERSON" :
(cameraMode == CAMERA_THIRD_PERSON) ? "THIRD_PERSON" :
(cameraMode == CAMERA_ORBITAL) ? "ORBITAL" : "CUSTOM"),
610, 30, 10, BLACK);
DrawText(TextFormat("- Projection: %s",
(camera.projection == CAMERA_PERSPECTIVE) ? "PERSPECTIVE" :
(camera.projection == CAMERA_ORTHOGRAPHIC) ?
"ORTHOGRAPHIC" : "CUSTOM"), 610, 45, 10, BLACK);
DrawText(TextFormat("- Position: (%06.3f, %06.3f, %06.3f)",
camera.position.x, camera.position.y, camera.position.z),
610, 60, 10, BLACK);
DrawText(TextFormat("- Target: (%06.3f, %06.3f, %06.3f)",
camera.target.x, camera.target.y, camera.target.z),
610, 75, 10, BLACK);
DrawText(TextFormat("- Up: (%06.3f, %06.3f, %06.3f)",
camera.up.x, camera.up.y, camera.up.z), 610, 90, 10, BLACK);
EndDrawing();
}
// De-Initialization
// Close window and OpenGL context
CloseWindow();
return 0;
}Код выше — чуть порезанная версия оригинального примера библиотеки raylib, из которого был удален блок работы с pro-камерой, не особо актуальный для простого демо.
Еще я чуть-чуть увеличил fov и сменил тип камеры по-умолчанию, чтобы сразу получить вращение камеры вокруг сцены.
Так выглядит финальный результат в динамике:
Нативная сборка для FreeBSD
На сладкое покажу сборку нашего демо-проекта непосредственно на FreeBSD, причем без изменения исходного кода.
Оказалось, в пакетах FreeBSD есть готовая сборка библиотеки raylib, поэтому достаточно выполнить:
pkg install raylib
Теперь переходим в каталог с проектом:
cd ~/.wine/drive_c/dev/raylib-demo
Запускаем сборку с помощью обычного gcc14 уже на хосте:
gcc14 main.c -o raylib_bsd -I/usr/local/include -L/usr/local/lib -lraylib -Wno-implicit-function-declaration
Опция -Wno-implicit-function-declaration нужна для пропуска ругани компилятора на вызываемые методы raylib.
В оригинальном примере подключается заголовок rcamera.h, являющийся частью исходников самого raylib и по этой причине отсутствующий в готовом пакете.
Получаем готовую сборку демо-проекта для FreeBSD:
Эту сборку можно запускать на хосте, без всяких Wine: