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:
2026-10-28 21:42:08 +01:00
parent d081108ea5
commit a36ed3a902
10 changed files with 399 additions and 288 deletions

View File

@@ -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) {

View File

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

View File

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

View File

@@ -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(); } Rc<Material> create_textured_material(const Rc<Shader>& shader, const std::string& texture_path)
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)
{ {
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);
}
}

View File

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

View File

@@ -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,

View File

@@ -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);

View File

@@ -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

View File

@@ -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);

View File

@@ -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