#pragma once #include #include "helpers.hpp" #include "opengl.hpp" // GL state manager object for use in the OpenGL GPU renderer and potentially other things in the future (such as a potential ImGui GUI) // This object is meant to help us avoid duplicate OpenGL calls (such as binding the same program twice, enabling/disabling a setting twice, etc) // by checking if we actually *need* a state change. This is meant to avoid expensive driver calls and minimize unneeded state changes // A lot of code is in the header file instead of the relevant source file to make sure stuff gets inlined even without LTO, and // because this header should ideally not be getting included in too many places // Code that does not need inlining however, like the reset() function should be in gl_state.cpp // This state manager may not handle every aspect of OpenGL, in which case anything not handled here should just be manipulated with raw // OpenGL/opengl.hpp calls However, anything that can be handled through the state manager should, or at least there should be an attempt to keep it // consistent with the current GL state to avoid bugs/suboptimal code. // The state manager must *also* be a trivially constructible/destructible type, to ensure that no OpenGL functions get called sneakily without us // knowing. This is important for when we want to eg add a Vulkan or misc backend. Would definitely not want to refactor all this. So we try to be as // backend-agnostic as possible struct GLStateManager { // We only support 6 clipping planes in our state manager because that's the minimum for GL_MAX_CLIP_PLANES // And nobody needs more than 6 clip planes anyways static constexpr GLint clipPlaneCount = 6; bool blendEnabled; bool logicOpEnabled; bool depthEnabled; bool scissorEnabled; bool stencilEnabled; u32 enabledClipPlanes; // Bitfield of enabled clip planes // Colour/depth masks bool redMask, greenMask, blueMask, alphaMask; bool depthMask; float clearRed, clearBlue, clearGreen, clearAlpha; GLuint stencilMask; GLuint boundVAO; GLuint boundVBO; GLuint currentProgram; GLenum depthFunc; GLenum logicOp; void reset(); void resetBlend(); void resetClearing(); void resetClipping(); void resetColourMask(); void resetDepth(); void resetVAO(); void resetVBO(); void resetProgram(); void resetScissor(); void resetStencil(); void enableDepth() { if (!depthEnabled) { depthEnabled = true; OpenGL::enableDepth(); } } void disableDepth() { if (depthEnabled) { depthEnabled = false; OpenGL::disableDepth(); } } void enableBlend() { if (!blendEnabled) { blendEnabled = true; OpenGL::enableBlend(); } } void disableBlend() { if (blendEnabled) { blendEnabled = false; OpenGL::disableBlend(); } } void enableScissor() { if (!scissorEnabled) { scissorEnabled = true; OpenGL::enableScissor(); } } void disableScissor() { if (scissorEnabled) { scissorEnabled = false; OpenGL::disableScissor(); } } void enableStencil() { if (!stencilEnabled) { stencilEnabled = true; OpenGL::enableStencil(); } } void disableStencil() { if (stencilEnabled) { stencilEnabled = false; OpenGL::disableStencil(); } } void enableLogicOp() { if (!logicOpEnabled) { logicOpEnabled = true; OpenGL::enableLogicOp(); } } void disableLogicOp() { if (logicOpEnabled) { logicOpEnabled = false; OpenGL::disableLogicOp(); } } void setLogicOp(GLenum op) { if (logicOp != op) { logicOp = op; OpenGL::setLogicOp(op); } } void enableClipPlane(GLuint index) { if (index >= clipPlaneCount) [[unlikely]] { Helpers::panic("Enabled invalid clipping plane %d\n", index); } if ((enabledClipPlanes & (1 << index)) == 0) { enabledClipPlanes |= 1 << index; // Enable relevant bit in clipping plane bitfield OpenGL::enableClipPlane(index); // Enable plane } } void disableClipPlane(GLuint index) { if (index >= clipPlaneCount) [[unlikely]] { Helpers::panic("Disabled invalid clipping plane %d\n", index); } if ((enabledClipPlanes & (1 << index)) != 0) { enabledClipPlanes ^= 1 << index; // Disable relevant bit in bitfield by flipping it OpenGL::disableClipPlane(index); // Disable plane } } void setStencilMask(GLuint mask) { if (stencilMask != mask) { stencilMask = mask; OpenGL::setStencilMask(mask); } } void bindVAO(GLuint handle) { if (boundVAO != handle) { boundVAO = handle; glBindVertexArray(handle); } } void bindVBO(GLuint handle) { if (boundVBO != handle) { boundVBO = handle; glBindBuffer(GL_ARRAY_BUFFER, handle); } } void useProgram(GLuint handle) { if (currentProgram != handle) { currentProgram = handle; glUseProgram(handle); } } void bindVAO(const OpenGL::VertexArray& vao) { bindVAO(vao.handle()); } void bindVBO(const OpenGL::VertexBuffer& vbo) { bindVBO(vbo.handle()); } void useProgram(const OpenGL::Program& program) { useProgram(program.handle()); } void setColourMask(bool r, bool g, bool b, bool a) { if (r != redMask || g != greenMask || b != blueMask || a != alphaMask) { r = redMask; g = greenMask; b = blueMask; a = alphaMask; OpenGL::setColourMask(r, g, b, a); } } void setDepthMask(bool mask) { if (depthMask != mask) { depthMask = mask; OpenGL::setDepthMask(mask); } } void setDepthFunc(GLenum func) { if (depthFunc != func) { depthFunc = func; glDepthFunc(func); } } void setClearColour(float r, float g, float b, float a) { if (clearRed != r || clearGreen != g || clearBlue != b || clearAlpha != a) { clearRed = r; clearGreen = g; clearBlue = b; clearAlpha = a; OpenGL::setClearColor(r, g, b, a); } } void setDepthFunc(OpenGL::DepthFunc func) { setDepthFunc(static_cast(func)); } }; static_assert(std::is_trivially_constructible(), "OpenGL State Manager class is not trivially constructible!"); static_assert(std::is_trivially_destructible(), "OpenGL State Manager class is not trivially destructible!");