#define STAPLEGL_DEBUG
#include "glad.h"
#include <GLFW/glfw3.h>
 
#include <algorithm>
#include <array>
#include <cstddef>
#include <iostream>
#include <span>
#include <utility>
 
#include "box.h"
#include "screen_quad.h"
#include "teapot_data.h"
 
#include "glm.hpp"
#include "gtc/matrix_transform.hpp"
#include "gtc/type_ptr.hpp"
 
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
 
 
 
 
#define STAPLEGL_TEAPOT_LOW_MSAA
 
#ifdef STAPLEGL_TEAPOT_LOW_MSAA
#else
#endif
 
 
 
 
void GLAPIENTRY
    GLenum type,
    GLuint id [[maybe_unused]],
    GLenum severity,
    GLsizei length [[maybe_unused]],
    const GLchar* message,
    const void* userParam [[maybe_unused]])
{
    
    
    
 
    
    if (severity == GL_DEBUG_SEVERITY_NOTIFICATION || type == GL_DEBUG_TYPE_PERFORMANCE || type == GL_DEBUG_TYPE_OTHER) {
        return;
    }
 
    fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x,\nmessage = %s\n", 
        (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
        type, severity, message);
 
    
    fprintf(stderr, "source = 0x%x, id = %d\n", source, id); 
}
 
{
    uint32_t levels = 1;
    while (width > 2 && height > 2) {
        width /= 2;
        height /= 2;
        levels++;
    }
    return levels;
}
 
 
{
 
    std::string_view const hello_message {
        "Hello! This is a more complex example of staplegl usage, featuring the Utah Teapot model.\n"
        "Press the U and D keys to increase (U) and decrease (D) the luminosity of the light source.\n"
        "Play around with them to observe how the bloom effect changes."
    };
 
    std::cout << hello_message << std::endl;
 
    
 
    
    
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
 
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
 
#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
 
    
    
    if (window == nullptr) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
 
    
    
    if (gladLoadGLLoader(reinterpret_cast<GLADloadproc>(glfwGetProcAddress)) == 0) { 
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
 
    
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_DEBUG_OUTPUT);
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
 
#ifdef STAPLEGL_DEBUG
    glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
    std::clog << "Printing OpenGL version info:" << std::endl;
    std::clog << "OpenGL version: " << glGetString(GL_VERSION) << std::endl;
    std::clog << "OpenGL vendor: " << glGetString(GL_VENDOR) << std::endl;
    std::clog << "OpenGL renderer: " << glGetString(GL_RENDERER) << std::endl;
#endif
 
    
 
    skybox_shader.upload_uniform1i("skybox", 0);
 
    teapot_shader.bind();
    teapot_shader.upload_uniform1i("environment", 0);
 
    passthrough_shader.bind();
    passthrough_shader.upload_uniform1i("scene", 1);
 
    tonemap_shader.bind();
    tonemap_shader.upload_uniform1i("scene", 1);
    tonemap_shader.upload_uniform1i("bloom", 2);
 
    downsample_shader.bind();
    downsample_shader.upload_uniform1i("scene", 1);
 
    upsample_shader.bind();
    upsample_shader.upload_uniform1i("scene", 1);
 
    
 
        std::span<const float> {},
            .
min_filter = GL_LINEAR, .mag_filter = GL_LINEAR, .clamping = GL_CLAMP_TO_EDGE },
 
    };
 
        std::span<const float> {},
            .
min_filter = GL_LINEAR, .mag_filter = GL_LINEAR, .clamping = GL_CLAMP_TO_EDGE }
 
    };
 
    
    
    
    for (int i = 0; auto& tex : pyramid_textures) {
            std::span<const float> {},
                .
min_filter = GL_LINEAR, .mag_filter = GL_LINEAR, .clamping = GL_CLAMP_TO_EDGE }
 
        };
        i++;
    }
 
 
    
    
 
    
 
 
        { teapot_indices } 
    };
 
 
 
    
 
 
 
    
    std::vector<unsigned int> skybox_indices(SKYBOX_VERTS / 3);
    std::generate(skybox_indices.begin(), skybox_indices.end(),
        [n = 0]() mutable { return n++; });
 
 
 
    
    glm::mat4 const model = glm::mat4(1.0F);
 
    
 
 
 
 
 
    
 
 
 
 
        { u_type::mat4, "projection" },
        { u_type::mat4, "view" },
        { u_type::mat4, "model" },
        { u_type::vec4, "camera_pos" }
    };
 
 
        { u_type::vec4, "light_pos" },
        { u_type::vec4, "light_color" },
        { u_type::vec4, "light_attenuation" }, 
        { u_type::vec2, "light_intensities" } 
    };
 
 
    const glm::vec4 light_pos { 1.0F, 1.0F, 10.0F, 1.0F };
    const glm::vec4 light_color { 0.9333F, 0.5098, 0.9333F, 1.0F };
 
    light_block.set_attribute_data(std::span { glm::value_ptr(light_pos), 4 }, "light_pos");
    light_block.set_attribute_data(std::span { glm::value_ptr(light_color), 4 }, "light_color");
    light_block.set_attribute_data(std::span { glm::value_ptr(glm::vec4(1.0F, 0.22F, 0.20F, 0.0F)), 4 }, "light_attenuation");
    light_block.set_attribute_data(std::span { glm::value_ptr(glm::vec2(
luminosity, 1.2F)), 2 }, 
"light_intensities");
 
    light_block.unbind();
 
        { u_type::vec4, "material_color" },
        { u_type::float32, "material_shininess" },
        { u_type::float32, "material_roughness" }
    };
 
    
    const glm::vec4 teapot_color { 0.51F, 0.55F, 0.66F, 1.0F };
    const float teapot_shininess = 32.0F;
    const float teapot_roughness = 0.80F;
 
    material_block.set_attribute_data(std::span { glm::value_ptr(teapot_color), 4 }, "material_color");
    material_block.set_attribute_data(std::span { &teapot_shininess, 1 }, "material_shininess");
    material_block.set_attribute_data(std::span { &teapot_roughness, 1 }, "material_roughness");
    material_block.unbind();
 
    
    
    std::array<std::string, 6> const faces {
        "./assets/skybox/right.jpg", "./assets/skybox/left.jpg",
        "./assets/skybox/top.jpg", "./assets/skybox/bottom.jpg",
        "./assets/skybox/front.jpg", "./assets/skybox/back.jpg"
    };
 
    
    std::array<std::span<std::byte>, 6> cube_data;
 
    std::int32_t width = 0, height = 0, nrChannels = 0;
    int i = 0;
    for (auto& face : faces) {
        auto* data = reinterpret_cast<std::byte*>( 
            stbi_load(face.c_str(), &width, &height, &nrChannels, 0));
        if (data == nullptr) {
            std::cout << "Failed to load texture" << std::endl;
            return -1;
        }
        
        cube_data[i++] = std::span<std::byte> { data, static_cast<size_t>(width * height * nrChannels) };
    }
 
    
    
        cube_data, { width, height },
        { .internal_format = GL_SRGB8, .format = GL_RGB, .datatype = GL_UNSIGNED_BYTE },
        { .min_filter = GL_LINEAR, .mag_filter = GL_LINEAR, .clamping = GL_CLAMP_TO_EDGE },
        true
    };
 
    for (auto& face : cube_data) {
        stbi_image_free(face.data());
    }
 
    
    skybox.set_unit(0);
 
    glClearColor(0.F, 0.F, 0.F, 1.0F);
 
    while (glfwWindowShouldClose(window) == 0) {
        
        
 
        light_block.bind();
        light_block.set_attribute_data(std::span { glm::value_ptr(glm::vec2(
luminosity, 1.2F)), 2 }, 
"light_intensities");
 
 
        
        const auto time = static_cast<float>(glfwGetTime());
        const float radius = 4.0F;
        const float slow_factor = 0.25F;
 
        const float camX = sin(time * slow_factor) * radius;
        const float camZ = cos(time * slow_factor) * radius;
        const float camY = sin(time * slow_factor) + 0.5F; 
 
        
        glm::vec4 camera_pos { camX, camY, camZ, 1.0F };
 
        
        glm::mat4 view = glm::lookAt(glm::vec3(camera_pos), glm::vec3(0.0F, 0.0F, 0.0F),
            glm::vec3(0.0F, 1.0F, 0.0F));
        glm::mat4 projection = glm::perspective(glm::radians(90.0F), 
aspect_ratio, 0.01F, 100.0F);
 
 
        
        camera_block.bind();
        camera_block.set_attribute_data(
            std::span { glm::value_ptr(camera_pos), 4 }, "camera_pos");
        camera_block.set_attribute_data(std::span { glm::value_ptr(view), 16 }, "view");
        camera_block.set_attribute_data(std::span { glm::value_ptr(projection), 16 }, "projection");
 
        
        msaa_fbo.bind();
        msaa_fbo.set_texture(msaa_color);
        msaa_fbo.set_renderbuffer(
 
        if (!msaa_fbo.assert_completeness()) [[unlikely]] {
            std::cerr << "Framebuffer not complete, line: " << __LINE__ << std::endl;
            return EXIT_FAILURE; 
        }
 
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
 
        
 
 
 
        
        skybox_shader.bind();
 
        
        
        glm::mat4 skybox_mat = glm::scale(glm::mat4(1.0F), glm::vec3(50.0F, 50.0F, 50.0F));
        camera_block.set_attribute_data(std::span { glm::value_ptr(skybox_mat), 16 }, "model");
 
        
        
        glDepthMask(GL_FALSE);
        glDrawElements(GL_TRIANGLES, skybox_VAO.
index_data().count(), GL_UNSIGNED_INT, 
nullptr);
 
        glDepthMask(GL_TRUE);
 
        
        
 
        glm::mat4 light_mat = glm::translate(glm::mat4(1.0F), glm::vec3(light_pos));
        camera_block.set_attribute_data(std::span { glm::value_ptr(light_mat), 16 }, "model");
 
        light_shader.bind();
        
        
        glFrontFace(GL_CW);
        glDrawElements(GL_TRIANGLES, skybox_VAO.
index_data().count(), GL_UNSIGNED_INT, 
nullptr);
 
        glFrontFace(GL_CCW); 
 
        
 
        
        glm::mat4 model_mat = glm::scale(model, glm::vec3(1.0F, -1.0F, 1.0F));
        camera_block.set_attribute_data(std::span { glm::value_ptr(model_mat), 16 }, "model");
 
        teapot_shader.bind();
 
        glDrawElements(GL_TRIANGLES, VAO.
index_data().count(), GL_UNSIGNED_INT, 
nullptr);
 
 
        
 
 
 
        post_fbo.bind();
        post_fbo.set_texture(hdr_color);
 
 
        post_fbo.bind();
 
        
 
 
 
        
        
 
 
        
        passthrough_shader.bind();
        hdr_color.set_unit(1);
        post_fbo.set_texture(pyramid_textures[0], 0);
 
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 
 
        downsample_shader.bind();
 
        
        for (size_t i = 0; i < pyramid_textures.size() - 1; i++) {
            
 
            auto& draw_source = pyramid_textures[i];
            auto& draw_target = pyramid_textures[i + 1];
            auto const& t_res = draw_target.get_resolution();
 
            draw_source.set_unit(1);
 
            post_fbo.set_texture(draw_target, 0);
            post_fbo.set_viewport(
                { t_res.width,
                    t_res.height });
 
            downsample_shader.upload_uniform2f(
                "uResolution", static_cast<float>(t_res.width),
                static_cast<float>(t_res.height));
 
            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        }
 
        
        
 
        
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glBlendEquation(GL_FUNC_ADD);
 
        upsample_shader.bind();
 
        for (int i = pyramid_textures.size() - 1; i > 0; i--) {
            
 
            auto& draw_source = pyramid_textures[i];
            auto& draw_target = pyramid_textures[i - 1];
 
            draw_source.set_unit(1);
 
            post_fbo.set_texture(draw_target, 0);
            post_fbo.set_viewport(
                { draw_target.get_resolution().width,
                    draw_target.get_resolution().height });
            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        }
        glDisable(GL_BLEND);
 
        
        
 
        
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
        tonemap_shader.bind();
        
        hdr_color.set_unit(1);
        pyramid_textures[0].set_unit(2);
 
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
 
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
 
    
 
    
    
    glfwTerminate();
    return 0;
}
 
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, 1);
    }
    if (glfwGetKey(window, GLFW_KEY_U) == GLFW_PRESS) {
    }
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
    }
    if (glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS) {
        
        static bool wireframe = false;
        wireframe = !wireframe;
        glPolygonMode(GL_FRONT_AND_BACK, wireframe ? GL_LINE : GL_FILL);
    }
}
 
{
    
    
    glViewport(0, 0, width, height);
    aspect_ratio = 
static_cast<float>(width) / 
static_cast<float>(height);
 
}
void processInput(GLFWwindow *window)
 
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
 
void GLAPIENTRY MessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam)
 
Cube map texture wrapper.
 
Framebuffer Object (FBO) wrapper.
 
static void bind_default()
Bind the default framebuffer.
 
static void transfer_data(framebuffer const &src, framebuffer const &dst, resolution res)
Transfer the contents of a framebuffer to another.
 
Element Buffer Object (EBO) wrapper.
 
void bind() const
Bind the shader program.
 
Vertex Array Object (VAO) wrapper.
 
void bind() const
Bind the vertex array object.
 
void set_index_buffer(index_buffer &&ibo)
Set the index buffer object.
 
static void unbind()
Unbind the vertex array object.
 
constexpr auto index_data() -> index_buffer &
Get the index buffer object.
 
auto add_vertex_buffer(vertex_buffer &&vbo) -> vertex_array::iterator_t
Add a vertex buffer to the vertex array object.
 
Vertex Buffer Object (VBO) wrapper.
 
void set_layout(const vertex_buffer_layout &layout)
Set the layout object.
 
@ ATTACH_DEPTH_STENCIL_BUFFER
 
StapleGL, a C++20 wrapper for OpenGL.
 
A struct that represents an image's dimensions.
 
OpenGL texture details relating to the color format and data type.
 
std::int32_t internal_format
 
OpenGL texture details relating to filtering and clamping of the image.
 
constexpr auto MSAA_SAMPLES
 
constexpr auto calc_pyramid_levels(uint32_t width, uint32_t height) -> uint32_t