1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
#include "Render.hpp"
#include "Assets.hpp"
#include "Defines.hpp"
#include "ThreadRole.hpp"
#include "Time.hpp"
#include "Common/Assert.hpp"
#include "GFX/Image/PPMParser.hpp"
#include "GFX/Shading/Program.hpp"
#include "Math/MVP.hpp"
#include <thread>
#define FPS_LIMIT 60
namespace MC {
void Render::run() {
HELLO_I_AM(ThreadRole::Render);
m_window.attach();
setup_gl();
m_resources.initialize();
glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
m_window.on_size_change([](GLFWwindow* _, I32 w, I32 h) {
glViewport(0, 0, w, h);
});
auto atlas_asset = MC::asset<MC::Assets::Images::atlas>();
auto image = GFX::Image::PPMParser(atlas_asset).parse();
auto texture = GFX::Texture(image);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glCullFace(GL_BACK);
Time render_time{};
while (!m_window.should_close()) {
Scene scene = m_control->wait_for_render_data();
render_time.start_frame();
m_window.start_render();
render_scene(scene, texture);
render_time.end_frame();
wait_until_next_frame_start(render_time.delta_raw());
// This signal has to be the last thing in the loop,
// otherwise the logic thread could be blocked during
// it's `wait_for_render_finish` call.
// Still weird though, maybe there's a better way?
m_control->finish_render();
}
}
void Render::render_scene(Scene const& scene, GFX::Texture const& texture) const {
Vector<3, F32> sky_color = SKY_COLOR;
glClearColor(sky_color.x(), sky_color.y(), sky_color.z(), 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
auto view = Math::MVP::view<F32>(scene.camera.position(), scene.camera.angles());
auto projection = Math::MVP::perspective_projection<F32>(ASPECT, FOV, 0.1f, 1000.0f);
for (auto& action : scene.actions.actions()) {
auto& program = m_resources.program(action.program);
program.bind();
auto model_uniform = program.uniform("model_matrix");
auto view_uniform = program.uniform("view_matrix");
auto projection_uniform = program.uniform("projection_matrix");
auto sun_direction_uniform = program.uniform("sun_direction");
auto sky_color_uniform = program.uniform("sky_color");
auto mesh_alpha_uniform = program.uniform("mesh_alpha");
ASSERT(model_uniform.has_value() && view_uniform.has_value() && projection_uniform.has_value(),
"Program does not have the necessary MVP uniforms");
auto transform = action.transform;
auto model = Math::MVP::model<F32>(transform.position(), transform.scale(), transform.rotation());
model_uniform->set(model);
view_uniform->set(view);
projection_uniform->set(projection);
if (sun_direction_uniform.has_value())
sun_direction_uniform->set(SUN_DIRECTION);
if (sky_color_uniform.has_value())
sky_color_uniform->set(SKY_COLOR);
if (mesh_alpha_uniform.has_value())
mesh_alpha_uniform->set(action.alpha);
texture.bind();
action.mesh->bind();
glDrawElements(TO(GLenum, action.draw_mode), action.mesh->size(), GL_UNSIGNED_INT, nullptr);
action.mesh->unbind();
texture.unbind();
program.unbind();
}
}
void Render::setup_gl() {
GLenum error = glewInit();
// We get a NO_GLX_DISPLAY error on Linux/Wayland during GLEW initialization.
// Since we're not using GLX (under Wayland), it is presumably fine to ignore this error,
// and it seems to appear due to some sort of confusion in GLEW when to use GLX or EGL.
// See this GLEW issue: https://github.com/nigels-com/glew/issues/273
if (error != GLEW_OK && error != GLEW_ERROR_NO_GLX_DISPLAY) {
Char error_code[8];
snprintf(error_code, sizeof(error_code), "%X", error);
std::string error_string { reinterpret_cast<Char const *>(glewGetErrorString(error)) };
throw std::runtime_error("Failed to load GL functions: " + error_string + " (0x" + error_code + ")");
}
}
void Render::wait_until_next_frame_start(Real spent_time_budget) const {
constexpr Real total_frame_time_budget = 1.0 / FPS_LIMIT;
Real remaining_time_budget = total_frame_time_budget - spent_time_budget;
if (remaining_time_budget > 0) {
auto frame_end = Time::now() + TO(U64, remaining_time_budget * 1000);
while (Time::now() < frame_end) std::this_thread::yield();
}
}
}
|