inching towards a first MVP with a renderer working again

This commit is contained in:
Hartmut Seichter 2024-07-24 23:49:09 +02:00
parent 9cb55edbb4
commit 058f7ca23d
17 changed files with 149 additions and 154 deletions

View file

@ -9,7 +9,7 @@ add_subdirectory(core)
#add_subdirectory(ui) #add_subdirectory(ui)
# add_subdirectory(binding) # add_subdirectory(binding)
# add_subdirectory(visual) add_subdirectory(visual)
# add_subdirectory(geometry) # add_subdirectory(geometry)
# add_subdirectory(runtime) # add_subdirectory(runtime)

View file

@ -17,7 +17,7 @@ set(hdrs
include/pw/core/serialize.hpp include/pw/core/serialize.hpp
include/pw/core/size.hpp include/pw/core/size.hpp
include/pw/core/time.hpp include/pw/core/time.hpp
include/pw/core/geometry.hpp include/pw/core/primitives.hpp
include/pw/core/image.hpp include/pw/core/image.hpp
include/pw/core/vector.hpp include/pw/core/vector.hpp
include/pw/core/matrix_transform.hpp include/pw/core/matrix_transform.hpp

View file

@ -29,7 +29,7 @@
namespace pw { namespace pw {
struct color { struct color final {
vector4f rgba{vector4f::all(1)}; vector4f rgba{vector4f::all(1)};

View file

@ -36,7 +36,8 @@ template <std::floating_point Scalar> struct frustum final {
Scalar aspect_ratio, Scalar aspect_ratio,
Scalar z_near, Scalar z_near,
Scalar z_far) -> frustum { Scalar z_far) -> frustum {
const auto tangent_half{std::tan(pw::deg_to_rad(fov_h_deg / Scalar{2}))}; const auto tangent_half{
std::tan(pw::deg_to_rad(fov_h_deg / Scalar{2}))};
const auto top{tangent_half * z_near}; const auto top{tangent_half * z_near};
const auto right{top * aspect_ratio}; const auto right{top * aspect_ratio};

View file

@ -258,6 +258,22 @@ constexpr auto operator*(const matrix<ScalarA, Ra, Ca>& A,
return result; return result;
} }
//
// matrix aliases
//
template <typename Scalar> using matrix2x2 = matrix<Scalar, 2, 2>;
template <typename Scalar> using matrix3x3 = matrix<Scalar, 3, 3>;
template <typename Scalar> using matrix4x4 = matrix<Scalar, 4, 4>;
using matrix2x2f = matrix2x2<float>;
using matrix3x3f = matrix3x3<float>;
using matrix4x4f = matrix4x4<float>;
using matrix2x2d = matrix2x2<double>;
using matrix3x3d = matrix3x3<double>;
using matrix4x4d = matrix4x4<double>;
} // namespace pw } // namespace pw
// //

View file

@ -8,8 +8,8 @@
* copies of the Software, and to permit persons to whom the Software is * copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions: * furnished to do so, subject to the following conditions:
* *
* The above copyright notice and this permission notice shall be included in all * The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@ -24,9 +24,41 @@
#define PW_CORE_MESH_HPP #define PW_CORE_MESH_HPP
#include <pw/core/globals.hpp> #include <pw/core/globals.hpp>
#include <pw/core/primitives.hpp>
#include <atomic>
namespace pw { namespace pw {
struct attribute final {
using mask_type = std::vector<bool>;
using int8_type = std::vector<int8_t>;
using int32_type = std::vector<int32_t>;
using float_type = std::vector<float>;
using vector2_type = std::vector<vector2<float>>;
using vector3_type = std::vector<vector3<float>>;
using vector4_type = std::vector<vector4<float>>;
using attribute_data =
std::variant<std::monostate, mask_type, int8_type, int32_type,
float_type, vector2_type, vector3_type, vector4_type>;
enum attribute_type {
normals,
texture_coordinates,
};
attribute_type type{};
attribute_data data{};
};
struct mesh {
primitives geometry{};
std::vector<attribute> attributes;
std::atomic<uint64_t> change_count{0};
};
} // namespace pw } // namespace pw
#endif #endif

View file

@ -8,8 +8,8 @@
* copies of the Software, and to permit persons to whom the Software is * copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions: * furnished to do so, subject to the following conditions:
* *
* The above copyright notice and this permission notice shall be included in all * The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@ -27,25 +27,18 @@
namespace pw { namespace pw {
template <typename T_> template <typename Scalar> struct point final {
struct point_ {
using value_type = T_; using value_type = Scalar;
T_ x {0}, y {0}; Scalar x{}, y{};
template <typename To_>
point_<To_> cast() const { return point_<To_>(static_cast<To_>(x),static_cast<To_>(y)); }
template <typename ScalarOut>
constexpr auto cast() const noexcept -> point<ScalarOut> {
return {.x{ScalarOut(x)}, .y{ScalarOut(y)}};
}
}; };
using point = point_<real_t>; } // namespace pw
using pointf = point_<float>;
using pointd = point_<double>;
using pointi = point_<int>;
}
#endif #endif

View file

@ -8,8 +8,8 @@
* copies of the Software, and to permit persons to whom the Software is * copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions: * furnished to do so, subject to the following conditions:
* *
* The above copyright notice and this permission notice shall be included in all * The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@ -23,53 +23,28 @@
#ifndef PW_CORE_RECTANGLE_HPP #ifndef PW_CORE_RECTANGLE_HPP
#define PW_CORE_RECTANGLE_HPP #define PW_CORE_RECTANGLE_HPP
#include <initializer_list>
#include <pw/core/point.hpp> #include <pw/core/point.hpp>
#include <pw/core/size.hpp> #include <pw/core/size.hpp>
namespace pw { namespace pw {
template <typename T_> template <typename Scalar> struct rectangle final {
struct rectangle_ {
point_<T_> position; point<Scalar> position{};
size_<T_> size; size<Scalar> size{};
rectangle_() = default; constexpr bool contains(const point<Scalar>& p) const noexcept {
rectangle_(const T_ l[4])
: position(point_<T_>(l[0],l[1]))
, size(size_<T_>(l[2],l[3]))
{
}
rectangle_(const T_(&l)[4])
: position(point_<T_>(l[0],l[1]))
, size(size_<T_>(l[2],l[3]))
{
}
rectangle_(point_<T_> const & p,size_<T_> const & s) : size(s), position(p) {}
bool contains(const point_<T_>& p) const noexcept
{
return p.x >= position.x && p.x <= position.x + size.width && return p.x >= position.x && p.x <= position.x + size.width &&
p.y >= position.y && p.y <= position.y + size.height; p.y >= position.y && p.y <= position.y + size.height;
} }
template <typename To_> template <typename ScalarOut>
rectangle_<To_> cast() const constexpr auto cast() const noexcept -> rectangle<ScalarOut> {
{ return {.position = position.template cast<ScalarOut>(),
return rectangle_<To_>(position.template cast<To_>(),size.template cast<To_>()); .size = size.template cast<ScalarOut>()};
} }
}; };
using rectangle = rectangle_<real_t>; } // namespace pw
using rectanglei = rectangle_<int>;
using rectanglef = rectangle_<float>;
using rectangled = rectangle_<double>;
}
#endif #endif

View file

@ -42,8 +42,15 @@ template <typename Scalar> struct size {
} }
} }
template <typename Other> constexpr auto operator()() const noexcept { template <typename ScalarOut>
return Other{width, height}; constexpr auto cast(this auto&& self) noexcept -> size<ScalarOut> {
return {.width = ScalarOut(self.width),
.height = ScalarOut(self.height)};
}
template <typename Other>
constexpr auto operator()(this auto&& self) noexcept {
return Other{self.width, self.height};
} }
}; };

View file

@ -3,6 +3,7 @@
#include <pw/core/primitives.hpp> #include <pw/core/primitives.hpp>
#include <pw/core/serialize.hpp> #include <pw/core/serialize.hpp>
#include <pw/core/vector.hpp> #include <pw/core/vector.hpp>
#include <pw/core/mesh.hpp>
#include <print> #include <print>
#include <variant> #include <variant>
@ -10,33 +11,6 @@
namespace pw { namespace pw {
struct attribute final {
using mask_type = std::vector<bool>;
using int8_type = std::vector<int8_t>;
using int32_type = std::vector<int32_t>;
using float_type = std::vector<float>;
using vector2_type = std::vector<vector2<float>>;
using vector3_type = std::vector<vector3<float>>;
using vector4_type = std::vector<vector4<float>>;
using attribute_data =
std::variant<std::monostate, mask_type, int8_type, int32_type,
float_type, vector2_type, vector3_type, vector4_type>;
enum attribute_type {
normals,
texture_coordinates,
};
attribute_type type{};
attribute_data data{};
};
struct mesh_n {
primitives geometry{};
std::vector<attribute> attributes;
};
} // namespace pw } // namespace pw
auto main() -> int { auto main() -> int {
@ -48,7 +22,7 @@ auto main() -> int {
.indices = {0, 1, 2}, .indices = {0, 1, 2},
.topology = pw::primitives::topology_type::triangle_list}; .topology = pw::primitives::topology_type::triangle_list};
auto mesh = pw::mesh_n{.geometry = geom}; auto mesh = pw::mesh{.geometry = geom};
mesh.attributes.emplace_back( mesh.attributes.emplace_back(
pw::attribute{.type = pw::attribute::normals, pw::attribute{.type = pw::attribute::normals,

View file

@ -8,8 +8,8 @@
* copies of the Software, and to permit persons to whom the Software is * copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions: * furnished to do so, subject to the following conditions:
* *
* The above copyright notice and this permission notice shall be included in all * The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@ -24,17 +24,15 @@
#ifndef PW_VISUAL_CONTEXT_HPP #ifndef PW_VISUAL_CONTEXT_HPP
#define PW_VISUAL_CONTEXT_HPP #define PW_VISUAL_CONTEXT_HPP
#include <pw/core/color.hpp>
#include <pw/core/point.hpp> #include <pw/core/point.hpp>
#include <pw/core/rectangle.hpp>
#include <pw/core/size.hpp> #include <pw/core/size.hpp>
#include <pw/core/vector.hpp> #include <pw/core/vector.hpp>
#include <pw/core/rectangle.hpp>
#include <pw/core/color.hpp>
namespace pw { namespace pw {
class context { struct context final {
public:
context(); context();
~context(); ~context();
@ -42,24 +40,24 @@ public:
void set_blend(); void set_blend();
void set_depth(); void set_depth();
void set_viewport(const rectangle& v); void set_viewport(const rectangle<int32_t>& v);
rectangle viewport() const; rectangle<int32_t> viewport() const;
size viewport_size() const; size<int32_t> viewport_size() const;
point viewport_origin() const; point<int32_t> viewport_origin() const;
void set_clearcolor(const color &c); void set_clearcolor(const color& c);
color clearcolor() const; color clearcolor() const;
void clear(); void clear();
uint32_t get_error() const; uint32_t get_error() const;
protected: protected:
struct impl; struct impl;
std::unique_ptr<impl> _impl; std::unique_ptr<impl> _impl;
}; };
} } // namespace pw
#endif #endif

View file

@ -6,22 +6,20 @@
namespace pw { namespace pw {
class framebuffer { struct framebuffer final {
public:
framebuffer(); framebuffer();
~framebuffer(); ~framebuffer();
bool create(const size& s); bool create(const size<int32_t>& s);
void bind(); void bind();
void blit(); void blit();
void unbind(); void unbind();
protected: protected:
struct impl; struct impl;
std::unique_ptr<impl> _impl; std::unique_ptr<impl> _impl;
}; };
} } // namespace pw
#endif #endif

View file

@ -6,26 +6,23 @@
namespace pw { namespace pw {
struct geometry; struct mesh;
/** /**
* @brief builds a renderer for geometry * @brief builds a renderer for meshes
*/ */
struct renderer final { struct renderer final {
renderer(); renderer();
renderer(const geometry& m); renderer(const mesh& m);
~renderer(); ~renderer();
bool update(const geometry& m); bool update(const mesh& m);
void release(); void release();
void draw(); void draw();
bool ready() const; bool ready() const;
uint64_t change_count() const;
void set_change_count(uint64_t n);
protected: protected:
struct impl; struct impl;
std::unique_ptr<impl> _impl; std::unique_ptr<impl> _impl;

View file

@ -2,6 +2,7 @@
#include "pw/core/vector.hpp" #include "pw/core/vector.hpp"
#include "glad/glad.h" #include "glad/glad.h"
#include <cstdint>
namespace pw { namespace pw {
@ -10,16 +11,16 @@ struct context::impl {
vector4f _clear_color { 0, 1, 0, 1}; vector4f _clear_color { 0, 1, 0, 1};
void set_viewport(const rectangle& v) void set_viewport(const rectangle<int32_t>& v)
{ {
glViewport(v.position.x,v.position.y,v.size.width,v.size.height); glViewport(v.position.x,v.position.y,v.size.width,v.size.height);
} }
const rectangle viewport() const rectangle<int32_t> viewport()
{ {
float _viewport[4] = {0,0,1,1}; auto vp = rectangle<float>{0,0,1,1};
glGetFloatv(GL_VIEWPORT,&_viewport[0]); glGetFloatv(GL_VIEWPORT,static_cast<float*>(&vp.position.x));
return rectangle(_viewport); return vp.cast<int32_t>();
} }
void clear() void clear()
@ -56,12 +57,12 @@ void context::set_blend()
} }
void context::set_viewport(const rectangle& v) void context::set_viewport(const rectangle<int32_t>& v)
{ {
_impl->set_viewport(v); _impl->set_viewport(v);
} }
rectangle context::viewport() const rectangle<int32_t> context::viewport() const
{ {
return _impl->viewport(); return _impl->viewport();
} }
@ -78,15 +79,14 @@ u_int32_t context::get_error() const
color context::clearcolor() const color context::clearcolor() const
{ {
return _impl->_clear_color; // return _impl->_clear_color;
return color{};
} }
void context::set_clearcolor(const color& c) void context::set_clearcolor(const color& c)
{ {
_impl->_clear_color = c; // _impl->_clear_color = c;
} }
} }

View file

@ -5,6 +5,7 @@
#include "glad/glad.h" #include "glad/glad.h"
#include <cstdint>
#include <limits> #include <limits>
namespace pw { namespace pw {
@ -13,7 +14,7 @@ namespace pw {
struct framebuffer::impl { struct framebuffer::impl {
size _size; size<int32_t> _size;
GLuint _fbo_draw { std::numeric_limits<GLuint>::max() }; GLuint _fbo_draw { std::numeric_limits<GLuint>::max() };
GLuint _fbo_msaa { std::numeric_limits<GLuint>::max() }; GLuint _fbo_msaa { std::numeric_limits<GLuint>::max() };
@ -21,7 +22,7 @@ struct framebuffer::impl {
GLuint _rbo_color { std::numeric_limits<GLuint>::max() }; GLuint _rbo_color { std::numeric_limits<GLuint>::max() };
GLuint _rbo_depth { std::numeric_limits<GLuint>::max() }; GLuint _rbo_depth { std::numeric_limits<GLuint>::max() };
bool create(const size &s) bool create(const size<int32_t> &s)
{ {
// TODO mak color resolution and // TODO mak color resolution and
_size = s; _size = s;
@ -107,7 +108,7 @@ framebuffer::~framebuffer()
{ {
} }
bool framebuffer::create(const size &s) bool framebuffer::create(const size<int32_t> &s)
{ {
return _impl->create(s); return _impl->create(s);
} }

View file

@ -1,19 +1,21 @@
#include "pw/visual/renderer.hpp" #include "pw/visual/renderer.hpp"
#include "pw/core/debug.hpp" #include "pw/core/debug.hpp"
#include "pw/core/geometry.hpp"
#include "pw/core/matrix.hpp" #include "pw/core/matrix.hpp"
#include "pw/core/mesh.hpp"
#include "pw/core/primitives.hpp"
#include "pw/core/size.hpp" #include "pw/core/size.hpp"
#include "glad/glad.h" #include "glad/glad.h"
#include <algorithm> #include <algorithm>
#include <atomic>
namespace pw { namespace pw {
struct renderer::impl { struct renderer::impl {
uint64_t _change_count{0}; std::atomic<uint64_t> change_count{};
uint32_t _vao{0}; uint32_t _vao{0};
uint32_t _ebo{0}; uint32_t _ebo{0};
@ -29,15 +31,15 @@ struct renderer::impl {
return glIsVertexArray != nullptr && GL_TRUE == glIsVertexArray(_vao); return glIsVertexArray != nullptr && GL_TRUE == glIsVertexArray(_vao);
} }
bool update(const geometry& m) { bool update(const mesh& m) {
if (_change_count == m.change_count()) if (this->change_count == m.change_count)
return false; return false;
// reset if the renderer already in use // reset if the renderer already in use
if (ready()) if (ready())
release(); release();
_mesh_elements = m.indices().size(); _mesh_elements = m.geometry.indices.size();
// //
glGenVertexArrays(1, &_vao); glGenVertexArrays(1, &_vao);
@ -51,31 +53,34 @@ struct renderer::impl {
// indices // indices
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, glBufferData(GL_ELEMENT_ARRAY_BUFFER,
m.indices().size() * sizeof(uint32_t), m.indices().data(), m.geometry.indices.size() * sizeof(uint32_t),
GL_STATIC_DRAW); m.geometry.indices.data(), GL_STATIC_DRAW);
_vbos.resize(_vbos.size() + 1); _vbos.resize(_vbos.size() + 1);
glGenBuffers(1, &_vbos.back()); glGenBuffers(1, &_vbos.back());
// vertices // vertices
glBindBuffer(GL_ARRAY_BUFFER, _vbos.back()); glBindBuffer(GL_ARRAY_BUFFER, _vbos.back());
glVertexAttribPointer(_vbos.size() - 1, vector3::coefficients, GL_FLOAT, glVertexAttribPointer(_vbos.size() - 1, vector3f::coefficients, GL_FLOAT,
GL_FALSE, 0, nullptr); GL_FALSE, 0, nullptr);
glBufferData(GL_ARRAY_BUFFER, m.vertices().size() * sizeof(vector3), glBufferData(GL_ARRAY_BUFFER,
m.vertices().data(), GL_STATIC_DRAW); m.geometry.vertices.size() * sizeof(vector3f),
m.geometry.vertices.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(_vbos.size() - 1); glEnableVertexAttribArray(_vbos.size() - 1);
debug::d() << "vertices at " << _vbos.size() - 1; debug::d() << "vertices at " << _vbos.size() - 1;
#if 0
if (!m.normals().empty()) { if (!m.normals().empty()) {
_vbos.resize(_vbos.size() + 1); _vbos.resize(_vbos.size() + 1);
glGenBuffers(1, &_vbos.back()); glGenBuffers(1, &_vbos.back());
// normals // normals
glBindBuffer(GL_ARRAY_BUFFER, _vbos.back()); glBindBuffer(GL_ARRAY_BUFFER, _vbos.back());
glVertexAttribPointer(_vbos.size() - 1, vector3::coefficients, glVertexAttribPointer(_vbos.size() - 1, vector3f::coefficients,
GL_FLOAT, GL_FALSE, 0, nullptr); GL_FLOAT, GL_FALSE, 0, nullptr);
glBufferData(GL_ARRAY_BUFFER, m.normals().size() * sizeof(vector3), glBufferData(GL_ARRAY_BUFFER, m.normals().size() * sizeof(vector3f),
m.normals().data(), GL_STATIC_DRAW); m.normals().data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(_vbos.size() - 1); glEnableVertexAttribArray(_vbos.size() - 1);
@ -99,10 +104,12 @@ struct renderer::impl {
} }
} }
#endif
// stop binding // stop binding
glBindVertexArray(0); glBindVertexArray(0);
this->_change_count = m.change_count(); this->change_count.store(m.change_count.load());
#if 1 #if 1
// get errors // get errors
@ -118,7 +125,7 @@ struct renderer::impl {
void release() { void release() {
for (auto & vbo : _vbos) for (auto& vbo : _vbos)
glDeleteBuffers(1, &vbo); glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &_ebo); glDeleteBuffers(1, &_ebo);
@ -148,7 +155,7 @@ struct renderer::impl {
renderer::renderer() : _impl(std::make_unique<renderer::impl>()) {} renderer::renderer() : _impl(std::make_unique<renderer::impl>()) {}
renderer::renderer(const geometry& m) { renderer::renderer(const mesh& m) {
renderer(); renderer();
// directly update // directly update
_impl->update(m); _impl->update(m);
@ -158,14 +165,10 @@ renderer::~renderer() = default;
bool renderer::ready() const { return _impl->ready(); } bool renderer::ready() const { return _impl->ready(); }
bool renderer::update(const geometry& m) { return _impl->update(m); } bool renderer::update(const mesh& m) { return _impl->update(m); }
void renderer::release() { _impl->release(); } void renderer::release() { _impl->release(); }
void renderer::draw() { _impl->draw(); } void renderer::draw() { _impl->draw(); }
uint64_t renderer::change_count() const { return _impl->_change_count; }
void renderer::set_change_count(uint64_t n) { _impl->_change_count = n; }
} // namespace pw } // namespace pw

View file

@ -141,15 +141,15 @@ struct shader::impl {
} }
void bind(int location, const matrix3x3f& m) { void bind(int location, const matrix3x3f& m) {
glUniformMatrix3fv(location, 1, GL_FALSE, m.ptr()); glUniformMatrix3fv(location, 1, GL_FALSE, m.data());
} }
void bind(int location, const matrix4x4f& m) { void bind(int location, const matrix4x4f& m) {
glUniformMatrix4fv(location, 1, GL_FALSE, m.ptr()); glUniformMatrix4fv(location, 1, GL_FALSE, m.data());
} }
void bind(int location, const vector4f& v) { void bind(int location, const vector4f& v) {
glUniform4fv(location, 1, v.ptr()); glUniform4fv(location, 1, v.data());
} }
void bind(int location, const float& v) { glUniform1f(location, v); } void bind(int location, const float& v) { glUniform1f(location, v); }