paradiso/src/lib/src/renderer.cpp
2023-07-03 16:59:28 +02:00

249 lines
8.2 KiB
C++

/*
* Copyright 2023 Hartmut Seichter
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "paradiso/renderer.hpp"
#include "paradiso/bitmap.hpp"
#include "paradiso/shader.hpp"
#include "paradiso/sprite.hpp"
#include "glad/glad.h"
#include <algorithm>
#include <array>
#include <iomanip>
#include <iostream>
#include <vector>
#undef max
namespace paradiso {
struct Renderer::impl {
uint64_t change_count{std::numeric_limits<uint64_t>::max()};
uint64_t change_count_texture{std::numeric_limits<uint64_t>::max()};
// below corresponds to GL state
uint32_t vertex_array_obj{};
uint32_t element_buffer_obj{};
std::vector<uint32_t> vertex_buffer_ob{};
uint32_t texture_id{};
~impl() { release(); }
bool ready() const {
return glIsVertexArray != nullptr &&
GL_TRUE == glIsVertexArray(vertex_array_obj);
}
bool build(const Sprite& sprite) {
// reset if the Renderer already in use
if (ready())
release();
//
glGenVertexArrays(1, &vertex_array_obj);
glBindVertexArray(vertex_array_obj);
// element buffer
glGenBuffers(1, &element_buffer_obj);
// indices -> elements
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_obj);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
sprite.indices.size() * sizeof(uint32_t),
sprite.indices.data(), GL_STATIC_DRAW);
vertex_buffer_ob.resize(vertex_buffer_ob.size() + 1);
glGenBuffers(1, &vertex_buffer_ob.back());
// vertices
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_ob.back());
glVertexAttribPointer(vertex_buffer_ob.size() - 1, 3, GL_FLOAT,
GL_FALSE, 0, nullptr);
glBufferData(GL_ARRAY_BUFFER,
sprite.vertices.size() * sizeof(float) * 3,
sprite.vertices.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(vertex_buffer_ob.size() - 1);
vertex_buffer_ob.resize(vertex_buffer_ob.size() + 1);
glGenBuffers(1, &vertex_buffer_ob.back());
// normals
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_ob.back());
glVertexAttribPointer(vertex_buffer_ob.size() - 1, 3, GL_FLOAT,
GL_FALSE, 0, nullptr);
glBufferData(GL_ARRAY_BUFFER, sprite.normals.size() * sizeof(float) * 3,
sprite.normals.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(vertex_buffer_ob.size() - 1);
vertex_buffer_ob.resize(vertex_buffer_ob.size() + 1);
glGenBuffers(1, &vertex_buffer_ob.back());
// texture coordinates
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_ob.back());
glVertexAttribPointer(vertex_buffer_ob.size() - 1, 2, GL_FLOAT,
GL_FALSE, 0, nullptr);
glBufferData(GL_ARRAY_BUFFER,
sprite.texture_coordinates.size() * sizeof(float) * 2,
sprite.texture_coordinates.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(vertex_buffer_ob.size() - 1);
// stop binding
glBindVertexArray(0);
change_count = sprite.change_count;
return ready();
}
void texture_bind() {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture_id);
}
void texture_unbind() { glBindTexture(GL_TEXTURE_2D, 0); }
void texture_update(const Bitmap& image) {
if (GL_FALSE == glIsTexture(texture_id)) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glGenTextures(1, &texture_id);
glActiveTexture(GL_TEXTURE0 + 0);
glBindTexture(GL_TEXTURE_2D, texture_id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// setup new texture
glTexImage2D(GL_TEXTURE_2D, // target
0, // level
GL_RGBA, // internal format
image.size.width, // width
image.size.height, // height
0, // border
GL_BGRA, // format
GL_UNSIGNED_BYTE, // type
image.data.data() // pointer to data
);
// generate MipMaps
glGenerateMipmap(GL_TEXTURE_2D);
glGenerateTextureMipmap(texture_id);
} else if (image.change_count != change_count_texture) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture_id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D,
0, // level
0, // x-offset
0, // y-offset
image.size.width, // width
image.size.height, // height
GL_BGRA, // format
GL_UNSIGNED_BYTE, // type
image.data.data()); // pointer
change_count_texture = image.change_count;
}
glBindTexture(GL_TEXTURE_2D, 0);
}
void texture_release() { glDeleteTextures(1, &texture_id); }
void release() {
for (auto vbo : vertex_buffer_ob)
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &element_buffer_obj);
glDeleteVertexArrays(1, &vertex_array_obj);
texture_release();
}
void just_draw(const Sprite& sprite) {
glBindVertexArray(vertex_array_obj);
glDrawElements(GL_TRIANGLES, sprite.indices.size(), GL_UNSIGNED_INT,
nullptr);
glBindVertexArray(0);
}
static void fetch_errors(std::string_view tag) {
std::cout << tag << '\n';
GLenum e{};
while ((e = glGetError()) != GL_NO_ERROR) {
std::cerr << '\t' << " OpenGL error 0x" << std::hex << e << '\n';
}
}
};
//
// Outer wrapper
//
Renderer::Renderer() : impl_(std::make_unique<Renderer::impl>()) {}
Renderer::~Renderer() {}
bool Renderer::ready() const { return impl_->ready(); }
bool Renderer::draw(const Sprite& sprite, const Shader& shader) {
// if interna are not ready or sprite has been altered - rebuild
if (!impl_->ready() || sprite.change_count != impl_->change_count) {
impl_->build(sprite);
impl_->texture_update(sprite.bitmap);
}
// render the sprite with the shader
if (impl_->ready() && sprite.change_count == impl_->change_count) {
shader.use();
shader.set_uniform("tex_color", 0u); // texture unit 0
impl_->texture_bind();
impl_->just_draw(sprite);
// Renderer::impl::fetch_errors(__PRETTY_FUNCTION__);
impl_->texture_unbind(); // overkill but some cards are finicky
return true;
}
return false;
}
} // namespace paradiso