aboutsummaryrefslogtreecommitdiff
path: root/src/client/render/render.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/render/render.cc')
-rw-r--r--src/client/render/render.cc602
1 files changed, 602 insertions, 0 deletions
diff --git a/src/client/render/render.cc b/src/client/render/render.cc
new file mode 100644
index 0000000..18aa32a
--- /dev/null
+++ b/src/client/render/render.cc
@@ -0,0 +1,602 @@
+#include "render.hh"
+
+namespace client {
+namespace render {
+
+static void check_sdl_call(const auto& sdl_call) {
+ if (sdl_call()) {
+ throw std::runtime_error{SDL_GetError()};
+ }
+}
+
+static void check_sdl_pointer(const void* const ptr) {
+ if (ptr == nullptr) {
+ throw std::runtime_error{SDL_GetError()};
+ }
+}
+
+SDL_Window* const& get_sdl_window() noexcept {
+ static SDL_Window* const window = []() -> SDL_Window* {
+ check_sdl_call(std::bind(SDL_Init, SDL_INIT_VIDEO));
+ check_sdl_call(std::bind(SDL_GL_SetAttribute,
+ SDL_GL_CONTEXT_PROFILE_MASK,
+ SDL_GL_CONTEXT_PROFILE_CORE));
+ check_sdl_call(
+ std::bind(SDL_GL_SetAttribute, SDL_GL_CONTEXT_MAJOR_VERSION, 4));
+ check_sdl_call(
+ std::bind(SDL_GL_SetAttribute, SDL_GL_CONTEXT_MINOR_VERSION, 2));
+
+ SDL_Window* const ret =
+ SDL_CreateWindow("blockgame_linux", 0, 0, 0, 0,
+ SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN_DESKTOP |
+ SDL_WINDOW_BORDERLESS);
+ check_sdl_pointer(ret);
+ return ret;
+ }();
+ return window;
+}
+
+static SDL_GLContext& get_sdl_glcontext() noexcept {
+ static SDL_GLContext context = []() -> SDL_GLContext {
+ const SDL_GLContext ret = SDL_GL_CreateContext(get_sdl_window());
+ check_sdl_pointer(ret);
+ check_sdl_call(std::bind(SDL_GL_SetSwapInterval, 0));
+ check_sdl_call(std::bind(SDL_SetRelativeMouseMode, SDL_TRUE));
+
+ // Default gl attributes.
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_CULL_FACE);
+ glEnable(GL_BLEND);
+ glEnable(GL_DEBUG_OUTPUT);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glDebugMessageCallback(
+ [](GLenum, GLenum, GLuint, GLenum severity, GLsizei,
+ const GLchar* message, const void*) {
+ if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) {
+ return;
+ }
+ throw std::runtime_error{std::string{"gl severe error: "} +
+ message};
+ },
+ nullptr);
+
+ glClear(GL_COLOR_BUFFER_BIT |
+ GL_DEPTH_BUFFER_BIT); // anti ub first frame
+ return ret;
+ }();
+ return context;
+}
+
+// When we support window resizing, just make this non const etc.
+const glm::ivec2& get_window_size() noexcept {
+ static glm::ivec2 window_size = []() -> glm::ivec2 {
+ int width, height;
+ SDL_GetWindowSize(get_sdl_window(), &width, &height);
+ return {width, height};
+ }();
+ return window_size;
+}
+
+static int& get_framecount() noexcept {
+ static int framecount = 0;
+ return framecount;
+}
+
+int get_fps() noexcept {
+ constexpr auto rate = std::chrono::milliseconds(500); // rate of updates
+
+ static auto prev = std::chrono::steady_clock::now();
+ static int fps = []() {
+ get_framecount() = 0; // so we don't report absurdly high numbers @ init
+ return 0;
+ }();
+
+ if (const auto now = std::chrono::steady_clock::now(); now >= prev + rate) {
+ const auto ms_elapsed = (now - prev) / std::chrono::milliseconds(1);
+
+ int& framecount = get_framecount();
+ fps = int(float(framecount) * (1000.0f / float(ms_elapsed)));
+ framecount = 0;
+
+ prev = now;
+ }
+ return fps;
+}
+
+void init() {
+ // Creates the glcontext, traverses the tree of sdl calls required for our
+ // window to initialise.
+ get_sdl_glcontext();
+}
+
+void quit() noexcept {
+ // Epoxy doesn't like this, so we're commenting it out for now.
+ /*
+ SDL_GL_DeleteContext(get_sdl_glcontext());
+ SDL_DestroyWindow(get_sdl_window());
+ SDL_Quit();
+ */
+}
+
+void swap_window() noexcept {
+ SDL_GL_SwapWindow(get_sdl_window());
+ ++get_framecount();
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+void render_cube_outline(const glm::vec3& pos,
+ const glm::vec4& colour) noexcept {
+ const auto generate_vbo = [](const auto& vertices) -> GLuint {
+ GLuint vbo = 0;
+ glGenBuffers(1, &vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), std::data(vertices),
+ GL_STATIC_DRAW);
+ return vbo;
+ };
+ const auto generate_vao = []() -> GLuint {
+ GLuint vao = 0;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+ // position
+ glVertexAttribPointer(0, sizeof(glm::vec3) / sizeof(float), GL_FLOAT,
+ GL_FALSE, sizeof(glm::vec3), nullptr);
+ glEnableVertexAttribArray(0);
+ return vao;
+ };
+ const auto generate_ebo = [](const auto& indices) -> GLuint {
+ GLuint ebo = 0;
+ glGenBuffers(1, &ebo);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), &indices,
+ GL_STATIC_DRAW);
+ return ebo;
+ };
+ const auto make_matrix = [&]() -> glm::mat4 {
+ constexpr auto identity = glm::mat4(1.0f);
+ const auto proj = camera::get_proj();
+ const auto trans = glm::translate(identity, pos);
+ const auto& view = camera::get_view();
+ return proj * view * trans;
+ };
+
+ static constexpr std::array<glm::vec3, 8> vertices = {
+ glm::vec3{-0.5f, -0.5f, -0.5f}, glm::vec3{0.5f, -0.5f, -0.5f},
+ glm::vec3{-0.5f, -0.5f, 0.5f}, glm::vec3{0.5f, -0.5f, 0.5f},
+ glm::vec3{-0.5f, 0.5f, -0.5f}, glm::vec3{0.5f, 0.5f, -0.5f},
+ glm::vec3{-0.5f, 0.5f, 0.5f}, glm::vec3{0.5f, 0.5f, 0.5f}};
+ static constexpr std::array<unsigned, 24> indices = {
+ 0, 1, 0, 2, 0, 4, 6, 4, 6, 7, 6, 2, 5, 4, 5, 1, 5, 7, 3, 2, 3, 1, 3, 7};
+ static const program program{"res/shaders/line.vs", "res/shaders/line.fs"};
+ static const GLuint vbo [[maybe_unused]] = generate_vbo(vertices);
+ static const GLuint vao = generate_vao();
+ static const GLuint ebo [[maybe_unused]] = generate_ebo(indices);
+ static const GLint u_colour = glGetUniformLocation(program, "_u_colour");
+ static const GLint u_matrix = glGetUniformLocation(program, "_u_matrix");
+
+ glEnable(GL_BLEND);
+ glUseProgram(program);
+ glBindVertexArray(vao);
+
+ glUniform4fv(u_colour, 1, glm::value_ptr(colour));
+ glUniformMatrix4fv(u_matrix, 1, GL_FALSE, glm::value_ptr(make_matrix()));
+
+ glDrawElements(GL_LINES, std::size(indices), GL_UNSIGNED_INT, nullptr);
+}
+
+void render_rectangle(const glm::vec2& pos, const glm::vec2& size,
+ const glm::vec4& colour) noexcept {
+ const auto generate_vbo = [](const auto& vertices) -> GLuint {
+ GLuint vbo = 0;
+ glGenBuffers(1, &vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), std::data(vertices),
+ GL_STATIC_DRAW);
+ return vbo;
+ };
+ const auto generate_vao = []() -> GLuint {
+ GLuint vao = 0;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+ // position
+ glVertexAttribPointer(0, sizeof(glm::vec2) / sizeof(float), GL_FLOAT,
+ GL_FALSE, sizeof(glm::vec2), nullptr);
+ glEnableVertexAttribArray(0);
+ return vao;
+ };
+ const auto generate_ebo = [](const auto& indices) -> GLuint {
+ GLuint ebo = 0;
+ glGenBuffers(1, &ebo);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), &indices,
+ GL_STATIC_DRAW);
+ return ebo;
+ };
+ const auto make_matrix = [&]() -> glm::mat4 {
+ constexpr auto identity = glm::mat4(1.0f);
+ const glm::vec2& window_size = get_window_size();
+ const auto proj = glm::ortho(0.f, window_size.x, 0.f, window_size.y);
+ const auto scale =
+ glm::scale(identity, glm::vec3(size.x, size.y, 0.0f));
+ const auto trans =
+ glm::translate(identity, glm::vec3(pos.x, pos.y, 0.0f));
+ return proj * trans * scale;
+ };
+
+ static constexpr std::array<glm::vec2, 4> vertices = {
+ glm::vec2{-0.5f, -0.5f}, glm::vec2{0.5f, -0.5f}, glm::vec2{0.5f, 0.5f},
+ glm::vec2{-0.5f, 0.5f}};
+ static constexpr std::array<unsigned, 6> indices = {0, 1, 2, 2, 3, 0};
+ static const program program{"res/shaders/rectangle.vs",
+ "res/shaders/rectangle.fs"};
+ static const GLuint vbo [[maybe_unused]] = generate_vbo(vertices);
+ static const GLuint vao = generate_vao();
+ static const GLuint ebo [[maybe_unused]] = generate_ebo(indices);
+ static const GLint u_colour = glGetUniformLocation(program, "_u_colour");
+ static const GLint u_matrix = glGetUniformLocation(program, "_u_matrix");
+
+ glDisable(GL_DEPTH_TEST);
+ glEnable(GL_BLEND);
+ glUseProgram(program);
+ glBindVertexArray(vao);
+
+ glUniform4fv(u_colour, 1, glm::value_ptr(colour));
+ glUniformMatrix4fv(u_matrix, 1, GL_FALSE, glm::value_ptr(make_matrix()));
+
+ glDrawElements(GL_TRIANGLES, std::size(indices), GL_UNSIGNED_INT, nullptr);
+}
+
+// http://blog.wolfire.com/2013/03/High-quality-text-rendering
+// When rendering our fonts, despite what usually occurs in graphics,
+// downscaling will result in a decrease in visual quality (even if by a small
+// margin). If we want nice looking fonts, we must NEVER scale it - meaning if
+// we want a change in size, we will have to generate a new font atlas each time
+// with a different size. In addition, we are limited only to rendering at
+// integers.
+
+// This class solves this problem by lazily loading characters when necessary,
+// so we can avoid the runtime penalty of generating a couple thousand chars
+// in our atlas every time we request a new size.
+class font_atlas {
+public:
+ struct character_info {
+ float advance_x;
+ float advance_y;
+ float bitmap_width;
+ float bitmap_height;
+ float bitmap_left;
+ float bitmap_top;
+ float x_offset; // offset in texture atlas.
+ float y_offset;
+ };
+ struct texture_info {
+ GLuint texture;
+ GLint width; // max width of a row in our atlas
+ GLint height;
+ unsigned cur_max_glyph_height; // max height of a row in our atlas
+ glm::ivec2 last_offset; // where to upload next
+
+ operator GLuint() const noexcept { return this->texture; }
+ };
+
+private:
+ static constexpr const /*CPP SUCKS*/ char* const font_dir =
+ "res/fonts/NotoSansMono-SemiBold.ttf";
+ static constexpr auto flags =
+ 0u; // FT_LOAD_TARGET_LIGHT | FT_LOAD_FORCE_AUTOHINT;
+
+ unsigned font_size;
+ texture_info texture;
+ std::unordered_map<char, character_info> characters;
+
+private:
+ static FT_Library get_ft_library() noexcept {
+ static FT_Library library = []() -> FT_Library {
+ FT_Library library;
+ if (FT_Init_FreeType(&library)) {
+ throw std::runtime_error("failed to init freetype2");
+ }
+ return library;
+ }();
+ return library;
+ }
+ static GLint get_row_size() noexcept {
+ static const GLint row_size = []() -> GLint {
+ GLint row_size;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &row_size);
+ return row_size;
+ }();
+ return row_size;
+ }
+ // This will set cur_max_glyph_height to size and our last_offset to {0, 0}
+ // so it may be necessary to set them manually.
+ static struct texture_info make_texture_info(const unsigned size) noexcept {
+ GLuint texture;
+ glActiveTexture(GL_TEXTURE0);
+ glGenTextures(1, &texture);
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+
+ // Allocate a big strip so that we do not frequently reallocate.
+ const GLint row_size = get_row_size();
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, row_size, size, 0, GL_ALPHA,
+ GL_UNSIGNED_BYTE, nullptr);
+
+ return {.texture = texture,
+ .width = row_size,
+ .height = static_cast<int>(size),
+ .cur_max_glyph_height = size,
+ .last_offset = {0, 0}};
+ }
+
+private:
+ // Occasionally we will fill the texture atlas too much. This may occur if
+ // we stuff too many characters in and we run out of x, or if a glyph just
+ // happens to be bigger on the y than any other glyph we have inserted.
+ // We must allocate a new texture with the appropriate dimensions that
+ // accomodates for this new glyph while copying over our atlas's previous
+ // contents.
+ void prepare_texture_info(const glm::ivec2 glyph) noexcept {
+ const bool over_x =
+ this->texture.last_offset.x + glyph.x > this->texture.width;
+ const bool over_y =
+ this->texture.last_offset.y + glyph.y > this->texture.height;
+
+ this->texture.cur_max_glyph_height = std::max(
+ this->texture.cur_max_glyph_height, static_cast<unsigned>(glyph.y));
+
+ if (!over_x && !over_y) {
+ return;
+ }
+
+ const struct texture_info old = this->texture;
+ if (over_x) {
+ this->texture = make_texture_info(
+ static_cast<unsigned>(old.height) + old.cur_max_glyph_height);
+ this->texture.last_offset = {0.0f, old.last_offset.y + old.height};
+ } else if (over_y) {
+ this->texture = make_texture_info(old.cur_max_glyph_height);
+ this->texture.last_offset = old.last_offset;
+ }
+ this->texture.cur_max_glyph_height = old.cur_max_glyph_height;
+ glCopyImageSubData(old.texture, GL_TEXTURE_2D, 0, 0, 0, 0,
+ this->texture, GL_TEXTURE_2D, 0, 0, 0, 0, old.width,
+ old.height, 1);
+
+ glDeleteTextures(1, &old.texture);
+ }
+
+ struct character_info make_character_info(const char c) noexcept {
+ const FT_Library library = this->get_ft_library();
+
+ FT_Face face;
+ if (FT_New_Face(library, font_dir, 0, &face)) {
+ throw std::runtime_error("failed to load font");
+ }
+
+ FT_Set_Pixel_Sizes(face, 0, this->font_size);
+ if (FT_Load_Char(face, static_cast<unsigned long>(c),
+ flags | FT_LOAD_RENDER)) {
+ return this->operator[]('?'); // placeholder
+ }
+
+ const int width = static_cast<int>(face->glyph->bitmap.width);
+ const int height = static_cast<int>(face->glyph->bitmap.rows);
+
+ this->prepare_texture_info(glm::ivec2{width, height});
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, this->texture.texture);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, this->texture.last_offset.x,
+ this->texture.last_offset.y, width, height, GL_RED,
+ GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer);
+
+ character_info character_info = {
+ .advance_x = static_cast<float>(face->glyph->advance.x >> 6),
+ .advance_y = static_cast<float>(face->glyph->advance.y >> 6),
+ .bitmap_width = static_cast<float>(face->glyph->bitmap.width),
+ .bitmap_height = static_cast<float>(face->glyph->bitmap.rows),
+ .bitmap_left = static_cast<float>(face->glyph->bitmap_left),
+ .bitmap_top = static_cast<float>(face->glyph->bitmap_top),
+ .x_offset = static_cast<float>(this->texture.last_offset.x),
+ .y_offset = static_cast<float>(this->texture.last_offset.y)};
+
+ this->texture.last_offset.x += width;
+
+ FT_Done_Face(face);
+ return character_info;
+ }
+
+public:
+ // Use this operator to get specific chars.
+ const struct character_info& operator[](const char c) noexcept {
+ if (const auto find_it = this->characters.find(c);
+ find_it != std::end(this->characters)) {
+
+ return find_it->second;
+ }
+
+ const auto emplace =
+ this->characters.emplace(c, make_character_info(c));
+
+ return emplace.first->second;
+ }
+
+ font_atlas(const unsigned size) noexcept
+ : font_size(size), texture(make_texture_info(size)) {}
+ ~font_atlas() noexcept { glDeleteTextures(1, &this->texture.texture); }
+
+ GLint get_height() const noexcept { return this->texture.height; }
+ GLint get_width() const noexcept { return this->texture.width; }
+ unsigned get_glyph_height() const noexcept {
+ return this->texture.cur_max_glyph_height;
+ }
+ GLuint get_texture() const noexcept { return this->texture; }
+};
+
+void render_text(const std::string_view text, const unsigned int size,
+ const bool is_centered, const bool is_vcentered,
+ const glm::vec4& colour, const glm::mat4& matrix) noexcept {
+ const auto generate_vao = []() -> GLuint {
+ GLuint vao = 0;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+ return vao;
+ };
+ struct vertex {
+ glm::vec2 pos;
+ glm::vec2 tex;
+ };
+ const auto generate_vbo = []() -> GLuint {
+ GLuint vbo = 0;
+ glGenBuffers(1, &vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 5, nullptr,
+ GL_DYNAMIC_DRAW);
+
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, sizeof(glm::vec2) / sizeof(float), GL_FLOAT,
+ GL_FALSE, sizeof(vertex), 0);
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(1, sizeof(glm::vec2) / sizeof(float), GL_FLOAT,
+ GL_FALSE, sizeof(vertex),
+ reinterpret_cast<void*>(sizeof(glm::vec2)));
+ return vbo;
+ };
+ static const program program("res/shaders/text.vs", "res/shaders/text.fs");
+ static const GLuint vao = generate_vao();
+ static const GLuint vbo = generate_vbo();
+ static const GLint u_matrix = glGetUniformLocation(program, "_u_matrix");
+ static const GLint u_colour = glGetUniformLocation(program, "_u_colour");
+ static std::unordered_map<unsigned, font_atlas> font_atlases;
+
+ // Find or generate a font atlas in our unordered_map.
+ font_atlas& atlas = [&]() -> font_atlas& {
+ if (const auto find_it = font_atlases.find(size);
+ find_it != std::end(font_atlases)) {
+
+ return find_it->second;
+ }
+
+ const auto emplace =
+ font_atlases.emplace(std::pair<unsigned, unsigned>(size, size));
+ return emplace.first->second;
+ }();
+
+ const std::vector<vertex> vertices = [&]() {
+ std::vector<vertex> vertices;
+
+ const float atlas_width = static_cast<float>(atlas.get_width());
+ const float atlas_height = static_cast<float>(atlas.get_height());
+ float total_width = 0.0f;
+ glm::vec3 pos{0.0f};
+ for (const auto& c : text) {
+ const auto& character = atlas[c];
+
+ const float w = character.bitmap_width;
+ const float h = character.bitmap_height;
+ const float x = pos.x + character.bitmap_left;
+ const float y = -pos.y - (character.bitmap_top);
+
+ pos.x += character.advance_x;
+ pos.y += character.advance_y;
+
+ total_width += character.advance_x;
+
+ if (w == 0.0f || h == 0.0f) {
+ continue;
+ }
+
+ // clang-format off
+ vertices.push_back(vertex{{x , -y }, {character.x_offset / atlas_width , character.y_offset / atlas_height}});
+ vertices.push_back(vertex{{x , -y - h}, {character.x_offset / atlas_width , (character.y_offset + character.bitmap_height) / atlas_height}});
+ vertices.push_back(vertex{{x + w, -y }, {(character.x_offset + character.bitmap_width) / atlas_width, character.y_offset / atlas_height}});
+ vertices.push_back(vertex{{x + w, -y }, {(character.x_offset + character.bitmap_width) / atlas_width, character.y_offset / atlas_height}});
+ vertices.push_back(vertex{{x , -y - h}, {character.x_offset / atlas_width , (character.y_offset + character.bitmap_height) / atlas_height}});
+ vertices.push_back(vertex{{x + w, -y - h}, {(character.x_offset + character.bitmap_width) / atlas_width, (character.y_offset + character.bitmap_height) / atlas_height}});
+ // clang-format on
+ }
+
+ const float xoffset =
+ is_centered ? std::floor(total_width / 2.0f) : 0.0f;
+ const float yoffset =
+ is_vcentered
+ ? std::floor(static_cast<float>(atlas.get_glyph_height()) /
+ 3.0f)
+ : 0.0f;
+ for (auto& v : vertices) {
+ v.pos.x -= xoffset;
+ v.pos.y -= yoffset;
+ }
+
+ return vertices;
+ }();
+
+ glEnable(GL_BLEND);
+ glDisable(GL_CULL_FACE);
+ glUseProgram(program);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, atlas.get_texture());
+ glBindVertexArray(vao);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+
+ glBufferData(GL_ARRAY_BUFFER, std::size(vertices) * sizeof(vertex),
+ std::data(vertices), GL_DYNAMIC_DRAW);
+
+ glUniform4f(u_colour, colour.x, colour.y, colour.z, colour.w);
+ glUniformMatrix4fv(u_matrix, 1, GL_FALSE, glm::value_ptr(matrix));
+
+ glDrawArrays(GL_TRIANGLES, 0, std::size(vertices));
+
+ glEnable(GL_CULL_FACE);
+}
+
+// Updates our ebos, we want to call this once per frame.
+void update_uniforms() noexcept {
+ struct ubo_data {
+ glm::mat4 proj;
+ glm::mat4 view;
+ std::array<glm::vec4, 6> frustum;
+ float znear;
+ float zfar;
+ float xfov; // IN RADIANS
+ float yfov; // IN RADIANS
+ unsigned time; // resolution of 1000/s
+ float xwindow;
+ float ywindow;
+ };
+ const auto make_ubo = [&]() -> GLuint {
+ GLuint ubo;
+ glGenBuffers(1, &ubo);
+ glBindBuffer(GL_UNIFORM_BUFFER, ubo);
+ glBufferData(GL_UNIFORM_BUFFER, sizeof(ubo_data), nullptr,
+ GL_DYNAMIC_DRAW);
+ glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo); // binding @ 0
+ return ubo;
+ };
+ static const GLuint ubo = make_ubo();
+
+ ubo_data upload = {.proj = camera::get_proj(),
+ .view = camera::get_view(),
+ .frustum = camera::get_frustum(),
+ .znear = camera::get_znear(),
+ .zfar = camera::get_zfar(),
+ .xfov = glm::radians(camera::get_xfov()),
+ .yfov = glm::radians(camera::get_yfov()),
+ .time = []() -> unsigned {
+ static const auto start =
+ std::chrono::steady_clock::now();
+ const auto now = std::chrono::steady_clock::now();
+ return static_cast<unsigned>(
+ (now - start) / std::chrono::milliseconds(1));
+ }(),
+ .xwindow = static_cast<float>(get_window_size().x),
+ .ywindow = static_cast<float>(get_window_size().y)};
+ glBindBuffer(GL_UNIFORM_BUFFER, ubo);
+ glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(upload), &upload);
+}
+
+} // namespace render
+} // namespace client