You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

564 lines
21 KiB

1 year ago
  1. #include <stdio.h> // printf, snprintf
  2. #include <stdint.h> // uint16_t
  3. #include <stdlib.h> // srand, rand
  4. #include <time.h> // time
  5. #include <math.h> // cos, sin
  6. #include "glad.h"
  7. #include "glfw3.h"
  8. /*
  9. you can compile this (windows only, debug version, assuming you have cl.exe available) using:
  10. cl.exe glad.c main.cpp /nologo /W3 /MTd /Od /Zi /RTC1 /fsanitize=address /link /out:game.exe /incremental:no glfw3.lib
  11. the .exe will be around 1.5mb in this case.
  12. or, in release version with a smaller .exe size and better perf (181kb with clang-cl, dang just barely missed 128kb total size!), no debugging symbols etc.
  13. clang-cl.exe glad.c main.cpp /nologo /W3 /O2 /GS /clang:-fno-asynchronous-unwind-tables /link /out:game.exe /fixed /incremental:no /opt:icf /opt:ref libvcruntime.lib glfw3.lib
  14. OR
  15. cl.exe glad.c main.cpp /nologo /W3 /O2 /GS /link /out:game.exe /fixed /incremental:no /opt:icf /opt:ref libvcruntime.lib glfw3.lib
  16. the first 10-20 frames in debug mode are prone to run below 60fps on my machine, which I unfortunately wasn't able to fix in 16 hours
  17. - my computer is fairly weak though, it's about 10 years old and has no external GPU. You may fare better :). Subsequent frames perfome
  18. many times better.
  19. - [Y] - snake-like 0 player game
  20. - [Y] - need to support on the order of 8k+ snakes concurrently (we're going to choose a max of 8192)
  21. - [Y] - if the head of a snake touches the tail of another, the snake who's head touched the tail will merge up with the 'eaten' snake.
  22. - [Y] - all snakes start with one segment
  23. - [Y] - when there is only one snake left, the game must restart
  24. - [Y?] - game data under 128kb - achieved if this means game state, not quite if it means .exe size (we're about ~1mb .exe size after trying to minimize size)
  25. - [Y] - 60fps minimum, or locked
  26. - [Y] - static memory allocation, avoid heap
  27. - [Y] - minimal or zero usage of libraries (except glfw and opengl)
  28. */
  29. static GLFWwindow *Window;
  30. inline bool checkError__(int line) {
  31. GLenum errorCode;
  32. while ((errorCode = glGetError()) != GL_NO_ERROR) {
  33. switch (errorCode) {
  34. case GL_INVALID_ENUM:
  35. printf("%d, INVALID_ENUM", line);
  36. break;
  37. case GL_INVALID_VALUE:
  38. printf("%d, INVALID_VALUE", line);
  39. break;
  40. case GL_INVALID_OPERATION:
  41. printf("%d, INVALID_OPERATION", line);
  42. break;
  43. case GL_OUT_OF_MEMORY:
  44. printf("%d, OUT_OF_MEMORY", line);
  45. break;
  46. case GL_INVALID_FRAMEBUFFER_OPERATION:
  47. printf("%d, INVALID_FRAMEBUFFER_OPERATION", line);
  48. break;
  49. }
  50. return true;
  51. }
  52. return false;
  53. }
  54. #define checkError() checkError__(__LINE__)
  55. static const float PI = 3.14159f;
  56. //--------------------------------------------------------------------------------
  57. // game-data
  58. // between QUADRANTS and pointBuffer, the only real data structures in use,
  59. // you have 116.736kb of game state, assuming 8192 snakes.
  60. int windowWidth;
  61. int windowHeight;
  62. struct Snake {
  63. // coordinates are normalized unsigned integers.
  64. // |numSegments| is just a count, where 0 indicates a dead snake.
  65. uint16_t numSegments, headX, headY, theta;
  66. };
  67. static constexpr float SNAKE_SEGMENT_RADIUS = 8; // glPointSize
  68. static constexpr unsigned int NUM_SNAKES = 8192; // must be greater than 1
  69. struct Rectangle {
  70. uint16_t left, bottom, right, top;
  71. };
  72. static constexpr uint32_t NUM_QUADRANTS = 64;
  73. static constexpr unsigned int SNAKES_PER_QUADRANT = NUM_SNAKES/NUM_QUADRANTS;
  74. struct Quadrant {
  75. Snake snakes[SNAKES_PER_QUADRANT];
  76. Rectangle rect;
  77. };
  78. static Quadrant QUADRANTS[NUM_QUADRANTS];
  79. struct Point {
  80. uint16_t x, y;
  81. uint16_t weight;
  82. };
  83. static Point pointBuffer[NUM_SNAKES]; // each snake starts as one segment which is represented as a point, so you can have at most NUM_SNAKES points.
  84. uint32_t numDead = 0;
  85. uint32_t longest = 0;
  86. uint32_t numPoints = 0;
  87. double msPerFrame = 0.0;
  88. double deltaTime = 0.0;
  89. double lastFrameCountTime = 0.0;
  90. double lastFrameStartTime = 0.0;
  91. bool doFancyRestart = false;
  92. long long int numFramesInLastSecond = 0;
  93. long long int frameCounter = 0;
  94. //--------------------------------------------------------------------------------
  95. static inline uint16_t normalizeUint(uint16_t input, uint32_t oldMax) {
  96. return input * USHRT_MAX / oldMax;
  97. }
  98. static inline float invlerp(float a, float b, float v) {
  99. return (v - a) / (b - a);
  100. }
  101. static inline float lerp(float a, float b, float t) {
  102. return a + t * (b - a);
  103. }
  104. static inline float snakeThetaToRadians(uint16_t a) {
  105. return ((float)a/(float)USHRT_MAX)*2.f*PI;
  106. }
  107. static inline uint16_t radiansToSnakeTheta(float r) {
  108. return (uint16_t) ((r + (0.f*PI)) / (2.f*PI) * (float)USHRT_MAX);
  109. }
  110. static inline void splitRectangleIntoQuadrants(Rectangle input, Rectangle quadrants[4]) {
  111. auto centerX = (input.left + input.right) / 2;
  112. auto centerY = (input.top + input.bottom) / 2;
  113. // top-left quadrant, proceeding clockwise
  114. quadrants[0].left = input.left;
  115. quadrants[0].right = centerX;
  116. quadrants[0].top = input.top;
  117. quadrants[0].bottom = centerY;
  118. quadrants[1].left = centerX;
  119. quadrants[1].right = input.right;
  120. quadrants[1].top = input.top;
  121. quadrants[1].bottom = centerY;
  122. quadrants[2].left = centerX;
  123. quadrants[2].right = input.right;
  124. quadrants[2].top = centerY;
  125. quadrants[2].bottom = input.bottom;
  126. quadrants[3].left = input.left;
  127. quadrants[3].right = centerX;
  128. quadrants[3].top = centerY;
  129. quadrants[3].bottom = input.bottom;
  130. }
  131. static inline uint16_t randInRange(unsigned int min, unsigned int max) {
  132. const unsigned int range = max - min + 1;
  133. return (uint16_t) (min + (unsigned int)((double)rand() / (RAND_MAX + 1.0) * range));
  134. }
  135. static inline void calculateSegment(Snake* snake, uint16_t segmentNumber, Point* outPoint) {
  136. const float weight = (float)segmentNumber / (float)snake->numSegments;
  137. const float radians = snakeThetaToRadians(snake->theta);
  138. const float dx = cos(radians);
  139. const float dy = sin(radians);
  140. outPoint->x = snake->headX + dx * segmentNumber * SNAKE_SEGMENT_RADIUS;
  141. outPoint->y = snake->headY + dy * segmentNumber * SNAKE_SEGMENT_RADIUS;
  142. outPoint->weight = (uint16_t) (weight * USHRT_MAX);
  143. }
  144. static inline void initializeQuadrants(int windowWidth, int windowHeight) {
  145. Rectangle windowQuad = { 0, 0, normalizeUint(windowWidth, windowWidth), normalizeUint(windowHeight, windowHeight) };
  146. Rectangle topLevelQuads[4];
  147. splitRectangleIntoQuadrants(windowQuad, topLevelQuads);
  148. // there's probably a better way to iteratively construct a quadtree...
  149. for (int i = 0; i < 4; i++) {
  150. Rectangle iquads[4];
  151. splitRectangleIntoQuadrants(topLevelQuads[i], iquads);
  152. for (int j = 0; j < 4; j++) {
  153. Rectangle jquads[4];
  154. splitRectangleIntoQuadrants(iquads[j], jquads);
  155. for (int k = 0; k < 4; k++) {
  156. QUADRANTS[i*16+j*4+k].rect = jquads[k];
  157. }
  158. }
  159. }
  160. for (int i = 0; i < NUM_QUADRANTS; i++) {
  161. Quadrant* q= &QUADRANTS[i];
  162. for (int j = 0; j < SNAKES_PER_QUADRANT; j++) {
  163. Snake* snake = &q->snakes[j];
  164. snake->numSegments = 1;
  165. snake->headX = randInRange(q->rect.left, q->rect.right);
  166. snake->headY = randInRange(q->rect.bottom, q->rect.top);
  167. //printf("Snake #%d, quadrant %d - head (x/y): %u/%u, numSegments: %u\n", j+1, i, snake->headX, snake->headY, snake->numSegments);
  168. }
  169. }
  170. }
  171. static inline unsigned int computeQuadrant(int x, int y, int left, int top, int bottom, int right) {
  172. if (x < left || y < bottom || x > right || y > top) {
  173. printf("outside rect!\n");
  174. return -1;
  175. } else if (x < right/2 && y < top/2) {
  176. return 0;
  177. } else if (x >= right/2 && y < top/2) {
  178. return 1;
  179. } else if (x < right/2 && y >= top/2) {
  180. return 2;
  181. } else {
  182. return 3;
  183. }
  184. }
  185. static inline bool findNearestSnakeInQuad(
  186. Quadrant* quad,
  187. Snake* snake,
  188. int searchingSnakeIndex,
  189. float& nearestD,
  190. uint16_t& nearestX,
  191. uint16_t& nearestY,
  192. bool* outHadLiveSnake
  193. ) {
  194. for (int j = 0; j < SNAKES_PER_QUADRANT; j++) {
  195. if (searchingSnakeIndex == j) continue;
  196. Snake* other = &quad->snakes[j];
  197. if (other->numSegments == 0) continue; // dead snake, can't bite it
  198. // if we have at least one snake that has a segment, that means this quad has at least one other live snake.
  199. if (outHadLiveSnake) *outHadLiveSnake = true;
  200. // find tail coordinates for this snake
  201. Point tail;
  202. calculateSegment(other, other->numSegments-1, &tail);
  203. // compute distance, check if it's closer than previously recorded
  204. float dx = ((float)snake->headX - (float)tail.x);
  205. float dy = ((float)snake->headY - (float)tail.y);
  206. float distance = sqrt(dx*dx + dy*dy);
  207. if (distance < nearestD) {
  208. nearestD = distance;
  209. nearestX = tail.x;
  210. nearestY = tail.y;
  211. if (distance < SNAKE_SEGMENT_RADIUS*3) {
  212. // if we're close enough, we can call it a bite and early-out.
  213. snake->numSegments += other->numSegments;
  214. other->numSegments = 0;
  215. return 1;
  216. }
  217. }
  218. }
  219. return 0;
  220. }
  221. static inline bool updateQuadrant(unsigned int quadIndex) {
  222. Quadrant* quad = &QUADRANTS[quadIndex];
  223. for (int i = 0; i < SNAKES_PER_QUADRANT; i++) {
  224. // update each snake in the quad. there are only really a few things to do:
  225. // - find nearest neighbour to snake
  226. // - check if bite
  227. // - move and rotate snake
  228. bool foundAtLeastOneOtherLiveSnakeInQuad = false;
  229. Snake* snake = &quad->snakes[i];
  230. uint16_t numSegments = snake->numSegments;
  231. if (numSegments == 0) continue;
  232. if (numSegments > longest) {
  233. longest = numSegments; // just for fun.
  234. }
  235. // we now linear search for nearest tail to bite, early-outing on dead snakes,
  236. // and very-close snakes (we technically do not always garuntee finding the 'nearest')
  237. const uint16_t headX = snake->headX;
  238. const uint16_t headY = snake->headY;
  239. float nearestD = 999999.f; // arbitrary large-ish number
  240. uint16_t nearestX = 0;
  241. uint16_t nearestY = 0;
  242. if (findNearestSnakeInQuad(quad, snake, i, nearestD, nearestX, nearestY, &foundAtLeastOneOtherLiveSnakeInQuad)) {
  243. numDead++;
  244. goto nextSnake; // we killed another snake, we can move on to the next snake.
  245. };
  246. if (!foundAtLeastOneOtherLiveSnakeInQuad) {
  247. // we found 0 snakes in this quad (besides ourselves)
  248. // this circumstance can only arise when there are a small number of snakes left
  249. // , less than 2k at least, so we can just lazily iterate the rest of the quadrants.
  250. for (int j = 0; j < NUM_QUADRANTS; j++) {
  251. if (quadIndex == j) continue;
  252. if (findNearestSnakeInQuad(&QUADRANTS[j], snake, -1, nearestD, nearestX, nearestY, &foundAtLeastOneOtherLiveSnakeInQuad)) {
  253. numDead++;
  254. goto nextSnake; // we killed another snake, we can move on to the next snake, after appending our draw data
  255. }
  256. }
  257. }
  258. if (!foundAtLeastOneOtherLiveSnakeInQuad) {
  259. // we must be the only snake left!
  260. return true;
  261. }
  262. // do the movement and rotation for this snake.
  263. const float angle = atan2f((float)headY - (float)nearestY, (float)headX - (float)nearestX);
  264. // it probably makes more sense for the smaller guys to be faster, but the simulation gets quite slow towards the end of it, so I do the opposite.
  265. const float dx = -cos(angle) * 500.f*log(0.25f*snake->numSegments + 1) * deltaTime;
  266. const float dy = -sin(angle) * 500.f*log(0.25f*snake->numSegments + 1) * deltaTime;
  267. snake->headX += dx;
  268. snake->headY += dy;
  269. #if 1
  270. // move the snake's angle towards angle |angle|, with some fixed radial speed, smoothly
  271. const float snakeAngle = snakeThetaToRadians(snake->theta);
  272. const float angleDelta = angle - snakeAngle;
  273. //snake->theta = radiansToSnakeTheta(snakeAngle + ((2.f*PI*angleDelta)*0.0015f/(2.f*PI));
  274. //snake->theta = radiansToSnakeTheta(fmin(snakeAngle + (2.f*PI*angleDelta)*(deltaTime/snake->numSegments)/(2.f*PI), angle));
  275. snake->theta = radiansToSnakeTheta(snakeAngle + (angleDelta+2.f*PI)*deltaTime*100.f/snake->numSegments);
  276. #else
  277. snake->theta = radiansToSnakeTheta(angle);
  278. #endif
  279. // at this point, we know everything we need to know about this snake to populate the draw data for it.
  280. // we have the head position, an angle, and a count of the number of segments, so we use this to generate
  281. // points from head --> tail.
  282. for (uint16_t p = 0; p < snake->numSegments; p++) {
  283. Point* point = &pointBuffer[numPoints++];
  284. calculateSegment(snake, p, point);
  285. }
  286. nextSnake: continue;
  287. }
  288. return false;
  289. }
  290. int main(void) {
  291. glfwInit();
  292. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  293. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
  294. glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  295. glfwWindowHint(GLFW_VISIBLE, false); // hide window initially - while we set up stuff. show after.
  296. glfwWindowHint(GLFW_RESIZABLE, false); // don't allow the user to re-size the window
  297. glfwWindowHint(GLFW_CENTER_CURSOR, true); // center the cursor in the window when opening a fullscreen window
  298. glfwWindowHint(GLFW_FOCUS_ON_SHOW, true); // focus the window when glfwShowWindow is called
  299. #ifdef __APPLE__
  300. // removes all deprecated opengl functionality from older opengl versions, required on MacOSX
  301. glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  302. #endif
  303. GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();
  304. const GLFWvidmode* primaryMonitorMode = glfwGetVideoMode(primaryMonitor);
  305. float initialWidth = 1138.0f;
  306. float initialHeight = 640.0f;
  307. // scale up the dimensions of the initial size of the window until it exceeds the size of the screen, then go with the one just before that
  308. while (1) {
  309. if ((initialHeight * 1.2f > primaryMonitorMode->height) || (initialWidth * 1.2f) > primaryMonitorMode->width) {
  310. break;
  311. }
  312. initialHeight *= 1.2f;
  313. initialWidth *= 1.2f;
  314. }
  315. windowWidth = (int) initialWidth;
  316. windowHeight = (int) initialHeight;
  317. Window = glfwCreateWindow(windowWidth, windowHeight, "flOw MMo", nullptr, nullptr);
  318. if (Window == nullptr) {
  319. printf("%s", "Failed to initialize GLFWwindow\n");
  320. return 1;
  321. }
  322. glfwMakeContextCurrent(Window);
  323. //--------------------------------------------------------------------------------
  324. // initialize glad, resolve OpenGL function pointers
  325. if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
  326. printf("%s", "Failed to initialize GLAD\n");
  327. return 1;
  328. }
  329. //--------------------------------------------------------------------------------
  330. // compile and link our shader.
  331. // my apologies if this fails compilation on your machine.
  332. // My main computer's OpenGL driver has been known to allow compilations where others fail it.
  333. int success;
  334. char infoLog[512];
  335. // vertex...
  336. const char* vertexShaderCode = R"(
  337. #version 330 core
  338. layout (location = 0) in vec2 aPos;
  339. layout (location = 1) in float aWeight;
  340. flat out float vWeight;
  341. void main() {
  342. vWeight = aWeight;
  343. gl_Position = vec4(aPos*2.0-1.0, 0.0, 1.0);
  344. gl_PointSize = 10 + (1.0f - aWeight)*10;
  345. }
  346. )";
  347. unsigned int vertexShaderId = glCreateShader(GL_VERTEX_SHADER);
  348. glShaderSource(vertexShaderId, 1, &vertexShaderCode, nullptr);
  349. glCompileShader(vertexShaderId);
  350. glGetShaderiv(vertexShaderId, GL_COMPILE_STATUS, &success);
  351. if (!success) {
  352. glGetShaderInfoLog(vertexShaderId, 512, nullptr, infoLog);
  353. printf("Failure compiling vertex shader!\n%s\nSource: %s\n", infoLog, vertexShaderCode);
  354. return 1;
  355. }
  356. // fragment...
  357. const char* fragmentShaderCode = R"(
  358. #version 330 core
  359. flat in float vWeight;
  360. out vec4 FragColor;
  361. void main() {
  362. FragColor = vec4(vWeight + 0.5, 0.5f * vWeight, 0.2f / (vWeight+0.01), 1.0f);
  363. }
  364. )";
  365. unsigned int fragmentShaderId = glCreateShader(GL_FRAGMENT_SHADER);
  366. glShaderSource(fragmentShaderId, 1, &fragmentShaderCode, nullptr);
  367. glCompileShader(fragmentShaderId);
  368. glGetShaderiv(fragmentShaderId, GL_COMPILE_STATUS, &success);
  369. if (!success) {
  370. glGetShaderInfoLog(fragmentShaderId, 512, nullptr, infoLog);
  371. printf("Failure compiling fragment shader!\n%s\nSource: %s\n", infoLog, fragmentShaderCode);
  372. return 1;
  373. }
  374. unsigned int shaderId = glCreateProgram();
  375. glAttachShader(shaderId, vertexShaderId);
  376. glAttachShader(shaderId, fragmentShaderId);
  377. glLinkProgram(shaderId);
  378. glGetProgramiv(shaderId, GL_LINK_STATUS, &success);
  379. if (!success) {
  380. glGetProgramInfoLog(shaderId, 512, nullptr, infoLog);
  381. printf("Failure linking shader!\n%s\n", infoLog);
  382. return 1;
  383. }
  384. checkError();
  385. // use it, we only have one shader
  386. glUseProgram(shaderId);
  387. checkError();
  388. //--------------------------------------------------------------------------------
  389. srand(time(nullptr));
  390. for (int i = 0; i < rand(); i++) rand(); // discard first few calls, randomly. 'rand()' has poor entropy
  391. // initialize the game state. set up all the initial snake positions, etc.
  392. initializeQuadrants(windowWidth, windowHeight);
  393. //--------------------------------------------------------------------------------
  394. // setup the VAO/VBO
  395. unsigned int VAO, VBO;
  396. glGenVertexArrays(1, &VAO);
  397. glBindVertexArray(VAO);
  398. glGenBuffers(1, &VBO);
  399. glBindBuffer(GL_ARRAY_BUFFER, VBO);
  400. glEnableVertexAttribArray(0);
  401. glVertexAttribPointer(0, 2, GL_UNSIGNED_SHORT, GL_TRUE, sizeof(Point), (void*)offsetof(Point, x));
  402. glEnableVertexAttribArray(1);
  403. glVertexAttribPointer(1, 1, GL_UNSIGNED_SHORT, GL_TRUE, sizeof(Point), (void*)offsetof(Point, weight));
  404. //--------------------------------------------------------------------------------
  405. // set global opengl state
  406. glClearColor(0.25f, 0.52f, 0.54f, 1.0f);
  407. glEnable(GL_PROGRAM_POINT_SIZE);
  408. glEnable(GL_DEPTH_TEST);
  409. glDepthFunc(GL_LESS);
  410. glEnable(GL_CULL_FACE);
  411. glfwShowWindow(Window); // make window visible before entering main loop
  412. glfwRequestWindowAttention(Window);
  413. while (!glfwWindowShouldClose(Window)) {
  414. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  415. // calculate timing metrics, delta time, etc.
  416. double currentTime = glfwGetTime();
  417. frameCounter++;
  418. if (currentTime - lastFrameCountTime >= 1.0) {
  419. msPerFrame = 1000.0/frameCounter;
  420. numFramesInLastSecond = frameCounter;
  421. frameCounter = 0;
  422. lastFrameCountTime += 1.0;
  423. static char windowTitle[256];
  424. snprintf(windowTitle, 256, "flOw MMo - %.2f ms per frame, %lld fps, delta %.2f, num snakes: %u, num alive: %u, dead: %u, longest boi: %u", msPerFrame, numFramesInLastSecond, deltaTime, NUM_SNAKES, NUM_SNAKES-numDead, numDead, longest);
  425. glfwSetWindowTitle(Window, windowTitle); // <-- this call is very slow :(
  426. }
  427. deltaTime = currentTime - lastFrameStartTime;
  428. lastFrameStartTime = currentTime;
  429. // set ourselves to close if you press escape
  430. if (glfwGetKey(Window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
  431. glfwSetWindowShouldClose(Window, true);
  432. continue;
  433. }
  434. static double lastUpdateTime = 0.0f;
  435. if ((currentTime - lastUpdateTime) < 0.0167f) {
  436. continue; // we already updated in the last 16ms, skip to next frame
  437. }
  438. if (doFancyRestart) {
  439. initializeQuadrants(windowWidth, windowHeight);
  440. doFancyRestart = false;
  441. } else {
  442. // update game state:
  443. bool gameOver = false;
  444. for (int q = 0; q < NUM_QUADRANTS; q++) {
  445. gameOver = updateQuadrant(q);
  446. if (gameOver) break;
  447. }
  448. if (gameOver) {
  449. printf("game over!\n");
  450. doFancyRestart = true;
  451. continue;
  452. }
  453. }
  454. // upload draw data and render
  455. glBufferData(GL_ARRAY_BUFFER, sizeof(Point)*NUM_SNAKES, nullptr, GL_DYNAMIC_DRAW);
  456. glBufferData(GL_ARRAY_BUFFER, sizeof(Point)*NUM_SNAKES, pointBuffer, GL_DYNAMIC_DRAW);
  457. glDrawArrays(GL_POINTS, 0, numPoints);
  458. numPoints = 0;
  459. checkError();
  460. glfwSwapBuffers(Window);
  461. glfwPollEvents(); // performs better on windows at the end of frame vs. start
  462. }
  463. glDeleteShader(vertexShaderId);
  464. glDeleteShader(fragmentShaderId);
  465. glDeleteProgram(shaderId);
  466. glfwTerminate();
  467. return 0;
  468. }