we can see a quad

This commit is contained in:
Hartmut Seichter 2023-06-30 22:05:23 +02:00
parent 612677c52d
commit ddc0f85805
10 changed files with 261 additions and 140 deletions

10
.vscode/settings.json vendored
View file

@ -130,5 +130,13 @@
"program": "cpp", "program": "cpp",
"shape": "cpp", "shape": "cpp",
"observer_ptr": "cpp" "observer_ptr": "cpp"
} },
"spellright.language": [
"en_US"
],
"spellright.documentTypes": [
"markdown",
"latex",
"plaintext"
]
} }

7
LICENSE.md Normal file
View file

@ -0,0 +1,7 @@
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.

0
README.de.md Normal file
View file

10
README.md Normal file
View file

@ -0,0 +1,10 @@
# ParadisSO - a minimal 2D rendering engine for
**ParadiSO** was conceived as a heavily stripped down version of my pixwerx engine. It is a minimalistic 2D graphics engine for educational purposes. It uses modern C++ and a datadriven design, but no ECS.
## Educational
* plenty of mix and match of concepts and paradigms to write expressive but concise code
* math code is eager evaluation but constexpr to compensate overheads
* it hides old-style APIs behind a renovated facade
* it leans heavily on the STL and its algorithms

View file

@ -7,13 +7,24 @@
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
#include <cassert>
namespace paradiso { namespace paradiso {
struct Bitmap final { struct Bitmap final {
constexpr static Bitmap create(Size size) noexcept {
constexpr static Bitmap empty(Size size) noexcept {
return {.size = size, .data = std::vector<RGBA>{size.area()}}; return {.size = size, .data = std::vector<RGBA>{size.area()}};
} }
template <typename... Arguments>
static constexpr Bitmap from_data(Size size, Arguments... values) noexcept {
assert(sizeof...(Arguments) == size.height * size.width);
return {
.size = size,
.data = {values...}
};
}
constexpr auto fill(const RGBA& color) noexcept { constexpr auto fill(const RGBA& color) noexcept {
std::fill(data.begin(), data.end(), color); std::fill(data.begin(), data.end(), color);
return *this; return *this;

View file

@ -8,18 +8,19 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <vector> #include <vector>
#include <iostream>
#include <iomanip>
namespace paradiso { namespace paradiso {
struct Renderer::impl { struct Renderer::impl {
uint64_t change_count{}; uint64_t change_count{ std::numeric_limits<uint64_t>::max() };
uint32_t vertex_array_obj{}; uint32_t vertex_array_obj{};
uint32_t element_buffer_obj{}; uint32_t element_buffer_obj{};
std::vector<uint32_t> vertex_buffer_ob{}; std::vector<uint32_t> vertex_buffer_ob{};
uint32_t texture_id{}; uint32_t texture_id{};
GLint _mesh_elements = {0};
impl() = default; impl() = default;
@ -37,11 +38,10 @@ struct Renderer::impl {
void unbind_texture() { glBindTexture(GL_TEXTURE_2D, 0); } void unbind_texture() { glBindTexture(GL_TEXTURE_2D, 0); }
bool build(const Sprite& s) { bool build(const Sprite& sprite) {
// reset if the Renderer already in use // reset if the Renderer already in use
if (ready()) if (ready())
release(); release();
// //
glGenVertexArrays(1, &vertex_array_obj); glGenVertexArrays(1, &vertex_array_obj);
glBindVertexArray(vertex_array_obj); glBindVertexArray(vertex_array_obj);
@ -52,7 +52,7 @@ struct Renderer::impl {
// indices -> elements // indices -> elements
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_obj); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_obj);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, glBufferData(GL_ELEMENT_ARRAY_BUFFER,
s.indices.size() * sizeof(uint32_t), s.indices.data(), sprite.indices.size() * sizeof(uint32_t), sprite.indices.data(),
GL_STATIC_DRAW); GL_STATIC_DRAW);
vertex_buffer_ob.resize(vertex_buffer_ob.size() + 1); vertex_buffer_ob.resize(vertex_buffer_ob.size() + 1);
@ -62,8 +62,8 @@ struct Renderer::impl {
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_ob.back()); glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_ob.back());
glVertexAttribPointer(vertex_buffer_ob.size() - 1, 3, GL_FLOAT, glVertexAttribPointer(vertex_buffer_ob.size() - 1, 3, GL_FLOAT,
GL_FALSE, 0, nullptr); GL_FALSE, 0, nullptr);
glBufferData(GL_ARRAY_BUFFER, s.vertices.size() * sizeof(float) * 3, glBufferData(GL_ARRAY_BUFFER, sprite.vertices.size() * sizeof(float) * 3,
s.vertices.data(), GL_STATIC_DRAW); sprite.vertices.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(vertex_buffer_ob.size() - 1); glEnableVertexAttribArray(vertex_buffer_ob.size() - 1);
vertex_buffer_ob.resize(vertex_buffer_ob.size() + 1); vertex_buffer_ob.resize(vertex_buffer_ob.size() + 1);
@ -73,8 +73,8 @@ struct Renderer::impl {
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_ob.back()); glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_ob.back());
glVertexAttribPointer(vertex_buffer_ob.size() - 1, 3, GL_FLOAT, glVertexAttribPointer(vertex_buffer_ob.size() - 1, 3, GL_FLOAT,
GL_FALSE, 0, nullptr); GL_FALSE, 0, nullptr);
glBufferData(GL_ARRAY_BUFFER, s.normals.size() * sizeof(float) * 3, glBufferData(GL_ARRAY_BUFFER, sprite.normals.size() * sizeof(float) * 3,
s.normals.data(), GL_STATIC_DRAW); sprite.normals.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(vertex_buffer_ob.size() - 1); glEnableVertexAttribArray(vertex_buffer_ob.size() - 1);
vertex_buffer_ob.resize(vertex_buffer_ob.size() + 1); vertex_buffer_ob.resize(vertex_buffer_ob.size() + 1);
@ -85,13 +85,18 @@ struct Renderer::impl {
glVertexAttribPointer(vertex_buffer_ob.size() - 1, 2, GL_FLOAT, glVertexAttribPointer(vertex_buffer_ob.size() - 1, 2, GL_FLOAT,
GL_FALSE, 0, nullptr); GL_FALSE, 0, nullptr);
glBufferData(GL_ARRAY_BUFFER, glBufferData(GL_ARRAY_BUFFER,
s.texture_coordinates.size() * sizeof(float) * 2, sprite.texture_coordinates.size() * sizeof(float) * 2,
s.texture_coordinates.data(), GL_STATIC_DRAW); sprite.texture_coordinates.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(vertex_buffer_ob.size() - 1); glEnableVertexAttribArray(vertex_buffer_ob.size() - 1);
// stop binding // stop binding
glBindVertexArray(0); glBindVertexArray(0);
change_count = sprite.change_count;
return ready(); return ready();
} }
@ -156,14 +161,23 @@ struct Renderer::impl {
glDeleteVertexArrays(1, &vertex_array_obj); glDeleteVertexArrays(1, &vertex_array_obj);
glDeleteTextures(1, &texture_id); // glDeleteTextures(1, &texture_id);
} }
void draw() { void just_draw(const Sprite& sprite)
{
glBindVertexArray(vertex_array_obj); glBindVertexArray(vertex_array_obj);
glDrawElements(GL_TRIANGLES, _mesh_elements, GL_UNSIGNED_INT, nullptr); glDrawElements(GL_TRIANGLES, sprite.indices.size(), GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0); 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';
}
}
}; };
// //
@ -172,19 +186,27 @@ struct Renderer::impl {
Renderer::Renderer() : impl_(std::make_unique<Renderer::impl>()) {} Renderer::Renderer() : impl_(std::make_unique<Renderer::impl>()) {}
Renderer::~Renderer() { Renderer::~Renderer() {}
}
bool Renderer::ready() const { return impl_->ready(); } bool Renderer::ready() const { return impl_->ready(); }
bool Renderer::draw(const Sprite& m) { return impl_->build(m); } bool Renderer::draw(const Sprite& m)
{
if (!impl_->ready() || m.change_count != impl_->change_count)
{
impl_->build(m);
}
// void Renderer::release() { impl_->release(); }
// void renderer::draw() { impl_->draw(); } if (impl_->ready() && m.change_count == impl_->change_count) {
// uint64_t Renderer::change_count() const { return impl_->change_count; } impl_->just_draw(m);
// void Renderer::set_change_count(uint64_t n) { impl_->change_count = n; } return true;
}
return false;
}
} // namespace paradiso } // namespace paradiso

View file

@ -11,6 +11,7 @@ struct Sprite;
* @brief a Renderer2D for sprites * @brief a Renderer2D for sprites
*/ */
struct Renderer final { struct Renderer final {
Renderer(); Renderer();
~Renderer(); ~Renderer();
Renderer(const Renderer&) = delete; Renderer(const Renderer&) = delete;

View file

@ -3,61 +3,61 @@
#include "globals.hpp" #include "globals.hpp"
namespace paradiso namespace paradiso {
{ struct RGBA final {
struct RGBA final
{
using value_type = std::uint32_t; using value_type = std::uint32_t;
value_type pixel{0x0}; value_type pixel{0x0};
static constexpr RGBA from_rgb(uint8_t red, uint8_t green, uint8_t blue) noexcept static constexpr RGBA from_rgb(uint8_t red, uint8_t green,
{ uint8_t blue) noexcept {
return from_rgba(red, green, blue, 0xFF); return from_rgba(red, green, blue, 0xFF);
} }
static constexpr RGBA from_rgba(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) noexcept static constexpr RGBA from_rgba(uint8_t red, uint8_t green, uint8_t blue,
{ uint8_t alpha) noexcept {
return { return {.pixel =
.pixel = (value_type)(((((red << 8) | green) << 8) | blue) << 8) | alpha}; (value_type)(((((red << 8) | green) << 8) | blue) << 8) |
alpha};
} }
constexpr void to_float(float rgba_f[4]) const noexcept constexpr void to_float(float rgba_f[4]) const noexcept {
{
rgba_f[0] = static_cast<float>(this->red()) / 0xFF; rgba_f[0] = static_cast<float>(this->red()) / 0xFF;
rgba_f[1] = static_cast<float>(this->green()) / 0xFF; rgba_f[1] = static_cast<float>(this->green()) / 0xFF;
rgba_f[2] = static_cast<float>(this->blue()) / 0xFF; rgba_f[2] = static_cast<float>(this->blue()) / 0xFF;
rgba_f[3] = static_cast<float>(this->alpha()) / 0xFF; rgba_f[3] = static_cast<float>(this->alpha()) / 0xFF;
} }
constexpr uint8_t red() const noexcept { return (pixel & 0xFF000000) >> 24; } constexpr uint8_t red() const noexcept {
constexpr uint8_t green() const noexcept { return uint8_t((pixel & 0xFF0000) >> 16); } return (pixel & 0xFF000000) >> 24;
constexpr uint8_t blue() const noexcept { return uint8_t((pixel & 0xFF00) >> 8); } }
constexpr uint8_t green() const noexcept {
return uint8_t((pixel & 0xFF0000) >> 16);
}
constexpr uint8_t blue() const noexcept {
return uint8_t((pixel & 0xFF00) >> 8);
}
constexpr uint8_t alpha() const noexcept { return uint8_t(pixel & 0xFF); } constexpr uint8_t alpha() const noexcept { return uint8_t(pixel & 0xFF); }
constexpr RGBA &set_red(uint8_t v) noexcept constexpr RGBA& set_red(uint8_t v) noexcept {
{
pixel = (pixel & 0x00FFFFFF) | (v << 24); pixel = (pixel & 0x00FFFFFF) | (v << 24);
return *this; return *this;
} }
constexpr RGBA &set_green(uint8_t v) noexcept constexpr RGBA& set_green(uint8_t v) noexcept {
{
pixel = (pixel & 0xFF00FFFF) | (v << 16); pixel = (pixel & 0xFF00FFFF) | (v << 16);
return *this; return *this;
} }
constexpr RGBA &set_blue(uint8_t v) noexcept constexpr RGBA& set_blue(uint8_t v) noexcept {
{
pixel = (pixel & 0xFFFF00FF) | (v << 8); pixel = (pixel & 0xFFFF00FF) | (v << 8);
return *this; return *this;
} }
constexpr RGBA &set_alpha(uint8_t v) noexcept constexpr RGBA& set_alpha(uint8_t v) noexcept {
{
pixel = (pixel & 0xFFFFFF00) | v; pixel = (pixel & 0xFFFFFF00) | v;
return *this; return *this;
} }
}; };
} } // namespace paradiso
#endif #endif

View file

@ -3,32 +3,24 @@
#include "globals.hpp" #include "globals.hpp"
#include <memory>
#include <string>
#include <tuple>
#include <unordered_map> #include <unordered_map>
#include <variant> #include <variant>
#include <string>
#include <vector> #include <vector>
#include <memory>
#include <tuple>
namespace paradiso namespace paradiso {
{
struct Shader final struct Shader final {
{
Shader(); Shader();
~Shader(); ~Shader();
Shader(const Shader &) = delete; Shader(const Shader&) = delete;
Shader(Shader &&) = default; Shader(Shader&&) = default;
enum class Type enum class Type { Vertex, Fragment, Geometry, Compute };
{
Vertex,
Fragment,
Geometry,
Compute
};
void set_source(Type t, const std::string &c) { source_[t] = c; } void set_source(Type t, const std::string& c) { source_[t] = c; }
std::string source(Type t) const { return source_.at(t); } std::string source(Type t) const { return source_.at(t); }
bool ready() const; bool ready() const;
@ -37,35 +29,43 @@ namespace paradiso
void use(); void use();
Shader &set_uniform_at_location(int location, float v); //!< sets a float in a shader Shader& set_uniform_at_location(int location,
Shader &set_uniform_at_location(int location, uint32_t v); //!< sets a 32bit unsigned in a shader float v); //!< sets a float in a shader
Shader &set_uniform_at_location(int location, int32_t v); //!< sets a 32bit signed in a shader Shader&
set_uniform_at_location(int location,
uint32_t v); //!< sets a 32bit unsigned in a shader
Shader&
set_uniform_at_location(int location,
int32_t v); //!< sets a 32bit signed in a shader
/** /**
* @brief retrieves the position of a uniform * @brief retrieves the position of a uniform
* @param name of the uniform * @param name of the uniform
* @return position of the uniform or negative if it doesn't exist * @return position of the uniform or negative if it doesn't exist
*/ */
int uniform_location(std::string const &name) const; int uniform_location(std::string const& name) const;
/** /**
* @brief check if a uniform with the given name exists * @brief check if a uniform with the given name exists
* @param name of the uniform * @param name of the uniform
* @return true if found * @return true if found
*/ */
bool has_uniform(std::string const &name) const { return uniform_location(name) >= 0; } bool has_uniform(std::string const& name) const {
return uniform_location(name) >= 0;
}
/** /**
* sets data of the * sets data of the
*/ */
template <typename T> template <typename T>
Shader &set_uniform(std::string const &name, T &&value) Shader& set_uniform(std::string const& name, T&& value) {
{ return set_uniform_at_location(uniform_location(name),
return set_uniform_at_location(uniform_location(name), std::forward<T>(value)); std::forward<T>(value));
} }
using uniform_t =
using uniform_t = std::variant<bool, int, float, double /*,vector2f,vector3f,vector4f,matrix4x4f*/>; std::variant<bool, int, float,
double /*,vector2f,vector3f,vector4f,matrix4x4f*/>;
using uniform_entry_t = std::tuple<std::string, uniform_t, int>; using uniform_entry_t = std::tuple<std::string, uniform_t, int>;
using uniform_cache_t = std::vector<uniform_entry_t>; using uniform_cache_t = std::vector<uniform_entry_t>;
@ -79,8 +79,8 @@ namespace paradiso
struct impl; struct impl;
std::unique_ptr<impl> impl_; std::unique_ptr<impl> impl_;
}; };
} } // namespace paradiso
#endif #endif

View file

@ -8,17 +8,64 @@
#include "bitmap.hpp" #include "bitmap.hpp"
#include "context.hpp" #include "context.hpp"
#include "geometry.hpp" #include "geometry.hpp"
#include "renderer.hpp"
#include "shader.hpp"
#include "sprite.hpp" #include "sprite.hpp"
#include "window.hpp" #include "window.hpp"
#include <iomanip> #include <iomanip>
#include <iostream> #include <iostream>
void setup_shaders(paradiso::Shader& shader) {
const auto unlit_v = R"(
#version 330 core
layout (location = 0) in vec3 vertices;
layout (location = 1) in vec3 normals;
layout (location = 2) in vec2 texture_coords;
// uniform mat4 model;
// uniform mat4 view;
// uniform mat4 projection;
out vec2 tex_c;
void main() {
tex_c = texture_coords;
// gl_Position = projection * view * model * vec4(vertices, 1.0);
gl_Position = vec4(vertices, 1.0);
}
)";
const auto unlit_f = R"(
#version 330 core
uniform vec4 color = vec4(1.0, 0.0, 0.0, 1.0);
uniform sampler2D tex_color;
in vec2 tex_c;
out vec4 frag_color;
void main() {
frag_color = texture(tex_color,tex_c); // * color;
})";
shader.set_source(paradiso::Shader::Type::Vertex, unlit_v);
shader.set_source(paradiso::Shader::Type::Fragment, unlit_f);
shader.build();
}
auto main() -> int { auto main() -> int {
auto checker_board = paradiso::Bitmap::empty(paradiso::Size{4, 4});
auto checker_board = auto checker_board_2 = paradiso::Bitmap::from_data(
paradiso::Size{2, 2}, paradiso::RGBA::from_rgb(0x00, 0xFF, 0x00),
paradiso::RGBA::from_rgb(0x00, 0xFF, 0x00),
paradiso::RGBA::from_rgb(0x00, 0xFF, 0x00),
paradiso::RGBA::from_rgb(0x00, 0xFF, 0x00));
auto window = paradiso::Window(); auto window = paradiso::Window();
@ -29,28 +76,43 @@ auto main() -> int {
auto ctx = paradiso::Context{}; auto ctx = paradiso::Context{};
auto sprite = paradiso::Sprite { auto sprite = paradiso::Sprite{
// //
}; };
auto renderer = paradiso::Renderer{};
auto shader = paradiso::Shader{};
setup_shaders(shader);
uint8_t green_slider = 0x00;
bool want_close{false}; bool want_close{false};
int frame_counter = 10;
window.set_keyboardcallback( window.set_keyboardcallback(
[&](auto& w, int key, int scancode, int action, int mods) { [&](auto& w, int key, int scancode, int action, int mods) {
if (key == 'Q' || key == 256) if (key == 'Q' || key == 256)
want_close = true; want_close = true;
else if (key == 'A' ) {
green_slider += 10;
}
}); });
while (window.update([&](auto& w) -> bool { while (window.update([&](auto& w) -> bool {
static uint8_t green_slider = 0x00;
ctx.set_clearcolor(paradiso::RGBA::from_rgb(0xFF, green_slider, 0x00)); ctx.set_clearcolor(paradiso::RGBA::from_rgb(0xFF, green_slider, 0x00));
ctx.set_viewport(paradiso::Rectangle{
.size = w.client_size()
});
ctx.clear(); ctx.clear();
green_slider++;
shader.use();
renderer.draw(sprite);
// if (frame_counter-- == 0) want_close = true;
return !want_close; return !want_close;
})) { })) {