diff --git a/CHANGES.rst b/CHANGES.rst index 491aae30..be4facf9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,95 @@ Revision history for CG_Labs +v2020.1 2020-09-27 +================== + +New features +------------ + +* Allow hiding the control points from the GUI in the second assignment of + EDAF80. +* Allow pausing the wave animation from the GUI in the fourth assignment of + EDAF80. + +Improvements +------------ + +* Several debug improvements: + - Ask for a debug context; + - Re-work which debug messages are enabled and disabled, and how push and pop + groups are printed. + - Always show shader compilation and link logs when available, rather than + only when it fialed; + - Label vertex arrays, buffers, and textures when importing new objects via + `bonobo::loadObjects()`; + - Add new debug helpers to reduce casting-related warning messages; +- Enable mipmaps even for opacity textures. + +Fixes +----- + +* Disable file logging when unable to open the file. +* Unlock the mutex on shutdown when file logging was disabled. +* Add missing includes. +* Tell MSVC to use UTF-8 for source and executable character sets. +* Fix more typos. +* Avoid mismatching type assignments in the basis vector shader. +* Fix framework deinitialisation in the first assignment of EDAF80. +* Address several compilation warnings. + + +v2021.0 2021-09-01 +================== + +New features +------------ + +* Time the different steps in `bonobo::loadObjects()` to easily determine which + parts are taking longer. +* Add helper code to render a right-handed orthonormal basis. +* Add elapsed time queries to the second assignment of EDAN35, allowing to + measure the time taken by the different passes. + +Breaking changes +---------------- + +* `bonobo::createProgram()` takes shader files relative to the “shaders/” folder. + +Improvements +------------ + +* Switch away from Travis CI and AppVeyor, and use GitHub Actions instead. +* Add a more detailed build guide. +* Avoid loading unused materials in `bonobo::loadObjects()` +* Several performance and code improvements in the second assignment of EDAN35. +* Various improvements to the `InputHandler` class, included a rework of the + modifier handling that should fix some issues. +* Label shader programs to help with debugging. +* Remove duplicated shaders. + +Fixes +----- + +* Fixed typos. +* Fix the rendering of shadow maps in the second assignment of EDAN35. +* Dereference a CMake variable before using it in a generator expression. + + +Removal +------- + +* Drop `GLStateInspection` and `GLStateInspectionView`; debuggers provide more + details than those and at a finer granularity. + +Dependencies updates +-------------------- + +* Update Dear ImGui to 1.84.2. +* Update the referenced commits for stb. +* Update the referenced commits for tinyfiledialogs. + + v2020.1 2020-09-29 ================== diff --git a/CMakeLists.txt b/CMakeLists.txt index 9aa73845..89470c20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ endif () # Set project attributes. project ( CG_Labs - VERSION 2020.1 + VERSION 2021.1 DESCRIPTION [[Repository for Computer Graphics courses EDAF80 and EDAN35 at Lund University, Sweden.]] HOMEPAGE_URL [[https://github.com/LUGGPublic/CG_Labs/]] LANGUAGES CXX C diff --git a/shaders/common/basis.frag b/shaders/common/basis.frag index 39c46fc4..883947ed 100644 --- a/shaders/common/basis.frag +++ b/shaders/common/basis.frag @@ -1,7 +1,7 @@ #version 410 in VS_OUT { - flat uint axis_index; + flat int axis_index; } vs_out; out vec4 frag_color; diff --git a/shaders/common/basis.vert b/shaders/common/basis.vert index ec44b01a..65f774ba 100644 --- a/shaders/common/basis.vert +++ b/shaders/common/basis.vert @@ -8,7 +8,7 @@ uniform float thickness_scale; uniform float length_scale; out VS_OUT { - flat uint axis_index; + flat int axis_index; } vs_out; diff --git a/src/EDAF80/assignment1.cpp b/src/EDAF80/assignment1.cpp index e50c2979..ee61f244 100644 --- a/src/EDAF80/assignment1.cpp +++ b/src/EDAF80/assignment1.cpp @@ -57,6 +57,8 @@ int main() if (objects.empty()) { LogError("Failed to load the sphere geometry: exiting."); + bonobo::deinit(); + return EXIT_FAILURE; } bonobo::mesh_data const& sphere = objects.front(); @@ -75,8 +77,7 @@ int main() if (celestial_body_shader == 0u) { LogError("Failed to generate the “Celestial Body” shader program: exiting."); - Log::View::Destroy(); - Log::Destroy(); + bonobo::deinit(); return EXIT_FAILURE; } @@ -88,8 +89,7 @@ int main() if (celestial_ring_shader == 0u) { LogError("Failed to generate the “Celestial Ring” shader program: exiting."); - Log::View::Destroy(); - Log::Destroy(); + bonobo::deinit(); return EXIT_FAILURE; } diff --git a/src/EDAF80/assignment2.cpp b/src/EDAF80/assignment2.cpp index a0162d01..a51d7210 100644 --- a/src/EDAF80/assignment2.cpp +++ b/src/EDAF80/assignment2.cpp @@ -121,6 +121,10 @@ edaf80::Assignment2::run() // always be changed at runtime through the "Scene Controls" window. bool interpolate = true; + // Set whether to show the control points or not; it can always be changed + // at runtime through the "Scene Controls" window. + bool show_control_points = true; + auto circle_rings = Node(); circle_rings.set_geometry(shape); circle_rings.set_program(&fallback_shader, set_uniforms); @@ -159,7 +163,7 @@ edaf80::Assignment2::run() auto lastTime = std::chrono::high_resolution_clock::now(); std::int32_t program_index = 0; - float ellapsed_time_s = 0.0f; + float elapsed_time_s = 0.0f; auto cull_mode = bonobo::cull_mode_t::disabled; auto polygon_mode = bonobo::polygon_mode_t::fill; bool show_logs = true; @@ -181,7 +185,7 @@ edaf80::Assignment2::run() glfwPollEvents(); inputHandler.Advance(); mCamera.Update(deltaTimeUs, inputHandler); - ellapsed_time_s += std::chrono::duration(deltaTimeUs).count(); + elapsed_time_s += std::chrono::duration(deltaTimeUs).count(); if (inputHandler.GetKeycodeState(GLFW_KEY_F3) & JUST_RELEASED) show_logs = !show_logs; @@ -225,8 +229,10 @@ edaf80::Assignment2::run() } circle_rings.render(mCamera.GetWorldToClipMatrix()); - for (auto const& control_point : control_points) { - control_point.render(mCamera.GetWorldToClipMatrix()); + if (show_control_points) { + for (auto const& control_point : control_points) { + control_point.render(mCamera.GetWorldToClipMatrix()); + } } bool const opened = ImGui::Begin("Scene Controls", nullptr, ImGuiWindowFlags_None); @@ -241,6 +247,7 @@ edaf80::Assignment2::run() circle_rings.set_program(selection_result.program, set_uniforms); } ImGui::Separator(); + ImGui::Checkbox("Show control points", &show_control_points); ImGui::Checkbox("Enable interpolation", &interpolate); ImGui::Checkbox("Use linear interpolation", &use_linear); ImGui::SliderFloat("Catmull-Rom tension", &catmull_rom_tension, 0.0f, 1.0f); diff --git a/src/EDAF80/assignment4.cpp b/src/EDAF80/assignment4.cpp index 3c6a4c1d..70de8907 100644 --- a/src/EDAF80/assignment4.cpp +++ b/src/EDAF80/assignment4.cpp @@ -62,7 +62,7 @@ edaf80::Assignment4::run() // (Check how it was done in assignment 3.) // - float ellapsed_time_s = 0.0f; + float elapsed_time_s = 0.0f; // // Todo: Load your geometry @@ -75,6 +75,7 @@ edaf80::Assignment4::run() auto lastTime = std::chrono::high_resolution_clock::now(); + bool pause_animation = true; auto cull_mode = bonobo::cull_mode_t::disabled; auto polygon_mode = bonobo::polygon_mode_t::fill; bool show_logs = true; @@ -90,7 +91,9 @@ edaf80::Assignment4::run() auto const nowTime = std::chrono::high_resolution_clock::now(); auto const deltaTimeUs = std::chrono::duration_cast(nowTime - lastTime); lastTime = nowTime; - ellapsed_time_s += std::chrono::duration(deltaTimeUs).count(); + if (!pause_animation) { + elapsed_time_s += std::chrono::duration(deltaTimeUs).count(); + } auto& io = ImGui::GetIO(); inputHandler.SetUICapture(io.WantCaptureMouse, io.WantCaptureKeyboard); @@ -155,6 +158,7 @@ edaf80::Assignment4::run() bool opened = ImGui::Begin("Scene Control", nullptr, ImGuiWindowFlags_None); if (opened) { + ImGui::Checkbox("Pause animation", &pause_animation); auto const cull_mode_changed = bonobo::uiSelectCullMode("Cull mode", cull_mode); if (cull_mode_changed) { changeCullMode(cull_mode); diff --git a/src/EDAF80/parametric_shapes.cpp b/src/EDAF80/parametric_shapes.cpp index c2f2cd88..2cc1dcfe 100644 --- a/src/EDAF80/parametric_shapes.cpp +++ b/src/EDAF80/parametric_shapes.cpp @@ -271,7 +271,7 @@ parametric_shapes::createCircleRing(float const radius, glBindBuffer(GL_ARRAY_BUFFER, 0u); - data.indices_nb = index_sets.size() * 3u; + data.indices_nb = static_cast(index_sets.size() * 3u); glGenBuffers(1, &data.ibo); assert(data.ibo != 0u); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.ibo); diff --git a/src/EDAN35/assignment2.cpp b/src/EDAN35/assignment2.cpp index 86c980c9..3d053999 100644 --- a/src/EDAN35/assignment2.cpp +++ b/src/EDAN35/assignment2.cpp @@ -262,6 +262,9 @@ edan35::Assignment2::run() glEnable(GL_CULL_FACE); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[toU(FBO::Resolve)]); + + auto seconds_nb = 0.0f; std::array pass_elapsed_times; auto lastTime = std::chrono::high_resolution_clock::now(); @@ -323,11 +326,7 @@ edan35::Assignment2::run() // // Pass 1: Render scene into the g-buffer // - if (utils::opengl::debug::isSupported()) - { - std::string const group_name = "Fill G-buffer"; - glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0u, group_name.size(), group_name.data()); - } + utils::opengl::debug::beginDebugGroup("Fill G-buffer"); glBeginQuery(GL_TIME_ELAPSED, elapsed_time_queries[toU(ElapsedTimeQuery::GbufferGeneration)]); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[toU(FBO::GBuffer)]); @@ -339,10 +338,7 @@ edan35::Assignment2::run() element.render(mCamera.GetWorldToClipMatrix(), element.get_transform().GetMatrix(), fill_gbuffer_shader, set_uniforms); glEndQuery(GL_TIME_ELAPSED); - if (utils::opengl::debug::isSupported()) - { - glPopDebugGroup(); - } + utils::opengl::debug::endDebugGroup(); @@ -361,11 +357,7 @@ edan35::Assignment2::run() // // Pass 2.1: Generate shadow map for light i // - if (utils::opengl::debug::isSupported()) - { - std::string const group_name = "Create shadow map " + std::to_string(i); - glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0u, group_name.size(), group_name.data()); - } + utils::opengl::debug::beginDebugGroup("Create shadow map " + std::to_string(i)); glBeginQuery(GL_TIME_ELAPSED, elapsed_time_queries[toU(ElapsedTimeQuery::ShadowMap0Generation) + i]); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[toU(FBO::ShadowMap)]); @@ -376,10 +368,7 @@ edan35::Assignment2::run() element.render(light_world_to_clip_matrix, element.get_transform().GetMatrix(), fill_shadowmap_shader, set_uniforms); glEndQuery(GL_TIME_ELAPSED); - if (utils::opengl::debug::isSupported()) - { - glPopDebugGroup(); - } + utils::opengl::debug::endDebugGroup(); glCullFace(GL_FRONT); @@ -390,11 +379,7 @@ edan35::Assignment2::run() glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, GL_ONE); // // Pass 2.2: Accumulate light i contribution - if (utils::opengl::debug::isSupported()) - { - std::string const group_name = "Accumulate light " + std::to_string(i); - glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0u, group_name.size(), group_name.data()); - } + utils::opengl::debug::beginDebugGroup("Accumulate light " + std::to_string(i)); glBeginQuery(GL_TIME_ELAPSED, elapsed_time_queries[toU(ElapsedTimeQuery::Light0Accumulation) + i]); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[toU(FBO::LightAccumulation)]); @@ -434,10 +419,7 @@ edan35::Assignment2::run() glBindSampler(0u, 0u); glEndQuery(GL_TIME_ELAPSED); - if (utils::opengl::debug::isSupported()) - { - glPopDebugGroup(); - } + utils::opengl::debug::endDebugGroup(); glDepthMask(GL_TRUE); glDepthFunc(GL_LESS); @@ -449,11 +431,7 @@ edan35::Assignment2::run() // // Pass 3: Compute final image using both the g-buffer and the light accumulation buffer // - if (utils::opengl::debug::isSupported()) - { - std::string const group_name = "Resolve"; - glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0u, group_name.size(), group_name.data()); - } + utils::opengl::debug::beginDebugGroup("Resolve"); glBeginQuery(GL_TIME_ELAPSED, elapsed_time_queries[toU(ElapsedTimeQuery::Resolve)]); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[toU(FBO::Resolve)]); @@ -475,10 +453,13 @@ edan35::Assignment2::run() glUseProgram(0u); glEndQuery(GL_TIME_ELAPSED); - if (utils::opengl::debug::isSupported()) - { - glPopDebugGroup(); - } + utils::opengl::debug::endDebugGroup(); + } + + + auto const show_debug_elements = show_cone_wireframe || show_basis; + if (show_debug_elements) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[toU(FBO::FinalWithDepth)]); } @@ -487,12 +468,7 @@ edan35::Assignment2::run() // glBeginQuery(GL_TIME_ELAPSED, elapsed_time_queries[toU(ElapsedTimeQuery::ConeWireframe)]); if (show_cone_wireframe) { - if (utils::opengl::debug::isSupported()) - { - std::string const group_name = "Draw cone wireframe"; - glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0u, group_name.size(), group_name.data()); - } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[toU(FBO::FinalWithDepth)]); + utils::opengl::debug::beginDebugGroup("Draw cone wireframe"); glDisable(GL_CULL_FACE); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); @@ -503,32 +479,26 @@ edan35::Assignment2::run() } glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_CULL_FACE); - if (utils::opengl::debug::isSupported()) - { - glPopDebugGroup(); - } + utils::opengl::debug::endDebugGroup(); } glEndQuery(GL_TIME_ELAPSED); - if (utils::opengl::debug::isSupported()) - { - std::string const group_name = "Draw GUI"; - glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0u, group_name.size(), group_name.data()); - } + utils::opengl::debug::beginDebugGroup("Draw GUI"); glBeginQuery(GL_TIME_ELAPSED, elapsed_time_queries[toU(ElapsedTimeQuery::GUI)]); // // Display 3D helpers // - if (show_basis) - { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[toU(FBO::FinalWithDepth)]); - + if (show_basis) { bonobo::renderBasis(basis_thickness_scale, basis_length_scale, mCamera.GetWorldToClipMatrix()); } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[toU(FBO::Resolve)]); + // If the basis and cone wireframe were not shown, FBO::Resolve + // is still bound so there is no need to rebind it. + if (show_debug_elements) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[toU(FBO::Resolve)]); + } // // Output content of the g-buffer as well as of the shadowmap, for debugging purposes @@ -625,40 +595,31 @@ edan35::Assignment2::run() mWindowManager.RenderImGuiFrame(show_gui); glEndQuery(GL_TIME_ELAPSED); - if (utils::opengl::debug::isSupported()) - { - glPopDebugGroup(); - } + utils::opengl::debug::endDebugGroup(); // // Blit the result back to the default framebuffer. // - if (utils::opengl::debug::isSupported()) - { - std::string const group_name = "Copy to default framebuffer"; - glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0u, group_name.size(), group_name.data()); - } + utils::opengl::debug::beginDebugGroup("Copy to default framebuffer"); glBeginQuery(GL_TIME_ELAPSED, elapsed_time_queries[toU(ElapsedTimeQuery::CopyToFramebuffer)]); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[toU(FBO::Resolve)]); + // FBO::Resolve has already been bound to GL_READ_FRAMEBUFFER before rendering the first frame, + // as no other frame buffer gets bound to it. glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0u); glBlitFramebuffer(0, 0, framebuffer_width, framebuffer_height, 0, 0, framebuffer_width, framebuffer_height, GL_COLOR_BUFFER_BIT, GL_NEAREST); glEndQuery(GL_TIME_ELAPSED); - if (utils::opengl::debug::isSupported()) - { - glPopDebugGroup(); - } + utils::opengl::debug::endDebugGroup(); glfwSwapBuffers(window); first_frame = false; } - glDeleteQueries(elapsed_time_queries.size(), elapsed_time_queries.data()); - glDeleteSamplers(samplers.size(), samplers.data()); - glDeleteFramebuffers(fbos.size(), fbos.data()); - glDeleteTextures(textures.size(), textures.data()); + glDeleteQueries(static_cast(elapsed_time_queries.size()), elapsed_time_queries.data()); + glDeleteSamplers(static_cast(samplers.size()), samplers.data()); + glDeleteFramebuffers(static_cast(fbos.size()), fbos.data()); + glDeleteTextures(static_cast(textures.size()), textures.data()); glDeleteProgram(resolve_deferred_shader); resolve_deferred_shader = 0u; @@ -691,58 +652,39 @@ namespace Textures createTextures(GLsizei framebuffer_width, GLsizei framebuffer_height) { Textures textures; - glGenTextures(textures.size(), textures.data()); + glGenTextures(static_cast(textures.size()), textures.data()); glBindTexture(GL_TEXTURE_2D, textures[toU(Texture::DepthBuffer)]); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, framebuffer_width, framebuffer_height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); + utils::opengl::debug::nameObject(GL_TEXTURE, textures[toU(Texture::DepthBuffer)], "Depth buffer"); glBindTexture(GL_TEXTURE_2D, textures[toU(Texture::ShadowMap)]); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, constant::shadowmap_res_x, constant::shadowmap_res_y, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); + utils::opengl::debug::nameObject(GL_TEXTURE, textures[toU(Texture::ShadowMap)], "Shadow map"); glBindTexture(GL_TEXTURE_2D, textures[toU(Texture::GBufferDiffuse)]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, framebuffer_width, framebuffer_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + utils::opengl::debug::nameObject(GL_TEXTURE, textures[toU(Texture::GBufferDiffuse)], "GBuffer diffuse"); glBindTexture(GL_TEXTURE_2D, textures[toU(Texture::GBufferSpecular)]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, framebuffer_width, framebuffer_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + utils::opengl::debug::nameObject(GL_TEXTURE, textures[toU(Texture::GBufferSpecular)], "GBuffer specular"); glBindTexture(GL_TEXTURE_2D, textures[toU(Texture::GBufferWorldSpaceNormal)]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, framebuffer_width, framebuffer_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + utils::opengl::debug::nameObject(GL_TEXTURE, textures[toU(Texture::GBufferWorldSpaceNormal)], "GBuffer normals"); glBindTexture(GL_TEXTURE_2D, textures[toU(Texture::LightDiffuseContribution)]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, framebuffer_width, framebuffer_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + utils::opengl::debug::nameObject(GL_TEXTURE, textures[toU(Texture::LightDiffuseContribution)], "Light diffuse contribution"); glBindTexture(GL_TEXTURE_2D, textures[toU(Texture::LightSpecularContribution)]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, framebuffer_width, framebuffer_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + utils::opengl::debug::nameObject(GL_TEXTURE, textures[toU(Texture::LightSpecularContribution)], "Light specular contribution"); glBindTexture(GL_TEXTURE_2D, textures[toU(Texture::Result)]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, framebuffer_width, framebuffer_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - - if (utils::opengl::debug::isSupported()) - { - std::string const depth_buffer_string = "Depth buffer"; - glObjectLabel(GL_TEXTURE, textures[toU(Texture::DepthBuffer)], depth_buffer_string.size(), depth_buffer_string.data()); - - std::string const shadow_map_string = "Shadow map"; - glObjectLabel(GL_TEXTURE, textures[toU(Texture::ShadowMap)], shadow_map_string.size(), shadow_map_string.data()); - - std::string const gbuffer_diffuse_string = "GBuffer diffuse"; - glObjectLabel(GL_TEXTURE, textures[toU(Texture::GBufferDiffuse)], gbuffer_diffuse_string.size(), gbuffer_diffuse_string.data()); - - std::string const gbuffer_specular_string = "GBuffer specular"; - glObjectLabel(GL_TEXTURE, textures[toU(Texture::GBufferSpecular)], gbuffer_specular_string.size(), gbuffer_specular_string.data()); - - std::string const gbuffer_normals_string = "GBuffer normals"; - glObjectLabel(GL_TEXTURE, textures[toU(Texture::GBufferWorldSpaceNormal)], gbuffer_normals_string.size(), gbuffer_normals_string.data()); - - std::string const light_diffuse_contribution_string = "Light diffuse contribution"; - glObjectLabel(GL_TEXTURE, textures[toU(Texture::LightDiffuseContribution)], light_diffuse_contribution_string.size(), light_diffuse_contribution_string.data()); - - std::string const light_specular_contribution_string = "Light specular contribution"; - glObjectLabel(GL_TEXTURE, textures[toU(Texture::LightSpecularContribution)], light_specular_contribution_string.size(), light_specular_contribution_string.data()); - - std::string const result_string = "Final result"; - glObjectLabel(GL_TEXTURE, textures[toU(Texture::Result)], result_string.size(), result_string.data()); - } + utils::opengl::debug::nameObject(GL_TEXTURE, textures[toU(Texture::Result)], "Final result"); glBindTexture(GL_TEXTURE_2D, 0u); return textures; @@ -751,25 +693,28 @@ Textures createTextures(GLsizei framebuffer_width, GLsizei framebuffer_height) Samplers createSamplers() { Samplers samplers; - glGenSamplers(samplers.size(), samplers.data()); + glGenSamplers(static_cast(samplers.size()), samplers.data()); // For sampling 2-D textures without interpolation. glSamplerParameteri(samplers[toU(Sampler::Nearest)], GL_TEXTURE_MIN_FILTER, GL_NEAREST); glSamplerParameteri(samplers[toU(Sampler::Nearest)], GL_TEXTURE_MAG_FILTER, GL_NEAREST); glSamplerParameteri(samplers[toU(Sampler::Nearest)], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glSamplerParameteri(samplers[toU(Sampler::Nearest)], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + utils::opengl::debug::nameObject(GL_SAMPLER, samplers[toU(Sampler::Nearest)], "Nearest"); // For sampling 2-D textures without mipmaps. glSamplerParameteri(samplers[toU(Sampler::Linear)], GL_TEXTURE_MIN_FILTER, GL_LINEAR); glSamplerParameteri(samplers[toU(Sampler::Linear)], GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(samplers[toU(Sampler::Linear)], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glSamplerParameteri(samplers[toU(Sampler::Linear)], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + utils::opengl::debug::nameObject(GL_SAMPLER, samplers[toU(Sampler::Linear)], "Linear"); // For sampling 2-D textures with mipmaps. glSamplerParameteri(samplers[toU(Sampler::Mipmaps)], GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glSamplerParameteri(samplers[toU(Sampler::Mipmaps)], GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(samplers[toU(Sampler::Mipmaps)], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glSamplerParameteri(samplers[toU(Sampler::Mipmaps)], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + utils::opengl::debug::nameObject(GL_SAMPLER, samplers[toU(Sampler::Mipmaps)], "Mimaps"); // For sampling 2-D shadow maps glSamplerParameteri(samplers[toU(Sampler::Shadow)], GL_TEXTURE_MIN_FILTER, GL_NEAREST); @@ -778,21 +723,7 @@ Samplers createSamplers() glSamplerParameteri(samplers[toU(Sampler::Shadow)], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glSamplerParameteri(samplers[toU(Sampler::Shadow)], GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); glSamplerParameteri(samplers[toU(Sampler::Shadow)], GL_TEXTURE_COMPARE_FUNC, GL_LESS); - - if (utils::opengl::debug::isSupported()) - { - std::string const nearest_string = "Nearest"; - glObjectLabel(GL_SAMPLER, samplers[toU(Sampler::Nearest)], nearest_string.size(), nearest_string.data()); - - std::string const linear_string = "Linear"; - glObjectLabel(GL_SAMPLER, samplers[toU(Sampler::Linear)], linear_string.size(), linear_string.data()); - - std::string const mipmaps_string = "Mimaps"; - glObjectLabel(GL_SAMPLER, samplers[toU(Sampler::Mipmaps)], mipmaps_string.size(), mipmaps_string.data()); - - std::string const shadow_string = "Shadow"; - glObjectLabel(GL_SAMPLER, samplers[toU(Sampler::Shadow)], shadow_string.size(), shadow_string.data()); - } + utils::opengl::debug::nameObject(GL_SAMPLER, samplers[toU(Sampler::Shadow)], "Shadow"); return samplers; } @@ -808,7 +739,7 @@ FBOs createFramebufferObjects(Textures const& textures) }; FBOs fbos; - glGenFramebuffers(fbos.size(), fbos.data()); + glGenFramebuffers(static_cast(fbos.size()), fbos.data()); glBindFramebuffer(GL_FRAMEBUFFER, fbos[toU(FBO::GBuffer)]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[toU(Texture::GBufferDiffuse)], 0); @@ -822,12 +753,14 @@ FBOs createFramebufferObjects(Textures const& textures) GL_COLOR_ATTACHMENT1, // The fragment shader output at location 1 will be written to colour attachment 1 (i.e. the specular texture). GL_COLOR_ATTACHMENT2 // The fragment shader output at location 2 will be written to colour attachment 2 (i.e. the normal texture). }; - glDrawBuffers(gbuffer_draws.size(), gbuffer_draws.data()); + glDrawBuffers(static_cast(gbuffer_draws.size()), gbuffer_draws.data()); validate_fbo("GBuffer"); + utils::opengl::debug::nameObject(GL_FRAMEBUFFER, fbos[toU(FBO::GBuffer)], "GBuffer"); glBindFramebuffer(GL_FRAMEBUFFER, fbos[toU(FBO::ShadowMap)]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textures[toU(Texture::ShadowMap)], 0); validate_fbo("Shadow map generation"); + utils::opengl::debug::nameObject(GL_FRAMEBUFFER, fbos[toU(FBO::ShadowMap)], "Shadow map generation"); glBindFramebuffer(GL_FRAMEBUFFER, fbos[toU(FBO::LightAccumulation)]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[toU(Texture::LightDiffuseContribution)], 0); @@ -839,14 +772,16 @@ FBOs createFramebufferObjects(Textures const& textures) GL_COLOR_ATTACHMENT0, // The fragment shader output at location 0 will be written to colour attachment 0 (i.e. the light diffuse contribution texture). GL_COLOR_ATTACHMENT1 // The fragment shader output at location 1 will be written to colour attachment 1 (i.e. the light specular contribution texture). }; - glDrawBuffers(light_accumulation_draws.size(), light_accumulation_draws.data()); + glDrawBuffers(static_cast(light_accumulation_draws.size()), light_accumulation_draws.data()); validate_fbo("Light accumulation"); + utils::opengl::debug::nameObject(GL_FRAMEBUFFER, fbos[toU(FBO::LightAccumulation)], "Light acccumulation"); glBindFramebuffer(GL_FRAMEBUFFER, fbos[toU(FBO::Resolve)]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[toU(Texture::Result)], 0); glReadBuffer(GL_COLOR_ATTACHMENT0); // Colour attachment result 0 (i.e. the rendering result texture) will be blitted to the screen. glDrawBuffer(GL_COLOR_ATTACHMENT0); // The fragment shader output at location 0 will be written to colour attachment 0 (i.e. the rendering result texture). validate_fbo("Resolve"); + utils::opengl::debug::nameObject(GL_FRAMEBUFFER, fbos[toU(FBO::Resolve)], "Resolve"); glBindFramebuffer(GL_FRAMEBUFFER, fbos[toU(FBO::FinalWithDepth)]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[toU(Texture::Result)], 0); @@ -854,24 +789,7 @@ FBOs createFramebufferObjects(Textures const& textures) glReadBuffer(GL_NONE); // Disable reading back from the colour attachments, as unnecessary in this assignment. glDrawBuffer(GL_COLOR_ATTACHMENT0); // The fragment shader output at location 0 will be written to colour attachment 0 (i.e. the rendering result texture). validate_fbo("Final with depth"); - - if (utils::opengl::debug::isSupported()) - { - std::string const gbuffer_string = "GBuffer"; - glObjectLabel(GL_FRAMEBUFFER, fbos[toU(FBO::GBuffer)], gbuffer_string.size(), gbuffer_string.data()); - - std::string const shadow_map_string = "Shadow map generation"; - glObjectLabel(GL_FRAMEBUFFER, fbos[toU(FBO::ShadowMap)], shadow_map_string.size(), shadow_map_string.data()); - - std::string const light_accumulation_string = "Light acccumulation"; - glObjectLabel(GL_FRAMEBUFFER, fbos[toU(FBO::LightAccumulation)], light_accumulation_string.size(), light_accumulation_string.data()); - - std::string const resolve_string = "Resolve"; - glObjectLabel(GL_FRAMEBUFFER, fbos[toU(FBO::Resolve)], resolve_string.size(), resolve_string.data()); - - std::string const cone_wireframe_string = "Cone wireframe"; - glObjectLabel(GL_FRAMEBUFFER, fbos[toU(FBO::FinalWithDepth)], cone_wireframe_string.size(), cone_wireframe_string.data()); - } + utils::opengl::debug::nameObject(GL_FRAMEBUFFER, fbos[toU(FBO::FinalWithDepth)], "Cone wireframe"); glBindFramebuffer(GL_FRAMEBUFFER, 0u); return fbos; @@ -880,7 +798,7 @@ FBOs createFramebufferObjects(Textures const& textures) ElapsedTimeQueries createElapsedTimeQueries() { ElapsedTimeQueries queries; - glGenQueries(queries.size(), queries.data()); + glGenQueries(static_cast(queries.size()), queries.data()); if (utils::opengl::debug::isSupported()) { @@ -893,31 +811,25 @@ ElapsedTimeQueries createElapsedTimeQueries() }; register_query(queries[toU(ElapsedTimeQuery::GbufferGeneration)]); - std::string const gbuffer_generation_string = "GBuffer generation"; - glObjectLabel(GL_QUERY, queries[toU(ElapsedTimeQuery::GbufferGeneration)], gbuffer_generation_string.size(), gbuffer_generation_string.data()); + utils::opengl::debug::nameObject(GL_QUERY, queries[toU(ElapsedTimeQuery::GbufferGeneration)], "GBuffer generation"); for (size_t i = 0; i < constant::lights_nb; ++i) { register_query(queries[toU(ElapsedTimeQuery::ShadowMap0Generation) + i]); - std::string const shadow_map_string = "Shadow map " + std::to_string(i) + " generation"; - glObjectLabel(GL_QUERY, queries[toU(ElapsedTimeQuery::ShadowMap0Generation) + i], shadow_map_string.size(), shadow_map_string.data()); + utils::opengl::debug::nameObject(GL_QUERY, queries[toU(ElapsedTimeQuery::ShadowMap0Generation) + i], "Shadow map " + std::to_string(i) + " generation"); register_query(queries[toU(ElapsedTimeQuery::Light0Accumulation) + i]); - std::string const light_accumulation_string = "Light" + std::to_string(i) + " accumulation"; - glObjectLabel(GL_QUERY, queries[toU(ElapsedTimeQuery::Light0Accumulation) + i], light_accumulation_string.size(), light_accumulation_string.data()); + utils::opengl::debug::nameObject(GL_QUERY, queries[toU(ElapsedTimeQuery::Light0Accumulation) + i], "Light" + std::to_string(i) + " accumulation"); } register_query(queries[toU(ElapsedTimeQuery::Resolve)]); - std::string const resolve_string = "Resolve"; - glObjectLabel(GL_QUERY, queries[toU(ElapsedTimeQuery::Resolve)], resolve_string.size(), resolve_string.data()); + utils::opengl::debug::nameObject(GL_QUERY, queries[toU(ElapsedTimeQuery::Resolve)], "Resolve"); register_query(queries[toU(ElapsedTimeQuery::ConeWireframe)]); - std::string const cone_wireframe_string = "Cone wireframe"; - glObjectLabel(GL_QUERY, queries[toU(ElapsedTimeQuery::ConeWireframe)], cone_wireframe_string.size(), cone_wireframe_string.data()); + utils::opengl::debug::nameObject(GL_QUERY, queries[toU(ElapsedTimeQuery::ConeWireframe)], "Cone wireframe"); register_query(queries[toU(ElapsedTimeQuery::GUI)]); - std::string const gui_string = "GUI"; - glObjectLabel(GL_QUERY, queries[toU(ElapsedTimeQuery::GUI)], gui_string.size(), gui_string.data()); + utils::opengl::debug::nameObject(GL_QUERY, queries[toU(ElapsedTimeQuery::GUI)], "GUI"); } return queries; @@ -1001,10 +913,13 @@ loadCone() assert(cone.vao != 0u); glBindVertexArray(cone.vao); { + utils::opengl::debug::nameObject(GL_VERTEX_ARRAY, cone.vao, "Cone VAO"); + glGenBuffers(1, &cone.bo); assert(cone.bo != 0u); glBindBuffer(GL_ARRAY_BUFFER, cone.bo); glBufferData(GL_ARRAY_BUFFER, cone.vertices_nb * 3 * sizeof(float), vertexArrayData, GL_STATIC_DRAW); + utils::opengl::debug::nameObject(GL_BUFFER, cone.bo, "Cone VBO"); glVertexAttribPointer(static_cast(bonobo::shader_bindings::vertices), 3, GL_FLOAT, GL_FALSE, 0, reinterpret_cast(0x0)); glEnableVertexAttribArray(static_cast(bonobo::shader_bindings::vertices)); diff --git a/src/core/BuildSettings.h b/src/core/BuildSettings.h index aa6356db..827e8a4c 100644 --- a/src/core/BuildSettings.h +++ b/src/core/BuildSettings.h @@ -2,7 +2,7 @@ /* * 0: Debugging disabled (maximum performance) -* 1: Minimal error reporting. Constatly checks glGetError(). +* 1: Minimal error reporting. Constantly checks glGetError(). * 2: Creates GL debug context, but only logs medium and severe problems. * 3: Creates GL debug context and enables all GL debugging features. */ diff --git a/src/core/Log.cpp b/src/core/Log.cpp index 224bcc5f..5348c433 100644 --- a/src/core/Log.cpp +++ b/src/core/Log.cpp @@ -55,8 +55,10 @@ void Init() void Destroy() { fileMutex.lock(); - if (!logfile) + if (!logfile) { + fileMutex.unlock(); return; + } fprintf(logfile, "\n === End of log === \n\n"); fflush(logfile); fclose(logfile); diff --git a/src/core/ShaderProgramManager.cpp b/src/core/ShaderProgramManager.cpp index 40ca5369..2da038f5 100644 --- a/src/core/ShaderProgramManager.cpp +++ b/src/core/ShaderProgramManager.cpp @@ -73,7 +73,7 @@ ShaderProgramManager::SelectedProgram ShaderProgramManager::SelectProgram(std::s return selection_result; } - selection_result.was_selection_changed = ImGui::Combo(label.c_str(), &program_index, program_names.data(), program_names.size()); + selection_result.was_selection_changed = ImGui::Combo(label.c_str(), &program_index, program_names.data(), static_cast(program_names.size())); selection_result.program = &program_entries.at(program_index).first; selection_result.name = program_names.at(program_index); return selection_result; @@ -107,11 +107,7 @@ void ShaderProgramManager::ProcessProgram(std::size_t const program_index) } program = utils::opengl::shader::generate_program(shaders); - if (utils::opengl::debug::isSupported()) - { - auto const& program_name = program_names[program_index]; - glObjectLabel(GL_PROGRAM, program, strlen(program_name), program_name); - } + utils::opengl::debug::nameObject(GL_PROGRAM, program, program_names[program_index]); for (auto& shader : shaders) glDeleteShader(shader); diff --git a/src/core/WindowManager.cpp b/src/core/WindowManager.cpp index 864461b3..2a212a33 100644 --- a/src/core/WindowManager.cpp +++ b/src/core/WindowManager.cpp @@ -94,6 +94,9 @@ GLFWwindow* WindowManager::CreateGLFWWindow(std::string const& title, WindowDatu glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); +#if DEBUG_LEVEL >= 2 + glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE); +#endif glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, default_opengl_major_version); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, default_opengl_minor_version); @@ -164,13 +167,27 @@ GLFWwindow* WindowManager::CreateGLFWWindow(std::string const& title, WindowDatu glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(utils::opengl::debug::opengl_error_callback, nullptr); + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_FALSE); #endif #if DEBUG_LEVEL == 2 - glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_FALSE); + // Enable all messages of severity medium or higher. glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, GL_TRUE); glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_HIGH, 0, nullptr, GL_TRUE); #elif DEBUG_LEVEL == 3 - glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE); + // Enable all messages. + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); + + // Comment out the next two calls to get scoped debug messages in the logs. + // Note that it can come at a significant performance cost. + glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_PUSH_GROUP, GL_DONT_CARE, 0, nullptr, GL_FALSE); + glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_POP_GROUP, GL_DONT_CARE, 0, nullptr, GL_FALSE); +#endif +#if DEBUG_LEVEL >= 2 + // Disable certain messages: + std::array api_other_ids = { + 131185u, // "Buffer detailed info: Buffer object Y will use VIDEO memory as the source for buffer object operations." + }; + glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_OTHER, GL_DONT_CARE, static_cast(api_other_ids.size()), api_other_ids.data(), GL_FALSE); #endif } else diff --git a/src/core/helpers.cpp b/src/core/helpers.cpp index c78ac335..6ccde99b 100644 --- a/src/core/helpers.cpp +++ b/src/core/helpers.cpp @@ -33,7 +33,10 @@ namespace } shader_locations; } basis; + GLuint debug_texture_id{ 0u }; + void setupBasisData(); + void createDebugTexture(); } namespace local @@ -56,6 +59,7 @@ void bonobo::init() { setupBasisData(); + createDebugTexture(); glGenVertexArrays(1, &local::display_vao); assert(local::display_vao != 0u); @@ -67,6 +71,9 @@ bonobo::init() void bonobo::deinit() { + glDeleteTextures(1, &debug_texture_id); + debug_texture_id = 0u; + glDeleteProgram(basis.shader); glDeleteBuffers(1, &basis.ibo); glDeleteBuffers(1, &basis.vbo); @@ -150,7 +157,7 @@ bonobo::loadObjects(std::string const& filename) LogWarning("Material \"%s\" has more than one %s texture: discarding all but the first one.", material->GetName().C_Str(), type_as_str.c_str()); aiString path; material->GetTexture(type, 0, &path); - auto const id = bonobo::loadTexture2D(parent_folder + std::string(path.C_Str()), type_as_str != "opacity"); + auto const id = bonobo::loadTexture2D(parent_folder + std::string(path.C_Str())); if (id == 0u) { LogWarning("Failed to load the %s texture for material \"%s\".", type_as_str.c_str(), material->GetName().C_Str()); return; @@ -158,6 +165,8 @@ bonobo::loadObjects(std::string const& filename) bindings.emplace(name, id); ++texture_count; + utils::opengl::debug::nameObject(GL_TEXTURE, id, std::string(material->GetName().C_Str()) + " " + type_as_str); + auto const texture_end_time = std::chrono::high_resolution_clock::now(); LogTrivia("│ %s Texture \"%s\" loaded in %.3f ms", bindings.size() == 1 ? "┌" : "├", path.C_Str(), @@ -285,6 +294,10 @@ bonobo::loadObjects(std::string const& filename) glBufferData(GL_ELEMENT_ARRAY_BUFFER, static_cast(object.indices_nb) * sizeof(GL_UNSIGNED_INT), reinterpret_cast(object_indices.get()), GL_STATIC_DRAW); object_indices.reset(nullptr); + utils::opengl::debug::nameObject(GL_VERTEX_ARRAY, object.vao, object.name + " VAO"); + utils::opengl::debug::nameObject(GL_BUFFER, object.bo, object.name + " VBO"); + utils::opengl::debug::nameObject(GL_BUFFER, object.ibo, object.name + " IBO"); + glBindVertexArray(0u); glBindBuffer(GL_ARRAY_BUFFER, 0u); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0u); @@ -307,7 +320,7 @@ bonobo::loadObjects(std::string const& filename) if (assimp_object_mesh->HasTextureCoords(0)) attributes += "texture coordinates"; LogTrivia("│ %s Mesh \"%s\" loaded with attributes [%s] in %.3f ms", - j == 0 ? "┌" : (j == assimp_scene->mNumMeshes - 1 ? "└" : "├"), + (assimp_scene->mNumMeshes == 1u) ? "╶" : (j == 0 ? "┌" : (j == assimp_scene->mNumMeshes - 1 ? "└" : "├")), assimp_object_mesh->mName.C_Str(), attributes.c_str(), std::chrono::duration(mesh_end_time - mesh_start_time).count()); } @@ -532,9 +545,18 @@ bonobo::drawFullscreen() glBindVertexArray(0u); } +GLuint +bonobo::getDebugTextureID() +{ + return debug_texture_id; +} + void bonobo::renderBasis(float thickness_scale, float length_scale, glm::mat4 const& view_projection, glm::mat4 const& world) { + if (basis.shader == 0u) + return; + glUseProgram(basis.shader); glBindVertexArray(basis.vao); glUniformMatrix4fv(basis.shader_locations.world, 1, GL_FALSE, glm::value_ptr(world)); @@ -552,7 +574,7 @@ bonobo::uiSelectCullMode(std::string const& label, enum cull_mode_t& cull_mode) auto cull_mode_index = static_cast(cull_mode); bool was_modified = ImGui::Combo(label.c_str(), &cull_mode_index, local::cull_mode_labels.data(), - local::cull_mode_labels.size()); + static_cast(local::cull_mode_labels.size())); cull_mode = static_cast(cull_mode_index); return was_modified; } @@ -581,7 +603,7 @@ bonobo::uiSelectPolygonMode(std::string const& label, enum polygon_mode_t& polyg auto polygon_mode_index = static_cast(polygon_mode); bool was_modified = ImGui::Combo(label.c_str(), &polygon_mode_index, local::polygon_mode_labels.data(), - local::polygon_mode_labels.size()); + static_cast(local::polygon_mode_labels.size())); polygon_mode = static_cast(polygon_mode_index); return was_modified; } @@ -669,15 +691,17 @@ namespace glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, basis.ibo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices.data(), GL_STATIC_DRAW); - basis.index_count = indices.size() * 3; + basis.index_count = static_cast(indices.size() * 3); glBindVertexArray(0u); glBindBuffer(GL_ARRAY_BUFFER, 0U); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0U); basis.shader = bonobo::createProgram("common/basis.vert", "common/basis.frag"); - if (basis.shader == 0u) + if (basis.shader == 0u) { LogError("Failed to load \"basis.vert\" and \"basis.frag\""); + return; + } GLint shader_location = glGetUniformLocation(basis.shader, "vertex_model_to_world"); assert(shader_location >= 0); @@ -694,5 +718,25 @@ namespace shader_location = glGetUniformLocation(basis.shader, "length_scale"); assert(shader_location >= 0); basis.shader_locations.length_scale = shader_location; + + utils::opengl::debug::nameObject(GL_VERTEX_ARRAY, basis.vao, "Basis VAO"); + utils::opengl::debug::nameObject(GL_BUFFER, basis.vbo, "Basis VBO"); + utils::opengl::debug::nameObject(GL_BUFFER, basis.ibo, "Basis IBO"); + } + + void createDebugTexture() + { + const GLsizei debug_texture_width = 16; + const GLsizei debug_texture_height = 16; + std::array debug_texture_content; + debug_texture_content.fill(0xFFE935DAu); + glGenTextures(1, &debug_texture_id); + glBindTexture(GL_TEXTURE_2D, debug_texture_id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, debug_texture_width, debug_texture_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, debug_texture_content.data()); + glBindTexture(GL_TEXTURE_2D, 0u); + + utils::opengl::debug::nameObject(GL_TEXTURE, debug_texture_id, "Debug texture"); } } diff --git a/src/core/helpers.hpp b/src/core/helpers.hpp index 1903c28f..ca08a75c 100644 --- a/src/core/helpers.hpp +++ b/src/core/helpers.hpp @@ -33,11 +33,11 @@ namespace bonobo GLuint vao{0u}; //!< OpenGL name of the Vertex Array Object GLuint bo{0u}; //!< OpenGL name of the Buffer Object GLuint ibo{0u}; //!< OpenGL name of the Buffer Object for indices - size_t vertices_nb{0u}; //!< number of vertices stored in bo - size_t indices_nb{0u}; //!< number of indices stored in ibo + GLsizei vertices_nb{0}; //!< number of vertices stored in bo + GLsizei indices_nb{0}; //!< number of indices stored in ibo texture_bindings bindings{}; //!< texture bindings for this mesh GLenum drawing_mode{GL_TRIANGLES}; //!< OpenGL drawing mode, i.e. GL_TRIANGLES, GL_LINES, etc. - std::string name{}; //!< Name of the mesh; used for debugging purposes. + std::string name{"un-named mesh"}; //!< Name of the mesh; used for debugging purposes. }; enum class cull_mode_t : unsigned int { @@ -165,6 +165,9 @@ namespace bonobo //! \brief Draw full screen. void drawFullscreen(); + //! \brief Retrieve the ID of a small placeholder texture. + GLuint getDebugTextureID(); + //! \brief Render a right-hand orthonormal basis. //! //! @param [in] thickness_scale By how much to scale the thickness of the axes diff --git a/src/core/node.cpp b/src/core/node.cpp index 5f3ad51c..299061cf 100644 --- a/src/core/node.cpp +++ b/src/core/node.cpp @@ -7,10 +7,6 @@ #include #include -Node::Node() : _vao(0u), _vertices_nb(0u), _indices_nb(0u), _drawing_mode(GL_TRIANGLES), _has_indices(true), _program(nullptr), _textures(), _transform(), _children() -{ -} - void Node::render(glm::mat4 const& view_projection, glm::mat4 const& parent_transform) const { @@ -24,10 +20,7 @@ Node::render(glm::mat4 const& view_projection, glm::mat4 const& world, GLuint pr if (_vao == 0u || program == 0u) return; - if (utils::opengl::debug::isSupported()) - { - glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0u, _name.size(), _name.data()); - } + utils::opengl::debug::beginDebugGroup(_name); glUseProgram(program); @@ -66,10 +59,7 @@ Node::render(glm::mat4 const& view_projection, glm::mat4 const& world, GLuint pr glUseProgram(0u); - if (utils::opengl::debug::isSupported()) - { - glPopDebugGroup(); - } + utils::opengl::debug::endDebugGroup(); } void @@ -80,7 +70,7 @@ Node::set_geometry(bonobo::mesh_data const& shape) _indices_nb = static_cast(shape.indices_nb); _drawing_mode = shape.drawing_mode; _has_indices = shape.ibo != 0u; - _name = shape.name; + _name = std::string("Render ") + shape.name; if (!shape.bindings.empty()) { for (auto const& binding : shape.bindings) @@ -100,6 +90,12 @@ Node::set_program(GLuint const* const program, std::function cons _set_uniforms = set_uniforms; } +void +Node::set_name(std::string const& name) +{ + _name = std::string("Render ") + name; +} + size_t Node::get_indices_nb() const { diff --git a/src/core/node.hpp b/src/core/node.hpp index 426dea77..e46dd926 100644 --- a/src/core/node.hpp +++ b/src/core/node.hpp @@ -20,9 +20,6 @@ namespace bonobo class Node { public: - //! \brief Default constructor. - Node(); - //! \brief Render this node. //! //! @param [in] view_projection Matrix transforming from world-space to clip-space @@ -71,14 +68,24 @@ class Node //! A node without a program will not render itself, but its children //! will be rendered if they have one. //! - //! @param [in] pointer to the program OpenGL shader program to use; - //! the pointer should not be nul. + //! @param [in] program pointer to the program OpenGL shader program to + //! use; the pointer should not be null. //! @param [in] set_uniforms function that will take as argument an //! OpenGL shader program, and will setup that program's //! uniforms void set_program(GLuint const* const program, std::function const& set_uniforms = [](GLuint /*programID*/){}); + //! \brief Set the name of this node. + //! + //! This name will be used when pushing debug groups to scope OpenGL + //! commands and help when debugging or profiling the application using + //! third-party applications. + //! + //! @param [in] name the name used when creating the debug group during + //! rendering; it will automatically be prefixed by "Render ". + void set_name(std::string const& name); + //! \brief Add a texture to this node. //! //! @param [in] name the variable name used by the attached OpenGL @@ -116,14 +123,14 @@ class Node private: // Geometry data - GLuint _vao; - GLsizei _vertices_nb; - GLsizei _indices_nb; - GLenum _drawing_mode; - bool _has_indices; + GLuint _vao{ 0u }; + GLsizei _vertices_nb{ 0u }; + GLsizei _indices_nb{ 0u }; + GLenum _drawing_mode{ GL_TRIANGLES }; + bool _has_indices{ false }; // Program data - GLuint const* _program; + GLuint const* _program{ nullptr }; std::function _set_uniforms; // Textures data @@ -136,5 +143,5 @@ class Node std::vector _children; // Debug data - std::string _name; + std::string _name{"Render un-named node"}; }; diff --git a/src/core/opengl.cpp b/src/core/opengl.cpp index 7804945f..9e3e86b2 100644 --- a/src/core/opengl.cpp +++ b/src/core/opengl.cpp @@ -3,6 +3,7 @@ #include "various.hpp" #include +#include #include #include #include @@ -92,6 +93,32 @@ getStringForSeverity( GLenum severity ) return(""); } } +void +beginDebugGroup(std::string const& message, GLuint id) +{ + if (!isSupported()) + return; + + glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, id, static_cast(message.size()), message.data()); +} + +void +endDebugGroup() +{ + if (!isSupported()) + return; + + glPopDebugGroup(); +} + +void +nameObject(GLenum type, GLuint id, std::string const& label) +{ + if (!isSupported()) + return; + + glObjectLabel(type, id, static_cast(label.size()), label.data()); +} void #ifdef _WIN32 @@ -102,8 +129,21 @@ opengl_error_callback( GLenum source, GLenum type, GLuint id, GLenum severity , void const* /*data*/ ) { - if (type == GL_DEBUG_TYPE_PUSH_GROUP || type == GL_DEBUG_TYPE_POP_GROUP) + if (type == GL_DEBUG_TYPE_PUSH_GROUP) { + LogInfo("%s\n{", msg); + return; + } + else if (type == GL_DEBUG_TYPE_POP_GROUP) { + LogInfo("}"); return; + } + + // "Texture state usage warning: The texture object (X) bound to texture unit Y does not have a defined base level and cannot be used for texture mapping." + if (source == GL_DEBUG_SOURCE_API && type == GL_DEBUG_TYPE_OTHER && id == 131204u) { + // Discard if this is about the “default texture”, i.e. ID 0. + if (std::strstr(msg, "The texture object (0)") != nullptr) + return; + } std::ostringstream oss; oss << "[id: " << id << "] of type " << getStringForType(type) @@ -115,16 +155,7 @@ opengl_error_callback( GLenum source, GLenum type, GLuint id, GLenum severity switch (severity) { case GL_DEBUG_SEVERITY_NOTIFICATION: case GL_DEBUG_SEVERITY_LOW: // fallthrough - if (id == 131185) // Will use VIDEO memory - break; - else if (id == 131204) { // Texture cannot be used for texture mapping - // Discard if this is about the “default texture”, i.e. ID 0. - if (s_msg.find("The texture object (0)") != std::string::npos) - break; - else - LogInfo(c_msg); - } else - LogInfo(c_msg); + LogInfo(c_msg); break; case GL_DEBUG_SEVERITY_MEDIUM: LogWarning(c_msg); @@ -151,28 +182,30 @@ source_and_build_shader(GLuint id, std::string const& source) glCompileShader(id); GLint state = GLint(0); glGetShaderiv(id, GL_COMPILE_STATUS, &state); - if (state == GL_FALSE) - { - GLint log_length = GLint(0); - glGetShaderiv(id, GL_INFO_LOG_LENGTH, &log_length); - - if (log_length > 0) { - std::unique_ptr log = std::make_unique(static_cast(log_length)); - glGetShaderInfoLog(id, log_length, NULL, log.get()); - std::ostringstream oss; - oss << "Shader compiling error:" << std::endl - << log.get(); - auto const s_msg = oss.str(); - auto const c_msg = s_msg.c_str(); - LogError("%s", c_msg); - } else { - LogError("Shader failed to compile but no log available."); - } + auto const wasCompilationSuccessful = state != GL_FALSE; + + GLint log_length = GLint(0); + glGetShaderiv(id, GL_INFO_LOG_LENGTH, &log_length); + + if (log_length > 0) { + std::unique_ptr log = std::make_unique(static_cast(log_length)); + glGetShaderInfoLog(id, log_length, NULL, log.get()); + + std::ostringstream oss; + oss << "Shader compiling log:" << std::endl + << log.get() << std::endl; + auto const s_msg = oss.str(); + auto const c_msg = s_msg.c_str(); - return false; + if (wasCompilationSuccessful) + LogInfo("%s", c_msg); + else + LogError("%s", c_msg); + } else if (!wasCompilationSuccessful) { + LogError("Shader failed to compile but no log available."); } - return true; + return wasCompilationSuccessful; } GLuint @@ -195,23 +228,30 @@ link_program(GLuint id) glLinkProgram(id); GLint state = GLint(0); glGetProgramiv(id, GL_LINK_STATUS, &state); - if (state == GL_FALSE) - { - GLint log_length = GLint(0); - glGetProgramiv(id, GL_INFO_LOG_LENGTH, &log_length); + auto const wasLinkingSuccessful = state != GL_FALSE; + + GLint log_length = GLint(0); + glGetProgramiv(id, GL_INFO_LOG_LENGTH, &log_length); + + if (log_length > 0) { std::unique_ptr log = std::make_unique(static_cast(log_length)); glGetProgramInfoLog(id, log_length, NULL, log.get()); + std::ostringstream oss; - oss << "Program linking error:" << std::endl - << log.get(); + oss << "Program linking log:" << std::endl + << log.get() << std::endl; auto const s_msg = oss.str(); auto const c_msg = s_msg.c_str(); - LogError("%s", c_msg); - return false; + if (wasLinkingSuccessful) + LogInfo("%s", c_msg); + else + LogError("%s", c_msg); + } else if (!wasLinkingSuccessful) { + LogError("Program failed to link but no log available."); } - return true; + return wasLinkingSuccessful; } void diff --git a/src/core/opengl.hpp b/src/core/opengl.hpp index 1c4f6787..02285afa 100644 --- a/src/core/opengl.hpp +++ b/src/core/opengl.hpp @@ -20,6 +20,38 @@ bool isSupported(); std::string getStringForType(GLenum type); std::string getStringForSource(GLenum source); std::string getStringForSeverity(GLenum severity); + +//! \brief Start a new debug group. +//! +//! This will allow tools like RenderDoc or Nsight Graphics, or the debug +//! messages, to group all the commands issued within this newly defined scope +//! under the specified name/ message. +//! +//! The call will be ignored if OpenGL debug facilities are not available. +//! +//! \param [in] message message or name for the new group to create +//! \param [in] id An ID for the current message, which could be used for +//! filtering some messages out but is currently unused +void beginDebugGroup(std::string const& message, GLuint id = 0u); + +//! \brief End the most recently-started debug group. +//! +//! The call will be ignored if OpenGL debug facilities are not available. +void endDebugGroup(); + +//! \brief Label an OpenGL object with a custon string. +//! +//! This will allow tools like RenderDoc or Nsight Graphics, or the debug +//! messages, to display those strings rather than the object ID, making it +//! easier to quickly find a given object. +//! +//! The call will be ignored if OpenGL debug facilities are not available. +//! +//! \param [in] type the type of the object to name, like GL_BUFFER, GL_TEXTURE +//! \param [in] id the ID of the OpenGL object to name +//! \param [in] label the label to associate to the given object +void nameObject(GLenum type, GLuint id, std::string const& label); + void #ifdef _WIN32 APIENTRY