summary refs log tree commit diff
path: root/src/Render.cpp
blob: 58d5a42d28ef3497bfe693eeb4285c95f31d0de6 (plain)
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
#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 image = GFX::Image::PPMParser(Assets::Images::atlas).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();
    if (error != GLEW_OK) {
        std::string error_string(reinterpret_cast<Char const*>(glewGetErrorString(error)));
        throw std::runtime_error("Failed to load GL functions: " + error_string);
    }
}

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();
    }
}

}