void NormalWrapper::setup()
{
    shaderProgram = loadShaderProgram("assets/shaders/normal.vert", "assets/shaders/normal.frag", { "in_pos", "in_color" });

    float posColorVertices[]{
        -0.25f, -0.25f, 0.f, 0.f, 0.f, 1.f, 1.f,
        0.25f, -0.25f, 0.f, 0.f, 0.f, 1.f, 1.f,
        -0.25f, 0.25f, 0.f, 0.f, 0.f, 1.f, 1.f,
        0.25f, 0.25f, 0.f, 0.f, 0.f, 1.f, 1.f
    };

    constexpr size_t vertexSize = sizeof(float) * 7;
    constexpr uint8_t vertexCount = 4;
    mesh.renderMode = GL_TRIANGLE_STRIP;

    glGenVertexArrays(1, &mesh.vao);
    glBindVertexArray(mesh.vao);

    glGenBuffers(1, &mesh.vbo);
    glBindBuffer(GL_ARRAY_BUFFER, mesh.vbo);
    glBufferData(GL_ARRAY_BUFFER, vertexCount * vertexSize, &posColorVertices, GL_STATIC_DRAW);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertexSize, 0);

    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, vertexSize, reinterpret_cast<const void*>(12));

    mesh.vertexCount = vertexCount;

    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    GL_ERROR_CHECK();
}
예제 #2
0
	ShaderProgram::ShaderProgram( const std::string& shaderName, Shader* vertex, Shader* frag )
		:	m_shaderProgramName( shaderName )
		,	m_vertex( vertex )
		,	m_fragment( frag )
	{
		loadShaderProgram();
	}
예제 #3
0
파일: part4.cpp 프로젝트: Knugn/CG-A1
void init(Context &ctx)
{
    ctx.program = loadShaderProgram(shaderDir() + "triangle.vert",
                                    shaderDir() + "triangle.frag");

    createTriangle(ctx);
}
예제 #4
0
void init(Context &ctx)
{
    ctx.program = loadShaderProgram(shaderDir() + "mesh.vert", shaderDir() + "mesh.frag");

	ctx.skyboxProgram = loadShaderProgram(shaderDir() + "skybox.vert", shaderDir() + "skybox.frag");

    loadMesh((modelDir() + "gargo.obj"), &ctx.mesh);
    createMeshVAO(ctx, ctx.mesh, &ctx.meshVAO);

	createSkyboxVAO(ctx, &ctx.skyboxVAO);

    // Load cubemap texture(s)
    // ...
	const std::string cubemap_path = cubemapDir() + "/Forrest/";
	ctx.cubemap = loadCubemap(cubemap_path);
	const std::string levels[] = { "2048", "512", "128", "32", "8", "2", "0.5", "0.125" };
	for (int i=0; i < NUM_CUBEMAP_LEVELS; i++) {
		ctx.cubemap_prefiltered_levels[i] = loadCubemap(cubemap_path + "prefiltered/" + levels[i]);
	}
	//ctx.cubemap_prefiltered_mipmap = loadCubemapMipmap(cubemap_path + "prefiltered/");
	ctx.cubemap_index = 0;

	ctx.zoom = 1.0f;
	ctx.lensType = LensType::PERSPECTIVE;

    initializeTrackball(ctx);

	ctx.background_color = glm::vec3(0.2f);

	ctx.ambient_light = glm::vec3(0.04f);

	ctx.light_position = glm::vec3(1.0, 1.0, 1.0);
	ctx.light_color = glm::vec3(1.0, 1.0, 1.0);

	ctx.diffuse_color = glm::vec3(0.1, 1.0, 0.1);
	ctx.specular_color = glm::vec3(0.04);
	ctx.specular_power = 60.0f;

	ctx.ambient_weight = 1.0f;
	ctx.diffuse_weight = 1.0f;
	ctx.specular_weight = 1.0f;

	ctx.color_mode = ColorMode::NORMAL_AS_RGB;
	ctx.use_gamma_correction = 1;
	ctx.use_color_inversion = 0;
}
GLuint ShaderProgramBuilder::buildShaderProgram(const char * pVertexSource, const char * pFragmentSource)
{
    GLuint vertexShader = loadShaderProgram(VERTEX_SHADER, pVertexSource);
    if (!vertexShader)
        return false;
    LOG_INFO("Render=> Pass Build Vertex Shader\n");

    GLuint pixelShader = loadShaderProgram(FRAGMENT_SHADER, pFragmentSource);
    if (!pixelShader)
        return false;
    LOG_INFO("Render=> Pass Build Fragment Shader\n");

    GLuint program = glCreateProgram();
    GL_ERROR_CHECK("ShaderProgramBuilder:Create Program");
    if (program)
        {
            glAttachShader(program, vertexShader);
            glAttachShader(program, pixelShader);
            GL_ERROR_CHECK("ShaderProgramBuilder:Attach Program");

            glLinkProgram(program);
            GL_ERROR_CHECK("ShaderProgramBuilder:Link Program");

            GLint linkStatus = GL_FALSE;
            glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
            if (linkStatus != GL_TRUE)
                {
                    GLint bufLength = 0;
                    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
                    if (bufLength)
                        {
                            char* buf = new char[bufLength];
                            if (buf)
                                {
                                    glGetProgramInfoLog(program, bufLength, NULL, buf);
                                    LOG_ERROR("Could not link program:\n%s\n", buf);
                                    delete buf;
                                }
                        }

                    glDeleteProgram(program);
                    program = 0;
                }
        }
    return program;
}
예제 #6
0
void init(Context &ctx)
{
    ctx.program = loadShaderProgram(shaderDir() + "mesh.vert",
                                    shaderDir() + "mesh.frag");

    loadMesh((modelDir() + "armadillo.obj"), &ctx.mesh);
    createMeshVAO(ctx, ctx.mesh, &ctx.meshVAO);

    initializeTrackball(ctx);
}
예제 #7
0
/******************************************************************************
* Renders a 2d polyline in the viewport.
******************************************************************************/
void ViewportSceneRenderer::render2DPolyline(const Point2* points, int count, const ColorA& color, bool closed)
{
	OVITO_STATIC_ASSERT(sizeof(points[0]) == 2*sizeof(GLfloat));

	// Load OpenGL shader.
	QOpenGLShaderProgram* shader = loadShaderProgram("line", ":/core/glsl/lines/line.vs", ":/core/glsl/lines/line.fs");
	if(!shader->bind())
		throw Exception(tr("Failed to bind OpenGL shader."));

	bool wasDepthTestEnabled = glIsEnabled(GL_DEPTH_TEST);
	glDisable(GL_DEPTH_TEST);

	GLint vc[4];
	glGetIntegerv(GL_VIEWPORT, vc);
	QMatrix4x4 tm;
	tm.ortho(vc[0], vc[0] + vc[2], vc[1] + vc[3], vc[1], -1, 1);
	OVITO_CHECK_OPENGL(shader->setUniformValue("modelview_projection_matrix", tm));

	QOpenGLBuffer vertexBuffer;
	if(glformat().majorVersion() >= 3) {
		if(!vertexBuffer.create())
			throw Exception(tr("Failed to create OpenGL vertex buffer."));
		if(!vertexBuffer.bind())
				throw Exception(tr("Failed to bind OpenGL vertex buffer."));
		vertexBuffer.allocate(points, 2 * sizeof(GLfloat) * count);
		OVITO_CHECK_OPENGL(shader->enableAttributeArray("position"));
		OVITO_CHECK_OPENGL(shader->setAttributeBuffer("position", GL_FLOAT, 0, 2));
		vertexBuffer.release();
	}
	else {
		OVITO_CHECK_OPENGL(glEnableClientState(GL_VERTEX_ARRAY));
		OVITO_CHECK_OPENGL(glVertexPointer(2, GL_FLOAT, 0, points));
	}

	if(glformat().majorVersion() >= 3) {
		OVITO_CHECK_OPENGL(shader->disableAttributeArray("color"));
		OVITO_CHECK_OPENGL(shader->setAttributeValue("color", color.r(), color.g(), color.b(), color.a()));
	}
	else {
		OVITO_CHECK_OPENGL(glColor4(color));
	}

	OVITO_CHECK_OPENGL(glDrawArrays(closed ? GL_LINE_LOOP : GL_LINE_STRIP, 0, count));

	if(glformat().majorVersion() >= 3) {
		shader->disableAttributeArray("position");
	}
	else {
		OVITO_CHECK_OPENGL(glDisableClientState(GL_VERTEX_ARRAY));
	}
	shader->release();
	if(wasDepthTestEnabled) glEnable(GL_DEPTH_TEST);
}
예제 #8
0
	void GLRenderer::init() {
		try {
			string vSrc = io::File::loadAsString(m_resourceFolder + "basic_shader.vert");
			string fSrc = io::File::loadAsString(m_resourceFolder + "basic_shader.frag");
			loadShaderProgram("basic", vSrc, fSrc);

			vSrc = io::File::loadAsString(m_resourceFolder + "basic_lighting_shader.vert");
			fSrc = io::File::loadAsString(m_resourceFolder + "basic_lighting_shader.frag");
			loadShaderProgram("basic_lighting", vSrc, fSrc);
		}
		catch (const io::FileReadException& ex) {
			std::cout << ex.what() << std::endl;
		}
		catch (const renderer::GLShaderCompileErrorException& ex) {
			std::cout << ex.what() << std::endl;
			std::cout << ex.getInfoLog() << std::endl;
		}
		catch (const renderer::GLShaderLinkErrorException& ex) {
			std::cout << ex.what() << std::endl;
			std::cout << ex.getInfoLog() << std::endl;
		}
		m_isInitialized = true;
	}
예제 #9
0
파일: cpuEmu.cpp 프로젝트: kireisun/chip8
void chip8::setupTexture()
{	
	glGenVertexArrays(1, &VAOHandles[0]);
	glBindVertexArray(VAOHandles[0]);

	glGenBuffers(1, &VBOHandles[0]);
	glBindBuffer(GL_ARRAY_BUFFER, VBOHandles[0]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertex) + sizeof(texCoordinates), NULL, GL_STATIC_DRAW);
	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertex), vertex);
	glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertex), sizeof(texCoord), texCoordinates);

	glGenBuffers(1, elemIndHandles);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elemIndHandles[0]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(vertexIndex), vertexIndex, GL_STATIC_DRAW);

	glGenTextures(1, &texBufferHandles[0]);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texBufferHandles[0]);
	glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, SCREEN_WIDTH, SCREEN_HEIGTH);
	glTexSubImage2D(GL_TEXTURE_2D, 0,
		0, 0,
		SCREEN_WIDTH, SCREEN_HEIGTH,
		GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)gfx);

	loadShaderProgram();

	uniformBuffHandles[0] = glGetUniformLocation(pHandle, "tex");
	glUniform1ui(uniformBuffHandles[0], 0);

	glVertexAttribPointer(vertexPos, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(vertexPos);

	glVertexAttribPointer(texCoord, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(vertex));
	glEnableVertexAttribArray(texCoord);

	glBindBuffer(GL_ARRAY_BUFFER, VBOHandles[0]);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elemIndHandles[0]);
	glBindTexture(GL_TEXTURE_2D, texBufferHandles[0]);

	glBindVertexArray(0);

	glClear(GL_COLOR_BUFFER_BIT);

	glActiveTexture(GL_TEXTURE0);
	glBindVertexArray(VAOHandles[0]);

	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
	glutSwapBuffers();
}
void InstanceWrapper::setup()
{
    shaderProgram = loadShaderProgram("assets/shaders/instanced.vert", "assets/shaders/instanced.frag", { "in_sPos", "in_pos" });

    float posVertices[]{
        -0.25f, -0.25f, 0.f,
        0.25f, -0.25f, 0.f,
        -0.25f, 0.25f, 0.f,
        0.25f, 0.25f, 0.f
    };

    constexpr size_t vertexSize = sizeof(float) * 3;
    constexpr uint8_t vertexCount = 4;
    mesh.renderMode = GL_TRIANGLE_STRIP;

    glGenVertexArrays(1, &mesh.vao);
    glBindVertexArray(mesh.vao);

    // Instanced
    constexpr size_t instanceSize = sizeof(float) * 3;
    glGenBuffers(1, &mesh.instanceVBO);
    glBindBuffer(GL_ARRAY_BUFFER, mesh.instanceVBO);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, instanceSize, 0);
    glVertexAttribDivisor(0, 1);

    // Normal
    glGenBuffers(1, &mesh.vbo);
    glBindBuffer(GL_ARRAY_BUFFER, mesh.vbo);
    glBufferData(GL_ARRAY_BUFFER, vertexCount * vertexSize, &posVertices, GL_STATIC_DRAW);

    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, vertexSize, 0);

    mesh.vertexCount = vertexCount;

    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    GL_ERROR_CHECK();
}
예제 #11
0
파일: main.cpp 프로젝트: jhk2/glsandbox
void initGL(unsigned int width, unsigned int height)
{
	glClearColor(0, 0, 0, 0);
	//glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
	glDisable(GL_DEPTH_TEST);
	resizeGL(width, height);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	loadFunctions();
	loadResources();
	loadShaderProgram(SHADER_SRC);
	//glPolygonMode( GL_FRONT_AND_BACK, GL_LINE);
	//glFrontFace(GL_CW);
	//glEnable(GL_CULL_FACE);
	//glCullFace(GL_BACK);
	// set up default camera
	g_cam_pos.x = 0;
	g_cam_pos.y = 0;
	g_cam_pos.z = -10;
	g_cam_rot.x = 0;
	g_cam_rot.y = 0;
}
예제 #12
0
void init(void) 
{

	/*
		GLEW initialization.
		activate GLEW experimental features to have access to the most recent OpenGL, and then call glewInit.
		it is important to know that this must be done only when a OpenGL context already exists, meaning, in this case, glutCreateWindow has already been called.

	*/

	glewExperimental = GL_TRUE;
	GLenum err = glewInit();

	if (err != GLEW_OK) {
		fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
	} else {
		if (GLEW_VERSION_3_3)
		{
			fprintf(stderr, "Driver supports OpenGL 3.3\n");
		}
	}
	fprintf(stdout, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));

	FreeImage_Initialise();

	loadShaderProgram("../shaders/vertex_shader.vs", "../shaders/frag_shader.fs");

	baseTextureId = loadTexture(params.texture);

	object.init();
	Particle::init();
	initGeometry();

	glClearColor(0.0f, 0.0f, 0.0f, 1.0f); //Defines the clear color, i.e., the color used to wipe the display
	glEnable(GL_DEPTH_TEST);
	checkError("init");
}
예제 #13
0
void reloadShaders(Context *ctx)
{
    glDeleteProgram(ctx->program);
    ctx->program = loadShaderProgram(shaderDir() + "mesh.vert", shaderDir() + "mesh.frag");
	ctx->skyboxProgram = loadShaderProgram(shaderDir() + "skybox.vert", shaderDir() + "skybox.frag");
}
예제 #14
0
파일: main.cpp 프로젝트: yuriks/Pong
int main() {
	if (!initWindow(WINDOW_WIDTH, WINDOW_HEIGHT)) {
		std::cerr << "Failed to initialize window.\n";
		return 1;
	}

	int tex_width, tex_height;
	GLuint main_texture = loadTexture(&tex_width, &tex_height, "graphics.png");
	assert(main_texture != 0);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	GLuint shader_program = loadShaderProgram();
	glUseProgram(shader_program);

	GLuint u_view_matrix_location = glGetUniformLocation(shader_program, "u_view_matrix");
	GLfloat view_matrix[9] = {
		2.0f/WINDOW_WIDTH,  0.0f,              -1.0f,
		0.0f,              -2.0/WINDOW_HEIGHT,  1.0f,
		0.0f,               0.0f,               1.0f
	};
	glUniformMatrix3fv(u_view_matrix_location, 1, GL_TRUE, view_matrix);

	GLuint u_texture_location = glGetUniformLocation(shader_program, "u_texture");
	glUniform1i(u_texture_location, 0);

	CHECK_GL_ERROR;

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, main_texture);

	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

	CHECK_GL_ERROR;

	SpriteBuffer sprite_buffer;
	sprite_buffer.tex_width = static_cast<float>(tex_width);
	sprite_buffer.tex_height = static_cast<float>(tex_height);

	GLuint vao_id;
	glGenVertexArrays(1, &vao_id);
	glBindVertexArray(vao_id);

	GLuint vbo_id;
	glGenBuffers(1, &vbo_id);
	glBindBuffer(GL_ARRAY_BUFFER, vbo_id);

	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexData), reinterpret_cast<void*>(offsetof(VertexData, pos_x)));
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_TRUE,  sizeof(VertexData), reinterpret_cast<void*>(offsetof(VertexData, tex_s)));
	glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE,  sizeof(VertexData), reinterpret_cast<void*>(offsetof(VertexData, color)));
	for (int i = 0; i < 3; ++i)
		glEnableVertexAttribArray(i);

	GLuint ibo_id;
	glGenBuffers(1, &ibo_id);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id);

	///////////////////////////
	// Initialize game state //
	///////////////////////////
	GameState game_state;
	RandomGenerator& rng = game_state.rng;
	rng.seed(123);

	{
		Paddle& p = game_state.paddle;
		p.pos_x = WINDOW_WIDTH / 2;
		p.pos_y = WINDOW_HEIGHT - 32;
		p.rotation = 0;
	}

	Sprite paddle_spr;
	paddle_spr.setImg(0, 0, 64, 16);

	Sprite gem_spr;
	gem_spr.setImg(0, 16, 16, 16);

	static const int GEM_SPAWN_INTERVAL = 60*5;
	int gem_spawn_timer = GEM_SPAWN_INTERVAL;

	CHECK_GL_ERROR;

	////////////////////
	// Main game loop //
	////////////////////
	bool running = true;
	while (running) {
		/* Update paddle */
		{
			Paddle& paddle = game_state.paddle;

			fixed24_8 paddle_speed(0);
			fixed8_24 rotation = 0;
			if (glfwGetKey(GLFW_KEY_LEFT)) {
				paddle_speed -= PADDLE_MOVEMENT_SPEED;
				rotation -= PADDLE_ROTATION_RATE;
			}
			if (glfwGetKey(GLFW_KEY_RIGHT)) {
				paddle_speed += PADDLE_MOVEMENT_SPEED;
				rotation += PADDLE_ROTATION_RATE;
			}

			if (rotation == 0) {
				paddle.rotation = stepTowards(paddle.rotation, fixed8_24(0), PADDLE_ROTATION_RETURN_RATE);
			} else {
				paddle.rotation = clamp(-PADDLE_MAX_ROTATION, paddle.rotation + rotation, PADDLE_MAX_ROTATION);
			}
			paddle.pos_x += paddle_speed;
		}

		/* Spawn new gems */
		if (--gem_spawn_timer == 0) {
			gem_spawn_timer = GEM_SPAWN_INTERVAL;

			Gem b;
			b.pos_x = randRange(rng, WINDOW_WIDTH * 1 / 6, WINDOW_WIDTH * 5 / 6);
			b.pos_y = -10;
			b.vel_x = b.vel_y = 0;
			b.score_value = Gem::INITIAL_VALUE;

			game_state.gems.push_back(b);
		}

		/* Update balls */
		for (unsigned int i = 0; i < game_state.gems.size(); ++i) {
			Gem& ball = game_state.gems[i];

			ball.vel_y += fixed16_16(0, 1, 8);

			ball.pos_x += fixed24_8(ball.vel_x);
			ball.pos_y += fixed24_8(ball.vel_y);

			collideBallWithBoundary(ball);
			for (unsigned int j = i + 1; j < game_state.gems.size(); ++j) {
				collideBallWithBall(ball, game_state.gems[j]);
			}
			collideBallWithPaddle(ball, game_state.paddle);
		}

		/* Clean up dead gems */
		remove_if(game_state.gems, [](const Gem& gem) {
			return gem.pos_y > WINDOW_HEIGHT + 128 && gem.vel_y > 0;
		});

		/* Draw scene */
		sprite_buffer.clear();
		
		paddle_spr.setPos(game_state.paddle.pos_x.integer(), game_state.paddle.pos_y.integer());
		sprite_buffer.append(paddle_spr, game_state.paddle.getSpriteMatrix());

		for (const Gem& gem : game_state.gems) {
			gem_spr.setPos(gem.pos_x.integer() - gem_spr.img_w / 2, gem.pos_y.integer() - gem_spr.img_h / 2);
			float r, g, b;
			hsvToRgb(mapScoreToHue(gem.score_value), 1.0f, 1.0f, &r, &g, &b);
			gem_spr.color = makeColor(uint8_t(r*255 + 0.5f), uint8_t(g*255 + 0.5f), uint8_t(b*255 + 0.5f), 255);
			sprite_buffer.append(gem_spr);
		}

		// HUD
		{
			static const int HUD_X_POS = 1;
			static const int HUD_Y_POS = 1;

			Sprite hud_spr;
			hud_spr.setImg(64, 0, 29, 11);
			hud_spr.setPos(HUD_X_POS, HUD_Y_POS);
			sprite_buffer.append(hud_spr);

			hud_spr.setImg(64, 12, 29, 11);
			hud_spr.setPos(HUD_X_POS, HUD_Y_POS + 13);
			sprite_buffer.append(hud_spr);

			std::string score_text = std::to_string(game_state.score);
			FontInfo font('0', 40, 24, 8, 12);
			drawText(HUD_X_POS + 31, HUD_Y_POS, score_text, sprite_buffer, font);
		}

		/* Submit sprites */
		// More superfluous drawcalls to change the GPU into high-performance mode? Sure, why not.
		glClear(GL_COLOR_BUFFER_BIT);
		for (int i = 0; i < 1000; ++i) {
			sprite_buffer.upload();
			sprite_buffer.draw();
		}

		for (const Sprite& spr : debug_sprites) {
			sprite_buffer.append(spr);
		}
		debug_sprites.clear();

		glClear(GL_COLOR_BUFFER_BIT);
		sprite_buffer.upload();
		sprite_buffer.draw();

		glfwSwapBuffers();
		running = running && glfwGetWindowParam(GLFW_OPENED);

		CHECK_GL_ERROR;
	}

	glfwCloseWindow();
	glfwTerminate();
}
예제 #15
0
파일: part4.cpp 프로젝트: Knugn/CG-A1
void reloadShaders(Context *ctx)
{
    glDeleteProgram(ctx->program);
    ctx->program = loadShaderProgram(shaderDir() + "triangle.vert",
                                     shaderDir() + "triangle.frag");
}