Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • gamedev/fggl
  • onuralpsezer/fggl
2 results
Show changes
Showing
with 1147 additions and 787 deletions
......@@ -25,16 +25,16 @@
#include "imgui.h" // IMGUI_IMPL_API
// Backend API
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = NULL);
IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);
IMGUI_IMPL_API auto ImGui_ImplOpenGL3_Init(const char *glsl_version = nullptr) -> bool;
IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData *draw_data);
// (Optional) Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture();
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects();
IMGUI_IMPL_API auto ImGui_ImplOpenGL3_CreateFontsTexture() -> bool;
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture();
IMGUI_IMPL_API auto ImGui_ImplOpenGL3_CreateDeviceObjects() -> bool;
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects();
// Specific OpenGL ES versions
//#define IMGUI_IMPL_OPENGL_ES2 // Auto-detected on Emscripten
......@@ -66,22 +66,22 @@ IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects();
// Otherwise try to detect supported Desktop OpenGL loaders..
#elif defined(__has_include)
#if __has_include(<GL/glew.h>)
#define IMGUI_IMPL_OPENGL_LOADER_GLEW
#define IMGUI_IMPL_OPENGL_LOADER_GLEW
#elif __has_include(<glad/glad.h>)
#define IMGUI_IMPL_OPENGL_LOADER_GLAD
#define IMGUI_IMPL_OPENGL_LOADER_GLAD
#elif __has_include(<glad/gl.h>)
#define IMGUI_IMPL_OPENGL_LOADER_GLAD2
#define IMGUI_IMPL_OPENGL_LOADER_GLAD2
#elif __has_include(<GL/gl3w.h>)
#define IMGUI_IMPL_OPENGL_LOADER_GL3W
#define IMGUI_IMPL_OPENGL_LOADER_GL3W
#elif __has_include(<glbinding/glbinding.h>)
#define IMGUI_IMPL_OPENGL_LOADER_GLBINDING3
#define IMGUI_IMPL_OPENGL_LOADER_GLBINDING3
#elif __has_include(<glbinding/Binding.h>)
#define IMGUI_IMPL_OPENGL_LOADER_GLBINDING2
#define IMGUI_IMPL_OPENGL_LOADER_GLBINDING2
#else
#error "Cannot detect OpenGL loader!"
#error "Cannot detect OpenGL loader!"
#endif
#else
#define IMGUI_IMPL_OPENGL_LOADER_GL3W // Default to GL3W embedded in our repository
#define IMGUI_IMPL_OPENGL_LOADER_GL3W // Default to GL3W embedded in our repository
#endif
#endif
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 11/09/22.
//
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>
namespace fggl::debug {
auto demangle(const char *name) -> std::string {
int status = -4;
std::unique_ptr<char, decltype(&std::free)> res{
abi::__cxa_demangle(name, nullptr, nullptr, &status),
std::free
};
return (status == 0) ? res.get() : name;
}
}
#else
namespace fggl::debug {
std::string demangle(const char* name) {
return name;
}
}
#endif
#include <fggl/ecs/ecs.hpp>
#include <iostream>
using namespace fggl::ecs;
Archetype::Archetype(const archToken_t& token ) : type(token) {
for ( archToken_t::size_type i = 0; i < token.size(); ++i ) {
data.push_back( new unsigned char[default_cap] );
dataSizes.push_back( default_cap );
}
}
ECS::ECS() : m_entityIDCounter(1) {}
ECS::~ECS() {
for( Archetype* arch : m_archetypes ) {
for ( std::size_t i=0; i < arch->type.size(); ++i ) {
const ComponentBase* const comp = m_componentMap[arch->type[i]];
const std::size_t& size = comp->size();
for ( std::size_t e=0; e<arch->entities.size(); ++e ) {
comp->destroy(&arch->data[i][e*size]);
}
delete[] arch->data[i];
}
delete arch;
}
for( componentmap_t::value_type& p : m_componentMap )
delete p.second;
}
entity_t ECS::getNewID() {
return m_entityIDCounter++;
}
entity_t ECS::createEntity( ) {
auto eid = getNewID();
Record dummy;
dummy.archetype = nullptr;
dummy.index = 0;
m_entityArchtypes[ eid ] = dummy;
return eid;
}
Archetype* ECS::getArchetype(const archToken_t& id) {
for ( auto* arch : m_archetypes ) {
if ( arch->type == id ) {
return arch;
}
}
// didn't exist, need to make one
Archetype* arch = new Archetype(id);
m_archetypes.push_back( arch );
return arch;
}
//
// Created by webpigeon on 23/10/2021.
//
#include <fggl/ecs3/fast/Container.h>
namespace fggl::ecs3 {
std::ostream& operator<<(std::ostream& out, RecordIdentifier const& curr) {
out << "record(";
for (int i=0; i<curr.count; i++) {
out << i;
if ( i != curr.count-1) {
out << ",";
}
}
out << ")";
return out;
}
std::size_t Container::create() {
ensure(m_size + 1);
auto pos = m_size++;
// setup entry
for (std::size_t i = 0; i < m_identifier.count; ++i) {
auto compMeta = m_types.meta(m_identifier.types[i]);
auto offset = offsets[i] + (pos * compMeta->size());
auto compPtr = backingStore + offset;
compMeta->construct(compPtr);
}
return pos;
}
void Container::remove(std::size_t pos) {
// cleanup entry
for (std::size_t i = 0; i < m_identifier.count; ++i) {
auto compMeta = m_types.meta(m_identifier.types[i]);
auto offset = offsets[i] + (pos * compMeta->size());
auto compPtr = backingStore + offset;
compMeta->destroy(compPtr);
}
// if we're not the last one, swap places
if (pos != m_size - 1) {
move(pos, *this, m_size - 1);
}
m_size--;
}
void Container::move(std::size_t newPos, Container &oldContainer, std::size_t oldPos) {
for (std::size_t i = 0; i < m_identifier.count; ++i) {
auto thisComp = m_identifier.types[i];
auto compMeta = m_types.meta(thisComp);
auto newPtr = backingStore + (offsets[i] + (newPos * compMeta->size()));
auto oldPtr = oldContainer.data_raw(thisComp) + (oldPos * compMeta->size());
compMeta->move(oldPtr, newPtr);
compMeta->destroy(oldPtr);
}
}
std::size_t Container::expand(Container &other, std::size_t otherPos, component_type_t newComp) {
ensure(m_size + 1);
auto pos = m_size++;
for (std::size_t i = 0; i < m_identifier.count; ++i) {
auto thisComp = m_identifier.types[i];
auto compMeta = m_types.meta(thisComp);
auto newPtr = backingStore + (offsets[i] + (pos * compMeta->size()));
if (newComp != thisComp) {
// we're moving the component
auto oldPtr = other.data_raw(thisComp) + (otherPos * compMeta->size());
compMeta->move(oldPtr, newPtr);
} else {
// this is the new comp
compMeta->construct(newPtr);
}
}
// remove the old entity
other.remove(otherPos);
return pos;
}
void Container::contract(Container &other, std::size_t otherPos) {
ensure(m_size + 1);
auto pos = m_size++;
move(pos, other, otherPos);
for (std::size_t i = 0; i < m_identifier.count; ++i) {
auto thisComp = m_identifier.types[i];
auto compMeta = m_types.meta(thisComp);
auto newPtr = backingStore + (offsets[i] + (pos * compMeta->size()));
auto oldPtr = other.data_raw(thisComp) + (otherPos * compMeta->size());
compMeta->move(oldPtr, newPtr);
}
other.remove(otherPos);
}
void Container::ensure(std::size_t size) {
if (size < m_capacity) {
return;
}
std::size_t required = 0;
for (std::size_t i = 0; i < m_identifier.count; i++) {
auto meta = m_types.meta(m_identifier.types[i]);
required += meta->size() * size;
}
auto *newBacking = new unsigned char[required];
std::size_t newOffsets[RecordIdentifier::MAX_COMPS];
std::size_t currOffset = 0;
// bulk copy routine
for (std::size_t cid = 0; cid < m_identifier.count; ++cid) {
auto compMeta = m_types.meta(m_identifier.types[cid]);
newOffsets[cid] = currOffset;
compMeta->bulkMove(
backingStore + offsets[cid],
newBacking + newOffsets[cid],
m_size);
currOffset += size * compMeta->size();
}
// swap the backing store and cleanup the old one
auto *tmp = backingStore;
backingStore = newBacking;
delete[] tmp;
// update size info
std::memcpy(offsets, newOffsets, m_identifier.count * sizeof(std::size_t));
m_capacity = size;
}
}
//
// Created by webpigeon on 23/10/2021.
//
#include <fggl/ecs3/module/module.h>
namespace fggl::ecs3 {
// default empty implementions
void Module::onUpdate() {}
void Module::onFrameStart() {}
void Module::onFrameEnd() {}
}
//
// Created by webpigeon on 23/10/2021.
//
#include <fggl/ecs3/prototype/world.h>
target_sources(fggl
PRIVATE
loader/loader.cpp
module.cpp
)
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 24/07/22.
//
#include "fggl/entity/loader/loader.hpp"
#include "fggl/debug/logging.hpp"
#include "fggl/scenes/game.hpp"
namespace fggl::entity {
auto load_scene(assets::Loader* /*loader*/, const assets::AssetID& /*asset*/, const assets::LoaderContext& data, void* ptr) -> assets::AssetRefRaw {
auto* gamePtr = (scenes::Game*)ptr;
auto filePath = data.assetPath;
// load assets
auto* entityFactory = gamePtr->owner().service<EntityFactory>();
auto nodes = YAML::LoadAllFromFile(filePath.c_str());
for (const auto& node : nodes) {
auto scene = node["scene"];
if ( !scene ) {
debug::warning("no scene node in YAML file...");
return nullptr;
}
// create and personalize
for (const auto& item : scene) {
// only safe if personalize is called BEFORE end of loop itr
auto personalize = [&entityFactory, &item, gamePtr](EntityManager& manager, const EntityID eid) {
for ( const auto& compConfig : item["components"]) {
auto compName = compConfig.first.as<fggl::util::GUID>();
auto& compInfo = entityFactory->getInfo(compName);
// setup config data
ComponentSpec spec;
spec.config = compConfig.second;
// re-run the factory with the modified arguments (might need to support a 'patch' function...)
compInfo.factory( spec, manager, eid, gamePtr->owner().services());
}
};
// finally, load the entity and trigger personalize
auto prefab = item["prefab"].as<fggl::util::GUID>();
auto entity = entityFactory->create(prefab, gamePtr->world(), personalize);
// metadata
if ( item["name"].IsDefined() ) {
gamePtr->world().setName(item["name"].as<std::string>(), entity);
}
if ( item["tags"].IsDefined() ) {
for ( auto tag : item["tags"] ) {
auto tagGuid = tag.as<fggl::util::GUID>();
gamePtr->world().addTag(entity, tagGuid);
}
}
}
}
return nullptr;
}
auto load_prototype(assets::Loader* /*loader*/, const assets::AssetID &/*guid*/, const assets::LoaderContext& data, EntityFactory* factory) -> assets::AssetRefRaw {
auto filePath = data.assetPath;
// We need to process the prototypes, and load them into the asset system.
auto nodes = YAML::LoadAllFromFile(filePath.c_str());
for (const auto &node : nodes) {
auto prefabs = node["prefabs"];
for (const auto &prefab : prefabs) {
auto name = prefab["name"].as<fggl::util::GUID>();
debug::info("found prefab: {}", name);
// set up the components
EntitySpec entity{};
entity.parent = prefab["parent"].as<fggl::util::GUID>(NO_PARENT);
for (const auto &compEntry : prefab["components"]) {
auto compId = compEntry.first.as<fggl::util::GUID>();
ComponentSpec compSpec{};
compSpec.config = compEntry.second;
entity.components[compId] = compSpec;
entity.ordering.push_back(compId);
debug::trace("prefab {} has component {}", name, compId);
}
if ( prefab["tags"].IsDefined() ) {
for ( const auto& tagNode : prefab["tags"] ) {
entity.tags.push_back( tagNode.as< util::GUID >() );
}
}
factory->define(name, entity);
}
}
return nullptr;
}
} // namespace fggl::entity
\ No newline at end of file
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// stateless component factories that are probably safe
//
#include "fggl/entity/module.hpp"
#include "fggl/math/types.hpp"
#include "fggl/scenes/game.hpp"
namespace fggl::entity {
void make_transform(const entity::ComponentSpec &spec, EntityManager &manager, const entity::EntityID entity, modules::Services &/*svc*/) {
auto &transform = manager.add<math::Transform>(entity);
//FIXME won't work for patching!
transform.origin(spec.get<math::vec3>("origin", math::VEC3_ZERO));
transform.euler(spec.get<math::vec3>("rotation", math::VEC3_ZERO));
transform.scale(spec.get<math::vec3>("scale", math::VEC3_ONES));
debug::trace("created transform for entity {}", (uint64_t) entity);
}
void install_component_factories(entity::EntityFactory *factory) {
factory->bind(math::Transform::guid, make_transform);
}
static auto is_scene(std::filesystem::path path) -> assets::AssetTypeID {
if ( path.extension() == ".yml" ) {
return ENTITY_SCENE;
}
return assets::INVALID_ASSET_TYPE;
}
auto get_scene_deps(const std::string& /*packName*/, std::filesystem::path /*packRoot*/, assets::ResourceRecord& rr) -> bool {
auto nodes = YAML::LoadAllFromFile( rr.m_path );
for ( auto& node : nodes ) {
auto scripts = node["scripts"];
if ( !scripts ) {
continue;
}
for (auto script : scripts) {
auto scriptName = script.as<std::string>();
auto scriptRef = assets::asset_from_user(scriptName, rr.m_pack);
rr.m_requires.push_back(scriptRef);
}
}
return true;
}
auto ECS::factory(modules::ServiceName service, modules::Services &services) -> bool {
if (service == EntityFactory::service) {
auto *factory = services.create<EntityFactory>(services);
install_component_factories(factory);
// we are responsible for prefabs...
auto *assetLoader = services.get<assets::Loader>();
assetLoader->setFactory(ENTITY_PROTOTYPE, [factory](assets::Loader* loader, const assets::AssetID& a, assets::LoaderContext b, void* ptr) {
EntityFactory* facPtr = factory;
if ( ptr != nullptr ) {
facPtr = (EntityFactory*)ptr;
}
return load_prototype(loader, a, b, facPtr);
}, assets::LoadType::PATH);
assetLoader->setFactory(ENTITY_SCENE, load_scene, assets::LoadType::PATH);
// allow auto-detection
auto *checkin = services.get<assets::CheckinAdapted>();
checkin->setLoader(MIME_SCENE, assets::NEEDS_CHECKIN, is_scene);
checkin->setProcessor(MIME_SCENE, get_scene_deps);
return true;
}
return false;
}
}
\ No newline at end of file
# Sources
find_package( glfw3 REQUIRED )
find_package(glfw3 REQUIRED)
include(CMakePrintHelpers)
cmake_print_variables(GLFW_TARGETS)
target_link_libraries(fggl PUBLIC glfw::glfw )
target_link_libraries(fggl PUBLIC glfw fggl-glad)
target_sources(fggl
PRIVATE
window.cpp
input.cpp
PRIVATE
window.cpp
input.cpp
atlas.cpp
)
)
# OpenGL backend
add_subdirectory(ogl)
......
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#include <fggl/gfx/atlas.hpp>
#include <fggl/math/types.hpp>
#include <array>
#include <vector>
#include <string>
#define STBRP_STATIC
#define STB_RECT_PACK_IMPLEMENTATION
......@@ -12,57 +23,56 @@
using Query = std::vector<fggl::gfx::Bounds2D>;
static void populate_stbrp_query(Query& query, std::vector<stbrp_rect>& data) {
for ( std::size_t i = 0; i < query.size(); i++ ) {
data.push_back({
(int)i,
query[i].size.x,
query[i].size.y,
0,
0,
0
});
}
static void populate_stbrp_query(Query &query, std::vector<stbrp_rect> &data) {
for (std::size_t i = 0; i < query.size(); i++) {
data.push_back({
(int) i,
query[i].size.x,
query[i].size.y,
0,
0,
0
});
}
}
static void unpack_stbrp_query(Query& query, std::vector<stbrp_rect>& data) {
for( const auto& rect : data ) {
query[ rect.id ].pos = { rect.x, rect.y };
}
static void unpack_stbrp_query(Query &query, std::vector<stbrp_rect> &data) {
for (const auto &rect : data) {
query[rect.id].pos = {rect.x, rect.y};
}
}
bool pack_iter(int width, int height, std::vector<stbrp_rect>& query) {
stbrp_node* tmp = new stbrp_node[width];
auto pack_iter(int width, int height, std::vector<stbrp_rect> &query) -> bool {
auto *tmp = new stbrp_node[width];
// setup context
stbrp_context context;
stbrp_init_target( &context, width, height, tmp, width );
// setup context
stbrp_context context;
stbrp_init_target(&context, width, height, tmp, width);
// see if it worked
auto result = stbrp_pack_rects( &context, query.data(), query.size() );
return result == 1;
// see if it worked
auto result = stbrp_pack_rects(&context, query.data(), query.size());
return result == 1;
}
namespace fggl::gfx {
bool pack(std::vector<Bounds2D>& pack){
// setup query structure
std::vector<stbrp_rect> query;
query.reserve( pack.size() );
populate_stbrp_query(pack, query);
// try packing into powers of 2, starting with 32, up to 4096
for ( int i=5; i <= 12; i++ ) {
int dim = i * i;
if ( pack_iter(dim, dim, query) ) {
unpack_stbrp_query(pack, query);
return true;
}
}
return false;
}
auto pack(std::vector<Bounds2D> &pack) -> bool {
// setup query structure
std::vector<stbrp_rect> query;
query.reserve(pack.size());
populate_stbrp_query(pack, query);
// try packing into powers of 2, starting with 32, up to 4096
for (int i = 5; i <= 12; i++) {
int dim = i * i;
if (pack_iter(dim, dim, query)) {
unpack_stbrp_query(pack, query);
return true;
}
}
return false;
}
} // namespace fggl::gfx
#include <fggl/gfx/input.hpp>
#include <cassert>
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#include <bitset>
#include <iostream>
#include <fggl/gfx/input.hpp>
using fggl::gfx::Input;
Input::Input() : m_mouse_curr(), m_mouse_last(), m_joydata(), m_joysticks(), m_pad_last(), m_pad_curr() {
Input::Input() : m_mouse_curr(), m_mouse_last(), m_joysticks(), m_joydata(), m_pad_curr(), m_pad_last() {
clear();
}
......@@ -20,10 +30,10 @@ void Input::clear() {
m_mouse_last = m_mouse_curr;
// reset pad data
for ( auto& pad : m_pad_curr ) {
for (auto &pad : m_pad_curr) {
pad.present = false;
pad.buttons.reset();
for (float & axe : pad.axes){
for (float &axe : pad.axes) {
axe = 0.0F;
}
}
......@@ -43,15 +53,15 @@ void Input::mousePos(double x, double y) {
m_mouse_curr.cursor[1] = y;
}
double Input::cursorDeltaX() const {
auto Input::cursorDeltaX() const -> double {
return m_mouse_last.cursor[0] - m_mouse_curr.cursor[0];
}
double Input::cursorDeltaY() const {
auto Input::cursorDeltaY() const -> double {
return m_mouse_last.cursor[1] - m_mouse_curr.cursor[1];
}
const double* Input::mousePos() const {
auto Input::mousePos() const -> const double * {
return m_mouse_curr.scroll.data();
}
......@@ -60,78 +70,78 @@ void Input::mouseScroll(double deltaX, double deltaY) {
m_mouse_curr.scroll[1] = deltaY;
}
const double* Input::mouseScroll() const {
auto Input::mouseScroll() const -> const double * {
return m_mouse_curr.scroll.data();
}
double Input::scrollDeltaX() const {
auto Input::scrollDeltaX() const -> double {
return m_mouse_curr.scroll[0];
}
double Input::scrollDeltaY() const {
auto Input::scrollDeltaY() const -> double {
return m_mouse_curr.scroll[1];
}
void Input::mouseBtn(const MouseButton btn, bool state) {
if ( state ) {
if (state) {
m_mouse_curr.buttons |= btn;
} else {
m_mouse_curr.buttons &= ~btn;
}
}
bool Input::mouseDown(const MouseButton btn) const {
auto Input::mouseDown(const MouseButton btn) const -> bool {
return m_mouse_curr.buttons & btn;
}
bool Input::mousePressed(const MouseButton btn) const {
auto Input::mousePressed(const MouseButton btn) const -> bool {
return (m_mouse_curr.buttons & btn) && !(m_mouse_last.buttons & btn);
}
bool Input::mouseReleased(const MouseButton btn) const {
auto Input::mouseReleased(const MouseButton btn) const -> bool {
return !(m_mouse_curr.buttons & btn) && (m_mouse_last.buttons & btn);
}
void Input::joystickConnect(int id, Joystick &data){
void Input::joystickConnect(int id, Joystick &data) {
// joystick data is polled, so we need to call this every frame >.<
m_joysticks[id] = true;
m_joydata[id] = data;
}
void Input::joystickDisconnect( int id ){
void Input::joystickDisconnect(int id) {
// reset to empty joystick
m_joysticks[id] = false;
m_joydata[id] = Joystick();
}
bool Input::hasJoystick(int id) const {
auto Input::hasJoystick(int id) const -> bool {
return m_joysticks[id];
}
const fggl::gfx::Joystick& Input::joystick(int id) const {
auto Input::joystick(int id) const -> const fggl::gfx::Joystick & {
return m_joydata[id];
}
void Input::padState(int id, const PadState& state) {
void Input::padState(int id, const PadState &state) {
m_pad_curr[id] = state;
}
bool Input::padDown(int id, PadButton btn) {
return m_pad_curr[id].buttons[(int)btn];
auto Input::padDown(int id, PadButton btn) -> bool {
return m_pad_curr[id].buttons[(int) btn];
}
bool Input::padPressed(int id, PadButton btn) {
return m_pad_curr[id].buttons[(int)btn] && !m_pad_last[id].buttons[(int)btn];
auto Input::padPressed(int id, PadButton btn) -> bool {
return m_pad_curr[id].buttons[(int) btn] && !m_pad_last[id].buttons[(int) btn];
}
bool Input::padReleased(int id, PadButton btn) {
return !m_pad_curr[id].buttons[(int)btn] && m_pad_last[id].buttons[(int)btn];
auto Input::padReleased(int id, PadButton btn) -> bool {
return !m_pad_curr[id].buttons[(int) btn] && m_pad_last[id].buttons[(int) btn];
}
float Input::padAxis(int id, PadAxis axis) {
return m_pad_curr[id].axes[(int)axis];
auto Input::padAxis(int id, PadAxis axis) -> float {
return m_pad_curr[id].axes[(int) axis];
}
float Input::padAxisDelta(int id, PadAxis axis) {
return m_pad_last[id].axes[(int)axis] - m_pad_curr[id].axes[(int)axis];
auto Input::padAxisDelta(int id, PadAxis axis) -> float {
return m_pad_last[id].axes[(int) axis] - m_pad_curr[id].axes[(int) axis];
}
# Sources
target_sources(fggl
PRIVATE
backend.cpp
shader.cpp
renderer.cpp
types.cpp
)
# Math
# probably shouldn't be graphics dependent...
find_package( glm REQUIRED )
target_link_libraries(${PROJECT_NAME} PUBLIC glm::glm)
PRIVATE
backend.cpp
shader.cpp
renderer.cpp
types.cpp
)
# OpenGL Backend
if ( WIN32 )
message("There is a hack to make opengl detection work on windows, someone should look into this...")
find_package( opengl REQUIRED )
include_directories( ${OPENGL_INCLUDE_DIR} )
target_link_libraries(${PROJECT_NAME} PUBLIC opengl::opengl)
else()
find_package( OpenGL REQUIRED )
include_directories( ${OPENGL_INCLUDE_DIR} )
target_link_libraries(${PROJECT_NAME} PUBLIC OpenGL::OpenGL)
endif()
find_package(OpenGL REQUIRED)
if (MSVC)
target_link_libraries(${PROJECT_NAME} PUBLIC OpenGL::GL)
else ()
target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::OpenGL)
endif ()
# GLEW
find_package( glad CONFIG )
target_link_libraries(${PROJECT_NAME} PUBLIC glad::glad)
# FreeType
#find_package(Freetype REQUIRED )
......
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#include <spdlog/spdlog.h>
#include <fggl/gfx/ogl/backend.hpp>
......
#include <fggl/util/service.h>
#include <spdlog/spdlog.h>
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#include "fggl/debug/logging.hpp"
#include "fggl/debug/pragmas.hpp"
#include "fggl/gfx/ogl/common.hpp"
#include "fggl/gfx/window.hpp"
#include "fggl/display/glfw/window.hpp"
#include <fggl/data/model.hpp>
#include <fggl/gfx/camera.hpp>
#include <fggl/gfx/ogl/renderer.hpp>
#include <fggl/gfx/paint.hpp>
#include <fggl/math/triangulation.hpp>
#include "fggl/data/model.hpp"
#include "fggl/gfx/ogl4/fallback.hpp"
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <memory>
......@@ -50,58 +63,51 @@ constexpr auto fggl_ogl_source(GLenum source) -> const char * {
constexpr auto static fggl_ogl_type(GLenum type) -> const char * {
switch (type) {
case GL_DEBUG_TYPE_ERROR: return "Error";
break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "Deprecated Behaviour";
break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "Undefined Behaviour";
break;
case GL_DEBUG_TYPE_PERFORMANCE: return "Performance";
break;
case GL_DEBUG_TYPE_PORTABILITY: return "Portability";
break;
case GL_DEBUG_TYPE_MARKER: return "Marker";
break;
case GL_DEBUG_TYPE_PUSH_GROUP: return "Push Group";
break;
case GL_DEBUG_TYPE_POP_GROUP: return "Pop Group";
break;
default:
case GL_DEBUG_TYPE_OTHER: return "Other";
break;
}
assert(false);
return "unknown";
}
#ifndef _MSC_VER
#pragma clang diagnostic push
#pragma ide diagnostic ignored "bugprone-easily-swappable-parameters"
#endif
PRAGMA_DIAGNOSTIC_PUSH
#pragma ide diagnostic ignored "bugprone-easily-swappable-parameters"
constexpr const char *OGL_LOG_FMT{"[GL] {}, {}: [{}]: {}"};
void static fggl_ogl_to_spdlog(GLenum source, GLenum type, unsigned int msgId, GLenum severity, GLsizei /*length*/,
const char *message, const void * /*userParam*/) {
std::string fmt = "[GL] {}, {}: [{}]: {}";
void static GLAPIENTRY fggl_ogl_log(GLenum source,
GLenum type,
unsigned int msgId,
GLenum severity,
GLsizei /*length*/,
const char *message,
const void * /*userParam*/) {
const auto *const sourceStr = fggl_ogl_source(source);
const auto *const typeStr = fggl_ogl_type(type);
// table 20.3, GL spec 4.5
switch (severity) {
case GL_DEBUG_SEVERITY_HIGH: spdlog::error(fmt, sourceStr, typeStr, msgId, message);
case GL_DEBUG_SEVERITY_HIGH: fggl::debug::error(OGL_LOG_FMT, sourceStr, typeStr, msgId, message);
break;
default:
case GL_DEBUG_SEVERITY_MEDIUM: spdlog::warn(fmt, sourceStr, typeStr, msgId, message);
case GL_DEBUG_SEVERITY_MEDIUM: fggl::debug::warning(OGL_LOG_FMT, sourceStr, typeStr, msgId, message);
break;
case GL_DEBUG_SEVERITY_LOW: spdlog::info(fmt, sourceStr, typeStr, msgId, message);
case GL_DEBUG_SEVERITY_LOW: fggl::debug::info(OGL_LOG_FMT, sourceStr, typeStr, msgId, message);
break;
case GL_DEBUG_SEVERITY_NOTIFICATION: spdlog::debug(fmt, sourceStr, typeStr, msgId, message);
case GL_DEBUG_SEVERITY_NOTIFICATION: fggl::debug::debug(OGL_LOG_FMT, sourceStr, typeStr, msgId, message);
break;
}
}
#ifndef _MSC_VER
#pragma clang diagnostic pop
#endif
PRAGMA_DIAGNOSTIC_POP
}
/**
......@@ -123,62 +129,113 @@ namespace fggl::gfx {
using data::Mesh2D;
using data::Vertex2D;
GlRenderToken setupVertex2D() {
GlRenderToken token{};
glGenVertexArrays(1, &token.vao);
glBindVertexArray(token.vao);
static void setup_shaders(ShaderCache* cache) {
// FIXME this should not be hard-coded, it should be part of the scene loading process
cache->load(ShaderConfig::named("phong"));
cache->load(ShaderConfig::named("redbook/lighting"));
cache->load(ShaderConfig::named("redbook/debug"));
// debug shaders
cache->load(ShaderConfig::named("normals", true));
cache->load(ShaderConfig::named("debug"));
}
static void splat_checkerboard(GLuint* memory, unsigned int width = 128, unsigned int height = 128) {
int counter = 0;
auto colour = ogl4::TEX_CHECKER;
glGenBuffers(2, token.buffs);
for ( auto i = 0u; i < width * height; ++i) {
memory[i] = ogl4::TEX_CHECKER;
counter++;
if (counter == 5) {
counter = 0;
colour = colour == ogl4::TEX_CHECKER ? ogl4::TEX_WHITE : ogl4::TEX_CHECKER;
}
}
}
glBindBuffer(GL_ARRAY_BUFFER, token.buffs[0]);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D),
reinterpret_cast<void *>(offsetof(Vertex2D, position)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex2D),
reinterpret_cast<void *>(offsetof(Vertex2D, colour)));
static auto make_solid(uint8_t width, uint8_t height, GLuint colour) -> ogl::Image {
ogl::PixelDataArray data(ogl::PixelFormat::UNSIGNED_INT, width * height);
for ( auto i = 0u; i < width * height; ++i) {
data.ui[i] = colour;
}
glBindVertexArray(0);
return token;
return ogl::Image(
ogl::ImageFormat::RGBA,
{width, height},
std::move(data)
);
}
OpenGL4Backend::OpenGL4Backend(const Window &owner) : fggl::gfx::Graphics() {
// initialise GLEW, or fail
// FIXME this binds the graphics stack to GLFW :'(
int version = gladLoadGLLoader( (GLADloadproc)glfwGetProcAddress );
static void setup_fallback_texture(assets::AssetManager* assets) {
{
// generate the image
ogl::Image image(ogl::ImageFormat::RGBA, ogl::PixelFormat::UNSIGNED_INT, {128,128});
splat_checkerboard( image.data.ui );
// setup the texture
auto *fallback2D = new ogl::Texture(ogl::TextureType::Tex2D);
fallback2D->setData(ogl::InternalImageFormat::RedGreenBlueAlpha, image, ogl::PixelFormat::UNSIGNED_BYTE);
assets->set(ogl4::FALLBACK_TEX, fallback2D);
}
{
ogl::Image image = make_solid(128, 128, ogl4::TEX_WHITE);
auto *solid2D = new ogl::Texture(ogl::TextureType::Tex2D);
solid2D->setData(ogl::InternalImageFormat::RedGreenBlueAlpha, image);
assets->set(ogl4::SOLID_TEX, solid2D);
}
}
OpenGL4Backend::OpenGL4Backend(data::Storage *storage, gui::FontLibrary *fonts, assets::AssetManager *assets, GlFunctionLoader loader)
: fggl::gfx::Graphics(), m_canvasPipeline(nullptr), m_storage(storage) {
// load OpenGL context, or fail.
int version = gladLoadGLLoader(loader);
if (version == 0) {
printf("Failed to initialize OpenGL context\n");
debug::error("Failed to initialize OpenGL context\n");
return;
}
// OpenGL debug Support
GLint flags = 0;
glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
if ( (flags & GL_CONTEXT_FLAG_DEBUG_BIT) == GL_CONTEXT_FLAG_DEBUG_BIT) { // NOLINT(hicpp-signed-bitwise)
spdlog::info("enabling OpenGL debug output");
if ((flags & GL_CONTEXT_FLAG_DEBUG_BIT) == GL_CONTEXT_FLAG_DEBUG_BIT) { // NOLINT(hicpp-signed-bitwise)
debug::info("enabling OpenGL debug output");
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(fggl_ogl_to_spdlog, nullptr);
glDebugMessageCallback(fggl_ogl_log, nullptr);
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
}
// setup the viewport based on the window's framebuffer
auto fbSize = owner.frameSize();
glViewport(0, 0, fbSize.x, fbSize.y);
//auto fbSize = owner.frameSize();
//glViewport(0, 0, fbSize.x, fbSize.y);
// setup the shader cache
auto &locator = util::ServiceLocator::instance();
auto storage = locator.get<data::Storage>();
m_cache = std::make_unique<ShaderCache>(storage);
m_cache = std::make_unique<ShaderCache>(m_storage);
setup_shaders(m_cache.get());
// setup 2D rendering system
ShaderConfig shader2DConfig = ShaderFromName("shader2D");
m_cache->load(shader2DConfig);
ShaderConfig shader3DConfig = ShaderFromName("phong");
m_cache->load(shader3DConfig);
ShaderConfig shader2DConfig = ShaderConfig::named("canvas");
m_canvasPipeline = m_cache->load(shader2DConfig);
if (m_canvasPipeline == nullptr) {
debug::error("failed to load shader2D - using fallback");
m_canvasPipeline = m_cache->get(ogl4::FALLBACK_CANVAS_PIPELINE);
}
// fallback textures
setup_fallback_texture(assets);
// rendering helpers
m_canvasRenderer = std::make_unique<ogl4::CanvasRenderer>();
m_modelRenderer = std::make_unique<ogl4::StaticModelRenderer>(m_cache.get());
m_canvasRenderer = std::make_unique<ogl4::CanvasRenderer>(fonts);
m_modelRenderer = std::make_unique<ogl4::StaticModelRenderer>(m_cache.get(), assets);
m_debugRenderer = std::make_unique<ogl4::DebugRenderer>(m_cache->getOrLoad(ShaderConfig::named("debug")));
if (m_debugRenderer) {
dd::initialize(m_debugRenderer.get());
}
};
void OpenGL4Backend::clear() {
......@@ -187,140 +244,40 @@ namespace fggl::gfx {
}
void OpenGL4Backend::draw2D(const gfx::Paint &paint) {
if ( !m_canvasRenderer ) {
if (!m_canvasRenderer) {
return;
}
auto shader2D = m_cache->get("shader2D");
m_canvasRenderer->render(shader2D, paint);
m_canvasRenderer->render(*m_canvasPipeline, paint);
}
void OpenGL4Backend::drawScene(ecs3::World &world) {
if ( m_modelRenderer ) {
m_modelRenderer->render(world);
void OpenGL4Backend::drawScene(entity::EntityManager &world, bool debugMode) {
if (m_modelRenderer) {
m_modelRenderer->render(world, debugMode);
}
}
void OpenGL4Backend::resize(int width, int height) {
glViewport(0, 0, width, height);
}
template<typename T>
static GLuint createArrayBuffer(std::vector<T> &vertexData) {
GLuint buffId;
glGenBuffers(1, &buffId);
glBindBuffer(GL_ARRAY_BUFFER, buffId);
glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(T),
vertexData.data(), GL_STATIC_DRAW);
return buffId;
}
static GLuint createIndexBuffer(std::vector<uint32_t> &indexData) {
GLuint buffId;
glGenBuffers(1, &buffId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexData.size() * sizeof(uint32_t),
indexData.data(), GL_STATIC_DRAW);
return buffId;
}
GlRenderToken MeshRenderer::upload(fggl::data::Mesh &mesh) {
GlRenderToken token{};
glGenVertexArrays(1, &token.vao);
glBindVertexArray(token.vao);
token.buffs[0] = createArrayBuffer<fggl::data::Vertex>(mesh.vertexList());
glEnableVertexAttribArray(0);
glVertexAttribPointer(
0, 3, GL_FLOAT, GL_FALSE, sizeof(fggl::data::Vertex),
reinterpret_cast<void *>(offsetof(fggl::data::Vertex, posititon)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(
1, 3, GL_FLOAT, GL_FALSE, sizeof(fggl::data::Vertex),
reinterpret_cast<void *>(offsetof(fggl::data::Vertex, normal)));
token.buffs[1] = createIndexBuffer(mesh.indexList());
token.idxOffset = 0;
token.idxSize = mesh.indexCount();
token.restartVertex = mesh.restartVertex;
glBindVertexArray(0);
return token;
}
// TODO(webpigeon): this shouldn't be hard-coded
constexpr glm::vec3 DEFAULT_LIGHTPOS = glm::vec3(20.0F, 20.0F, 15.0F);
void MeshRenderer::render(fggl::ecs3::World &ecs, ecs3::entity_t camera, float dt) {
if (camera == ecs::NULL_ENTITY) {
spdlog::warn("tried to render a scene, but no camera exists!");
return;
}
auto entities = ecs.findMatching<GlRenderToken>();
if (entities.empty()) {
spdlog::warn("asked to render, but no entities are renderable");
return;
}
total += dt;
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glEnable(GL_DEPTH_TEST);
// camera logic
auto *const camTransform = ecs.get<math::Transform>(camera);
auto *const camComp = ecs.get<gfx::Camera>(camera);
glm::mat4 proj = glm::perspective(camComp->fov, camComp->aspectRatio,
camComp->nearPlane, camComp->farPlane);
glm::mat4 view =
glm::lookAt(camTransform->origin(), camComp->target, camTransform->up());
// lighting
glm::vec3 lightPos = DEFAULT_LIGHTPOS;
// TODO(webpigeon): better performance if grouped by vao first
// TODO(webpigeon): the nvidia performance presentation said I shouldn't use uniforms for large data
for (auto &entity : entities) {
const auto &transform = ecs.get<fggl::math::Transform>(entity);
const auto &mesh = ecs.get<GlRenderToken>(entity);
glm::mat4 model = transform->model();
// model = glm::rotate(model, glm::radians(total/2048.0f * 360.0f),
//glm::vec3(0.0f,1.0f,0.0f));
auto shader = mesh->pipeline;
glUseProgram(shader);
if (m_debugRenderer) {
auto cameras = world.find<gfx::Camera>();
if (cameras.empty()) {
return;
}
glUniformMatrix4fv(glGetUniformLocation(shader, "model"), 1, GL_FALSE,
glm::value_ptr(model));
glUniformMatrix4fv(glGetUniformLocation(shader, "view"), 1, GL_FALSE,
glm::value_ptr(view));
glUniformMatrix4fv(glGetUniformLocation(shader, "projection"), 1, GL_FALSE,
glm::value_ptr(proj));
auto cameraEnt = cameras.front();
// lighting
GLint lightID = glGetUniformLocation(shader, "lightPos");
if (lightID != -1) {
glUniform3fv(lightID, 1, glm::value_ptr(lightPos));
}
const auto &camTransform = world.get<math::Transform>(cameraEnt);
const auto &camComp = world.get<gfx::Camera>(cameraEnt);
glBindVertexArray(mesh->vao);
const math::mat4 projectionMatrix =
glm::perspective(camComp.fov, camComp.aspectRatio, camComp.nearPlane, camComp.farPlane);
const math::mat4 viewMatrix = glm::lookAt(camTransform.origin(), camComp.target, camTransform.up());
m_debugRenderer->mvpMatrix = projectionMatrix * viewMatrix;
if (mesh->renderType == GlRenderType::triangle_strip) {
glEnable(GL_PRIMITIVE_RESTART);
glPrimitiveRestartIndex(mesh->restartVertex);
}
glDrawElements(mesh->renderType, mesh->idxSize, GL_UNSIGNED_INT,
reinterpret_cast<void *>(mesh->idxOffset));
if (mesh->renderType == GlRenderType::triangle_strip) {
glDisable(GL_PRIMITIVE_RESTART);
}
dd::flush();
}
}
glBindVertexArray(0);
void OpenGL4Backend::resize(int width, int height) {
glViewport(0, 0, width, height);
}
} // namespace fggl::gfx
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#include "fggl/gfx/ogl/types.hpp"
#include <fggl/gfx/ogl/shader.hpp>
#include "fggl/gfx/ogl4/fallback.hpp"
#include <iostream>
#include <fstream>
#include <vector>
#include <spdlog/spdlog.h>
using namespace fggl::gfx;
namespace fggl::gfx {
bool ShaderCache::compileShader(const std::string& fname, GLuint sid) {
std::string source;
bool result = m_storage->load(fggl::data::Data, fname, &source);
if ( !result ) {
spdlog::warn("could not load shader source from disk: {}", fname.c_str());
return false;
}
auto ShaderCache::compileShaderFromSource(const std::string &source, GLuint sid) -> bool {
// upload and compile shader
const char *src = source.c_str();
glShaderSource(sid, 1, &src, nullptr);
glCompileShader(sid);
// upload and compile shader
const auto *src_cstr = (const GLchar *)source.c_str();
glShaderSource(sid, 1, &src_cstr, 0);
glCompileShader(sid);
// check it worked
GLint compiled = GL_FALSE;
glGetShaderiv(sid, GL_COMPILE_STATUS, &compiled);
if (compiled == GL_FALSE) {
// check it worked
GLint compiled = GL_FALSE;
glGetShaderiv(sid, GL_COMPILE_STATUS, &compiled);
if ( compiled == GL_FALSE ) {
GLint maxLength = 0;
glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &maxLength);
char *infoLog = new char[maxLength];
glGetShaderInfoLog(sid, maxLength, &maxLength, infoLog);
GLint maxLength = 0;
glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &maxLength);
char* infoLog = new char[maxLength];
glGetShaderInfoLog(sid, maxLength, &maxLength, infoLog);
spdlog::warn("could not compile shader source: {}", infoLog);
delete[] infoLog;
return false;
}
spdlog::warn("could not compile shader, '{}': {}", fname, infoLog);
delete[] infoLog;
return true;
}
return false;
auto ShaderCache::readAndCompileShader(const std::string &filename, GLuint shader) -> bool {
std::string source;
bool result = m_storage->load(fggl::data::Data, filename, &source);
if (!result) {
spdlog::warn("could not load shader source from disk: {}", filename.c_str());
return false;
}
return compileShaderFromSource(source, shader);
}
return true;
}
ShaderCache::ShaderCache(fggl::data::Storage *storage) : m_storage(storage), m_shaders(), m_binary(true) {
ShaderCache::ShaderCache(std::shared_ptr<fggl::data::Storage> storage) : m_storage(storage), m_shaders(), m_binary(true) {
if (!GLAD_GL_ARB_get_program_binary) {
spdlog::warn("the graphics card doesn support shader caching, disabling");
m_binary = false;
} else {
// debug - disable shader cache
m_binary = false;
}
if (GLAD_GL_ARB_get_program_binary) {
spdlog::warn("the graphics card doesn support shader caching, disabling");
m_binary = false;
if (GLAD_GL_ARB_shading_language_include) {
setupIncludes();
}
initFallbackPipelines();
}
}
void ShaderCache::setupIncludes() {
auto root = m_storage->resolvePath(data::StorageType::Data, "include");
auto paths = m_storage->findResources(root, ".glsl");
bool ShaderCache::loadFromDisk(GLuint pid, const ShaderConfig& config) {
for (auto &path : paths) {
std::string source;
m_storage->load(fggl::data::Data, path.string(), &source);
BinaryCache cache;
auto fname = "shader_" + config.name + ".bin";
bool status = m_storage->load( fggl::data::Cache, fname, &cache );
auto relPath = std::filesystem::relative(path, root);
const auto relPathStr = "/" + relPath.string();
glNamedStringARB(GL_SHADER_INCLUDE_ARB, -1, relPathStr.c_str(), -1, source.c_str());
}
if ( !status ) {
spdlog::info("cached shader '{}' could not be loaded from disk", config.name);
return false;
}
bool result = cacheLoad(pid, &cache);
std::free(cache.data);
return result;
}
auto ShaderCache::loadFromDisk(GLuint pid, const std::string &pipelineName) -> bool {
void ShaderCache::saveToDisk(GLuint pid, const ShaderConfig& config) {
BinaryCache cache;
auto fname = "shader_" + pipelineName + ".bin";
bool status = m_storage->load(fggl::data::Cache, fname, &cache);
BinaryCache cache;
cacheSave(pid, &cache);
if (!status) {
spdlog::info("cached shader '{}' could not be loaded from disk", pipelineName);
return false;
}
auto fname = "shader_" + config.name + ".bin";
m_storage->save( fggl::data::Cache, fname, &cache);
}
bool result = cacheLoad(pid, &cache);
std::free(cache.data);
return result;
}
void ShaderCache::saveToDisk(GLuint pid, const std::string &pipelineName) {
BinaryCache cache;
cacheSave(pid, &cache);
GLuint ShaderCache::getOrLoad(const ShaderConfig& config) {
try {
return m_shaders.at(config.name);
} catch ( std::out_of_range& e) {
return load(config);
auto fname = "shader_" + pipelineName + ".bin";
m_storage->save(fggl::data::Cache, fname, &cache);
}
}
GLuint ShaderCache::get(const std::string& name) {
return m_shaders.at(name);
}
auto ShaderCache::getOrLoad(const ShaderConfig &config) -> ShaderCache::ShaderPtr {
try {
return m_shaders.at(config.name);
} catch (std::out_of_range &e) {
return load(config);
}
}
auto ShaderCache::get(const std::string &name) -> ShaderCache::ShaderPtr {
auto itr = m_shaders.find(name);
if ( itr != m_shaders.end() ){
return itr->second;
}
return nullptr;
}
GLuint ShaderCache::load(const ShaderConfig& config) {
auto ShaderCache::load(const ShaderConfig &config) -> ShaderCache::ShaderPtr {
spdlog::debug("starting shader program generation for {}", config.name);
spdlog::debug("starting shader program generation for {}", config.name);
GLuint pid = glCreateProgram();
GLuint pid = glCreateProgram();
if (m_binary) {
// if we have support for shader cache, give that a go
bool worked = loadFromDisk(pid, config.name);
if (worked) {
auto shader = std::make_shared<ogl::Shader>(pid);
m_shaders[config.name] = shader;
return shader;
}
if ( m_binary ) {
// if we have support for shader cache, give that a go
bool worked = loadFromDisk(pid, config);
if ( worked ) {
m_shaders[config.name] = pid;
return pid;
spdlog::debug("could not use cached shader for '{}', doing full compile.", config.name);
}
spdlog::debug("could not use cached shader for '{}', doing full compile.", config.name);
}
// TODO actual shader loading
GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
readAndCompileShader(config.vertex, vertShader);
glAttachShader(pid, vertShader);
// TODO actual shader loading
GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
compileShader(config.vertex, vertShader);
glAttachShader(pid, vertShader);
GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
readAndCompileShader(config.fragment, fragShader);
glAttachShader(pid, fragShader);
GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
compileShader(config.fragment, fragShader);
glAttachShader(pid, fragShader);
GLuint geomShader = 0;
if (config.hasGeom) {
geomShader = glCreateShader(GL_GEOMETRY_SHADER);
readAndCompileShader(config.geometry, geomShader);
glAttachShader(pid, geomShader);
}
GLuint geomShader;
if ( config.hasGeom ) {
geomShader = glCreateShader(GL_GEOMETRY_SHADER);
compileShader( config.geometry, geomShader );
glAttachShader(pid, geomShader);
}
glLinkProgram(pid);
glDetachShader(pid, vertShader);
glDetachShader(pid, fragShader);
glLinkProgram(pid);
glDetachShader(pid, vertShader);
glDetachShader(pid, fragShader);
if (config.hasGeom) {
glDetachShader(pid, geomShader);
}
if ( config.hasGeom ) {
glDetachShader(pid, geomShader);
}
GLint linked = GL_FALSE;
glGetProgramiv(pid, GL_LINK_STATUS, &linked);
if (linked == GL_FALSE) {
// get the error
std::array<char, 512> infoLog;
glGetProgramInfoLog(pid, infoLog.size(), nullptr, infoLog.data());
spdlog::warn("linking shader program '{}' failed: {}", config.name, infoLog.data());
// cleanup
glDeleteProgram(pid);
glDeleteShader(vertShader);
glDeleteShader(fragShader);
if (config.hasGeom) {
glDeleteShader(geomShader);
}
GLint linked = GL_FALSE;
glGetProgramiv(pid, GL_LINK_STATUS, &linked);
if ( linked == GL_FALSE ) {
// get the error
std::array<char, 512> infoLog;
glGetProgramInfoLog(pid, infoLog.size(), nullptr, infoLog.data());
spdlog::warn("linking shader program '{}' failed: {}", config.name, infoLog.data());
// cleanup
glDeleteProgram( pid );
glDeleteShader( vertShader );
glDeleteShader( fragShader );
if ( config.hasGeom ) {
glDeleteShader( geomShader );
return nullptr;
}
return false;
}
if ( m_binary ) {
saveToDisk(pid, config);
if (m_binary) {
saveToDisk(pid, config.name);
}
// update the cache and return
auto shaderPtr = std::make_shared<ogl::Shader>(pid);
m_shaders[config.name] = shaderPtr;
return shaderPtr;
}
// update the cache and return
m_shaders[ config.name ] = pid;
return pid;
}
auto ShaderCache::load(const ShaderSources &sources, bool allowBinaryCache) -> ShaderCache::ShaderPtr {
void ShaderCache::cacheSave(GLuint pid, BinaryCache* cache) {
GLsizei length;
glGetProgramiv(pid, GL_PROGRAM_BINARY_LENGTH, &length);
spdlog::debug("starting shader program generation for {}", sources.name);
cache->data = std::malloc(length);
cache->size = length;
GLuint pid = glCreateProgram();
glGetProgramBinary(pid, length, &cache->size, &cache->format, cache->data);
}
if (m_binary && allowBinaryCache) {
// if we have support for shader cache, give that a go
bool worked = loadFromDisk(pid, sources.name);
if (worked) {
auto shader = std::make_shared<ogl::Shader>(pid);
m_shaders[sources.name] = shader;
return shader;
}
bool ShaderCache::cacheLoad(GLuint pid, const BinaryCache* cache) {
if ( !m_binary ) {
return false;
}
glProgramBinary(pid, cache->format, cache->data, cache->size);
spdlog::debug("could not use cached shader for '{}', doing full compile.", sources.name);
}
// check it loaded correctly
GLint status = GL_FALSE;
glGetProgramiv(pid, GL_LINK_STATUS, &status);
return status == GL_TRUE;
}
// TODO actual shader loading
GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
compileShaderFromSource(sources.vertexSource, vertShader);
glAttachShader(pid, vertShader);
template<>
bool fggl::data::fggl_deserialize(std::filesystem::path &data, fggl::gfx::BinaryCache *out) {
auto f =
#ifdef _MSC_VER
_wfopen(data.c_str(), L"r");
#else
std::fopen( data.c_str(), "r");
#endif
GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
compileShaderFromSource(sources.fragmentSource, fragShader);
glAttachShader(pid, fragShader);
if ( f == nullptr ) {
spdlog::warn("could not load cached shader, fp was null");
return false;
}
GLuint geomShader = INVALID_SHADER_ID;
if (!sources.geometrySource.empty()) {
geomShader = glCreateShader(GL_GEOMETRY_SHADER);
compileShaderFromSource(sources.geometrySource, geomShader);
glAttachShader(pid, geomShader);
}
auto rsize = std::fread( &out->format, sizeof(GLenum), 1, f);
if ( rsize != 1 ) {
spdlog::warn("could not load cached shader: type read failed");
std::fclose(f);
return false;
glLinkProgram(pid);
glDetachShader(pid, vertShader);
glDetachShader(pid, fragShader);
if (geomShader != INVALID_SHADER_ID) {
glDetachShader(pid, geomShader);
}
GLint linked = GL_FALSE;
glGetProgramiv(pid, GL_LINK_STATUS, &linked);
if (linked == GL_FALSE) {
// get the error
std::array<char, 512> infoLog{};
glGetProgramInfoLog(pid, infoLog.size(), nullptr, infoLog.data());
spdlog::warn("linking shader program '{}' failed: {}", sources.name, infoLog.data());
// cleanup
glDeleteProgram(pid);
glDeleteShader(vertShader);
glDeleteShader(fragShader);
if (geomShader != INVALID_SHADER_ID) {
glDeleteShader(geomShader);
}
return nullptr;
}
if (m_binary && allowBinaryCache) {
saveToDisk(pid, sources.name);
}
// update the cache and return
m_shaders[sources.name] = std::make_shared<ogl::Shader>(pid);
return m_shaders[sources.name];
}
out->size = 0;
rsize = std::fread( &out->size, sizeof(GLsizei), 1, f);
if ( rsize != 1 ) {
spdlog::warn("could not load cached shader: size read failed");
std::fclose(f);
return false;
void ShaderCache::cacheSave(GLuint pid, BinaryCache *cache) {
GLsizei length;
glGetProgramiv(pid, GL_PROGRAM_BINARY_LENGTH, &length);
cache->data = std::malloc(length);
cache->size = length;
glGetProgramBinary(pid, length, &cache->size, &cache->format, cache->data);
}
out->data = std::malloc(out->size);
auto readSize = std::fread( out->data, out->size, 1, f );
auto ShaderCache::cacheLoad(GLuint pid, const BinaryCache *cache) -> bool {
if (!m_binary) {
return false;
}
glProgramBinary(pid, cache->format, cache->data, cache->size);
auto result = true;
if ( readSize != 1 ) {
spdlog::warn("could not load cached shader: reading failed!");
std::free( out->data );
result = false;
// check it loaded correctly
GLint status = GL_FALSE;
glGetProgramiv(pid, GL_LINK_STATUS, &status);
return status == GL_TRUE;
}
void ShaderCache::initFallbackPipelines() {
// canvas fallback pipeline
load({
.name = ogl4::FALLBACK_CANVAS_PIPELINE,
.vertexSource = ogl4::FALLBACK_CANVAS_VERTEX_SHADER,
.fragmentSource = ogl4::FALLBACK_CANVAS_FRAGMENT_SHADER,
.geometrySource = ""
}, false);
}
std::fclose(f);
return result;
}
#include <iostream>
#include <fstream>
template<>
auto fggl::data::fggl_deserialize(std::filesystem::path &data, fggl::gfx::BinaryCache *out) -> bool {
auto* f =
#ifdef _MSC_VER
_wfopen(data.c_str(), L"r");
#else
std::fopen(data.c_str(), "r");
#endif
if (f == nullptr) {
spdlog::warn("could not load cached shader, fp was null");
return false;
}
auto rsize = std::fread(&out->format, sizeof(GLenum), 1, f);
if (rsize != 1) {
spdlog::warn("could not load cached shader: type read failed");
std::fclose(f);
return false;
}
out->size = 0;
rsize = std::fread(&out->size, sizeof(GLsizei), 1, f);
if (rsize != 1) {
spdlog::warn("could not load cached shader: size read failed");
std::fclose(f);
return false;
}
out->data = std::malloc(out->size);
auto readSize = std::fread(out->data, out->size, 1, f);
auto result = true;
if (readSize != 1) {
spdlog::warn("could not load cached shader: reading failed!");
std::free(out->data);
result = false;
}
std::fclose(f);
return result;
}
template<>
bool fggl::data::fggl_deserialize(std::filesystem::path& data, std::string *out) {
auto fggl::data::fggl_deserialize(std::filesystem::path &data, std::string *out) -> bool {
std::ifstream ifs(data);
out->assign( (std::istreambuf_iterator<char>(ifs)),
(std::istreambuf_iterator<char>()) );
out->assign((std::istreambuf_iterator<char>(ifs)),
(std::istreambuf_iterator<char>()));
return true;
}
template<>
bool fggl::data::fggl_serialize(std::filesystem::path &data, const fggl::gfx::BinaryCache *out) {
auto fggl::data::fggl_serialize(std::filesystem::path &data, const fggl::gfx::BinaryCache *out) -> bool {
// try and write
auto f =
auto *f =
#ifdef _MSC_VER
_wfopen( data.c_str(), L"w");
_wfopen( data.c_str(), L"w");
#else
std::fopen( data.c_str(), "w");
#endif
std::fopen(data.c_str(), "w");
#endif
if ( f == nullptr ){
if (f == nullptr) {
return false;
}
std::fwrite( &out->format, sizeof(GLenum), 1, f);
std::fwrite( &out->size, sizeof(GLsizei), 1, f);
std::fwrite( out->data, out->size, 1, f);
std::fwrite(&out->format, sizeof(GLenum), 1, f);
std::fwrite(&out->size, sizeof(GLsizei), 1, f);
std::fwrite(out->data, out->size, 1, f);
std::fclose(f);
std::free( out->data );
std::free(out->data);
return true;
}
/*
* ${license.title}
* Copyright (C) 2022 ${license.owner}
* ${license.mailto}
* This file is part of FGGL.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#include "fggl/gfx/ogl/types.hpp"
......@@ -46,12 +40,12 @@ namespace fggl::gfx::ogl {
release();
}
VertexArray::VertexArray(VertexArray &&other) noexcept : m_obj(other.m_obj) {
VertexArray::VertexArray(VertexArray &&other) noexcept: m_obj(other.m_obj) {
other.m_obj = 0;
}
VertexArray &VertexArray::operator=(VertexArray &&other) {
if ( this != &other ){
auto VertexArray::operator=(VertexArray &&other) -> VertexArray & {
if (this != &other) {
release();
std::swap(m_obj, other.m_obj);
}
......@@ -63,73 +57,81 @@ namespace fggl::gfx::ogl {
m_obj = 0;
}
void VertexArray::setAttribute(const ArrayBuffer& buff, GLuint idx, AttributeF& attr) {
assert( 0 <= idx && idx < GL_MAX_VERTEX_ATTRIBS);
assert( 1 <= attr.elmCount && attr.elmCount <= 4);
assert( buff.isValid() );
void VertexArray::setAttribute(const ArrayBuffer &buff, GLuint idx, AttributeF &attr) {
assert(0 <= idx && idx < GL_MAX_VERTEX_ATTRIBS);
assert(1 <= attr.elmCount && attr.elmCount <= 4);
assert(buff.isValid());
#ifndef FGGL_GL_I_BOUND
bind();
GLuint boundVertexArray = 0;
bind_buffer(&boundVertexArray, buff);
bind();
GLuint boundVertexArray = 0;
bind_buffer(&boundVertexArray, buff);
#endif
glEnableVertexAttribArray(idx);
glVertexAttribPointer( idx, attr.elmCount, (GLenum)attr.attrType, GL_FALSE, attr.stride, (void*)attr.offset);
glVertexAttribPointer(idx, attr.elmCount, (GLenum) attr.attrType, GL_FALSE, attr.stride, (void *) attr.offset);
#ifndef FGGL_GL_I_BOUND
unbind_buffer(&boundVertexArray, buff);
unbind_buffer(&boundVertexArray, buff);
#endif
}
void VertexArray::setAttribute(const ArrayBuffer& buff, GLuint idx, AttributeI& attr, bool normalized) {
assert( 0 <= idx && idx < GL_MAX_VERTEX_ATTRIBS);
assert( 1 <= attr.elmCount && attr.elmCount <= 4);
assert( buff.isValid() );
void VertexArray::setAttribute(const ArrayBuffer &buff, GLuint idx, AttributeI &attr, bool normalized) {
assert(0 <= idx && idx < GL_MAX_VERTEX_ATTRIBS);
assert(1 <= attr.elmCount && attr.elmCount <= 4);
assert(buff.isValid());
#ifndef FGGL_GL_I_BOUND
GLuint boundVertexArray = 0;
bind_buffer(&boundVertexArray, buff);
GLuint boundVertexArray = 0;
bind_buffer(&boundVertexArray, buff);
#endif
glVertexAttribPointer(idx, attr.elmCount, (GLenum)attr.attrType, (GLboolean)normalized, attr.stride, (void*)attr.offset);
glVertexAttribPointer(idx,
attr.elmCount,
(GLenum) attr.attrType,
(GLboolean) normalized,
attr.stride,
(void *) attr.offset);
#ifndef FGGL_GL_I_BOUND
unbind_buffer(&boundVertexArray, buff);
unbind_buffer(&boundVertexArray, buff);
#endif
}
void VertexArray::setAttributeI(const ArrayBuffer& buff, GLuint idx, AttributeI& attr) {
assert( 0 <= idx && idx < GL_MAX_VERTEX_ATTRIBS);
assert( 1 <= attr.elmCount && attr.elmCount <= 4);
assert( buff.isValid() );
void VertexArray::setAttributeI(const ArrayBuffer &buff, GLuint idx, AttributeI &attr) {
assert(0 <= idx && idx < GL_MAX_VERTEX_ATTRIBS);
assert(1 <= attr.elmCount && attr.elmCount <= 4);
assert(buff.isValid());
#ifndef FGGL_GL_I_BOUND
GLuint boundVertexArray = 0;
bind_buffer(&boundVertexArray, buff);
GLuint boundVertexArray = 0;
bind_buffer(&boundVertexArray, buff);
#endif
glVertexAttribIPointer( idx, attr.elmCount, (GLenum)attr.attrType, attr.stride, (void*)attr.offset);
glVertexAttribIPointer(idx, attr.elmCount, (GLenum) attr.attrType, attr.stride, (void *) attr.offset);
#ifndef FGGL_GL_I_BOUND
unbind_buffer(&boundVertexArray, buff);
unbind_buffer(&boundVertexArray, buff);
#endif
}
void VertexArray::drawElements(const ElementBuffer &buff, Primative drawType, std::size_t size) {
void VertexArray::drawElements(const ElementBuffer &buff, Primitive drawType, std::size_t size) {
bind();
#ifndef FGGL_I_BOUND
GLuint boundElementArray = 0;
bind_buffer(&boundElementArray, buff);
GLuint boundElementArray = 0;
bind_buffer(&boundElementArray, buff);
#endif
glDrawElements( (GLenum)drawType, (GLsizei)size, GL_UNSIGNED_INT, nullptr );
glDrawElements((GLenum) drawType, (GLsizei) size, GL_UNSIGNED_INT, nullptr);
#ifndef FGGL_I_BOUND
unbind_buffer(&boundElementArray, buff);
unbind_buffer(&boundElementArray, buff);
#endif
}
void VertexArray::draw(Primitive drawType, int first, std::size_t count) {
glDrawArrays((GLenum) drawType, first, count);
}
} // namespace fggl::gfx::ogl
\ No newline at end of file
# Sources
target_sources(fggl
PRIVATE
canvas.cpp
models.cpp
)
PRIVATE
setup.cpp
canvas.cpp
models.cpp
debug.cpp
meshes.cpp
module.cpp
)
/*
* ${license.title}
* Copyright (C) 2022 ${license.owner}
* ${license.mailto}
* This file is part of FGGL.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#include "fggl/gfx/ogl/common.hpp"
......@@ -27,19 +21,29 @@
#include <glm/gtc/type_ptr.hpp>
#include "fggl/math/triangulation.hpp"
#include "fggl/gui/fonts.hpp"
#define FGGL_OPENGL_CORRECTNESS
namespace fggl::gfx::ogl4 {
inline static void add_mesh_triangle(data::Mesh2D& mesh, const std::vector<data::Vertex2D>& verts) {
assert( verts.size() == 3);
for( const auto& vert : verts ) {
static void make_box(gfx::Path2D &path, glm::vec2 topLeft, glm::vec2 bottomRight) {
path.moveTo({topLeft.x, topLeft.y});
path.pathTo({bottomRight.x, topLeft.y});
path.pathTo({bottomRight.x, bottomRight.y});
path.pathTo({topLeft.x, bottomRight.y});
path.pathTo({topLeft.x, topLeft.y});
}
inline static void add_mesh_triangle(data::Mesh2D &mesh, const std::vector<data::Vertex2D> &verts) {
assert(verts.size() == 3);
for (const auto &vert : verts) {
auto idx = mesh.add_vertex(vert);
mesh.add_index(idx);
}
}
static void convert_to_mesh(const gfx::Paint& paint, data::Mesh2D& mesh) {
static void convert_to_mesh(const gfx::Paint &paint, data::Mesh2D &mesh) {
for (const auto &cmd : paint.cmds()) {
auto path = cmd.path;
......@@ -73,44 +77,49 @@ namespace fggl::gfx::ogl4 {
}
}
if (!verts.empty()) {
if (!verts.empty() && verts.size() > 3) {
math::fan_triangulation(verts, mesh);
}
}
}
CanvasRenderer::CanvasRenderer() {
CanvasRenderer::CanvasRenderer(fggl::gui::FontLibrary *fonts) :
m_bounds({0.0F, 1920.F, 1080.0F, 0.0F}),
m_fonts(fonts),
m_fontTex(ogl::TextureType::Tex2D) {
m_vao.bind();
#ifdef FGGL_GL_I_BOUND
// user will handle binding themselves usually, so attribute won't bind itself.
// which means it's our problem right now...
GLuint originalVertexList;
ogl::bind_buffer( &originalVertexList, m_vertexList );
// user will handle binding themselves usually, so attribute won't bind itself.
// which means it's our problem right now...
GLuint originalVertexList;
ogl::bind_buffer( &originalVertexList, m_vertexList );
#endif
// define our attributes
auto posAttr = ogl::attribute<data::Vertex2D, math::vec2>(offsetof(data::Vertex2D, position));
auto colAttr = ogl::attribute<data::Vertex2D, math::vec3>(offsetof(data::Vertex2D, colour));
auto texAttr = ogl::attribute<data::Vertex2D, math::vec2>(offsetof(data::Vertex2D, texPos));
// bind the attributes to the vao
m_vao.setAttribute(m_vertexList, 0, posAttr);
m_vao.setAttribute(m_vertexList, 1, colAttr);
m_vao.setAttribute(m_vertexList, 2, texAttr);
#ifdef FGGL_GL_I_BOUND
// cool, rebind whatever happened before, or not
ogl::unbind_buffer( &originalVertexList, m_vertexList );
// cool, rebind whatever happened before, or not
ogl::unbind_buffer( &originalVertexList, m_vertexList );
#endif
glBindVertexArray(0);
}
void CanvasRenderer::render(GLuint shader, const gfx::Paint &paint) {
void CanvasRenderer::renderShapes(const gfx::Paint &paint, ogl::Shader& shader) {
data::Mesh2D mesh;
convert_to_mesh(paint, mesh);
// nothing to render? give up
if ( mesh.indexList.empty() ){
if (mesh.indexList.empty()) {
return;
}
......@@ -120,8 +129,147 @@ namespace fggl::gfx::ogl4 {
m_indexList.replace(mesh.indexList.size(), mesh.indexList.data());
// draw
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
auto projMat = glm::ortho(m_bounds.left, m_bounds.right, m_bounds.bottom, m_bounds.top);
shader.use();
shader.setUniformMtx(shader.uniform("projection"), projMat);
// draw elements
m_vao.drawElements(m_indexList, ogl::Primitive::TRIANGLE, mesh.indexList.size());
// cleanup
glUseProgram(0);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
}
// slow version
void CanvasRenderer::renderText(const Paint &paint, ogl::Shader& shader) {
if (paint.textCmds().empty()) {
return;
}
// get the expected font
std::shared_ptr<gui::FontFace> face = m_fonts->getDefaultFont();
if (face == nullptr) {
// we don't know about that font...
return;
}
// set up the shader
shader.use();
auto projMat = glm::ortho(0.0F, 1920.0F, 1080.0F, 0.F);
shader.setUniformMtx(shader.uniform("projection"), projMat);
// FIXME: this should be abstracted into the shader class
// bind the vbo we'll use for writing
m_vao.bind();
// set up the openGL state we expect for rendering
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// for each text string, attempt to render it
for (const auto &textCmd : paint.textCmds()) {
const auto label = textCmd.text;
math::vec2 penPos(textCmd.pos);
// set up a non-owning holder for the characters
data::Texture2D tex;
tex.channels = 1;
for (auto letter : label) {
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
face->texture(letter, tex.size.x, tex.size.y, &tex.data);
m_fontTex.setData(ogl::InternalImageFormat::Red, &tex);
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_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
m_fontTex.bind(1);
shader.setUniformI(shader.uniform("tex"), 1);
// this is why this is called the slow version, we render each quad as a single call
auto &metrics = face->metrics(letter);
const float xPos = penPos.x + metrics.bearing.x;
const float yPos = (penPos.y - metrics.bearing.y);
const float w = metrics.size.x;
const float h = metrics.size.y;
std::array<data::Vertex2D, 6> verts{{
{{xPos, yPos + h}, textCmd.colour, {0.0F, 1.0F}},
{{xPos, yPos}, textCmd.colour, {0.0F, 0.0F}},
{{xPos + w, yPos}, textCmd.colour, {1.0F, 0.0F}},
{{xPos, yPos + h}, textCmd.colour, {0.0F, 1.0F}},
{{xPos + w, yPos}, textCmd.colour, {1.0F, 0.0F}},
{{xPos + w, yPos + h}, textCmd.colour, {1.0F, 1.0F}},
}};
m_vertexList.replace(verts.size(), verts.data());
m_vao.bind();
m_vao.draw(ogl::Primitive::TRIANGLE, 0, verts.size());
penPos.x += (metrics.advance >> 6);
}
// textures assume they own their contained data, we need to make sure we clear it
tex.data = nullptr;
}
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
}
/*
void renderTextWIP(const Paint& paint, GLuint shader) {
if ( paint.textCmds().empty() ) {
return;
}
auto fontFactory = util::ServiceLocator::instance().get<gui::FontLibrary>();
std::shared_ptr<gui::FontFace> face = fontFactory->getFont("LiberationSans-Regular.ttf");
if ( face == nullptr ){
return;
}
Paint textPaint;
for ( const auto& textCmd : paint.textCmds() ) {
auto label = textCmd.text;
gfx::Path2D textPath(textCmd.pos);
math::vec2 penPos(textCmd.pos);
// create a quad for this character
for (auto letter : label) {
auto& metrics = face->metrics(letter);
std::cerr << metrics.size.x << std::endl;
make_box(textPath, penPos, penPos + metrics.size);
penPos.x += (metrics.advance >> 6 );
}
textPaint.fill(textPath);
}
data::Mesh2D mesh;
convert_to_mesh(textPaint, mesh);
// render the text mesh
m_vao.bind();
m_vertexList.replace(mesh.vertexList.size(), mesh.vertexList.data());
m_indexList.replace(mesh.indexList.size(), mesh.indexList.data());
// draw
glUseProgram( shader );
auto projMat = glm::ortho(0.0f, 1920.0f, 1080.0f, 0.f);
glUniformMatrix4fv(glGetUniformLocation( shader, "projection"), 1, GL_FALSE,
......@@ -129,6 +277,11 @@ namespace fggl::gfx::ogl4 {
m_vao.drawElements(m_indexList, ogl::Primative::TRIANGLE, mesh.indexList.size());
glUseProgram( 0 );
}*/
void CanvasRenderer::render(ogl::Shader& shader, const gfx::Paint &paint) {
renderShapes(paint, shader);
renderText(paint, shader);
}
}
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 30/05/22.
//
#include "fggl/debug/draw.hpp"
#include "fggl/gfx/ogl/types.hpp"
#include "fggl/gfx/ogl4/debug.hpp"
#include <cassert>
#include <utility>
namespace fggl::gfx::ogl4 {
DebugRenderer::DebugRenderer(std::shared_ptr<ogl::Shader> shader) :
mvpMatrix(1.0F),
m_lineShader(std::move(shader)),
m_lineShaderMVP(m_lineShader->uniform("u_MvpMatrix")) {
// define our attributes
auto posAttr = ogl::attribute<dd::DrawVertex, math::vec3>(0);
auto colAttr = ogl::attribute<dd::DrawVertex, math::vec3>(sizeof(float) * 3);
// bind the attributes to the vao
m_lineVao.setAttribute(m_lineVbo, 0, posAttr);
m_lineVao.setAttribute(m_lineVbo, 1, colAttr);
}
void DebugRenderer::drawLineList(const dd::DrawVertex *lines, int count, bool depthEnabled) {
assert(lines != nullptr);
assert(count > 0 && count <= DEBUG_DRAW_VERTEX_BUFFER_SIZE);
m_lineVao.bind();
m_lineShader->use();
m_lineShader->setUniformMtx(m_lineShaderMVP, mvpMatrix);
if (depthEnabled) {
glEnable(GL_DEPTH_TEST);
} else {
glDisable(GL_DEPTH_TEST);
}
m_lineVbo.bind();
m_lineVbo.replace<dd::DrawVertex>(count, lines);
m_lineVao.draw(ogl::Primitive::LINE, 0, count);
glUseProgram(0);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
} // namespace fggl::gfx::ogl4
\ No newline at end of file