text: #include <GL/glew.h>
  #include <glcw/shader.hpp>
  #include <glm/gtc/matrix_inverse.hpp>
  #include <glm/gtc/matrix_transform.hpp>
  #include <glm/gtx/quaternion.hpp>
  #include <utils/logger.hpp>
  
  #include <core/engine.hpp>
  #include <graphics/graphics_system.hpp>
  #include <graphics/shader_manager.hpp>
  #include <graphics/texture_manager.hpp>
  #include <rendering/point_light.hpp>
  #include <rendering/renderer.hpp>
  #include <rendering/screen_quad.hpp>
  #include <rendering/vertex.hpp>
  
  namespace rendering {
  	renderer::renderer(core::engine& engine)
  	: engine_{engine}, mesh_manager_{engine_, *this}, debug_render_{false}, camera_position_{-10.f, 20.f, 0.f} {
  		vertex_layout_.emplace_back("_position", 3, GL_FLOAT, GL_FALSE, sizeof(vertex), 0);
  		vertex_layout_.emplace_back("_texcoord", 2, GL_FLOAT, GL_FALSE, sizeof(vertex), 3 * sizeof(float));
  		vertex_layout_.emplace_back("_normal", 3, GL_FLOAT, GL_FALSE, sizeof(vertex), 5 * sizeof(float));
  		vertex_layout_.emplace_back("_tangent", 3, GL_FLOAT, GL_FALSE, sizeof(vertex), 8 * sizeof(float));
  		vertex_layout_.emplace_back("_bitangent", 3, GL_FLOAT, GL_FALSE, sizeof(vertex), 11 * sizeof(float));
  
  		auto width = static_cast<float>(engine.graphics_system().width());
  		auto height = static_cast<float>(engine.graphics_system().height());
  		projection_ = glm::perspectiveFov(
  			glm::radians(45.f),
  			width,
  			height,
  			0.1f, 1000.f);
  
  		viewport_ = glm::vec4{0.f, 0.f, width, height};
  
  		ortho_projection_ = glm::ortho(0.f, width, height, 0.f);
  
  		g_buffer_.attach(GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, GL_DEPTH_COMPONENT24, width, height, GL_DEPTH_COMPONENT, GL_FLOAT);
  		g_buffer_.attach(GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, GL_RGBA32F, width, height, GL_RGBA, GL_FLOAT);
  		g_buffer_.attach(GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, GL_RGBA32F, width, height, GL_RGBA, GL_FLOAT);
  		g_buffer_.attach(GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, GL_RGBA32F, width, height, GL_RGBA, GL_FLOAT);
  
  		g_buffer_.bind();
  		glClearColor(0.4f, 0.6f, 0.9f, 1.f);
  		GLenum buf[5] = {
  			GL_COLOR_ATTACHMENT0,
  			GL_COLOR_ATTACHMENT1,
  			GL_COLOR_ATTACHMENT2,
  			GL_NONE
  		};
  		glDrawBuffers(5, buf);
  		g_buffer_.bind_default();
  
  		auto vertex_shader = engine_.graphics_system().shader_manager().load("shaders/screen_quad.vert", GL_VERTEX_SHADER);
  		if (!vertex_shader) {
  			throw std::runtime_error{"could not load screen_quad.vert"};
  		}
  		screen_quad_program_.attach_shader(vertex_shader);
  		auto fragment_shader = engine_.graphics_system().shader_manager().load("shaders/screen_quad.frag", GL_FRAGMENT_SHADER);
  		if (!fragment_shader) {
  			throw std::runtime_error{"could not load screen_quad.frag"};
  		}
  		screen_quad_program_.attach_shader(fragment_shader);
  		vertex_layout_.setup_program(screen_quad_program_, "frag_color");
  		screen_quad_program_.link();
  
  		for (auto i = 0; i < 4; ++i) {
  			debug_screen_quads_.emplace_back(std::make_unique<screen_quad>(
  				*this,
  				glm::vec2{(i * width / 4.f) + 0.01f * width, (3 * height / 4) + 0.01f * height},
  				glm::vec2{((i + 1) * width / 4.f) - 0.01f * width, height - 0.01f * height}
  			));
  		}
  
  		vertex_shader = engine_.graphics_system().shader_manager().load("shaders/world.vert", GL_VERTEX_SHADER);
  		if (!vertex_shader) {
  			throw std::runtime_error{"could not load world.vert"};
  		}
  		world_program_.attach_shader(vertex_shader);
  		fragment_shader = engine_.graphics_system().shader_manager().load("shaders/world.frag", GL_FRAGMENT_SHADER);
  		if (!fragment_shader) {
  			throw std::runtime_error{"could not load world.frag"};
  		}
  		world_program_.attach_shader(fragment_shader);
  		vertex_layout_.setup_program(world_program_, "albedo_color");
  		world_program_.bind_frag_data_location("normal_color", 1);
  		world_program_.bind_frag_data_location("specular_color", 2);
  		world_program_.bind_frag_data_location("position_color", 3);
  		world_program_.link();
  
  		suzanne_ = mesh_manager_.load("meshes/dungeon.msh");
  		if (!suzanne_) {
  			throw std::runtime_error{"could not load dungeon.msh"};
  		}
  
  		dummy_texture_ = engine_.graphics_system().texture_manager().load("textures/floor_checkboard_dif.dds");
  		if (!dummy_texture_) {
  			throw std::runtime_error{"could not load floor_checkboard_dif.dds"};
  		}
  
  		dummy_specular_ = engine_.graphics_system().texture_manager().load("textures/floor_checkboard_spec.dds");
  		if (!dummy_specular_) {
  			throw std::runtime_error{"could not load floor_checkboard_spec.dds"};
  		}
  
  		dummy_normal_ = engine_.graphics_system().texture_manager().load("textures/floor_checkboard_nor.dds");
  		if (!dummy_normal_) {
  			throw std::runtime_error{"could not load floor_checkboard_nor.dds"};
  		}
  
  		vertex_shader = engine_.graphics_system().shader_manager().load("shaders/deferred.vert", GL_VERTEX_SHADER);
  		if (!vertex_shader) {
  			throw std::runtime_error{"could not load deferred.vert"};
  		}
  		composition_program_.attach_shader(vertex_shader);
  		fragment_shader = engine_.graphics_system().shader_manager().load("shaders/deferred.frag", GL_FRAGMENT_SHADER);
  		if (!fragment_shader) {
  			throw std::runtime_error{"could not load deferred.frag"};
  		}
  		composition_program_.attach_shader(fragment_shader);
  		vertex_layout_.setup_program(composition_program_, "frag_color");
  		composition_program_.link();
  
  		screen_quad_ = std::make_unique<screen_quad>(*this, glm::vec2(0.f), glm::vec2(width, height));
  		
  		for (auto i = 0; i < 10; ++i) {
  			point_light p = {
  				glm::vec3{2.56f + 5.12f * i, 2.f, 0.f},
  				glm::vec3{1.f, 1.f, 1.f},
  				2.f
  			};
  			point_lights_.emplace_back(p);
  		}
  
  		point_light p = {
  				glm::vec3{11.52f, 2.f, 3.84f},
  				glm::vec3{1.f, 1.f, 0.5f},
  				4.f
  			};
  		point_lights_.emplace_back(p);
  
  		p = {
  			glm::vec3{11.52f, 2.f, -3.84f},
  			glm::vec3{1.f, 1.f, 0.5f},
  			4.f
  		};
  		point_lights_.emplace_back(p);
  
  		p = {
  			glm::vec3{21.76f, 2.f, 3.84f},
  			glm::vec3{1.f, 0.5f, 0.5f},
  			4.f
  		};
  		point_lights_.emplace_back(p);
  
  		p = {
  			glm::vec3{21.76f, 2.f, -3.84f},
  			glm::vec3{1.f, 0.5f, 0.5f},
  			4.f
  		};
  		point_lights_.emplace_back(p);
  
  		p = {
  			glm::vec3{11.52f, 2.f, -12.8f},
  			glm::vec3{0.4f, 0.5f, 0.9f},
  			10.f
  		};
  		point_lights_.emplace_back(p);
  
  		p = {
  			glm::vec3{21.76f, 1.f, -12.8f},
  			glm::vec3{0.4f, 0.5f, 0.9f},
  			10.f
  		};
  		point_lights_.emplace_back(p);
  
  		for (auto i = 0; i < 9; ++i) {
  			p = {
  				glm::vec3{29.44f, 2.f, -1.28f + i * -5.12f},
  				glm::vec3{0.f, 1.0f, 0.f},
  				2.f
  			};
  			point_lights_.emplace_back(p);
  		}
  
  		p = {
  			glm::vec3{53.76f, 2.f, 2.56f},
  			glm::vec3{0.f, 1.f, 1.f},
  			8.f
  		};
  		point_lights_.emplace_back(p);
  		moving_light_green_ = point_lights_.size() - 1;
  
  		p = {
  			glm::vec3{58.88f, 2.f, -15.36f},
  			glm::vec3{1.f, 1.f, 0.f},
  			8.f
  		};
  		point_lights_.emplace_back(p);
  		moving_light_yellow_ = point_lights_.size() - 1;
  
  		p = {
  			glm::vec3{43.52f, 2.f, -15.36f},
  			glm::vec3{1.0f, 0.5f, 0.f},
  			8.f
  		};
  		point_lights_.emplace_back(p);
  	}
  
  	renderer::~renderer() {}
  
  	void renderer::render(float delta_time) {
  		view_ = glm::lookAt(
  			camera_position_,
  			glm::vec3{camera_position_.x, 0.f, camera_position_.z} + glm::vec3{20.f, 0.f, 0.f},
  			glm::vec3{0.f, 1.f, 0.f}
  		);
  
  		static float sign = -1.f;
  		float speed = sign * 5.f * delta_time;
  		auto& light_pos = point_lights_.at(moving_light_green_).position;
  		light_pos += glm::vec3{0.f, 0.f, speed};
  		if (light_pos.z <= -15.36f) {
  			light_pos.z = -15.36f;
  			sign *= -1.f;
  		} else if (light_pos.z >= 2.56f) {
  			light_pos.z = 2.56f;
  			sign *= -1.f;
  		}
  
  		static float sign_yellow = 1.f;
  		speed = sign_yellow * 5.f * delta_time;
  		auto& light_pos_yellow = point_lights_.at(moving_light_yellow_).position;
  		light_pos_yellow += glm::vec3{0.f, 0.f, speed};
  		if (light_pos_yellow.z <= -15.36f) {
  			light_pos_yellow.z = -15.36f;
  			sign_yellow *= -1.f;
  		} else if (light_pos_yellow.z >= 2.56f) {
  			light_pos_yellow.z = 2.56f;
  			sign_yellow *= -1.f;
  		}
  
  		glEnable(GL_DEPTH_TEST);
  		g_buffer_.bind();
  		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  		world_program_.use();
  		world_program_.uniform("projection", false, projection_);
  		world_program_.uniform("view", false, view_);
  		auto model = glm::mat4{1.f};
  		world_program_.uniform("model", false, model);
  		world_program_.uniform("normal", false, glm::mat3{glm::inverseTranspose(model)});
  		world_program_.uniform("color_texture", 0);
  		world_program_.uniform("normal_texture", 1);
  		world_program_.uniform("specular_texture", 2);
  		suzanne_->draw();
  
  		glDisable(GL_DEPTH_TEST);
  		g_buffer_.bind_default();
  
  		static GLenum attachments[4] = {
  			GL_COLOR_ATTACHMENT0,
  			GL_COLOR_ATTACHMENT1,
  			GL_COLOR_ATTACHMENT2,
  			GL_DEPTH_ATTACHMENT
  		};
  
  		std::vector<glm::vec3> point_light_positions;
  		std::vector<glm::vec3> point_light_colors;
  		std::vector<float> point_light_radii;
  		for (auto& p : point_lights_) {
  			point_light_positions.emplace_back(p.position);
  			point_light_colors.emplace_back(p.color);
  			point_light_radii.emplace_back(p.radius);
  		}
  
  		composition_program_.use();
  		composition_program_.uniform("projection", false, ortho_projection_);
  		composition_program_.uniform("albedo_texture", 0);
  		composition_program_.uniform("normal_texture", 1);
  		composition_program_.uniform("specular_texture", 2);
  		composition_program_.uniform("depth_texture", 3);
  		composition_program_.uniform("inv_view_projection", false, glm::inverse(projection_ * view_));
  		composition_program_.uniform("view_vector", camera_position_);
  		composition_program_.uniform("point_light_num", static_cast<int32_t>(point_lights_.size()));
  		composition_program_.uniform("point_light_positions", point_lights_.size(), point_light_positions);
  		composition_program_.uniform("point_light_colors", point_lights_.size(), point_light_colors);
  		composition_program_.uniform("point_light_radii", point_lights_.size(), point_light_radii);
  
  		for (auto i = 0; i < 4; ++i) {
  			g_buffer_.attachment(attachments[i]).bind(i);
  		}
  		screen_quad_->draw();
  
  		if (debug_render_) {
  			screen_quad_program_.use();
  			screen_quad_program_.uniform("projection", false, ortho_projection_);
  			screen_quad_program_.uniform("near_plane", 0.1f);
  			screen_quad_program_.uniform("far_plane", 1000.f);
  			screen_quad_program_.uniform("color_texture", 0);
  
  			for (auto i = 0; i < debug_screen_quads_.size(); ++i) {
  				g_buffer_.attachment(attachments[i]).bind(0);
  
  				screen_quad_program_.uniform("linearize", false);
  				if (attachments[i] == GL_DEPTH_ATTACHMENT) {
  					screen_quad_program_.uniform("linearize", true);
  				}
  
  				debug_screen_quads_[i]->draw();
  			}
  		}
  		glEnable(GL_DEPTH_TEST);
  	}
  }