Sign in Sign up
kretrod/lodestone-cpp Public
Branches
main
262 lines (246 loc) · 11.7 KB Raw
// The slice of OpenGL we need, and the state dance around drawing into someone
// else's frame.
//
// We are called from inside the game's swap-buffers, so every bit of GL state
// is set up for whatever it was drawing. Two lessons, both paid for the hard
// way, are baked in here:
//
//   * a sampler object bound to texture unit 0 overrides the font atlas's own
//     parameters and renders the whole menu flat black;
//   * a non-zero GL_UNPACK_ROW_LENGTH (or a bound pixel-unpack buffer) turns
//     every texture upload into noise — which is what a garbled font is.
//
// The second one bites at *font atlas upload*, not just at draw time, so the
// neutralise has to wrap texture creation too, not only rendering.
#pragma once

#include <windows.h>

#include <cstdint>

namespace lodestone::gl {

using GLenum = unsigned int;
using GLboolean = unsigned char;
using GLint = int;
using GLuint = unsigned int;
using GLsizei = int;

constexpr GLenum TEXTURE_2D = 0x0DE1;
constexpr GLenum BLEND = 0x0BE2;
constexpr GLenum DEPTH_TEST = 0x0B71;
constexpr GLenum CULL_FACE = 0x0B44;
constexpr GLenum SCISSOR_TEST = 0x0C11;
constexpr GLenum STENCIL_TEST = 0x0B90;
constexpr GLenum FRAMEBUFFER_SRGB = 0x8DB9;
constexpr GLenum VIEWPORT = 0x0BA2;
constexpr GLenum SCISSOR_BOX = 0x0C10;
constexpr GLenum CURRENT_PROGRAM = 0x8B8D;
constexpr GLenum VERTEX_ARRAY_BINDING = 0x85B5;
constexpr GLenum ARRAY_BUFFER = 0x8892;
constexpr GLenum ARRAY_BUFFER_BINDING = 0x8894;
constexpr GLenum PIXEL_UNPACK_BUFFER = 0x88EC;
constexpr GLenum PIXEL_UNPACK_BUFFER_BINDING = 0x88EF;
constexpr GLenum TEXTURE_BINDING_2D = 0x8069;
constexpr GLenum ACTIVE_TEXTURE = 0x84E0;
constexpr GLenum TEXTURE0 = 0x84C0;
constexpr GLenum SAMPLER_BINDING = 0x8919;
constexpr GLenum DRAW_FRAMEBUFFER = 0x8CA9;
constexpr GLenum DRAW_FRAMEBUFFER_BINDING = 0x8CA6;
constexpr GLenum UNPACK_ALIGNMENT = 0x0CF5;
constexpr GLenum UNPACK_ROW_LENGTH = 0x0CF2;
constexpr GLenum UNPACK_SKIP_PIXELS = 0x0CF4;
constexpr GLenum UNPACK_SKIP_ROWS = 0x0CF3;
constexpr GLenum BLEND_SRC_RGB = 0x80C9;
constexpr GLenum BLEND_DST_RGB = 0x80C8;
constexpr GLenum BLEND_SRC_ALPHA = 0x80CB;
constexpr GLenum BLEND_DST_ALPHA = 0x80CA;
constexpr GLenum BLEND_EQUATION_RGB = 0x8009;
constexpr GLenum BLEND_EQUATION_ALPHA = 0x883D;
constexpr GLenum FRONT_AND_BACK = 0x0408;
constexpr GLenum FILL = 0x1B02;
constexpr GLenum NEAREST = 0x2600;
constexpr GLenum TEXTURE_MIN_FILTER = 0x2801;
constexpr GLenum TEXTURE_MAG_FILTER = 0x2800;

// Core 1.1, exported by opengl32 itself.
using PFNGetIntegerv = void(APIENTRY*)(GLenum, GLint*);
using PFNIsEnabled = GLboolean(APIENTRY*)(GLenum);
using PFNEnable = void(APIENTRY*)(GLenum);
using PFNDisable = void(APIENTRY*)(GLenum);
using PFNPixelStorei = void(APIENTRY*)(GLenum, GLint);
using PFNBindTexture = void(APIENTRY*)(GLenum, GLuint);
using PFNViewport = void(APIENTRY*)(GLint, GLint, GLsizei, GLsizei);
using PFNScissor = void(APIENTRY*)(GLint, GLint, GLsizei, GLsizei);
using PFNColorMask = void(APIENTRY*)(GLboolean, GLboolean, GLboolean, GLboolean);
using PFNPolygonMode = void(APIENTRY*)(GLenum, GLenum);
using PFNTexParameteri = void(APIENTRY*)(GLenum, GLenum, GLint);
// Later additions, which only wglGetProcAddress knows about.
using PFNActiveTexture = void(APIENTRY*)(GLenum);
using PFNBindSampler = void(APIENTRY*)(GLuint, GLuint);
using PFNBindBuffer = void(APIENTRY*)(GLenum, GLuint);
using PFNBindVertexArray = void(APIENTRY*)(GLuint);
using PFNUseProgram = void(APIENTRY*)(GLuint);
using PFNBindFramebuffer = void(APIENTRY*)(GLenum, GLuint);
using PFNBlendFuncSeparate = void(APIENTRY*)(GLenum, GLenum, GLenum, GLenum);
using PFNBlendEquationSeparate = void(APIENTRY*)(GLenum, GLenum);

struct Api {
    PFNGetIntegerv GetIntegerv = nullptr;
    PFNIsEnabled IsEnabled = nullptr;
    PFNEnable Enable = nullptr;
    PFNDisable Disable = nullptr;
    PFNPixelStorei PixelStorei = nullptr;
    PFNBindTexture BindTexture = nullptr;
    PFNViewport Viewport = nullptr;
    PFNScissor Scissor = nullptr;
    PFNColorMask ColorMask = nullptr;
    PFNPolygonMode PolygonMode = nullptr;
    PFNTexParameteri TexParameteri = nullptr;
    PFNActiveTexture ActiveTexture = nullptr;
    PFNBindSampler BindSampler = nullptr;
    PFNBindBuffer BindBuffer = nullptr;
    PFNBindVertexArray BindVertexArray = nullptr;
    PFNUseProgram UseProgram = nullptr;
    PFNBindFramebuffer BindFramebuffer = nullptr;
    PFNBlendFuncSeparate BlendFuncSeparate = nullptr;
    PFNBlendEquationSeparate BlendEquationSeparate = nullptr;
};

inline Api g_api;

/// Extensions come from wglGetProcAddress, core 1.1 only from the opengl32
/// export table — and wglGetProcAddress reports failure as 1, 2, 3 or -1 as
/// well as null, which is a classic way to end up calling into nothing.
inline void* proc(const char* name) {
    static HMODULE opengl32 = GetModuleHandleA("opengl32.dll");
    if (!opengl32) return nullptr;
    using PFNWglGetProcAddress = PROC(APIENTRY*)(LPCSTR);
    static auto wgl = reinterpret_cast<PFNWglGetProcAddress>(
        reinterpret_cast<void*>(GetProcAddress(opengl32, "wglGetProcAddress")));
    if (wgl) {
        void* p = reinterpret_cast<void*>(wgl(name));
        auto bad = p == nullptr || p == reinterpret_cast<void*>(1) ||
                   p == reinterpret_cast<void*>(2) || p == reinterpret_cast<void*>(3) ||
                   p == reinterpret_cast<void*>(-1);
        if (!bad) return p;
    }
    return reinterpret_cast<void*>(GetProcAddress(opengl32, name));
}

inline void load() {
    auto get = [](const char* n) { return proc(n); };
    g_api.GetIntegerv = reinterpret_cast<PFNGetIntegerv>(get("glGetIntegerv"));
    g_api.IsEnabled = reinterpret_cast<PFNIsEnabled>(get("glIsEnabled"));
    g_api.Enable = reinterpret_cast<PFNEnable>(get("glEnable"));
    g_api.Disable = reinterpret_cast<PFNDisable>(get("glDisable"));
    g_api.PixelStorei = reinterpret_cast<PFNPixelStorei>(get("glPixelStorei"));
    g_api.BindTexture = reinterpret_cast<PFNBindTexture>(get("glBindTexture"));
    g_api.Viewport = reinterpret_cast<PFNViewport>(get("glViewport"));
    g_api.Scissor = reinterpret_cast<PFNScissor>(get("glScissor"));
    g_api.ColorMask = reinterpret_cast<PFNColorMask>(get("glColorMask"));
    g_api.PolygonMode = reinterpret_cast<PFNPolygonMode>(get("glPolygonMode"));
    g_api.TexParameteri = reinterpret_cast<PFNTexParameteri>(get("glTexParameteri"));
    g_api.ActiveTexture = reinterpret_cast<PFNActiveTexture>(get("glActiveTexture"));
    g_api.BindSampler = reinterpret_cast<PFNBindSampler>(get("glBindSampler"));
    g_api.BindBuffer = reinterpret_cast<PFNBindBuffer>(get("glBindBuffer"));
    g_api.BindVertexArray = reinterpret_cast<PFNBindVertexArray>(get("glBindVertexArray"));
    g_api.UseProgram = reinterpret_cast<PFNUseProgram>(get("glUseProgram"));
    g_api.BindFramebuffer = reinterpret_cast<PFNBindFramebuffer>(get("glBindFramebuffer"));
    g_api.BlendFuncSeparate = reinterpret_cast<PFNBlendFuncSeparate>(get("glBlendFuncSeparate"));
    g_api.BlendEquationSeparate =
        reinterpret_cast<PFNBlendEquationSeparate>(get("glBlendEquationSeparate"));
}

/// Everything the game might have left set that we disturb, so its next frame
/// renders exactly as it would have.
struct State {
    GLint draw_framebuffer, program, vao, array_buffer;
    GLint active_texture, texture, sampler0;
    GLint unpack_buffer, unpack_alignment, unpack_row_length;
    GLint unpack_skip_pixels, unpack_skip_rows;
    GLint viewport[4], scissor[4];
    GLint blend_src_rgb, blend_dst_rgb, blend_src_alpha, blend_dst_alpha;
    GLint blend_eq_rgb, blend_eq_alpha;
    bool blend, depth_test, cull_face, scissor_test, stencil_test, srgb;

    static State save() {
        const Api& a = g_api;
        State s{};
        a.GetIntegerv(DRAW_FRAMEBUFFER_BINDING, &s.draw_framebuffer);
        a.GetIntegerv(CURRENT_PROGRAM, &s.program);
        a.GetIntegerv(VERTEX_ARRAY_BINDING, &s.vao);
        a.GetIntegerv(ARRAY_BUFFER_BINDING, &s.array_buffer);
        a.GetIntegerv(ACTIVE_TEXTURE, &s.active_texture);
        a.GetIntegerv(TEXTURE_BINDING_2D, &s.texture);
        a.GetIntegerv(SAMPLER_BINDING, &s.sampler0);
        a.GetIntegerv(PIXEL_UNPACK_BUFFER_BINDING, &s.unpack_buffer);
        a.GetIntegerv(UNPACK_ALIGNMENT, &s.unpack_alignment);
        a.GetIntegerv(UNPACK_ROW_LENGTH, &s.unpack_row_length);
        a.GetIntegerv(UNPACK_SKIP_PIXELS, &s.unpack_skip_pixels);
        a.GetIntegerv(UNPACK_SKIP_ROWS, &s.unpack_skip_rows);
        a.GetIntegerv(VIEWPORT, s.viewport);
        a.GetIntegerv(SCISSOR_BOX, s.scissor);
        a.GetIntegerv(BLEND_SRC_RGB, &s.blend_src_rgb);
        a.GetIntegerv(BLEND_DST_RGB, &s.blend_dst_rgb);
        a.GetIntegerv(BLEND_SRC_ALPHA, &s.blend_src_alpha);
        a.GetIntegerv(BLEND_DST_ALPHA, &s.blend_dst_alpha);
        a.GetIntegerv(BLEND_EQUATION_RGB, &s.blend_eq_rgb);
        a.GetIntegerv(BLEND_EQUATION_ALPHA, &s.blend_eq_alpha);
        s.blend = a.IsEnabled(BLEND);
        s.depth_test = a.IsEnabled(DEPTH_TEST);
        s.cull_face = a.IsEnabled(CULL_FACE);
        s.scissor_test = a.IsEnabled(SCISSOR_TEST);
        s.stencil_test = a.IsEnabled(STENCIL_TEST);
        s.srgb = a.IsEnabled(FRAMEBUFFER_SRGB);
        return s;
    }

    /// The plain state ImGui assumes. Also used around font-atlas upload, which
    /// is the other place the pixel-transfer settings matter.
    static void neutralise() {
        const Api& a = g_api;
        a.BindFramebuffer(DRAW_FRAMEBUFFER, 0);
        a.Disable(DEPTH_TEST);
        a.Disable(CULL_FACE);
        a.Disable(STENCIL_TEST);
        a.Disable(SCISSOR_TEST);
        // Plain sRGB values are written; a second hardware conversion washes the
        // whole menu out.
        a.Disable(FRAMEBUFFER_SRGB);
        a.ColorMask(1, 1, 1, 1);
        a.PolygonMode(FRONT_AND_BACK, FILL);
        a.ActiveTexture(TEXTURE0);
        if (a.BindSampler) a.BindSampler(0, 0);
        a.BindBuffer(PIXEL_UNPACK_BUFFER, 0);
        a.PixelStorei(UNPACK_ALIGNMENT, 4);
        a.PixelStorei(UNPACK_ROW_LENGTH, 0);
        a.PixelStorei(UNPACK_SKIP_PIXELS, 0);
        a.PixelStorei(UNPACK_SKIP_ROWS, 0);
    }

    void restore() const {
        const Api& a = g_api;
        a.BindFramebuffer(DRAW_FRAMEBUFFER, static_cast<GLuint>(draw_framebuffer));
        a.UseProgram(static_cast<GLuint>(program));
        a.BindVertexArray(static_cast<GLuint>(vao));
        a.BindBuffer(ARRAY_BUFFER, static_cast<GLuint>(array_buffer));
        a.BindBuffer(PIXEL_UNPACK_BUFFER, static_cast<GLuint>(unpack_buffer));
        a.PixelStorei(UNPACK_ALIGNMENT, unpack_alignment);
        a.PixelStorei(UNPACK_ROW_LENGTH, unpack_row_length);
        a.PixelStorei(UNPACK_SKIP_PIXELS, unpack_skip_pixels);
        a.PixelStorei(UNPACK_SKIP_ROWS, unpack_skip_rows);
        a.ActiveTexture(TEXTURE0);
        if (a.BindSampler) a.BindSampler(0, static_cast<GLuint>(sampler0));
        a.BindTexture(TEXTURE_2D, static_cast<GLuint>(texture));
        a.ActiveTexture(static_cast<GLenum>(active_texture));
        a.Viewport(viewport[0], viewport[1], viewport[2], viewport[3]);
        a.Scissor(scissor[0], scissor[1], scissor[2], scissor[3]);
        set(BLEND, blend);
        a.BlendEquationSeparate(static_cast<GLenum>(blend_eq_rgb),
                                static_cast<GLenum>(blend_eq_alpha));
        a.BlendFuncSeparate(static_cast<GLenum>(blend_src_rgb), static_cast<GLenum>(blend_dst_rgb),
                            static_cast<GLenum>(blend_src_alpha),
                            static_cast<GLenum>(blend_dst_alpha));
        set(DEPTH_TEST, depth_test);
        set(CULL_FACE, cull_face);
        set(SCISSOR_TEST, scissor_test);
        set(STENCIL_TEST, stencil_test);
        set(FRAMEBUFFER_SRGB, srgb);
    }

  private:
    static void set(GLenum cap, bool on) {
        if (on) {
            g_api.Enable(cap);
        } else {
            g_api.Disable(cap);
        }
    }
};

}  // namespace lodestone::gl