forked from Hartmut/paradiso
249 lines
8.2 KiB
C++
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
|