Make GL state changes predictable and clean up main.cpp
Engine: - Create, resize and update functions now restore the bindings they touch instead of leaving their own objects bound or resetting to 0 (Texture, FrameBuffer, Mesh, UniformBuffer) - Texture::bind/unbind switch back to texture unit 0 afterwards, so later texture binds can't replace a material's specular map - Add gl_get_binding() helper to read the current binding - Renderer remembers the wireframe mode (new is_wireframe()); draw_debug_wireframes restores it instead of always disabling it, which fixes H having no effect while debug wireframes are shown main.cpp (same behavior and UI): - main() only sets up the window and renderer; all GL resources live in run() and are released before Renderer::shutdown() - One function per scene and a shared create_textured_material() - Split app state into AppState, CameraInput and PerformanceStats; drop the duplicate delta_time and static toggle variables - Move the scene pass to render_scene() and the overlay to draw_overlay(); name the light box and overlay constants - Clear the mouse callback before AppState is destroyed
This commit is contained in:
@@ -16,6 +16,11 @@ Rc<FrameBuffer> FrameBuffer::create(GLsizei width, GLsizei height)
|
|||||||
|
|
||||||
gl_clear_errors();
|
gl_clear_errors();
|
||||||
|
|
||||||
|
// Restored afterwards so creating a framebuffer leaves the caller's bindings intact
|
||||||
|
GLuint previous_fbo = gl_get_binding(GL_FRAMEBUFFER_BINDING);
|
||||||
|
GLuint previous_texture = gl_get_binding(GL_TEXTURE_BINDING_2D);
|
||||||
|
GLuint previous_render_buffer = gl_get_binding(GL_RENDERBUFFER_BINDING);
|
||||||
|
|
||||||
GLuint id, texture, render_buffer;
|
GLuint id, texture, render_buffer;
|
||||||
|
|
||||||
glGenFramebuffers(1, &id);
|
glGenFramebuffers(1, &id);
|
||||||
@@ -40,9 +45,9 @@ Rc<FrameBuffer> FrameBuffer::create(GLsizei width, GLsizei height)
|
|||||||
|
|
||||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||||
GLenum err = gl_pop_error();
|
GLenum err = gl_pop_error();
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, previous_fbo);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, previous_texture);
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
glBindRenderbuffer(GL_RENDERBUFFER, previous_render_buffer);
|
||||||
|
|
||||||
if (status != GL_FRAMEBUFFER_COMPLETE || err != GL_NO_ERROR) {
|
if (status != GL_FRAMEBUFFER_COMPLETE || err != GL_NO_ERROR) {
|
||||||
glDeleteRenderbuffers(1, &render_buffer);
|
glDeleteRenderbuffers(1, &render_buffer);
|
||||||
@@ -124,6 +129,11 @@ bool FrameBuffer::resize(GLsizei width, GLsizei height)
|
|||||||
|
|
||||||
gl_clear_errors();
|
gl_clear_errors();
|
||||||
|
|
||||||
|
// Restored afterwards so resizing leaves the caller's bindings intact
|
||||||
|
GLuint previous_fbo = gl_get_binding(GL_FRAMEBUFFER_BINDING);
|
||||||
|
GLuint previous_texture = gl_get_binding(GL_TEXTURE_BINDING_2D);
|
||||||
|
GLuint previous_render_buffer = gl_get_binding(GL_RENDERBUFFER_BINDING);
|
||||||
|
|
||||||
m_width = width;
|
m_width = width;
|
||||||
m_height = height;
|
m_height = height;
|
||||||
|
|
||||||
@@ -137,15 +147,12 @@ bool FrameBuffer::resize(GLsizei width, GLsizei height)
|
|||||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8,
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8,
|
||||||
m_width, m_height);
|
m_width, m_height);
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
|
||||||
|
|
||||||
// Check completeness without disturbing the caller's framebuffer binding
|
|
||||||
GLint previous_fbo = 0;
|
|
||||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previous_fbo);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, m_id);
|
glBindFramebuffer(GL_FRAMEBUFFER, m_id);
|
||||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(previous_fbo));
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, previous_fbo);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, previous_texture);
|
||||||
|
glBindRenderbuffer(GL_RENDERBUFFER, previous_render_buffer);
|
||||||
|
|
||||||
GLenum err = gl_pop_error();
|
GLenum err = gl_pop_error();
|
||||||
if (status != GL_FRAMEBUFFER_COMPLETE || err != GL_NO_ERROR) {
|
if (status != GL_FRAMEBUFFER_COMPLETE || err != GL_NO_ERROR) {
|
||||||
|
|||||||
@@ -80,6 +80,13 @@ void set_gl_label(GLenum identifier, GLuint id, const std::string& label)
|
|||||||
glObjectLabel(identifier, id, length, label.c_str());
|
glObjectLabel(identifier, id, length, label.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLuint gl_get_binding(GLenum binding_query)
|
||||||
|
{
|
||||||
|
GLint id = 0;
|
||||||
|
glGetIntegerv(binding_query, &id);
|
||||||
|
return static_cast<GLuint>(id);
|
||||||
|
}
|
||||||
|
|
||||||
void gl_clear_errors()
|
void gl_clear_errors()
|
||||||
{
|
{
|
||||||
gl_pop_error();
|
gl_pop_error();
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ void enable_gl_debug_output();
|
|||||||
// Name a GL object so debug messages and tools like RenderDoc show it
|
// Name a GL object so debug messages and tools like RenderDoc show it
|
||||||
void set_gl_label(GLenum identifier, GLuint id, const std::string& label);
|
void set_gl_label(GLenum identifier, GLuint id, const std::string& label);
|
||||||
|
|
||||||
|
// Object currently bound for a binding query (e.g. GL_TEXTURE_BINDING_2D), used
|
||||||
|
// to restore the caller's bindings after creating or updating an object
|
||||||
|
GLuint gl_get_binding(GLenum binding_query);
|
||||||
|
|
||||||
// Discard errors left over from earlier calls
|
// Discard errors left over from earlier calls
|
||||||
void gl_clear_errors();
|
void gl_clear_errors();
|
||||||
|
|
||||||
|
|||||||
605
src/main.cpp
605
src/main.cpp
@@ -3,9 +3,9 @@
|
|||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_internal.h>
|
#include <imgui_internal.h>
|
||||||
@@ -14,6 +14,20 @@
|
|||||||
|
|
||||||
using namespace Fan;
|
using namespace Fan;
|
||||||
|
|
||||||
|
/******************** Constants ********************/
|
||||||
|
|
||||||
|
// Random lights are scattered inside this box in every scene
|
||||||
|
const glm::vec3 LIGHT_BOX_MIN{-10.0f, 1.0f, -3.0f};
|
||||||
|
const glm::vec3 LIGHT_BOX_MAX{10.0f, 5.0f, 3.0f};
|
||||||
|
|
||||||
|
// Overlay text in the top-left corner of the viewport
|
||||||
|
const ImVec4 OVERLAY_COLOR{0.0f, 1.0f, 0.0f, 1.0f};
|
||||||
|
constexpr float OVERLAY_X = 20.0f;
|
||||||
|
constexpr float OVERLAY_Y = 35.0f;
|
||||||
|
constexpr float OVERLAY_LINE_HEIGHT = 17.0f;
|
||||||
|
|
||||||
|
/******************** Application state ********************/
|
||||||
|
|
||||||
// Ring buffer of per-frame measurements, for plotting and stable averages
|
// Ring buffer of per-frame measurements, for plotting and stable averages
|
||||||
struct MetricHistory
|
struct MetricHistory
|
||||||
{
|
{
|
||||||
@@ -42,47 +56,72 @@ struct MetricHistory
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UserData
|
// Free-fly camera controls, active while the mouse is grabbed
|
||||||
|
struct CameraInput
|
||||||
{
|
{
|
||||||
Window& window;
|
|
||||||
Camera& camera;
|
|
||||||
float delta_time{};
|
|
||||||
bool mouse_grab{};
|
|
||||||
|
|
||||||
float speed{CAMERA_SPEED};
|
float speed{CAMERA_SPEED};
|
||||||
float sensitivity{CAMERA_SENSITIVITY};
|
float sensitivity{CAMERA_SENSITIVITY};
|
||||||
|
|
||||||
|
bool mouse_grab{};
|
||||||
|
bool first_mouse{true}; // Skips the jump from the cursor's position before grabbing
|
||||||
float last_mouse_x{};
|
float last_mouse_x{};
|
||||||
float last_mouse_y{};
|
float last_mouse_y{};
|
||||||
bool first_mouse{true};
|
};
|
||||||
|
|
||||||
bool vsync{true};
|
// Timings of the scene pass (gpu_timer is null if timer queries are unsupported)
|
||||||
bool show_debug{};
|
struct PerformanceStats
|
||||||
|
{
|
||||||
|
CpuTimer cpu_timer{};
|
||||||
|
Rc<GpuTimer> gpu_timer{};
|
||||||
|
|
||||||
Rc<Texture> logo{};
|
|
||||||
|
|
||||||
// Performance measurements of the scene pass (GPU timer is null if unsupported)
|
|
||||||
CpuTimer scene_cpu_timer{};
|
|
||||||
Rc<GpuTimer> scene_gpu_timer{};
|
|
||||||
MetricHistory frame_times{};
|
MetricHistory frame_times{};
|
||||||
MetricHistory scene_cpu_times{};
|
MetricHistory scene_cpu_times{};
|
||||||
MetricHistory scene_gpu_times{};
|
MetricHistory scene_gpu_times{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppState
|
||||||
|
{
|
||||||
|
Window& window;
|
||||||
|
Camera& camera;
|
||||||
|
|
||||||
|
CameraInput input{};
|
||||||
|
PerformanceStats stats{};
|
||||||
|
|
||||||
|
bool vsync{true};
|
||||||
|
bool show_debug{};
|
||||||
|
Rc<Texture> logo{};
|
||||||
|
|
||||||
std::vector<Rc<Scene>> scenes{};
|
std::vector<Rc<Scene>> scenes{};
|
||||||
size_t selected_scene_index{};
|
size_t selected_scene_index{};
|
||||||
};
|
};
|
||||||
|
|
||||||
void process_input(Window& window, Camera& camera, UserData& user_data);
|
/******************** Declarations ********************/
|
||||||
|
|
||||||
|
int run(Window& window);
|
||||||
|
|
||||||
|
// Scenes
|
||||||
|
std::vector<Rc<Scene>> create_scenes(const Rc<Shader>& shader, const Rc<Mesh>& cube_mesh);
|
||||||
|
Rc<Scene> create_sponza_scene(const Rc<Shader>& shader);
|
||||||
|
Rc<Scene> create_car_scene(const Rc<Shader>& shader);
|
||||||
|
Rc<Scene> create_backpack_scene(const Rc<Shader>& shader, const Rc<Mesh>& cube_mesh);
|
||||||
|
Rc<Scene> create_cubes_scene(const Rc<Shader>& shader, const Rc<Mesh>& cube_mesh);
|
||||||
|
Rc<Material> create_textured_material(const Rc<Shader>& shader, const std::string& texture_path);
|
||||||
|
void add_random_lights(Scene& scene, int point_light_count, int spot_light_count, unsigned int seed);
|
||||||
|
|
||||||
|
// Frame
|
||||||
|
void process_input(AppState& app);
|
||||||
void mouse_callback(double pos_x, double pos_y, void* user_data);
|
void mouse_callback(double pos_x, double pos_y, void* user_data);
|
||||||
|
void render_scene(AppState& app, FrameBuffer& framebuffer, const Rc<Shader>& debug_shader);
|
||||||
|
|
||||||
|
// UI
|
||||||
void setup_dockspace();
|
void setup_dockspace();
|
||||||
void draw_panel_window(UserData& user_data);
|
void draw_panel_window(AppState& app);
|
||||||
void draw_viewport_window(FrameBuffer& framebuffer, UserData& user_data);
|
void draw_viewport_window(AppState& app, FrameBuffer& framebuffer);
|
||||||
|
void draw_overlay(const AppState& app);
|
||||||
void plot_metric(const char* label, const MetricHistory& history);
|
void plot_metric(const char* label, const MetricHistory& history);
|
||||||
void add_random_lights(Scene& scene, int point_light_count, int spot_light_count,
|
|
||||||
const glm::vec3& box_min, const glm::vec3& box_max,
|
|
||||||
unsigned int seed = std::random_device{}());
|
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
void overlay_text(float offset_x, float offset_y, ImVec4 color, const char* format, Args... args)
|
void overlay_text(float offset_x, float offset_y, const char* format, Args... args)
|
||||||
{
|
{
|
||||||
char buffer[256];
|
char buffer[256];
|
||||||
std::snprintf(buffer, sizeof(buffer), format, args...);
|
std::snprintf(buffer, sizeof(buffer), format, args...);
|
||||||
@@ -92,102 +131,132 @@ void overlay_text(float offset_x, float offset_y, ImVec4 color, const char* form
|
|||||||
ImVec2 text_size = ImGui::CalcTextSize(buffer);
|
ImVec2 text_size = ImGui::CalcTextSize(buffer);
|
||||||
|
|
||||||
draw_list->AddRectFilled(pos - ImVec2{4, 2}, pos + text_size + ImVec2{4, 2}, IM_COL32(0, 0, 0, 160));
|
draw_list->AddRectFilled(pos - ImVec2{4, 2}, pos + text_size + ImVec2{4, 2}, IM_COL32(0, 0, 0, 160));
|
||||||
draw_list->AddText(pos, ImGui::ColorConvertFloat4ToU32(color), buffer);
|
draw_list->AddText(pos, ImGui::ColorConvertFloat4ToU32(OVERLAY_COLOR), buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******************** Entry point ********************/
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
/******************** Initialization ********************/
|
|
||||||
|
|
||||||
auto window = Window::create(WINDOW_WIDTH, WINDOW_HEIGHT, VERSION_STRING);
|
auto window = Window::create(WINDOW_WIDTH, WINDOW_HEIGHT, VERSION_STRING);
|
||||||
if (!window) return EXIT_FAILURE;
|
if (!window) return EXIT_FAILURE;
|
||||||
window->set_vsync(true);
|
window->set_vsync(true);
|
||||||
|
|
||||||
if (!Renderer::init()) return EXIT_FAILURE;
|
if (!Renderer::init()) return EXIT_FAILURE;
|
||||||
|
|
||||||
|
// Every GL resource created by run() is released when it returns,
|
||||||
|
// before the renderer and the window shut down
|
||||||
|
int exit_code = run(*window);
|
||||||
|
|
||||||
|
Renderer::shutdown();
|
||||||
|
return exit_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
int run(Window& window)
|
||||||
|
{
|
||||||
Camera camera{{6.0f, 3.0f, 1.0f}};
|
Camera camera{{6.0f, 3.0f, 1.0f}};
|
||||||
camera.fov = 60.0f;
|
camera.fov = 60.0f;
|
||||||
camera.look_at({0.0f, 1.0f, 0.0f});
|
camera.look_at({0.0f, 1.0f, 0.0f});
|
||||||
|
|
||||||
UserData user_data{*window, camera};
|
AppState app{window, camera};
|
||||||
window->set_callback_user_data(&user_data);
|
window.set_callback_user_data(&app);
|
||||||
window->set_mouse_callback(mouse_callback);
|
window.set_mouse_callback(mouse_callback);
|
||||||
|
|
||||||
/******************** Resource loading ********************/
|
|
||||||
|
|
||||||
|
// Assets never fail to load: missing ones are replaced by fallbacks
|
||||||
auto shader = ResourceCache::load_shader("./src/shaders/default.vert", "./src/shaders/default.frag");
|
auto shader = ResourceCache::load_shader("./src/shaders/default.vert", "./src/shaders/default.frag");
|
||||||
auto debug_shader = ResourceCache::load_shader("./src/shaders/debug.vert", "./src/shaders/debug.frag", false);
|
auto debug_shader = ResourceCache::load_shader("./src/shaders/debug.vert", "./src/shaders/debug.frag", false);
|
||||||
|
app.logo = ResourceCache::load_texture("assets/logo/logo.png");
|
||||||
Entity sponza = load_obj("./assets/sponza/sponza.obj", shader);
|
app.stats.gpu_timer = GpuTimer::create();
|
||||||
Entity car = load_obj("./assets/audi_r8/Audi_R8_2017.obj", shader);
|
|
||||||
Entity backpack = load_obj("./assets/backpack/backpack.obj", shader, true);
|
|
||||||
|
|
||||||
auto cube_texture = ResourceCache::load_texture("./assets/textures/container.jpg");
|
|
||||||
auto default_texture = ResourceCache::load_texture("./assets/textures/wall.jpg");
|
|
||||||
user_data.logo = ResourceCache::load_texture("assets/logo/logo.png");
|
|
||||||
|
|
||||||
user_data.scene_gpu_timer = GpuTimer::create();
|
|
||||||
|
|
||||||
auto cube_mesh = Mesh::create_cube();
|
|
||||||
auto framebuffer = FrameBuffer::create(window->get_width(), window->get_height());
|
|
||||||
|
|
||||||
// Built from memory or sized from the window, failing here means OpenGL is broken
|
// Built from memory or sized from the window, failing here means OpenGL is broken
|
||||||
if (!cube_mesh || !framebuffer) {
|
auto cube_mesh = Mesh::create_cube();
|
||||||
Renderer::shutdown();
|
auto framebuffer = FrameBuffer::create(window.get_width(), window.get_height());
|
||||||
return EXIT_FAILURE;
|
if (!cube_mesh || !framebuffer) return EXIT_FAILURE;
|
||||||
|
|
||||||
|
app.scenes = create_scenes(shader, cube_mesh);
|
||||||
|
|
||||||
|
while (!window.should_close()) {
|
||||||
|
window.begin();
|
||||||
|
window.update();
|
||||||
|
|
||||||
|
app.stats.frame_times.push(window.get_delta_time() * 1000.0f);
|
||||||
|
process_input(app);
|
||||||
|
|
||||||
|
Renderer::set_clear_color({0.0f, 0.0f, 0.0f, 1.0f});
|
||||||
|
Renderer::clear();
|
||||||
|
|
||||||
|
// UI layer
|
||||||
|
setup_dockspace();
|
||||||
|
draw_panel_window(app);
|
||||||
|
draw_viewport_window(app, *framebuffer);
|
||||||
|
|
||||||
|
// Scene layer, shown by the viewport window
|
||||||
|
render_scene(app, *framebuffer, debug_shader);
|
||||||
|
|
||||||
|
window.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************** Scene construction ********************/
|
// app is about to be destroyed, don't let the window call back into it
|
||||||
|
window.set_mouse_callback(nullptr);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
// Sponza scene
|
/******************** Scenes ********************/
|
||||||
Rc<Scene> sponza_scene = make_rc<Scene>();
|
|
||||||
user_data.scenes.push_back(sponza_scene);
|
|
||||||
|
|
||||||
|
std::vector<Rc<Scene>> create_scenes(const Rc<Shader>& shader, const Rc<Mesh>& cube_mesh)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
create_sponza_scene(shader),
|
||||||
|
create_car_scene(shader),
|
||||||
|
create_backpack_scene(shader, cube_mesh),
|
||||||
|
create_cubes_scene(shader, cube_mesh),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Rc<Scene> create_sponza_scene(const Rc<Shader>& shader)
|
||||||
|
{
|
||||||
|
auto scene = make_rc<Scene>();
|
||||||
|
|
||||||
|
Entity sponza = load_obj("./assets/sponza/sponza.obj", shader);
|
||||||
sponza.transform.position = {0.0f, -0.5f, 0.0f};
|
sponza.transform.position = {0.0f, -0.5f, 0.0f};
|
||||||
sponza.transform.scale = {0.008f, 0.008f, 0.008f};
|
sponza.transform.scale = {0.008f, 0.008f, 0.008f};
|
||||||
sponza_scene->add_entity(sponza);
|
scene->add_entity(sponza);
|
||||||
|
|
||||||
add_random_lights(*sponza_scene, 4, 4,
|
add_random_lights(*scene, 4, 4, 69420);
|
||||||
glm::vec3(-10.0f, 1.0f, -3.0f), glm::vec3(10.0f, 5.0f, 3.0f),
|
return scene;
|
||||||
69420);
|
}
|
||||||
|
|
||||||
// Car scene
|
Rc<Scene> create_car_scene(const Rc<Shader>& shader)
|
||||||
Rc<Scene> car_scene = make_rc<Scene>();
|
{
|
||||||
user_data.scenes.push_back(car_scene);
|
auto scene = make_rc<Scene>();
|
||||||
|
|
||||||
car_scene->add_entity(car);
|
scene->add_entity(load_obj("./assets/audi_r8/Audi_R8_2017.obj", shader));
|
||||||
|
|
||||||
add_random_lights(*car_scene, 4, 4,
|
add_random_lights(*scene, 4, 4, 42);
|
||||||
glm::vec3(-10.0f, 1.0f, -3.0f), glm::vec3(10.0f, 5.0f, 3.0f),
|
return scene;
|
||||||
42);
|
}
|
||||||
|
|
||||||
// Backpack scene
|
Rc<Scene> create_backpack_scene(const Rc<Shader>& shader, const Rc<Mesh>& cube_mesh)
|
||||||
Rc<Scene> backpack_scene = make_rc<Scene>();
|
{
|
||||||
user_data.scenes.push_back(backpack_scene);
|
auto scene = make_rc<Scene>();
|
||||||
|
|
||||||
backpack_scene->add_entity(backpack);
|
scene->add_entity(load_obj("./assets/backpack/backpack.obj", shader, true));
|
||||||
|
|
||||||
auto default_material = make_rc<Material>();
|
Entity floor{cube_mesh, create_textured_material(shader, "./assets/textures/wall.jpg")};
|
||||||
default_material->shader = shader;
|
|
||||||
default_material->diffuse_map = default_texture;
|
|
||||||
Entity floor{cube_mesh, default_material};
|
|
||||||
floor.transform.position = {0.0f, -3.0f, 0.0f};
|
floor.transform.position = {0.0f, -3.0f, 0.0f};
|
||||||
floor.transform.scale = {20.0f, 1.0f, 20.0f};
|
floor.transform.scale = {20.0f, 1.0f, 20.0f};
|
||||||
backpack_scene->add_entity(floor);
|
scene->add_entity(floor);
|
||||||
|
|
||||||
add_random_lights(*backpack_scene, 4, 4,
|
add_random_lights(*scene, 4, 4, 37);
|
||||||
glm::vec3(-10.0f, 1.0f, -3.0f), glm::vec3(10.0f, 5.0f, 3.0f),
|
return scene;
|
||||||
37);
|
}
|
||||||
|
|
||||||
// Cubes scene
|
Rc<Scene> create_cubes_scene(const Rc<Shader>& shader, const Rc<Mesh>& cube_mesh)
|
||||||
Rc<Scene> cubes_scene = make_rc<Scene>();
|
{
|
||||||
user_data.scenes.push_back(cubes_scene);
|
auto scene = make_rc<Scene>();
|
||||||
|
auto material = create_textured_material(shader, "./assets/textures/container.jpg");
|
||||||
|
|
||||||
auto cube_material = make_rc<Material>();
|
const std::vector<glm::vec3> positions{
|
||||||
cube_material->shader = shader;
|
|
||||||
cube_material->diffuse_map = cube_texture;
|
|
||||||
|
|
||||||
std::vector<glm::vec3> positions{
|
|
||||||
{ 0.0f, 0.0f, 0.0f}, { 2.0f, 5.0f, -15.0f}, {-1.5f, -2.2f, -2.5f},
|
{ 0.0f, 0.0f, 0.0f}, { 2.0f, 5.0f, -15.0f}, {-1.5f, -2.2f, -2.5f},
|
||||||
{-3.8f, -2.0f, -12.3f}, { 2.4f, -0.4f, -3.5f}, {-1.7f, 3.0f, -7.5f},
|
{-3.8f, -2.0f, -12.3f}, { 2.4f, -0.4f, -3.5f}, {-1.7f, 3.0f, -7.5f},
|
||||||
{ 1.3f, -2.0f, -2.5f}, { 1.5f, 2.0f, -2.5f}, { 1.5f, 0.2f, -1.5f},
|
{ 1.3f, -2.0f, -2.5f}, { 1.5f, 2.0f, -2.5f}, { 1.5f, 0.2f, -1.5f},
|
||||||
@@ -195,92 +264,110 @@ int main()
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (size_t i = 0; i < positions.size(); ++i) {
|
for (size_t i = 0; i < positions.size(); ++i) {
|
||||||
Entity entity{cube_mesh, cube_material};
|
Entity cube{cube_mesh, material};
|
||||||
entity.transform.position = positions[i];
|
cube.transform.position = positions[i];
|
||||||
entity.transform.set_rotation(glm::radians(20.0f * i), {1.0f, 0.3f, 0.5f});
|
cube.transform.set_rotation(glm::radians(20.0f * i), {1.0f, 0.3f, 0.5f});
|
||||||
cubes_scene->add_entity(entity);
|
scene->add_entity(cube);
|
||||||
}
|
}
|
||||||
|
|
||||||
add_random_lights(*cubes_scene, 4, 4,
|
add_random_lights(*scene, 4, 4, 152533);
|
||||||
glm::vec3(-10.0f, 1.0f, -3.0f), glm::vec3(10.0f, 5.0f, 3.0f),
|
return scene;
|
||||||
152533);
|
|
||||||
|
|
||||||
/******************** Main loop ********************/
|
|
||||||
|
|
||||||
while (!window->should_close()) {
|
|
||||||
window->begin();
|
|
||||||
window->update();
|
|
||||||
|
|
||||||
user_data.delta_time = window->get_delta_time();
|
|
||||||
user_data.frame_times.push(user_data.delta_time * 1000.0f);
|
|
||||||
process_input(*window, camera, user_data);
|
|
||||||
|
|
||||||
Renderer::set_clear_color({0.0f, 0.0f, 0.0f, 1.0f});
|
|
||||||
Renderer::clear();
|
|
||||||
|
|
||||||
// UI Layer
|
|
||||||
setup_dockspace();
|
|
||||||
draw_panel_window(user_data);
|
|
||||||
draw_viewport_window(*framebuffer, user_data);
|
|
||||||
|
|
||||||
// Scene Rendering Layer
|
|
||||||
const Scene& scene = *user_data.scenes[user_data.selected_scene_index];
|
|
||||||
|
|
||||||
int width = framebuffer->get_width();
|
|
||||||
int height = framebuffer->get_height();
|
|
||||||
framebuffer->bind();
|
|
||||||
user_data.scene_cpu_timer.begin();
|
|
||||||
if (user_data.scene_gpu_timer) { user_data.scene_gpu_timer->begin(); }
|
|
||||||
|
|
||||||
Renderer::set_viewport(0, 0, width, height);
|
|
||||||
Renderer::clear();
|
|
||||||
Renderer::draw_scene(scene, camera, width, height);
|
|
||||||
if (user_data.show_debug) {
|
|
||||||
Renderer::draw_debug_wireframes(scene, camera, width, height,
|
|
||||||
debug_shader, ResourceCache::get_fallback_mesh());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user_data.scene_gpu_timer) { user_data.scene_gpu_timer->end(); }
|
|
||||||
user_data.scene_cpu_timer.end();
|
|
||||||
framebuffer->unbind();
|
|
||||||
|
|
||||||
user_data.scene_cpu_times.push(user_data.scene_cpu_timer.get_elapsed_ms());
|
|
||||||
if (user_data.scene_gpu_timer) {
|
|
||||||
user_data.scene_gpu_times.push(user_data.scene_gpu_timer->get_elapsed_ms());
|
|
||||||
}
|
|
||||||
|
|
||||||
window->end();
|
|
||||||
}
|
|
||||||
|
|
||||||
Renderer::shutdown();
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void process_input(Window& window, Camera& camera, UserData& user_data)
|
Rc<Material> create_textured_material(const Rc<Shader>& shader, const std::string& texture_path)
|
||||||
{
|
{
|
||||||
|
auto material = make_rc<Material>();
|
||||||
|
material->shader = shader;
|
||||||
|
material->diffuse_map = ResourceCache::load_texture(texture_path);
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_random_lights(Scene& scene, int point_light_count, int spot_light_count, unsigned int seed)
|
||||||
|
{
|
||||||
|
std::mt19937 rng(seed);
|
||||||
|
|
||||||
|
std::uniform_real_distribution<float> random_x(LIGHT_BOX_MIN.x, LIGHT_BOX_MAX.x);
|
||||||
|
std::uniform_real_distribution<float> random_y(LIGHT_BOX_MIN.y, LIGHT_BOX_MAX.y);
|
||||||
|
std::uniform_real_distribution<float> random_z(LIGHT_BOX_MIN.z, LIGHT_BOX_MAX.z);
|
||||||
|
std::uniform_real_distribution<float> random_axis(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
const glm::vec3 light_colors[] = {
|
||||||
|
glm::vec3(1.0f, 1.0f, 1.0f), // White
|
||||||
|
glm::vec3(1.0f, 0.6f, 0.2f), // Warm orange
|
||||||
|
glm::vec3(0.2f, 0.5f, 1.0f), // Cool blue
|
||||||
|
glm::vec3(1.0f, 0.2f, 0.2f), // Red
|
||||||
|
glm::vec3(0.3f, 1.0f, 0.4f), // Green
|
||||||
|
glm::vec3(0.8f, 0.3f, 1.0f) // Purple
|
||||||
|
};
|
||||||
|
std::uniform_int_distribution<size_t> random_color_index(0, std::size(light_colors) - 1);
|
||||||
|
|
||||||
|
auto random_position = [&]() {
|
||||||
|
return glm::vec3(random_x(rng), random_y(rng), random_z(rng));
|
||||||
|
};
|
||||||
|
|
||||||
|
auto random_direction = [&]() {
|
||||||
|
glm::vec3 direction;
|
||||||
|
|
||||||
|
// Avoid generating a vector that is too close to zero
|
||||||
|
do {
|
||||||
|
direction = glm::vec3(random_axis(rng), random_axis(rng), random_axis(rng));
|
||||||
|
} while (glm::dot(direction, direction) < 0.001f);
|
||||||
|
|
||||||
|
return glm::normalize(direction);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto random_color = [&]() { return light_colors[random_color_index(rng)]; };
|
||||||
|
|
||||||
|
for (int i = 0; i < point_light_count; ++i) {
|
||||||
|
PointLight light;
|
||||||
|
light.position = random_position();
|
||||||
|
|
||||||
|
glm::vec3 color = random_color();
|
||||||
|
light.diffuse = color;
|
||||||
|
light.specular = glm::mix(color, glm::vec3(1.0f), 0.25f);
|
||||||
|
|
||||||
|
scene.light_environment.point_lights.push_back(light);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < spot_light_count; ++i) {
|
||||||
|
SpotLight light;
|
||||||
|
light.position = random_position();
|
||||||
|
light.direction = random_direction();
|
||||||
|
|
||||||
|
glm::vec3 color = random_color();
|
||||||
|
light.diffuse = color;
|
||||||
|
light.specular = glm::mix(color, glm::vec3(1.0f), 0.25f);
|
||||||
|
|
||||||
|
// Slightly wider soft outer cone than the default
|
||||||
|
light.outer_cut_off = glm::cos(glm::radians(17.5f));
|
||||||
|
|
||||||
|
scene.light_environment.spot_lights.push_back(light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************** Frame ********************/
|
||||||
|
|
||||||
|
void process_input(AppState& app)
|
||||||
|
{
|
||||||
|
Window& window = app.window;
|
||||||
|
CameraInput& input = app.input;
|
||||||
|
|
||||||
if (window.is_key_pressed(GLFW_KEY_ESCAPE)) window.set_should_close(true);
|
if (window.is_key_pressed(GLFW_KEY_ESCAPE)) window.set_should_close(true);
|
||||||
|
if (window.is_key_pressed(GLFW_KEY_H)) Renderer::set_wireframe(!Renderer::is_wireframe());
|
||||||
static bool wireframe = false;
|
if (window.is_key_pressed(GLFW_KEY_F11)) window.set_fullscreen(!window.is_fullscreen());
|
||||||
if (window.is_key_pressed(GLFW_KEY_H)) {
|
|
||||||
wireframe = !wireframe;
|
|
||||||
Renderer::set_wireframe(wireframe);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool fullscreen = false;
|
|
||||||
if (window.is_key_pressed(GLFW_KEY_F11)) {
|
|
||||||
fullscreen = !fullscreen;
|
|
||||||
window.set_fullscreen(fullscreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.is_key_pressed(GLFW_KEY_INSERT)) {
|
if (window.is_key_pressed(GLFW_KEY_INSERT)) {
|
||||||
user_data.mouse_grab = !user_data.mouse_grab;
|
input.mouse_grab = !input.mouse_grab;
|
||||||
user_data.first_mouse = true;
|
input.first_mouse = true;
|
||||||
window.set_mouse_grab(user_data.mouse_grab);
|
window.set_mouse_grab(input.mouse_grab);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user_data.mouse_grab) return;
|
if (!input.mouse_grab) return;
|
||||||
|
|
||||||
|
Camera& camera = app.camera;
|
||||||
|
float boost = window.is_key_down(GLFW_KEY_LEFT_SHIFT) ? 2.0f : 1.0f;
|
||||||
|
float velocity = input.speed * window.get_delta_time() * boost;
|
||||||
|
|
||||||
float velocity = user_data.speed * user_data.delta_time * (window.is_key_down(GLFW_KEY_LEFT_SHIFT) ? 2.0f : 1.0f);
|
|
||||||
if (window.is_key_down(GLFW_KEY_W)) camera.position += camera.get_front() * velocity;
|
if (window.is_key_down(GLFW_KEY_W)) camera.position += camera.get_front() * velocity;
|
||||||
if (window.is_key_down(GLFW_KEY_S)) camera.position -= camera.get_front() * velocity;
|
if (window.is_key_down(GLFW_KEY_S)) camera.position -= camera.get_front() * velocity;
|
||||||
if (window.is_key_down(GLFW_KEY_A)) camera.position -= camera.get_right() * velocity;
|
if (window.is_key_down(GLFW_KEY_A)) camera.position -= camera.get_right() * velocity;
|
||||||
@@ -289,28 +376,60 @@ void process_input(Window& window, Camera& camera, UserData& user_data)
|
|||||||
|
|
||||||
void mouse_callback(double pos_x, double pos_y, void* user_data)
|
void mouse_callback(double pos_x, double pos_y, void* user_data)
|
||||||
{
|
{
|
||||||
auto* data = static_cast<UserData*>(user_data);
|
AppState& app = *static_cast<AppState*>(user_data);
|
||||||
if (!data->mouse_grab) return;
|
CameraInput& input = app.input;
|
||||||
|
if (!input.mouse_grab) return;
|
||||||
|
|
||||||
if (data->first_mouse) {
|
if (input.first_mouse) {
|
||||||
data->last_mouse_x = pos_x;
|
input.last_mouse_x = pos_x;
|
||||||
data->last_mouse_y = pos_y;
|
input.last_mouse_y = pos_y;
|
||||||
data->first_mouse = false;
|
input.first_mouse = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float delta_x = (pos_x - data->last_mouse_x) * data->sensitivity;
|
float delta_x = (pos_x - input.last_mouse_x) * input.sensitivity;
|
||||||
float delta_y = (data->last_mouse_y - pos_y) * data->sensitivity;
|
float delta_y = (input.last_mouse_y - pos_y) * input.sensitivity;
|
||||||
|
|
||||||
data->last_mouse_x = pos_x;
|
input.last_mouse_x = pos_x;
|
||||||
data->last_mouse_y = pos_y;
|
input.last_mouse_y = pos_y;
|
||||||
data->camera.rotate(delta_x, delta_y);
|
app.camera.rotate(delta_x, delta_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void render_scene(AppState& app, FrameBuffer& framebuffer, const Rc<Shader>& debug_shader)
|
||||||
|
{
|
||||||
|
const Scene& scene = *app.scenes[app.selected_scene_index];
|
||||||
|
PerformanceStats& stats = app.stats;
|
||||||
|
|
||||||
|
int width = framebuffer.get_width();
|
||||||
|
int height = framebuffer.get_height();
|
||||||
|
|
||||||
|
framebuffer.bind();
|
||||||
|
stats.cpu_timer.begin();
|
||||||
|
if (stats.gpu_timer) { stats.gpu_timer->begin(); }
|
||||||
|
|
||||||
|
Renderer::set_viewport(0, 0, width, height);
|
||||||
|
Renderer::clear();
|
||||||
|
Renderer::draw_scene(scene, app.camera, width, height);
|
||||||
|
if (app.show_debug) {
|
||||||
|
Renderer::draw_debug_wireframes(scene, app.camera, width, height,
|
||||||
|
debug_shader, ResourceCache::get_fallback_mesh());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stats.gpu_timer) { stats.gpu_timer->end(); }
|
||||||
|
stats.cpu_timer.end();
|
||||||
|
framebuffer.unbind();
|
||||||
|
|
||||||
|
stats.scene_cpu_times.push(stats.cpu_timer.get_elapsed_ms());
|
||||||
|
if (stats.gpu_timer) { stats.scene_gpu_times.push(stats.gpu_timer->get_elapsed_ms()); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************** UI ********************/
|
||||||
|
|
||||||
void setup_dockspace()
|
void setup_dockspace()
|
||||||
{
|
{
|
||||||
ImGuiID dockspace_id = ImGui::GetID("Dockspace");
|
ImGuiID dockspace_id = ImGui::GetID("Dockspace");
|
||||||
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||||
|
|
||||||
|
// Build the default layout once: panel on the left, viewport on the right
|
||||||
static bool first_time = true;
|
static bool first_time = true;
|
||||||
if (first_time) {
|
if (first_time) {
|
||||||
first_time = false;
|
first_time = false;
|
||||||
@@ -329,44 +448,45 @@ void setup_dockspace()
|
|||||||
ImGui::DockSpaceOverViewport(dockspace_id, viewport, ImGuiDockNodeFlags_None);
|
ImGui::DockSpaceOverViewport(dockspace_id, viewport, ImGuiDockNodeFlags_None);
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_panel_window(UserData& user_data)
|
void draw_panel_window(AppState& app)
|
||||||
{
|
{
|
||||||
ImGui::Begin("Panel");
|
ImGui::Begin("Panel");
|
||||||
|
|
||||||
if (user_data.logo) ImGui::Image(user_data.logo->get_id(), {50.0f, 50.0f}, {0.0f, 1.0f}, {1.0f, 0.0f});
|
// Header: logo with the title vertically centered next to it
|
||||||
|
if (app.logo) ImGui::Image(app.logo->get_id(), {50.0f, 50.0f}, {0.0f, 1.0f}, {1.0f, 0.0f});
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (50.0f - ImGui::GetTextLineHeight()) * 0.5f);
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (50.0f - ImGui::GetTextLineHeight()) * 0.5f);
|
||||||
ImGui::Text("Fan Engine");
|
ImGui::Text("Fan Engine");
|
||||||
|
|
||||||
ImGui::SeparatorText("General Settings");
|
ImGui::SeparatorText("General Settings");
|
||||||
|
|
||||||
if (ImGui::Checkbox("VSync", &user_data.vsync)) {
|
if (ImGui::Checkbox("VSync", &app.vsync)) {
|
||||||
user_data.window.set_vsync(user_data.vsync);
|
app.window.set_vsync(app.vsync);
|
||||||
}
|
}
|
||||||
ImGui::Checkbox("Debug Wireframes", &user_data.show_debug);
|
ImGui::Checkbox("Debug Wireframes", &app.show_debug);
|
||||||
|
|
||||||
ImGui::SeparatorText("Scene Selector");
|
ImGui::SeparatorText("Scene Selector");
|
||||||
|
|
||||||
for (size_t i = 0; i < user_data.scenes.size(); ++i) {
|
for (size_t i = 0; i < app.scenes.size(); ++i) {
|
||||||
char label[64];
|
char label[64];
|
||||||
ImFormatString(label, IM_ARRAYSIZE(label), "Scene %zu", i);
|
ImFormatString(label, IM_ARRAYSIZE(label), "Scene %zu", i);
|
||||||
if (ImGui::RadioButton(label, user_data.selected_scene_index == i)) {
|
if (ImGui::RadioButton(label, app.selected_scene_index == i)) {
|
||||||
user_data.selected_scene_index = i;
|
app.selected_scene_index = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SeparatorText("Camera Settings");
|
ImGui::SeparatorText("Camera Settings");
|
||||||
|
|
||||||
ImGui::DragFloat3("Position", &user_data.camera.position.x, 0.1f, -20.0f, 20.0f, "%.1f");
|
ImGui::DragFloat3("Position", &app.camera.position.x, 0.1f, -20.0f, 20.0f, "%.1f");
|
||||||
ImGui::SliderFloat("FOV", &user_data.camera.fov, 30.0f, 100.0f, "%.1f");
|
ImGui::SliderFloat("FOV", &app.camera.fov, 30.0f, 100.0f, "%.1f");
|
||||||
|
|
||||||
ImGui::SeparatorText("Performance");
|
ImGui::SeparatorText("Performance");
|
||||||
|
|
||||||
ImGui::TextDisabled("Disable VSync for accurate frame times");
|
ImGui::TextDisabled("Disable VSync for accurate frame times");
|
||||||
plot_metric("Frame", user_data.frame_times);
|
plot_metric("Frame", app.stats.frame_times);
|
||||||
plot_metric("Scene CPU", user_data.scene_cpu_times);
|
plot_metric("Scene CPU", app.stats.scene_cpu_times);
|
||||||
if (user_data.scene_gpu_timer) {
|
if (app.stats.gpu_timer) {
|
||||||
plot_metric("Scene GPU", user_data.scene_gpu_times);
|
plot_metric("Scene GPU", app.stats.scene_gpu_times);
|
||||||
} else {
|
} else {
|
||||||
ImGui::TextDisabled("Scene GPU: timer queries not supported");
|
ImGui::TextDisabled("Scene GPU: timer queries not supported");
|
||||||
}
|
}
|
||||||
@@ -382,7 +502,7 @@ void draw_panel_window(UserData& user_data)
|
|||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_viewport_window(FrameBuffer& framebuffer, UserData& user_data)
|
void draw_viewport_window(AppState& app, FrameBuffer& framebuffer)
|
||||||
{
|
{
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{0.0f, 0.0f});
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{0.0f, 0.0f});
|
||||||
ImGui::Begin("Viewport");
|
ImGui::Begin("Viewport");
|
||||||
@@ -391,32 +511,48 @@ void draw_viewport_window(FrameBuffer& framebuffer, UserData& user_data)
|
|||||||
if (size.x > 0 && size.y > 0) {
|
if (size.x > 0 && size.y > 0) {
|
||||||
// Size the framebuffer in physical pixels so HiDPI displays render at full resolution
|
// Size the framebuffer in physical pixels so HiDPI displays render at full resolution
|
||||||
ImVec2 scale = ImGui::GetIO().DisplayFramebufferScale;
|
ImVec2 scale = ImGui::GetIO().DisplayFramebufferScale;
|
||||||
GLsizei fb_width = static_cast<GLsizei>(size.x * scale.x);
|
GLsizei width = static_cast<GLsizei>(size.x * scale.x);
|
||||||
GLsizei fb_height = static_cast<GLsizei>(size.y * scale.y);
|
GLsizei height = static_cast<GLsizei>(size.y * scale.y);
|
||||||
if (fb_width != framebuffer.get_width() || fb_height != framebuffer.get_height()) {
|
if (width != framebuffer.get_width() || height != framebuffer.get_height()) {
|
||||||
framebuffer.resize(fb_width, fb_height);
|
framebuffer.resize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flip vertically: OpenGL textures start at the bottom row
|
||||||
ImGui::Image(framebuffer.get_texture_id(), size, ImVec2{0.0f, 1.0f}, ImVec2{1.0f, 0.0f});
|
ImGui::Image(framebuffer.get_texture_id(), size, ImVec2{0.0f, 1.0f}, ImVec2{1.0f, 0.0f});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overlays (averaged over the last MetricHistory::SIZE frames so they're readable)
|
draw_overlay(app);
|
||||||
float frame_ms = user_data.frame_times.average();
|
|
||||||
float fps = frame_ms > 0.0f ? 1000.0f / frame_ms : 0.0f;
|
|
||||||
overlay_text(20.0f, 35.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "%s", VERSION_STRING);
|
|
||||||
overlay_text(20.0f, 52.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "FPS: %.1f", fps);
|
|
||||||
overlay_text(20.0f, 69.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "Frame time: %.2f ms", frame_ms);
|
|
||||||
overlay_text(20.0f, 86.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "Scene CPU: %.2f ms", user_data.scene_cpu_times.average());
|
|
||||||
if (user_data.scene_gpu_timer) {
|
|
||||||
overlay_text(20.0f, 103.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "Scene GPU: %.2f ms", user_data.scene_gpu_times.average());
|
|
||||||
} else {
|
|
||||||
overlay_text(20.0f, 103.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "%s", "Scene GPU: n/a");
|
|
||||||
}
|
|
||||||
if (user_data.mouse_grab) { overlay_text(20.0f, 120.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "%s", "INPUT LOCKED"); }
|
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void draw_overlay(const AppState& app)
|
||||||
|
{
|
||||||
|
const PerformanceStats& stats = app.stats;
|
||||||
|
|
||||||
|
// Averaged over the last MetricHistory::SIZE frames so the numbers are readable
|
||||||
|
float frame_ms = stats.frame_times.average();
|
||||||
|
float fps = frame_ms > 0.0f ? 1000.0f / frame_ms : 0.0f;
|
||||||
|
|
||||||
|
float y = OVERLAY_Y;
|
||||||
|
auto line = [&y](const char* format, auto... args) {
|
||||||
|
overlay_text(OVERLAY_X, y, format, args...);
|
||||||
|
y += OVERLAY_LINE_HEIGHT;
|
||||||
|
};
|
||||||
|
|
||||||
|
line("%s", VERSION_STRING);
|
||||||
|
line("FPS: %.1f", fps);
|
||||||
|
line("Frame time: %.2f ms", frame_ms);
|
||||||
|
line("Scene CPU: %.2f ms", stats.scene_cpu_times.average());
|
||||||
|
if (stats.gpu_timer) {
|
||||||
|
line("Scene GPU: %.2f ms", stats.scene_gpu_times.average());
|
||||||
|
} else {
|
||||||
|
line("%s", "Scene GPU: n/a");
|
||||||
|
}
|
||||||
|
if (app.input.mouse_grab) { line("%s", "INPUT LOCKED"); }
|
||||||
|
}
|
||||||
|
|
||||||
void plot_metric(const char* label, const MetricHistory& history)
|
void plot_metric(const char* label, const MetricHistory& history)
|
||||||
{
|
{
|
||||||
char overlay[32];
|
char overlay[32];
|
||||||
@@ -426,74 +562,3 @@ void plot_metric(const char* label, const MetricHistory& history)
|
|||||||
ImGui::PlotLines(label, history.values.data(), static_cast<int>(history.values.size()),
|
ImGui::PlotLines(label, history.values.data(), static_cast<int>(history.values.size()),
|
||||||
static_cast<int>(history.offset), overlay, 0.0f, FLT_MAX, ImVec2{0.0f, 40.0f});
|
static_cast<int>(history.offset), overlay, 0.0f, FLT_MAX, ImVec2{0.0f, 40.0f});
|
||||||
}
|
}
|
||||||
|
|
||||||
void add_random_lights(Scene& scene, int point_light_count, int spot_light_count,
|
|
||||||
const glm::vec3& box_min, const glm::vec3& box_max,
|
|
||||||
unsigned int seed/* = std::random_device{}()*/)
|
|
||||||
{
|
|
||||||
std::mt19937 rng(seed);
|
|
||||||
|
|
||||||
std::uniform_real_distribution<float> random_x(box_min.x, box_max.x);
|
|
||||||
std::uniform_real_distribution<float> random_y(box_min.y, box_max.y);
|
|
||||||
std::uniform_real_distribution<float> random_z(box_min.z, box_max.z);
|
|
||||||
|
|
||||||
std::uniform_real_distribution<float> random_direction(-1.0f, 1.0f);
|
|
||||||
std::uniform_real_distribution<float> random_intensity(0.5f, 1.5f);
|
|
||||||
|
|
||||||
const glm::vec3 light_colors[] = {
|
|
||||||
glm::vec3(1.0f, 1.0f, 1.0f), // White
|
|
||||||
glm::vec3(1.0f, 0.6f, 0.2f), // Warm orange
|
|
||||||
glm::vec3(0.2f, 0.5f, 1.0f), // Cool blue
|
|
||||||
glm::vec3(1.0f, 0.2f, 0.2f), // Red
|
|
||||||
glm::vec3(0.3f, 1.0f, 0.4f), // Green
|
|
||||||
glm::vec3(0.8f, 0.3f, 1.0f) // Purple
|
|
||||||
};
|
|
||||||
|
|
||||||
std::uniform_int_distribution<size_t> randomColor(0, std::size(light_colors) - 1);
|
|
||||||
|
|
||||||
auto generateDirection = [&]() {
|
|
||||||
glm::vec3 direction;
|
|
||||||
|
|
||||||
// Avoid generating a vector that is too close to zero.
|
|
||||||
do {
|
|
||||||
direction = glm::vec3(random_direction(rng), random_direction(rng), random_direction(rng));
|
|
||||||
} while (glm::dot(direction, direction) < 0.001f);
|
|
||||||
|
|
||||||
return glm::normalize(direction);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto generateColor = [&]() { return light_colors[randomColor(rng)]; };
|
|
||||||
|
|
||||||
// Generate point lights
|
|
||||||
for (int i = 0; i < point_light_count; ++i) {
|
|
||||||
PointLight light;
|
|
||||||
|
|
||||||
light.position = glm::vec3(random_x(rng), random_y(rng), random_z(rng));
|
|
||||||
glm::vec3 color = generateColor();
|
|
||||||
light.diffuse = color;
|
|
||||||
light.specular = glm::mix(color, glm::vec3(1.0f), 0.25f);
|
|
||||||
|
|
||||||
scene.light_environment.point_lights.push_back(light);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate spot lights
|
|
||||||
for (int i = 0; i < spot_light_count; ++i) {
|
|
||||||
SpotLight light;
|
|
||||||
|
|
||||||
light.position = glm::vec3(random_x(rng), random_y(rng), random_z(rng));
|
|
||||||
light.direction = generateDirection();
|
|
||||||
glm::vec3 color = generateColor();
|
|
||||||
light.ambient = glm::vec3(0.0f);
|
|
||||||
light.diffuse = color;
|
|
||||||
light.specular = glm::mix(color, glm::vec3(1.0f), 0.25f);
|
|
||||||
|
|
||||||
// Narrow inner cone and slightly wider soft outer cone
|
|
||||||
light.cut_off = glm::cos(glm::radians(12.5f));
|
|
||||||
light.outer_cut_off = glm::cos(glm::radians(17.5f));
|
|
||||||
light.constant = 1.0f;
|
|
||||||
light.linear = 0.09f;
|
|
||||||
light.quadratic = 0.032f;
|
|
||||||
|
|
||||||
scene.light_environment.spot_lights.push_back(light);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
10
src/mesh.cpp
10
src/mesh.cpp
@@ -39,6 +39,11 @@ Rc<Mesh> Mesh::create(const std::vector<Vertex>& vertices,
|
|||||||
|
|
||||||
gl_clear_errors();
|
gl_clear_errors();
|
||||||
|
|
||||||
|
// Restored afterwards so creating a mesh leaves the caller's bindings intact
|
||||||
|
// (the index buffer binding is part of the VAO, restoring the VAO covers it)
|
||||||
|
GLuint previous_vao = gl_get_binding(GL_VERTEX_ARRAY_BINDING);
|
||||||
|
GLuint previous_vbo = gl_get_binding(GL_ARRAY_BUFFER_BINDING);
|
||||||
|
|
||||||
GLuint VBO{}, VAO{}, EBO{};
|
GLuint VBO{}, VAO{}, EBO{};
|
||||||
glGenBuffers(1, &VBO);
|
glGenBuffers(1, &VBO);
|
||||||
glGenVertexArrays(1, &VAO);
|
glGenVertexArrays(1, &VAO);
|
||||||
@@ -66,8 +71,9 @@ Rc<Mesh> Mesh::create(const std::vector<Vertex>& vertices,
|
|||||||
glEnableVertexAttribArray(1);
|
glEnableVertexAttribArray(1);
|
||||||
glEnableVertexAttribArray(2);
|
glEnableVertexAttribArray(2);
|
||||||
|
|
||||||
// Unbind so later GL_ELEMENT_ARRAY_BUFFER binds can't modify this VAO
|
// Also stops later GL_ELEMENT_ARRAY_BUFFER binds from modifying this VAO
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(previous_vao);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, previous_vbo);
|
||||||
|
|
||||||
// Buffer uploads can fail with GL_OUT_OF_MEMORY on large meshes
|
// Buffer uploads can fail with GL_OUT_OF_MEMORY on large meshes
|
||||||
GLenum err = gl_pop_error();
|
GLenum err = gl_pop_error();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace Fan
|
|||||||
|
|
||||||
Rc<UniformBuffer> Renderer::s_camera_ubo = nullptr;
|
Rc<UniformBuffer> Renderer::s_camera_ubo = nullptr;
|
||||||
Rc<UniformBuffer> Renderer::s_light_ubo = nullptr;
|
Rc<UniformBuffer> Renderer::s_light_ubo = nullptr;
|
||||||
|
bool Renderer::s_wireframe = false;
|
||||||
|
|
||||||
bool Renderer::init()
|
bool Renderer::init()
|
||||||
{
|
{
|
||||||
@@ -52,9 +53,15 @@ void Renderer::set_clear_color(const glm::vec4& color)
|
|||||||
|
|
||||||
void Renderer::set_wireframe(bool enabled)
|
void Renderer::set_wireframe(bool enabled)
|
||||||
{
|
{
|
||||||
|
s_wireframe = enabled;
|
||||||
glPolygonMode(GL_FRONT_AND_BACK, enabled ? GL_LINE : GL_FILL);
|
glPolygonMode(GL_FRONT_AND_BACK, enabled ? GL_LINE : GL_FILL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Renderer::is_wireframe()
|
||||||
|
{
|
||||||
|
return s_wireframe;
|
||||||
|
}
|
||||||
|
|
||||||
void Renderer::clear()
|
void Renderer::clear()
|
||||||
{
|
{
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
@@ -129,6 +136,7 @@ void Renderer::draw_debug_wireframes(const Scene& scene, const Camera& camera,
|
|||||||
debug_shader->bind();
|
debug_shader->bind();
|
||||||
debug_shader->set_mat4("u_view_projection", camera.get_view_projection_matrix(screen_width, screen_height));
|
debug_shader->set_mat4("u_view_projection", camera.get_view_projection_matrix(screen_width, screen_height));
|
||||||
|
|
||||||
|
bool previous_wireframe = s_wireframe;
|
||||||
set_wireframe(true);
|
set_wireframe(true);
|
||||||
|
|
||||||
// Draw all standard entities (green)
|
// Draw all standard entities (green)
|
||||||
@@ -161,8 +169,7 @@ void Renderer::draw_debug_wireframes(const Scene& scene, const Camera& camera,
|
|||||||
default_mesh->draw();
|
default_mesh->draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore state
|
set_wireframe(previous_wireframe);
|
||||||
set_wireframe(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::update_global_data(const Scene& scene, const Camera& camera,
|
void Renderer::update_global_data(const Scene& scene, const Camera& camera,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public:
|
|||||||
static void set_viewport(int x, int y, int width, int height);
|
static void set_viewport(int x, int y, int width, int height);
|
||||||
static void set_clear_color(const glm::vec4& color);
|
static void set_clear_color(const glm::vec4& color);
|
||||||
static void set_wireframe(bool enabled); // Global, stays on until disabled
|
static void set_wireframe(bool enabled); // Global, stays on until disabled
|
||||||
|
static bool is_wireframe();
|
||||||
|
|
||||||
static void clear();
|
static void clear();
|
||||||
|
|
||||||
@@ -28,7 +29,8 @@ public:
|
|||||||
int screen_width, int screen_height);
|
int screen_width, int screen_height);
|
||||||
|
|
||||||
// debug_shader needs u_model, u_view_projection and u_color. default_mesh is
|
// debug_shader needs u_model, u_view_projection and u_color. default_mesh is
|
||||||
// drawn for entities without a mesh and for lights. Disables wireframe when done
|
// drawn for entities without a mesh and for lights. Restores the previous
|
||||||
|
// wireframe mode when done
|
||||||
static void draw_debug_wireframes(const Scene& scene, const Camera& camera,
|
static void draw_debug_wireframes(const Scene& scene, const Camera& camera,
|
||||||
int screen_width, int screen_height,
|
int screen_width, int screen_height,
|
||||||
const Rc<Shader>& debug_shader,
|
const Rc<Shader>& debug_shader,
|
||||||
@@ -37,6 +39,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
static Rc<UniformBuffer> s_camera_ubo;
|
static Rc<UniformBuffer> s_camera_ubo;
|
||||||
static Rc<UniformBuffer> s_light_ubo;
|
static Rc<UniformBuffer> s_light_ubo;
|
||||||
|
static bool s_wireframe;
|
||||||
|
|
||||||
static void update_global_data(const Scene& scene, const Camera& camera,
|
static void update_global_data(const Scene& scene, const Camera& camera,
|
||||||
int screen_width, int screen_height);
|
int screen_width, int screen_height);
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ Rc<Texture> Texture::create_from_pixels(GLsizei width, GLsizei height,
|
|||||||
|
|
||||||
gl_clear_errors();
|
gl_clear_errors();
|
||||||
|
|
||||||
|
GLuint previous_texture = gl_get_binding(GL_TEXTURE_BINDING_2D);
|
||||||
|
|
||||||
GLuint texture;
|
GLuint texture;
|
||||||
glGenTextures(1, &texture);
|
glGenTextures(1, &texture);
|
||||||
glBindTexture(GL_TEXTURE_2D, texture);
|
glBindTexture(GL_TEXTURE_2D, texture);
|
||||||
@@ -56,6 +58,7 @@ Rc<Texture> Texture::create_from_pixels(GLsizei width, GLsizei height,
|
|||||||
|
|
||||||
// Upload can fail with GL_OUT_OF_MEMORY on large images
|
// Upload can fail with GL_OUT_OF_MEMORY on large images
|
||||||
GLenum err = gl_pop_error();
|
GLenum err = gl_pop_error();
|
||||||
|
glBindTexture(GL_TEXTURE_2D, previous_texture);
|
||||||
if (err != GL_NO_ERROR) {
|
if (err != GL_NO_ERROR) {
|
||||||
Log::error() << "Could not upload texture " << name << ": " << gl_error_string(err);
|
Log::error() << "Could not upload texture " << name << ": " << gl_error_string(err);
|
||||||
glDeleteTextures(1, &texture);
|
glDeleteTextures(1, &texture);
|
||||||
@@ -97,12 +100,16 @@ void Texture::bind(GLuint slot/* = 0*/) const
|
|||||||
{
|
{
|
||||||
glActiveTexture(GL_TEXTURE0 + slot);
|
glActiveTexture(GL_TEXTURE0 + slot);
|
||||||
glBindTexture(GL_TEXTURE_2D, m_id);
|
glBindTexture(GL_TEXTURE_2D, m_id);
|
||||||
|
|
||||||
|
// Keep unit 0 active so later texture binds can't land on this slot
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Texture::unbind(GLuint slot/* = 0*/)
|
void Texture::unbind(GLuint slot/* = 0*/)
|
||||||
{
|
{
|
||||||
glActiveTexture(GL_TEXTURE0 + slot);
|
glActiveTexture(GL_TEXTURE0 + slot);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Fan
|
} // namespace Fan
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public:
|
|||||||
|
|
||||||
GLuint get_id() const;
|
GLuint get_id() const;
|
||||||
|
|
||||||
// slot is the texture unit, which is left as the active unit
|
// slot is the texture unit. The active unit is reset to 0 afterwards
|
||||||
void bind(GLuint slot = 0) const;
|
void bind(GLuint slot = 0) const;
|
||||||
static void unbind(GLuint slot = 0);
|
static void unbind(GLuint slot = 0);
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ Rc<UniformBuffer> UniformBuffer::create(size_t size, GLuint binding_point)
|
|||||||
|
|
||||||
gl_clear_errors();
|
gl_clear_errors();
|
||||||
|
|
||||||
|
// glBindBufferBase also changes the generic binding, restore it afterwards
|
||||||
|
GLuint previous_buffer = gl_get_binding(GL_UNIFORM_BUFFER_BINDING);
|
||||||
|
|
||||||
GLuint id;
|
GLuint id;
|
||||||
|
|
||||||
glGenBuffers(1, &id);
|
glGenBuffers(1, &id);
|
||||||
@@ -33,7 +36,7 @@ Rc<UniformBuffer> UniformBuffer::create(size_t size, GLuint binding_point)
|
|||||||
|
|
||||||
glBufferData(GL_UNIFORM_BUFFER, size, nullptr, GL_DYNAMIC_DRAW);
|
glBufferData(GL_UNIFORM_BUFFER, size, nullptr, GL_DYNAMIC_DRAW);
|
||||||
glBindBufferBase(GL_UNIFORM_BUFFER, binding_point, id);
|
glBindBufferBase(GL_UNIFORM_BUFFER, binding_point, id);
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
glBindBuffer(GL_UNIFORM_BUFFER, previous_buffer);
|
||||||
|
|
||||||
GLenum err = gl_pop_error();
|
GLenum err = gl_pop_error();
|
||||||
if (err != GL_NO_ERROR) {
|
if (err != GL_NO_ERROR) {
|
||||||
@@ -80,9 +83,11 @@ void UniformBuffer::update(const void* data, size_t size, size_t offset) const
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLuint previous_buffer = gl_get_binding(GL_UNIFORM_BUFFER_BINDING);
|
||||||
|
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, m_id);
|
glBindBuffer(GL_UNIFORM_BUFFER, m_id);
|
||||||
glBufferSubData(GL_UNIFORM_BUFFER, offset, size, data);
|
glBufferSubData(GL_UNIFORM_BUFFER, offset, size, data);
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
glBindBuffer(GL_UNIFORM_BUFFER, previous_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Fan
|
} // namespace Fan
|
||||||
|
|||||||
Reference in New Issue
Block a user