2
\$\begingroup\$

context.h:

#pragma once
#include "enums/primitive_type.h"
#include "enums/capability.h"

typedef struct GLFWwindow GLFWwindow; //"YUCK!", yeah..

namespace SOGL
{
    struct ContextSettings
    {
        ContextSettings
        (
            unsigned depth = 0,
            unsigned stencil = 0,
            unsigned antialiasing = 0, 
            unsigned major = 4,
            unsigned minor = 0
        );

        unsigned depth_bits;
        unsigned stencil_bits;
        unsigned antialiasing_level;
        unsigned major_version;
        unsigned minor_version;
    };

    class Context
    {
    public:
        Context(const ContextSettings& settings = ContextSettings());

        Context(unsigned depth, unsigned stencil, unsigned antialiasing, unsigned major, unsigned minor);

        ~Context();

        Context(Context&&);

        //make shared context
        Context(Context&);

        const ContextSettings& settings();

        void enable(const Capability& capability);

        void disable(const Capability& capability);

        bool is_enabled(const Capability& capability);

        bool is_disabled(const Capability& capability);

        void draw_arrays(const PrimitiveType& mode, int first, int count);

        void bind();

        void unbind();

        bool is_active();
    private:
        void set_settings(const ContextSettings& settings);

        GLFWwindow* m_handle;
        ContextSettings m_settings;

        friend class Window;
    };
};

context.cpp:

#include <context.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <cassert>
#include <iostream>

namespace SOGL
{
    static void APIENTRY glDebugOutput(GLenum source, 
                            GLenum type, 
                            GLuint id, 
                            GLenum severity, 
                            GLsizei length, 
                            const GLchar *message, 
                            const void *userParam)
{
    // ignore non-significant error/warning codes
    if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return; 

    std::cout << "---------------" << std::endl;
    std::cout << "Debug message (" << id << "): " <<  message << std::endl;

    switch (source)
    {
        case GL_DEBUG_SOURCE_API:             std::cout << "Source: API"; break;
        case GL_DEBUG_SOURCE_WINDOW_SYSTEM:   std::cout << "Source: Window System"; break;
        case GL_DEBUG_SOURCE_SHADER_COMPILER: std::cout << "Source: Shader Compiler"; break;
        case GL_DEBUG_SOURCE_THIRD_PARTY:     std::cout << "Source: Third Party"; break;
        case GL_DEBUG_SOURCE_APPLICATION:     std::cout << "Source: Application"; break;
        case GL_DEBUG_SOURCE_OTHER:           std::cout << "Source: Other"; break;
    } std::cout << std::endl;

    switch (type)
    {
        case GL_DEBUG_TYPE_ERROR:               std::cout << "Type: Error"; break;
        case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: std::cout << "Type: Deprecated Behaviour"; break;
        case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:  std::cout << "Type: Undefined Behaviour"; break; 
        case GL_DEBUG_TYPE_PORTABILITY:         std::cout << "Type: Portability"; break;
        case GL_DEBUG_TYPE_PERFORMANCE:         std::cout << "Type: Performance"; break;
        case GL_DEBUG_TYPE_MARKER:              std::cout << "Type: Marker"; break;
        case GL_DEBUG_TYPE_PUSH_GROUP:          std::cout << "Type: Push Group"; break;
        case GL_DEBUG_TYPE_POP_GROUP:           std::cout << "Type: Pop Group"; break;
        case GL_DEBUG_TYPE_OTHER:               std::cout << "Type: Other"; break;
    } std::cout << std::endl;

    switch (severity)
    {
        case GL_DEBUG_SEVERITY_HIGH:         std::cout << "Severity: high"; break;
        case GL_DEBUG_SEVERITY_MEDIUM:       std::cout << "Severity: medium"; break;
        case GL_DEBUG_SEVERITY_LOW:          std::cout << "Severity: low"; break;
        case GL_DEBUG_SEVERITY_NOTIFICATION: std::cout << "Severity: notification"; break;
    } std::cout << std::endl;
    std::cout << std::endl;
}

    ContextSettings::ContextSettings(unsigned depth, unsigned stencil, unsigned antialiasing, 
                                     unsigned major, unsigned minor):
        depth_bits(depth), stencil_bits(stencil), antialiasing_level(antialiasing),
        major_version(major), minor_version(minor)
    {}

    Context::Context(const ContextSettings& settings)
    {
        assert(glfwInit());

        static int at_exit = -1;

        if(at_exit != 0)
            at_exit = std::atexit([] { glfwTerminate(); });

        set_settings(settings);
        assert(m_handle = glfwCreateWindow(1, 1, "", nullptr, nullptr));
        bind();

        glewExperimental = true;
        assert(glewInit() == GLEW_OK);

        enable(Capability::DepthTest);
        enable(Capability::DebugOutput);
        enable(Capability::DebugOutputSynchronous);
        glDebugMessageCallback(glDebugOutput, nullptr);
        glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
    }

    Context::Context(unsigned depth, unsigned stencil, unsigned antialiasing, 
                     unsigned major, unsigned minor) :
        Context(ContextSettings(depth, stencil, antialiasing, major, minor))
    {}

    Context::~Context()
    {
        glfwDestroyWindow(m_handle);
    }

    Context::Context(Context&& o)
    {
        m_handle = o.m_handle;
        o.m_handle = 0;
    }

    Context::Context(Context& o)
    {
        set_settings(o.settings());

        assert(m_handle = glfwCreateWindow(1, 1, "", nullptr, o.m_handle));
        bind();

        glewExperimental = true;
        assert(glewInit() == GLEW_OK);
    }

    const ContextSettings& Context::settings()
    {
        return m_settings;
    }

    void Context::enable(const Capability& capability)
    {
        glEnable(remap(capability));
    }

    void Context::disable(const Capability& capability)
    {
        glDisable(remap(capability));
    }

    bool Context::is_enabled(const Capability& capability)
    {
        return glIsEnabled(remap(capability));
    }

    bool Context::is_disabled(const Capability & capability)
    {
        return !is_enabled(capability);
    }

    void Context::draw_arrays(const PrimitiveType& mode, int first, int count)
    {
        glDrawArrays(remap(mode), first, count);
    }

    void Context::bind()
    {
        glfwMakeContextCurrent(m_handle);
    }

    void Context::unbind()
    {
        glfwMakeContextCurrent(nullptr);
    }

    bool Context::is_active()
    {
        return glfwGetCurrentContext() == m_handle;
    }

    void Context::set_settings(const ContextSettings& settings)
    {
        m_settings = settings;

        glfwWindowHint(GLFW_VISIBLE, false);
        glfwWindowHint(GLFW_RESIZABLE, false);
        glfwWindowHint(GLFW_DEPTH_BITS, m_settings.depth_bits);
        glfwWindowHint(GLFW_STENCIL_BITS, m_settings.stencil_bits);
        glfwWindowHint(GLFW_SAMPLES, m_settings.antialiasing_level);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, m_settings.major_version);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, m_settings.minor_version);
        glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true);
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    }
};

I have a few questions:

  1. How can I hide this?:

    typedef struct GLFWwindow GLFWwindow; //"YUCK!", yeah..

  2. What is the best way to call glfwTerminate?:

    if(at_exit != 0) at_exit = std::atexit([] { glfwTerminate(); });

  3. What methods are missing?
  4. How to correctly handle errors? (not always OpenGL's debug output is supported)
  5. Any suggestions?

P.S. If you are interested it is a part of SOGL framework. Any help are welcome ^^

\$\endgroup\$
  • \$\begingroup\$ and yes, is using of "friend" word ugly? \$\endgroup\$ – nuke_bird Sep 9 '17 at 15:37
1
\$\begingroup\$

I find this code a little confusing. Here are some ways to make it easier to read and understand.

Reduce Clutter

You have 4 methods that could be done with only 2. I would remove the enable() and disable() methods and make one method:

void set_enabled(const Capability& capability, bool enabled);

The second parameter is true if you want to enable it and false otherwise. Likewise, you can remove is_disabled() because is_enabled() already answers the question of whether a capability is enabled or not. (BTW, in the future, it would be nice if you included the source for Capability to make reviewing this code easier.)

Rather than having a bunch of defaults in the constructor of ContextSettings, I would set the default values of the members (assuming you can use C++11 or later), like this:

struct ContextSettings
{
    ContextSettings(unsigned depth, unsigned stencil, unsigned antialiasing, 
        unsigned major, unsigned minor);

    unsigned depth_bits { 0 };
    unsigned stencil_bits { 0 };
    unsigned antialiasing_level { 0 };
    unsigned major_version { 4 };
    unsigned minor_version { 0 };
};

You ask:

How can I hide this?:

typedef struct GLFWwindow GLFWwindow; //"YUCK!", yeah..

My question is why do you need to hide it? What do you find so offensive about it? Also, why do you have it in the first place? Is it because you didn't include the header which contains its definition? You basically have 2 choices: forward declare it, or include the header which defines it. If users of your header will never need to get a GLFWwindow from your code, then you should forward declare it as you've done. Otherwise, if someone using your header will get one, then you should include the header because otherwise they'll need to always include the GLFW header whenever they include yours, but it's not obvious that's the case. Another option would be to declare m_handle as void* in the header, and then cast it in the source file. I like that idea least, personally, but it might be appropriate if you don't want to expose users of your library to GLFW.

Don't Confuse Readers Of Your Code

You've written a function named glDebugOutput(). You shouldn't be naming any of your functions gl anything. Someone reading the source will confuse calls to this function for calls into the OpenGL library.

Asserts Will Get Compiled Away In Release

Here's how the assert() macro is defined on my system:

#ifdef NDEBUG
#define assert(e)   ((void)0)
#else
...

This means that every line where you have assert(someFunction()) is going to get replaced by the compiler with ((void)0) when you build for release instead of debug. So those calls will not happen in the release version of your code.

Termination

You ask:

What is the best way to call glfwTerminate?:

if(at_exit != 0) at_exit = std::atexit([] { glfwTerminate(); });

You're calling glfwInit() in your constructor. I'm guessing you only want to call glfwInit() once per application instance. It's quite common to create more than 1 OpenGL context (for example when a machine has 2 GPUs you'll create an OpenGL context for each GPU). So you probably don't want glfwInit() to be called in the constructor (unless I've misunderstood what it does).

So I would follow the same pattern as GLFW and have an SOGLInit() and SOGLTerminate() function that users of you library are required to call to set up and tear down everything. You would then call GLFWInit() and GLFWTerminate() in those functions.

Missing Methods

You have a draw_arrays() method that is supposed to draw some primitives. But you don't have any way to actually set up the arrays. You'll need to wrap calls like glVertexPointer() and glTexCoordPointer() if you expect glDrawArrays() to work. Or, if you expect users to call them directly, then you don't need the Context::draw_arrays() method, as users can also just call glDrawArrays(), too.

Error Handling

You ask:

How to correctly handle errors? (not always OpenGL's debug output is supported)

What sorts of errors do you want to alert callers to? OpenGL uses the terrible paradigm of pushing an error onto an error stack and then requiring you to pop off the errors yourself. Calling it after every OpenGL call is not good for performance (though it is useful for debugging). So you need to figure out what functionality your library is going to supply to users before you can figure out what sorts of error handling you'll do. You'll also have to figure out how to deal with GLFW errors, too.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.