← index
libmx2/libmx/cross/mxcross.hpp
Source: libmx2/libmx/cross/mxcross.hpp
#ifndef MX_CROSS_H_
#define MX_CROSS_H_

#include "config.h"
#include "exception.hpp"
#include <SDL2/SDL.h>
#include <memory>
#include <string>

class MX_WindowBase;
class MX_App;

class MX_App {
  public:
    virtual ~MX_App() = default;
    virtual void load(MX_WindowBase *win) {}
    virtual void draw(MX_WindowBase *win) {}
    virtual void resize(MX_WindowBase *win, int w, int h) {}
    virtual void event(MX_WindowBase *win, SDL_Event &e) {}
    virtual void cleanup(MX_WindowBase *win) {}
};

class MX_WindowBase {
  public:
    virtual ~MX_WindowBase() = default;
    virtual void run(const std::string &title, int w, int h, bool fullscreen = false) = 0;
    virtual void loop() = 0;
    virtual void quit() = 0;
    virtual void cleanup() = 0;
    virtual void setApp(MX_App *a) = 0;
    virtual void setPath(const std::string &path) = 0;
    virtual void event(SDL_Event &e) = 0;
    virtual void draw() = 0;
    virtual void drawTriangle() {}
};

#if defined(WITH_GL)
#include "gl.hpp"

class MX_GLAdapter : public MX_WindowBase {
  public:
    MX_GLAdapter() = default;
    ~MX_GLAdapter() override { cleanup(); }

    void run(const std::string &title, int w, int h, bool fullscreen = false) override {
        window = std::make_unique<Window>(title, w, h, this);
        if (!pending_path.empty())
            window->setPath(pending_path);
        if (app)
            app->load(this);
        window->loop();
    }
    void loop() override {
        if (window)
            window->loop();
    }
    void quit() override {
        if (window)
            window->quit();
    }
    void cleanup() override {
        if (app)
            app->cleanup(this);
        window.reset();
    }
    void setApp(MX_App *a) override { app = a; }
    void setPath(const std::string &path) override {
        pending_path = path;
        if (window)
            window->setPath(path);
    }
    void event(SDL_Event &e) override {
        if (app)
            app->event(this, e);
    }
    void draw() override {
        if (window)
            window->draw();
    }

    void drawTriangle() override;

  private:
    class Bridge;
    class Window : public gl::GLWindow {
      public:
        Window(const std::string &title, int w, int h, MX_GLAdapter *owner)
            : gl::GLWindow(title, w, h, true), owner(owner) {
            setObject(new Bridge(owner));
        }
        void event(SDL_Event &e) override {
            if (owner)
                owner->event(e);
        }
        void draw() override {
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            if (owner)
                owner->drawTriangle();
            if (owner && owner->app)
                owner->app->draw(owner);
            swap();
        }

      private:
        MX_GLAdapter *owner;
    };

    class Bridge : public gl::GLObject {
      public:
        explicit Bridge(MX_GLAdapter *owner) : owner(owner) {}
        void load(gl::GLWindow *win) override {
            if (owner && owner->app)
                owner->app->load(owner);
        }
        void draw(gl::GLWindow *win) override {
            if (owner && owner->app)
                owner->app->draw(owner);
        }
        void event(gl::GLWindow *win, SDL_Event &e) override {
            if (owner && owner->app)
                owner->app->event(owner, e);
        }
        void resize(gl::GLWindow *win, int w, int h) override {
            if (owner && owner->app)
                owner->app->resize(owner, w, h);
        }

      private:
        MX_GLAdapter *owner;
    };

    std::unique_ptr<Window> window;
    MX_App *app = nullptr;
    std::string pending_path;
    unsigned int triProgram = 0;
    unsigned int triVao = 0;
    unsigned int triVbo = 0;
    bool triReady = false;
    int triModelLoc = -1;
    int triViewLoc = -1;
    int triProjLoc = -1;
    int triParamsLoc = -1;
    int triColorLoc = -1;
};

inline void MX_GLAdapter::drawTriangle() {
    if (!window)
        return;
    if (!triReady) {
        const char *vsrc = R"glsl(#version 410 core
            layout(location = 0) in vec3 inPosition;
            layout(location = 1) in vec2 inTexCoord;
            layout(location = 2) in vec3 inNormal;
            layout(location = 1) out vec2 fragTexCoord;
            uniform mat4 model;
            uniform mat4 view;
            uniform mat4 proj;
            void main() {
                fragTexCoord = inTexCoord;
                gl_Position = proj * view * model * vec4(inPosition, 1.0);
            }
            )glsl";

        const char *fsrc = R"glsl(#version 410 core
            layout(location = 1) in vec2 fragTexCoord;
            layout(location = 0) out vec4 outColor;
            uniform vec4 color;
            uniform vec4 params;
            void main() {
                float u = fragTexCoord.x;
                float v = fragTexCoord.y;
                float t = u * 6.2831853;
                float r = sin(t + v * 3.14159 + params.x);
                float g = sin(t * 0.8 + v * 2.0 + 2.0 + params.x);
                float b = sin(t * 1.2 + v * 1.5 + 4.0 + params.x);
                vec3 col = 0.5 + 0.5 * vec3(r, g, b);
                outColor = vec4(col * color.xyz, color.w);
            }
            )glsl";

        GLuint vs = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vs, 1, &vsrc, nullptr);
        glCompileShader(vs);
        GLint compiled = 0;
        glGetShaderiv(vs, GL_COMPILE_STATUS, &compiled);
        if (!compiled) {
            GLint len = 0;
            glGetShaderiv(vs, GL_INFO_LOG_LENGTH, &len);
            std::string log;
            log.resize(len > 0 ? len : 1);
            glGetShaderInfoLog(vs, len, nullptr, &log[0]);
            SDL_Log("GL vertex shader compile failed: %s", log.c_str());
            glDeleteShader(vs);
            return;
        }

        GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fs, 1, &fsrc, nullptr);
        glCompileShader(fs);
        glGetShaderiv(fs, GL_COMPILE_STATUS, &compiled);
        if (!compiled) {
            GLint len = 0;
            glGetShaderiv(fs, GL_INFO_LOG_LENGTH, &len);
            std::string log;
            log.resize(len > 0 ? len : 1);
            glGetShaderInfoLog(fs, len, nullptr, &log[0]);
            SDL_Log("GL fragment shader compile failed: %s", log.c_str());
            glDeleteShader(vs);
            glDeleteShader(fs);
            return;
        }

        triProgram = glCreateProgram();
        glAttachShader(triProgram, vs);
        glAttachShader(triProgram, fs);
        glLinkProgram(triProgram);
        GLint linked = 0;
        glGetProgramiv(triProgram, GL_LINK_STATUS, &linked);
        if (!linked) {
            GLint len = 0;
            glGetProgramiv(triProgram, GL_INFO_LOG_LENGTH, &len);
            std::string log;
            log.resize(len > 0 ? len : 1);
            glGetProgramInfoLog(triProgram, len, nullptr, &log[0]);
            SDL_Log("GL program link failed: %s", log.c_str());
            glDeleteProgram(triProgram);
            triProgram = 0;
            glDeleteShader(vs);
            glDeleteShader(fs);
            return;
        }
        glDeleteShader(vs);
        glDeleteShader(fs);

        float verts[] = {
            -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
            0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
            0.0f, 0.5f, 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f};

        glGenVertexArrays(1, &triVao);
        glGenBuffers(1, &triVbo);
        glBindVertexArray(triVao);
        glBindBuffer(GL_ARRAY_BUFFER, triVbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
        GLsizei stride = 8 * sizeof(float);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (void *)(0));
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, (void *)(3 * sizeof(float)));
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, stride, (void *)(5 * sizeof(float)));
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);

        triModelLoc = glGetUniformLocation(triProgram, "model");
        triViewLoc = glGetUniformLocation(triProgram, "view");
        triProjLoc = glGetUniformLocation(triProgram, "proj");
        triParamsLoc = glGetUniformLocation(triProgram, "params");
        triColorLoc = glGetUniformLocation(triProgram, "color");
        triReady = true;
    }

    glDisable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);
    glUseProgram(triProgram);

    float time = SDL_GetTicks() / 1000.0f;
    if (triModelLoc >= 0) {
        glm::mat4 model = glm::rotate(glm::mat4(1.0f), time * glm::radians(45.0f), glm::vec3(1.0f, 1.0f, 1.0f));
        glUniformMatrix4fv(triModelLoc, 1, GL_FALSE, &model[0][0]);
    }
    if (triViewLoc >= 0) {
        glm::vec3 cameraPos(0.0f, 0.0f, 3.0f);
        glm::vec3 cameraTarget(0.0f, 0.0f, 0.0f);
        glm::vec3 up(0.0f, 1.0f, 0.0f);
        glm::mat4 view = glm::lookAt(cameraPos, cameraTarget, up);
        glUniformMatrix4fv(triViewLoc, 1, GL_FALSE, &view[0][0]);
    }
    if (triProjLoc >= 0) {
        int w = window->w, h = window->h;
        float aspect = (h == 0) ? 1.0f : (float)w / (float)h;
        glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 1000.0f);

        proj[1][1] *= -1;
        glUniformMatrix4fv(triProjLoc, 1, GL_FALSE, &proj[0][0]);
    }
    if (triParamsLoc >= 0) {
        glUniform4f(triParamsLoc, time, 0.0f, 0.0f, 0.0f);
    }
    if (triColorLoc >= 0) {
        glUniform4f(triColorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
    }

    glBindVertexArray(triVao);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    glBindVertexArray(0);
    glUseProgram(0);
    GLenum err = glGetError();
    if (err != GL_NO_ERROR) {
        SDL_Log("GL error after draw: 0x%X", err);
    }
}

#endif

#if (defined(WITH_VULKAN) || defined(VULKAN) || defined(USE_VULKAN) || defined(WITH_VK))
#include "vk.hpp"

class MX_VKAdapter : public MX_WindowBase, public mx::VKWindow {
  public:
    MX_VKAdapter() = default;
    ~MX_VKAdapter() override { cleanup(); }

    void run(const std::string &title, int w, int h, bool fullscreen = false) override {
        initWindow(title, w, h, fullscreen);
        if (!pending_path.empty())
            mx::VKWindow::setPath(pending_path);
        initVulkan();
        if (app)
            app->load(this);
        loop();
    }

    void loop() override { mx::VKWindow::loop(); }
    void quit() override { mx::VKWindow::quit(); }
    void cleanup() override {
        if (app)
            app->cleanup(this);
        mx::VKWindow::cleanup();
    }
    void setApp(MX_App *a) override { app = a; }
    void setPath(const std::string &path) override {
        pending_path = path;
        mx::VKWindow::setPath(path);
    }

    void event(SDL_Event &e) override {
        if (app)
            app->event(this, e);
    }
    void draw() override {
        this->drawTriangle();
        this->currentPolygonMode = VK_POLYGON_MODE_FILL;
        mx::VKWindow::draw();
        if (app)
            app->draw(this);
    }
    void drawTriangle() override {
        if (vertexBuffer != VK_NULL_HANDLE && indexBuffer != VK_NULL_HANDLE && indexCount > 0)
            return;
        std::vector<mx::Vertex> verts = {
            mx::Vertex{{-0.5f, -0.5f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
            mx::Vertex{{0.5f, -0.5f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
            mx::Vertex{{0.0f, 0.5f, 0.0f}, {0.5f, 1.0f}, {0.0f, 0.0f, 1.0f}}};
        std::vector<uint32_t> inds = {0, 1, 2};

        VkDeviceSize vertexSize = sizeof(mx::Vertex) * verts.size();
        VkDeviceSize indexSize = sizeof(uint32_t) * inds.size();

        VkBuffer stagingBuffer;
        VkDeviceMemory stagingBufferMemory;
        createBuffer(vertexSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
                     VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
                     stagingBuffer, stagingBufferMemory);
        void *data;
        VK_CHECK_RESULT(vkMapMemory(device, stagingBufferMemory, 0, vertexSize, 0, &data));
        memcpy(data, verts.data(), static_cast<size_t>(vertexSize));
        vkUnmapMemory(device, stagingBufferMemory);

        if (vertexBuffer != VK_NULL_HANDLE) {
            vkDestroyBuffer(device, vertexBuffer, nullptr);
            vkFreeMemory(device, vertexBufferMemory, nullptr);
        }
        createBuffer(vertexSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
                     VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory);
        copyBuffer(stagingBuffer, vertexBuffer, vertexSize);
        vkDestroyBuffer(device, stagingBuffer, nullptr);
        vkFreeMemory(device, stagingBufferMemory, nullptr);

        createBuffer(indexSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
                     VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
                     stagingBuffer, stagingBufferMemory);
        VK_CHECK_RESULT(vkMapMemory(device, stagingBufferMemory, 0, indexSize, 0, &data));
        memcpy(data, inds.data(), static_cast<size_t>(indexSize));
        vkUnmapMemory(device, stagingBufferMemory);

        if (indexBuffer != VK_NULL_HANDLE) {
            vkDestroyBuffer(device, indexBuffer, nullptr);
            vkFreeMemory(device, indexBufferMemory, nullptr);
        }
        createBuffer(indexSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
                     VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory);
        copyBuffer(stagingBuffer, indexBuffer, indexSize);
        vkDestroyBuffer(device, stagingBuffer, nullptr);
        vkFreeMemory(device, stagingBufferMemory, nullptr);

        indexCount = static_cast<uint32_t>(inds.size());
        (void)vertexBuffer;
        (void)indexBuffer;
        (void)indexCount;
        if (textureImage == VK_NULL_HANDLE) {
            setupTextureImage(1, 1);
            uint8_t whitePixel[4] = {255, 255, 255, 255};
            updateTexture(whitePixel, 4);
            createTextureImageView();
            createTextureSampler();
            try {
                updateDescriptorSets();
            } catch (...) {
            }
        }
    }

  private:
    MX_App *app = nullptr;
    std::string pending_path;
};
#endif

inline std::unique_ptr<MX_WindowBase> create_gl_window() {
#if defined(WITH_GL)
    return std::make_unique<MX_GLAdapter>();
#else
    throw mx::Exception("GL support not compiled in");
#endif
}

#if defined(WITH_VULKAN) || defined(VULKAN) || defined(USE_VULKAN) || defined(WITH_VK)
inline std::unique_ptr<MX_WindowBase> create_vk_window() {
    return std::make_unique<MX_VKAdapter>();
}
#endif

#endif