Движок с нуля
Часть 2 · Первый треугольник · глава 2.1

Подключаем GLFW

Подключишь к проекту библиотеку GLFW, которая откроет окно, и проверишь, что код GLFW собирается вместе с app.

Откроем наш проект в Visual Studio. Сейчас программа app только печатает строку в консоль. Движку нужно окно, в котором видеокарта будет рисовать картинку.

Окно в Windows создают через WinAPI, и это несколько сотен строк кода: зарегистрировать в Windows вид окна, создать окно, разбирать события, которые Windows присылает окну (нажатия клавиш, движение мыши, щелчок по крестику), и отдельно подготовить окно к рисованию через OpenGL. Такой код работает только в Windows. Поэтому окно откроет библиотека GLFW: она делает ту же работу несколькими вызовами, и те же вызовы работают на Linux и macOS.

FetchContent

GLFW — чужой код, и этот код нужно как-то получить. Можно скачать готовую библиотеку с сайта GLFW и прописать в проекте пути к файлам, но тогда каждый, кто скачает твой проект, должен повторить скачивание и настройку путей руками и угадать ту же версию библиотеки. Мы попросим CMake скачать GLFW самому. Это умеет модуль CMake FetchContent («получить содержимое»). Адрес и версия GLFW будут записаны в CMakeLists.txt, поэтому любой, кто откроет проект, получит ту же GLFW 3.5.1.

Если не получилось1
Failed to clone repository: 'https://github.com/glfw/glfw.git' Строка стоит в окне OutputВывод среди строк CMake Error, и строки CMake generation finished. нет.
Почему
Скачивает GLFW программа Git, которую запустил CMake. Git не смог получить GLFW с GitHub: либо нет интернета, либо в адресе после GIT_REPOSITORY или в номере после GIT_TAG есть опечатка, и на GitHub нет такого репозитория или тега.
Что сделать

Проверь, что интернет работает, например открой в браузере https://github.com/glfw/glfw. Сравни строки GIT_REPOSITORY и GIT_TAG в твоём CMakeLists.txt с карточкой выше: совпадать должна каждая буква. Потом сохрани CMakeLists.txt (Ctrl+S), и Visual Studio запустит настройку заново.

Вот что делал CMake, пока шла настройка. Реплики идут по строкам раздела «Dependencies», который ты дописал в CMakeLists.txt.

Как CMake FetchContent скачивает и подключает библиотеку

CMakeLists.txtстроки 11–25коммит a24c0c1
include(FetchContent)# --- GLFW -------------------------------------------------------------------set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)set(GLFW_BUILD_TESTS    OFF CACHE BOOL "" FORCE)set(GLFW_BUILD_DOCS     OFF CACHE BOOL "" FORCE)set(GLFW_INSTALL        OFF CACHE BOOL "" FORCE)FetchContent_Declare(    glfw    GIT_REPOSITORY https://github.com/glfw/glfw.git    GIT_TAG        3.5.1    GIT_SHALLOW    TRUE)FetchContent_MakeAvailable(glfw)
На схеме четыре полки. Сверху — что CMake запомнил при настройке, ниже — GitHub, сайт, где лежит GLFW, затем папка _deps внутри папки сборки out\build\vs2026, а в самом низу — цели проекта. До этой правки в проекте была одна цель — app. Первая строка блока, include(FetchContent), подключает модуль FetchContent: в модуле записаны команды FetchContent_Declare и FetchContent_MakeAvailable, без этой строки CMake таких команд не знает.
Четыре строки set выключают части GLFW, которые нам не нужны: GLFW_BUILD_EXAMPLES — примеры программ, GLFW_BUILD_TESTS — тесты, GLFW_BUILD_DOCS — документацию, GLFW_INSTALL — правила установки. Каждая строка set берёт одну настройку GLFW и записывает для неё значение OFF («выключено») в файл CMakeCache.txt. Это общий файл настроек в папке сборки. Слово CACHE значит «записать в этот файл», BOOL — тип настройки: переключатель с двумя значениями, ON («включено») и OFF («выключено»), поэтому у нас стоит OFF, пустые кавычки — описание настройки, которое здесь не нужно. FORCE («принудительно») нужен, чтобы OFF записалось, даже если в файле уже лежит другое значение.
FetchContent_Declare ничего не скачивает, а только записывает, то есть объявляет (по-английски declare), что такое glfw и где GLFW взять. Слово glfw здесь — имя, под которым FetchContent запомнит эту запись. GIT_REPOSITORY — адрес репозитория GLFW на GitHub. GIT_TAG — какую версию взять: 3.5.1 — тег, то есть имя, которым авторы GLFW пометили в истории один коммит: тот, на котором работа над версией 3.5.1 закончена. Поэтому CMake получит ровно эту версию, а не то, что авторы дописали после неё. GIT_SHALLOW TRUE — скачать только этот коммит, без истории.
FetchContent_MakeAvailable скачивает GLFW. Для этого CMake запускает программу Git — ту же, что ты установил отдельно, когда ставил Visual Studio и CMake, — и Git копирует файлы GLFW с GitHub в папку glfw-src внутри _deps, которую можно найти на диске. Без GIT_SHALLOW TRUE Git скачал бы всю историю GLFW, тысячи коммитов за много лет: это дольше и занимает больше места на диске, а нужен нам только один коммит.
Затем та же команда FetchContent_MakeAvailable велит CMake прочитать файл CMakeLists.txt из папки glfw-src, как будто этот файл — часть твоего проекта. CMake выполняет команды, которые авторы GLFW записали в этом файле. Каждая из четырёх настроек, которые ты выключил строками set, проверяется в главном файле GLFW, glfw-src\CMakeLists.txt, который лежит прямо в папке glfw-src, условием вида if (GLFW_BUILD_EXAMPLES). CMake берёт значение настройки из CMakeCache.txt, видит там OFF и пропускает примеры, тесты, документацию и правила установки. Затем CMake записывает в папку glfw-build список дел для сборщика.
В файле GLFW, который CMake прочитал, стоит строка add_subdirectory(src). Эта строка велит CMake прочитать ещё один файл — CMakeLists.txt из подпапки src внутри glfw-src. В файле glfw-src\src\CMakeLists.txt есть команда add_library, и эта команда создаёт новую цель. До этого в проекте была одна цель, программа app. Теперь рядом с ней вторая цель — glfw. Это не программа, а библиотека: код GLFW, который при сборке превратится в один файл. Когда ты соберёшь проект, этот файл, готовая библиотека, появится в папке glfw-build, и цель app сможет его использовать.

Теперь проверь в Visual Studio, что цель glfw появилась. Окно Solution ExplorerОбозреватель решений сейчас показывает папку проекта, а цели, о которых знает CMake, видны в другом его представлении. Переключи окно на CMake Targets View. В дереве раскрой строку Engine Project: там будет строка app (executable), то есть твоя программа, и ниже папка GLFW3 со строкой glfw (static library). Это и есть новая цель: «static library» значит статическая библиотека. Рядом в папке GLFW3 стоит ещё служебная цель update_mappings. Чтобы вернуться к папке на диске, снова нажми Switch Views и дважды щёлкни строку Folder View.

Строки про компилятор C и слово Failed в Output

Когда CMake читает файл CMakeLists.txt из папки GLFW, в окне OutputВывод появляются строки, которых не было в проекте без GLFW.

Строка The C compiler identification is MSVC значит, что CMake нашёл компилятор языка C. В твоём CMakeLists.txt записан только язык C++ (LANGUAGES CXX), но GLFW написана на C, и в файле CMakeLists.txt из папки GLFW стоит строка project(GLFW VERSION 3.5.1 LANGUAGES C …). Компилятор C CMake нашёл в той же Visual Studio: компилятор MSVC собирает и C, и C++.

Строки со словами Failed и not found, например Performing Test CMAKE_HAVE_LIBC_PTHREAD - Failed и Looking for pthread_create in pthreads - not found, — не ошибка. CMake ищет для GLFW библиотеку pthread, которая есть в Linux и macOS. В Windows библиотеки pthread нет, и GLFW на Windows обходится без этой библиотеки, поэтому настройка идёт дальше. Настоящая ошибка выглядит иначе: строка начинается со слов CMake Error, и на этой строке настройка останавливается.

Подключить GLFW к app

Цель glfw есть в проекте, но app про неё ещё не знает. В CMakeLists.txt уже есть две команды, которые что-то сообщают цели app: target_compile_features говорила, что нужно программе («C++20»), а target_compile_options передавала компилятору флаг /W4. Третья команда того же вида, target_link_libraries, подключает к цели библиотеку.

Ты допишешь в CMakeLists.txt команду target_link_libraries. Эта одна команда настраивает сразу два шага сборки, и каждому нужно своё. Компилятору, который читает main.cpp, нужно знать, где лежит заголовок GLFW\glfw3.h, который подключает строка #include. Линкеру, который собирает готовую программу, нужен файл glfw3.lib с готовым кодом GLFW.

Ни путь к заголовку, ни имя файла glfw3.lib в команде писать не надо, хватает имени glfw: цель glfw сама хранит, что нужно тем, кто её подключает. Это записали авторы GLFW в файле glfw-src\src\CMakeLists.txt. Когда app подключает glfw, CMake сам передаёт компилятору папку out\build\vs2026\_deps\glfw-src\include, где лежит заголовок, а линкеру — файл glfw3.lib. Этот файл появится при сборке в папке out\build\vs2026\_deps\glfw-build\src\Debug.

Если не получилось2
C1083 Cannot open include file: 'GLFW/glfw3.h': No such file or directory Ошибка на вкладке Error List, сборка остановилась.
Почему
Компилятор ищет заголовок GLFW\glfw3.h только в папках, которые CMake передал цели app. Папку glfw-src\include CMake передаёт цели app только после строки target_link_libraries(app PRIVATE glfw). Значит, строки нет в файле, файл не сохранён или настройка после сохранения ещё не закончилась.
Что сделать

Проверь, что в конце CMakeLists.txt стоит строка target_link_libraries(app PRIVATE glfw), как в карточке выше, и сохрани файл (Ctrl+S). Дождись в окне OutputВывод строки CMake generation finished. и снова запусти программу без отладки (Ctrl+F5).

Строка #include <GLFW/glfw3.h> в main.cpp подчёркнута красной волнистой линией.
Почему
Подчёркивание рисует не компилятор, а подсказчик кода в редакторе Visual Studio, IntelliSense. IntelliSense узнаёт папки с заголовками от CMake и обновляет их, только когда настройка проекта закончилась.
Что сделать

Дождись в окне OutputВывод строки CMake generation finished.: через несколько секунд линия пропадёт. Если линия осталась, проверь строку target_link_libraries, как в совете выше.

glfwGetVersionString() только возвращает строку с версией GLFW. Эту функцию можно вызвать сразу, без подготовки библиотеки к работе, поэтому glfwGetVersionString удобна для проверки. Если строка напечаталась, сработали оба шага: компилятор нашёл заголовок GLFW/glfw3.h в папке, которую CMake передал от цели glfw, а линкер нашёл код функции в glfw3.lib.

Строка в консоли рассказывает, как собрана GLFW.

Консоль app
GLFW 3.5.1 Win32 WGL Null EGL OSMesa VisualC

Библиотеку можно собрать двумя способами: статической, файлом .lib, код которого входит в программу, или динамической, файлом .dll, который лежит рядом с программой отдельно. Если бы GLFW была динамической, строка версии в консоли заканчивалась бы словом DLL. Этого слова в строке нет, значит GLFW статическая: GLFW по умолчанию собирается статической, если в проекте не попросить другое, и код GLFW вошёл прямо в app.exe. Поэтому отдельный файл GLFW рядом с программой не нужен.

Итог

Проект скачивает GLFW 3.5.1 при настройке, собирает её как цель glfw и подключает к app, а программа печатает версию GLFW.

Твой ход · коммит

В окне Git ChangesИзменения Git дважды щёлкни каждый файл из списка ChangesИзменения и прочитай изменения. Если всё устраивает, закоммить и отправь на GitHub с сообщением Add GLFW and print its version.

Так выглядят оба файла после главы:

CMakeLists.txt
CMakeLists.txtкоммит a24c0c1
cmake_minimum_required(VERSION 3.28)# ============================================================================# Project# ============================================================================project(Engine LANGUAGES CXX)# ============================================================================# Dependencies# ============================================================================include(FetchContent)# --- GLFW -------------------------------------------------------------------set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)set(GLFW_BUILD_TESTS    OFF CACHE BOOL "" FORCE)set(GLFW_BUILD_DOCS     OFF CACHE BOOL "" FORCE)set(GLFW_INSTALL        OFF CACHE BOOL "" FORCE)FetchContent_Declare(    glfw    GIT_REPOSITORY https://github.com/glfw/glfw.git    GIT_TAG        3.5.1    GIT_SHALLOW    TRUE)FetchContent_MakeAvailable(glfw)# ============================================================================# App# ============================================================================add_executable(app main.cpp)target_compile_features(app PRIVATE cxx_std_20)if(MSVC)    target_compile_options(app PRIVATE /W4)endif()target_link_libraries(app PRIVATE glfw)
main.cpp
main.cppкоммит a24c0c1
#include <format>#include <iostream>#include <GLFW/glfw3.h>int main(){    std::cout << std::format("GLFW {}\n", glfwGetVersionString());    return 0;}