Багато зусиль було вкладено в цей туторіал, що б він компілювався і запускався як можна простіше. На жаль, це також означає, що CMakes приховує те, як це зробити в власній програмі.

Цей туторіал пояснить як побудувати Вашу власну програму на сі з нуля. Але для початку деякі базові знання про роботу компілятора.

Будь-ласка, не пропускайте перші дві секції. Якщо Ви вирішили читати цей туторіал, Вам напевне це потрібно знати.

Модель Сі програми

Препроцесор

Це все про #defines і #includes і що вони роблять.

Сі препроцесинг це простий процес - вирізати і вставити.

Коли препроцесор бачить наступний файл MyCode.c :

#include "MyHeader.h"

void main(){
    FunctionDefinedInHeader();
}

, він просто відкриває файл MyHeader.h, і вставляє його вміст в MyCode.c :

// початок MyCode.c
// початок MyHeader.h
#ifndef MYHEADER_H
#define MYHEADER_H

void FunctionDefinedInHeader(); // об'явлення функції

# endif
// кінець MyHeader.h

void main(){
    FunctionDefinedInHeader(); // використання
}

// кінець MyCode

Аналогічно, #define також “вирізати і вставити”, #if також аналізується і, за умови, видаляється, і тому подібне.

В кінці цього всього ми маємо препроцесований файл без усіх #define, #if, #ifdef, #include і готовий до компіляції.

Ось для прикладу main.cpp файл з 6 туторіалу, повністю препроцесований в Студії - tutorial06_preprocessed. Будьте уважні, це дуже великий текст! Але це дуже важливо розуміти, як виглядає cpp файл при компіляції.

Компіляція

Компілятор переводить с++ код в такий вигляд, який процесор безпосередньо розуміє. Наприклад, наступний код:

int i=3;
int j=4*i+2;

буде перетворений в наступні x86 інструкції.

mov         dword ptr [i],3
mov         eax,dword ptr [i]
lea         ecx,[eax*4+2]
mov         dword ptr [j],ecx

Кожний .cpp файл компілюється окремо і результат (бінарний код) записується в .o/.obj файли.

Зверніть увагу, у нас ще немає готової програми - залишився один крок.

Лінковка (зв’язування)

Лінковщик бере всі бінарні файли (Ваші та від сторонніх бібліотек) і генерує готовий до запуску файл (програму). Пара приміток:

  • Бібліотеки мають розширення .lib.
  • Деякі бібліотеки *статичні”. Це значить, що файл бібліотеки .lib містить весь x86 код, який потрібен.
  • Деякі бібліотеки динамічні (також відомі як “спільні” ). Це значить, що .lib не містить всього коду, він просто каже “я обіцяю, що що функії Foo, Bar та WhatsNot будуть доступні під час виконання. (від перекладача - тут трішки щось наплутали, але я залишив як є. Насправді воно так - якщо Ваша програма використовує статичну бібліотеку, то весь код цієї бібліотеки додається до файлу Вашої програми. Якщо Ваша програма використовує динамічну бібліотеку, то при лінковці вона не потрібна, а ось при запуску програми файл бібліотеки буде знайдено і завантажено для використання програмою.)

Після того, як лінкощик зробить свою роботу, ви отримаєте файл з програмою (.exe в випадку Windows, a.out чи без розширення взагалі на Linux/Unix):

Час виконання (Runtime)

Коли Ви запускаєте програму, операційна система відкриває файл і копіює x86 код в пам’ять. Як було сказано вище, не весь код доступний в цей момент - код з динамічних бібліотек. Але лінковщик залишає певну інформацію для пошуку. Наприклад, функцію glClearColor потрібно шукати в OpenGL32.dll.

Windows знайде відповідну dll і в ній функцію glClearColor:

Іноді операційна система не може знайти бібліотеку, можливо в процесі встановлення щось пішло не так (вона не скопіювалась або скопіювалась інша) і в такому випадку програма не запуститься.

Як мені зробити операцію X в IDE Y?

Інструкції щодо побудови OpenGL програм розділені на наступні прості операції з наступної причини:

  • Вам потрібно буде постійно виконувати їх, тому краще їх знати
  • Вам потрібно знати, що відноситься до OpenGL, а що ні

Visual Studio

Створення нового проекту

File -> New -> Project -> Empty project. Не використовуйте дивних майстрів (wizard). Не використовуйте те, чого не знаєте (вимкніть MFC, ATL, прикомпільовані заголовки, stdafx, файл main).

Додавання джерельних файлів в проект

Правий клік по Source Files -> Add new.

Додавання каталогів на включення в проект

Правий клік по назві проекту -> Project Properties -> C++ -> General -> Additional include directories. Тут насправді випадаючий список, його можна легко модифікувати.

Лінковка з бібліотекою

Правий клік по назві проекту -> Project Properties -> Linker -> Input -> Additional dependencies : впишіть ім’я бібліотеки .lib. Наприклад : opengl32.lib

В Project Properties -> Linker -> General -> Additional library directories, переконайтесь, що вказаний правильний шлях до бібліотеки.

Збирання, запуск і налагодження

Встановіть робочу діректорію (папку) туди, де зберігаються Ваші текстури та шейдери - Project Properties -> Debugging -> Working directory

Запуск - Shift-F5, але Вам зазвичай це не знадобиться - Налагоджуйте - F5.

Короткий список гарячих кнопок:

  • F9 на певному рядку або клікнути зліва біля номеру рядка - встановити точку зупинки, З’явиться червона крапка.
  • F10 - виконати один рядок коду
  • F11 - виконати один рядок коду, але якщо це функція - “зайти в середину”
  • Shift-F11 - виконувати функцію до виходу з неї

Також є багато допоміжних вікон для перегляду змінних, поточного стеку, потоків…

QtCreator

QtCreator доступний безкоштовно тут http://qt-project.org/.

Створення нового проекту

Використовуйте чистий C чи C++ проект; не використовуйте шаблон з Qt кодом.

Використовуйте налаштування за замовчуванням.

Додавання джерельних файлів в проект

Використовуйте GUI, або просто додайте файл в .pro :

SOURCES += main.cpp \
           other.cpp \
           foo.cpp

Додавання каталогів на включення в проект

В .pro файлі :

INCLUDEPATH += <потрібний каталог> \
               <інші каталоги>

Лінковка з бібліотекою

Правий клік по project -> Add library

  • Якщо Ви використовуєете Лінукс і встановили бібліотеку через apt-get чи подібні інструменти, існує вірогідність, що бібліотека буде “зареєстрована” в системі. Ви можете обрати “System package” і ввести ім’я бібліотеки ( наприклад : libglfw чи glew )

  • Якщо ні, то використовуйте “System Library”. Вкажіть шлях до бібліотеки.

Збирання, запуск і налагодження

Збирання : Ctrl-B, або натиснути на іконку молотка внизу зліва.

Запуск : зелена стрілка. Встановити аргументи програми і робочий каталог можна в Projects -> Run Settings

Налагодження :

  • Встановити точку зупинки : Клікніть мишкою зліва від номеру рядка. З’явиться червона крапка.
  • F10 : виконати поточний рядок
  • F11 : виконати поточний рядок, але якщо це інша функція - зайти в неї (“step into”)
  • Shift-F11 : виконувати до кінця функції (“step out”)

Також є велика кількість вікон налагодження - перегляд змінних, стек викликів, потоки …

XCode

В процесі…

Створення нового проекту

Додавання джерельних файлів в проект

Додавання каталогів на включення в проект

Лінковка з бібліотекою

Збирання, запуск і налагодження

CMake

CMake вміє створювати проекти для більшості систем збирання - Visual, QtCreator, XCode, make, Code::Blocks, Eclipse і подібне і на різні операційні системи. Це звільняє Вас від підтримки різний файлів проектів.

Створення нового проекту

Створіть файл CMakeLists.txt file і запишіть там наступне (модифікуйте за необхідності) :

cmake_minimum_required (VERSION 2.6)
project (your_project_name)

find_package(OpenGL REQUIRED)

add_executable(your_exe_name
    tutorial04_colored_cube/tutorial04.cpp
    common/shader.cpp
    common/shader.hpp
)

Запустіть CMake GUI, знайдіть Ваш CMakeLists.txt і вкажіть каталог, куди будуть зберігатися файли при збиранні проекту. Натисніть Configure (конфігурувати), потім Generate (генерувати). Ваш проект/рішення буде створено в вказаному вище каталозі.

Додавання джерельних файлів в проект

Просто додайте ім’я файлу в параметри команди add_executable.

Додавання каталогів на включення в проект

include_directories(
    external/AntTweakBar-1.15/include/
    external/glfw-2.7.2/include/
    external/glm-0.9.1/
    external/glew-1.5.8/include/
    .
)

Лінковка з бібліотекою

set(ALL_LIBS
    ${OPENGL_LIBRARY}
    GLFW_272
    GLEW_158
    ANTTWEAKBAR_151_OGLCORE_GLFW
)

target_link_libraries(tutorial01_first_window
    ${ALL_LIBS}
)

Збирання, запуск і налагодження

CMake не будує проекти, він їх створює. Використовуйте відповідну IDE чи інструмент.

(від перекладача - насправді, CMake вже вміє запускати відповідну систему збірки. Для тих, хто не боїться консолі:

mkdir build # створимо каталог, де будемо збирати, ім'я можна обрати на власний розсуд
cd build # перейдемо в нього
cmake .. # запустимо конфігурування
cmake --build . # запустимо збирання проекту

)

make

Буль ласка, не використовуйте його. (від перекладача - можна його використовувати, як по мені, то для маленьких проектів на Linux - саме воно, але це дуже специфічний інструмент)

gcc

Це може бути дуже корисно, скомпілювати невеликий проект “ручками”, що б краще розуміти деталі процесу. Але краще не робіть це в реальних проектах…

Це все можна зробити і Windows, використовуючи mingw.

Скомпілюємо кожний .cpp файл окремо:

g++ -c main.cpp
g++ -c tools.cpp

В результаті у нас буде два файли main.o та tools.o. Злінкуємо їх :

g++ main.o tools.o

з’явиться файл a.out; це і є Ваша програма, запускаємо:

./a.out

Це все!

Збирання Вашого власного Сі проекту

Після всіх отриманих знань, ми можемо зібрати влусну програму з використанням OpenGL.

  • Завантажимо залежності. Ми використовуємо GLFW, GLEW і GLM, але у Вас можуть бути інші. Збережемо їх в підкаталогу Вашого проекту, наприклад external/
  • Вони повинні бути попередньо скомпільовані, та деякі, такі як GLM не потребують цього (примітки перекладача - такі бібліотеки називаються “header only”)
  • Створіть новий проект в Вашій IDE за вибором
  • Додайте новий cpp файл (перекладач - код нижче - практично чистий Сі. В заголовку теж сі. У автора туторілу тут постійна плутанина сі і с++)
  • Скопіюйте і вставте наступний код (це насправді playground.cpp)
#include <stdio.h>
#include <stdlib.h>

#include <GL/glew.h>

#include <GL/glfw.h>

#include <glm/glm.hpp>
using namespace glm;

int main( void )
{
	// ініціалізація GLFW
	if( !glfwInit() )
	{
		fprintf( stderr, "Помилка ініціалізації GLFW\n" );
		return -1;
	}

	glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4);
	glfwOpenWindowHint(GLFW_WINDOW_NO_RESIZE,GL_TRUE);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3);
	glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	// Створимо нове вікно та ініціалізуємо OpenGL контекст
	if( !glfwOpenWindow( 1024, 768, 0,0,0,0, 32,0, GLFW_WINDOW ) )
	{
		fprintf( stderr, "Неможливо створити вікно GLFW. Якщо у Вас Intel відеокарта, вона може бути не сумісна з 3.3 Можливо, Вам прийдеться скористатись туторіалом для версії 2.1.\n" );
		glfwTerminate();
		return -1;
	}

	// Initialize GLEW
	if (glewInit() != GLEW_OK) {
		fprintf(stderr, "Помилка ініціалізації GLEW\n");
		return -1;
	}

	glfwSetWindowTitle( "Майданчик для дослідів" );

	// Переконаємося, що ми можемо перехопити кнопку Esc
	glfwEnable( GLFW_STICKY_KEYS );

	// Темно синє тло
	glClearColor(0.0f, 0.0f, 0.3f, 0.0f);

	do{
		// Тут нічого не малюється, дивіться туторіал 2!

		// Обміняємо буфери
		glfwSwapBuffers();

	} // Якщо користувач натиснув ESC, закриємо вікно
	while( glfwGetKey( GLFW_KEY_ESC ) != GLFW_PRESS &&
		   glfwGetWindowParam( GLFW_OPENED ) );

	// Закриємо вікно OpenGL та завершимо роботу GLFW
	glfwTerminate();

	return 0;
}
  • Скомпілюйте проект.

У Вас скоріше за все буде багато помилок. Ми проаналізуємо всі відомі, одна за одною.

Вирішення проблем

Помилки, наведені нижче, взяті з Visual Studio 2010, але в GCC та інших системах вони подібні. (від перекладача - я свідомо не перекладаю тексти помилок, тому що так буде їх легше знайти, але додаю короткий переклад)

Visual Studio - fatal error C1083: Cannot open filetype file: ‘GL/glew.h’ : No such file or directory

(файл не знайдено)

(будь який файл)

Деякі файли чи заголовки можуть знаходитись в дивних місцях. Наприклад, GLEW знаходяться в external/glew-x.y.z/include/ (ми їх туди розпакували, пам’ятаєте?). Компілятор не чарівник і не може магічним чином дізнатись де саме файл, Ви повинні йому підказати. В налаштуваннях проекту додайте каталог з даним файлом. Це файл з кодом, тому додавайте в секцію компілятора, а не лінковщика.

За жодних обставин Ви не повинні копіювати ці файли в каталоги компілятора (Program Files/Visual Studio/…). Технічно, це може працювати, але це дуже погана практика.

Також дуже гарною практикою буде використання відносних шляхів ( ./external/glew/… замість C:/Users/username/Downloads/… )

Наприклад, ось що містить CMake файл туторіалу:

external/glfw-2.7.2/include
external/glm-0.9.1
external/glew-1.5.8/include

Продовжуйте, поки компілятор не знайде всі файли.

GCC - fatal error: GL/glew.h: No such file or directory

(файл не знайдено)

(може бути будь-який файли з кодом)

Це значить, що бібліотека не встановлена. Якщо Ви везунчик і бібліотека достатньо відома, то можна просто спробувати її встановити. В випадку GLFW, GLEW та GLM:

sudo apt-get install libglfw-dev libglm-dev libglew1.6-dev

Якщо це не дуже відома бібліотека, то дивіться відповідь для Visual Studio.

(від перекладача - в Ubuntu скоріше за все Вам потрібні пакети, які закінчуються на -dev)

Visual Studio - error LNK2019: unresolved external symbol glfwGetWindowParam referenced in function main

(не вирішений/не знайдений зовнішній символ в певній функції)

(Або будь-який інший символ в будь-якій функції)

Поздоровляємо, у Вас помилка лінковщика. Тому це гарна новина - компіляція була успішна, залишився ще один крок.

Функції glfw знаходяться в зовнішній бібліотеці. Вам потрібно підказати лінковщику, де саме вона знаходиться. Так, це опція лінковщика. Не забудьте додати шлях до бібліотеки.

Як приклад, це те, що проект на Visual Studio використовує. Імена трішки дивні, тому що це користувацька збірка. Але для GLM не потребує окремої збірки і подібні проблеми лінковки для неї не характерні.

external\Debug\GLFW_272.lib
external\Debug\GLEW_158.lib

Якщо Ви завантажили ці бібліотеки з SourceForge (GLFW, GLEW) і зібрали бібліотеку самостійно, Вам потрібно вказати правильний шлях, наприклад десь такий:

C:\Where\You\Put\The\Library\glfw.lib
C:\Where\You\Put\The\Other\Library\glew32.lib

GCC - main.cpp: undefined reference to `glfwInit’

(або будь-який інше посилання в іншому файлі)

Відповідь аналогічна, як і для Visual Studio.

Зверніть увагу, що в випадку Лінукса, GLFW і GLEW (та багатьох інших) зазвичай вже встановлені через apt-get та подібні (sudo apt-get install libglew-dev libglfw-dev, може трішки відрізнятись). Якщо це так, то бібліотека буде скопійована в відомі компілятору каталоги і Вам не потрібно буде вказувати шлях. Просто лінкуйте, як було показано в першій секції.

Я все налаштував вірно, але отримую помилку “unresolved external symbol” !

Це може бути трішки складно, та ось декілька варіантів:

У мене помилка лінковщика, яка містить ім’я виду _imp_glewInit чи щось схоже, що починається з _imp

Це значить, що бібліотека (в даному випадку - glew) була скомпільована як статична бібліотека, але Ви намагаєтесь використовувати її як динамічну бібліотеку. В данному випадку просто додайте наступну директиву в опції компілятора (Вашого проекту, не glew):

GLEW_STATIC

(від перекладача - для інших проектів можуть бути інші рішення, читайте документацію)

У мене якісь дивні проблеми з GLFW

А може GLFW була скомпільована як динамічна бібліотека, але Ви намагаєтесь використовувати її як статичну?

Спробуйте додати таку опції препроцесора (для компілятору):

GLFW_DLL

У мене якась проблема лінковки! Допоможіть мені, я застряг !

Будь-ласка, надішліть нам детальній звіт і весь проект (в архіві) і ми спробуємо надати інструкції.

Я хочу вирішити це самостійно. Які загальні правила?

Давайте допустимо, що Ви автор GLFW. Ви хочете надати доступ до функції glfwInit().

Коли бібліотека будується як DLL, Ви повинні сказати компілятору, що glfwInit() не така як інші функції в цій DLL - вона повинна бути видна/доступна іншим. А уявна функція glfwPrivateImplementationMethodNobodyShouldCareAbout() - ні. Це можна зробити, об’явивши функцію зовнішньою - “external” (для GCC) чи “__declspec(dllexport)” (для Visual).

Коли Ви хочете використовувати glfw, Вам потрібно підказати компілятору/лінковщику, що реалізація (тіло) цієї функції відсутнє в коді і її потрібно злінкувати (додати) динамічно. Це можна зробити, об’явивши функцію зовнішньою - “external” (для GCC) чи “__declspec(dllimport)” (для Visual). (?????? external???)

Можна скористатись зручним #define : GLFWAPI і об’явити функцію наступним чином:

GLFWAPI int  glfwInit( void );
  • Коли Ви збираєте як DLL, Ви додаєте #define GLFW_BUILD_DLL. GLFWAPI перетворюється за допомогою #define в __declspec(dllexport)
  • Коли Ви використовуєте GLFW як DLL, Ви додаєте #define GLFW_DLL. GLFWAPI перетворюється за допомогою #define в __declspec(dllimport)
  • Коли Ви збираєте як статичну бібліотеку, GLFWAPI перетворюється за допомогою #define в пустий рядок
  • Коли Ви використовуєте GLFW як статичну бібліотеку, GLFWAPI перетворюється за допомогою #define в пустий рядок.

Отже ось правило: ці ключі повинні бути узгодженими. Якщо Ви будуєте бібліотеку (будь-яку бібліотеку, не тільки GLFW), Ви повинні правильні слова препроцесора - GLFW_DLL, GLEW_STATIC

Моя програма падає !

Є багато причин, чому програма на С++ з OpenGL може падати. Ось декілька. Якщо Ви не знаєте, в якому саме рядку Ваша програма падає, розберіться, як користуватись дебагером (дивіться інструкії вище). Будь-ласка, не використовуйте printf для налагодження програми.

Код в main навіть не починає виконуватись

Причина скоріше за все в тому, що деякі dll не були знайдені. Спробуйте відкрити Вашу програму за допомогою Dependency Walker (Windows) чи ldd (Linux; а ще подивіться тут)

Моя програма падає на виклику функції glfwOpenWindow(), чи інших фунціях, що створюють OpenGL контекст

Декілька можливих причин:

  • Ваша відеокарта не підтримує версію OpenGL, яку Ви запросили. Спробуйте перевірити підтримувані версії за допомогою GPU Caps Viewer чи чогось подібного. Оновіть драйвер, якщо його версія занадто стара. Інтегровані відеокарти від Інтел на ноутбуках особливо “примхливі”. Використовуйте ранні версії OpenGL (наприклад 2.1), та розширення, якщо Вам не хватає можливостей.
  • Ваша операційна система не підтримує потрібну версію OpenGL. Mac OS - та сама відповідь.
  • Ви намагаєтесь використовувати GLEW з контекстом OpenGL Core (у якого немає застарілих речей). Це баг GLEW. Використовуйте glewExperimental=true перед glewInit() або використовуйте профіль сумісності (тобто використовуйте GLFW_OPENGL_COMPAT_PROFILE замість GLFW_OPENGL_CORE_PROFILE)

Моя програма падає на першому виклику функцій OpenGL чи не першому створенні буферу

Можливі причини :

  • Ви не викликали glewInit() ПІСЛЯ glfwOpenWindow()
  • Ви використовуєте профіль core OpenGL і не створили VAO, додайте наступний код після glewInit():
	GLuint VertexArrayID;
	glGenVertexArrays(1, &VertexArrayID);
	glBindVertexArray(VertexArrayID);
  • Ви використовуєте стандартну збірку GLEW, що містить баг. Ви не можете використовувати профіль Core OpenGL через цей баг. Або використовуйте glewExperimental=true перед glewInit() або запросіть профіль сумісності у GLFW:
    glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);

Моя програма падає, коли я намагаюсь завантажити якийсь файл

Встановіть робочий каталог правильно, дивіться перший туторіал.

Створіть файл test.txt і спробуйте наступний код:

if ( fopen("test.txt", "r" ) == NULL ){
    printf("Скоріше за все моя програма запускається з невірним робочим каталогом");
}

Використовуйте дебагер (налагоджувач) !!!! Я серьйозно! Не робіть налагодження Вашої програми за допомогою printf(), використовуйте гарну IDE. http://www.dotnetperls.com/debugging в основному для C#, але працює і для с++. XCode та QtCreator можуть дещо відрізнятись, але принципи залишаються ті самі.

Щось інше зламалось

Напишіть нам на електронну пошту