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 1071 additions and 752 deletions
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
#ifndef FGGL_H
#define FGGL_H
#include <fggl/gfx/display.hpp>
#endif
# Sources
find_package(glfw3 REQUIRED)
include(CMakePrintHelpers)
cmake_print_variables(GLFW_TARGETS)
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)
add_subdirectory(ogl4)
/*
* 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 <array>
#include <vector>
#define STBRP_STATIC
#define STB_RECT_PACK_IMPLEMENTATION
#include "../stb/stb_rect_pack.h"
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 unpack_stbrp_query(Query &query, std::vector<stbrp_rect> &data) {
for (const auto &rect : data) {
query[rect.id].pos = {rect.x, rect.y};
}
}
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);
// see if it worked
auto result = stbrp_pack_rects(&context, query.data(), query.size());
return result == 1;
}
namespace fggl::gfx {
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
#ifndef FGGL_GFX_CAMERA_H
#define FGGL_GFX_CAMERA_H
#include <fggl/math/types.hpp>
namespace fggl::gfx {
struct Camera {
constexpr const static char name[] = "Camera";
math::vec3 target = math::vec3(0.0f, 0.0f, 0.0f);
float aspectRatio = 1280.0f / 720.0f;
float fov = glm::radians(45.0f);
float nearPlane = 0.1f;
float farPlane = 100.0f;
};
};
#endif
#ifndef FGGL_GFX_COMMON_H
#define FGGL_GFX_COMMON_H
// load the correct rendering backend (only opengl for now)
#include <fggl/gfx/ogl/common.hpp>
// now it's safe to load the windowing system
#include <GLFW/glfw3.h>
#include <fggl/data/model.hpp>
#include <string>
#include <utility>
namespace fggl::gfx {
struct StaticMesh {
constexpr static const char name[] = "StaticMesh";
data::Mesh mesh;
std::string pipeline;
inline StaticMesh() : mesh(), pipeline() {}
inline StaticMesh(const data::Mesh& aMesh, std::string aPipeline) :
mesh(aMesh), pipeline(std::move(aPipeline)) {}
};
}
#endif
#ifndef FGGL_GFX_GLFW_COMPAT_H
#define FGGL_GFX_GLFW_COMPAT_H
/**
* Window management Calls.
*
* This shouldn't be exposed to the demo app, but the ECS we're using isn't smart enouph to allow us to
* abstract this yet. It's next thing on the list, but this branch is about cleaning up OpenGL not about
* extending our ECS.
*
* Should be removed when the engine has suitable abstractions in place.
*/
#include <memory>
#include <utility>
#include <fggl/gfx/window.hpp>
#include <fggl/ecs3/ecs.hpp>
namespace fggl::gfx {
//
// fake module support - allows us to still RAII
//
struct ecsGlfwModule : ecs3::Module {
GlfwContext context;
inline explicit
ecsGlfwModule(std::shared_ptr<fggl::input::Input> inputs) : context(std::move(inputs) ) {
}
inline
std::shared_ptr<Window> createWindow(const std::string& title) {
auto window = std::make_shared<Window>();
window->title(title);
return window;
}
[[nodiscard]]
std::string name() const override {
return "gfx::glfw";
}
};
}
#endif
#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,11 +30,11 @@ 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 (int i=0; i<6; i++){
pad.axes[i] = 0.0f;
for (float &axe : pad.axes) {
axe = 0.0F;
}
}
m_pad_last = m_pad_curr;
......@@ -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
)
PRIVATE
backend.cpp
shader.cpp
renderer.cpp
types.cpp
)
# OpenGL Backend
find_package( OpenGL REQUIRED )
include_directories( ${OPENGL_INCLUDE_DIR} )
target_link_libraries(fggl OpenGL::OpenGL GLEW)
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( GLEW REQUIRED )
include_directories( ${GLEW_INCLUDE_DIRS} )
find_package( glm REQUIRED )
# FreeType
#find_package(Freetype REQUIRED )
#target_link_libraries(fggl PUBLIC Freetype::Freetype)
#include <fggl/gfx/ogl/backend.hpp>
/*
* 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 <stdexcept>
using namespace fggl::gfx;
GlGraphics::GlGraphics(const std::shared_ptr<Window> window) : m_window(window) {
std::cerr << "[OGL] attaching window context" << std::endl;
window->activate();
GLenum err = glewInit();
if ( GLEW_OK != err ) {
throw std::runtime_error("couldn't init glew");
}
glViewport(0, 0, 1920, 1080);
std::cerr << "[OGL] window ready!" << std::endl;
}
GlGraphics::~GlGraphics() {
std::cerr << "[GLFW] gl context killed!" << std::endl;
}
void GlGraphics::clear() {
m_window->activate();
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}
#ifndef FGGL_GFX_OGL_BACKEND_H
#define FGGL_GFX_OGL_BACKEND_H
#include <fggl/gfx/ogl/common.hpp>
#include <fggl/gfx/window.hpp>
#include <stdexcept>
/**
* FGGL OpenGL 4.x rendering backend.
*/
namespace fggl::gfx {
class GlGraphics {
public:
GlGraphics(const std::shared_ptr<Window> window);
~GlGraphics();
void clear();
private:
std::shared_ptr<Window> m_window;
};
class Shader {
public:
Shader();
~Shader();
inline void use() const {
glUseProgram( m_handle );
}
inline GLuint uniform(const std::string& name) {
GLint loc = glGetUniformLocation( m_handle, name.c_str() );
if ( loc == -1 ) {
throw std::runtime_error("invalid shader uniform");
}
return loc;
}
inline void setInt(const std::string& name, const GLint value) {
setInt( uniform(name), value );
}
inline void setInt(GLuint idx, const GLint value) {
glUniform1i( idx, value );
}
inline void setFloat(const std::string& name, const GLfloat value) {
setFloat( uniform(name), value );
}
inline void setFloat(GLuint idx, const GLfloat value) {
glUniform1f( idx, value );
}
inline void setVec2(const std::string& name, const GLfloat* vec) {
setVec2( uniform(name), vec );
}
inline void setVec2(GLuint idx, const GLfloat* vec) {
glUniform2f( idx, vec[0], vec[1] );
}
inline void setVec3(const std::string& name, const GLfloat* vec) {
setVec3( uniform(name), vec );
}
inline void setVec3(GLuint idx, const GLfloat* vec) {
glUniform3f( idx, vec[0], vec[1], vec[2] );
}
inline void setMat4(const std::string& name, const GLfloat* mat) {
setMat4( uniform(name), mat);
}
inline void setMat4(GLuint idx, const GLfloat* mat) {
glUniformMatrix4fv( idx, 1, GL_FALSE, mat );
}
private:
int m_handle;
};
}
#endif
#ifndef FGGL_GFX_OGL_COMMON_H
#define FGGL_GFX_OGL_COMMON_H
/**
* Ensure Graphics libraries are in the right order.
*/
#include <GL/glew.h>
#endif
#ifndef FGGL_GFX_OGL_COMPAT_H
#define FGGL_GFX_OGL_COMPAT_H
/**
* Legacy/Direct OpenGL calls.
*
* This shouldn't be exposed to the demo app, but the ECS we're using isn't smart enouph to allow us to
* abstract this yet. It's next thing on the list, but this branch is about cleaning up OpenGL not about
* extending our ECS.
*
* Should be removed when the engine has suitable abstractions in place.
*/
#include <functional>
#include <fggl/gfx/ogl/shader.hpp>
#include <fggl/gfx/ogl/renderer.hpp>
#include <fggl/gfx/common.hpp>
#include <fggl/gfx/camera.hpp>
#include <fggl/ecs/ecs.hpp>
#include <utility>
#include <fggl/input/camera_input.h>
namespace fggl::gfx {
//
// fake module support - allows us to still RAII
//
struct ecsOpenGLModule : ecs3::Module {
fggl::gfx::Graphics ogl;
fggl::gfx::MeshRenderer renderer;
fggl::gfx::ShaderCache cache;
ecsOpenGLModule(std::shared_ptr<Window> window, std::shared_ptr<fggl::data::Storage> storage) :
ogl(std::move(window)), renderer(), cache(std::move(storage)) { }
std::string name() const override {
return "gfx::opengl";
}
void uploadMesh(ecs3::World* world, ecs::entity_t entity) {
auto meshData = world->get<gfx::StaticMesh>(entity);
auto pipeline = cache.get(meshData->pipeline);
auto glMesh = renderer.upload(meshData->mesh);
glMesh.pipeline = pipeline;
world->set<fggl::gfx::GlRenderToken>(entity, &glMesh);
}
void onLoad(ecs3::ModuleManager& manager, ecs3::TypeRegistry& types) override {
// TODO implement dependencies
types.make<fggl::gfx::StaticMesh>();
types.make<fggl::gfx::Camera>();
// FIXME probably shouldn't be doing this...
types.make<fggl::input::FreeCamKeys>();
// opengl
types.make<fggl::gfx::GlRenderToken>();
// callbacks
auto upload_cb = [this](auto a, auto b) { this->uploadMesh(a, b); };
manager.onAdd<fggl::gfx::StaticMesh>( upload_cb );
}
};
using OglModule = std::shared_ptr<ecsOpenGLModule>;
//
// Loading related functions - should be handled in modules/data-driven
//
inline void loadPipeline(OglModule& mod, const std::string& name, bool hasGeom) {
fggl::gfx::ShaderConfig config;
config.name = name;
config.vertex = name+"_vert.glsl";
config.fragment = name+"_frag.glsl";
if ( hasGeom ) {
config.geometry = name+"_geom.glsl";
}
mod->cache.load(config);
}
inline void loadBasicPipeline(OglModule& mod, const std::string &name) {
loadPipeline(mod, name, false);
}
//
// fake module/callbacks - our ECS doesn't have module/callback support yet.
//
void onStaticMeshAdded(ecs3::World& ecs, ecs::entity_t entity, OglModule& mod) {
std::cerr << "[CALLBACK] static mesh added, renderable?" << std::endl;
/*
auto meshData = ecs.get<gfx::StaticMesh>(entity);
auto pipeline = mod->cache.get(meshData->pipeline);
auto glMesh = mod->renderer.upload(meshData->mesh);
glMesh.pipeline = pipeline;
ecs.set<fggl::gfx::GlRenderToken>(entity, &glMesh);
*/
}
inline void renderMeshes(OglModule& mod, ecs3::World& ecs, float dt) {
// get the camera
auto cameras = ecs.findMatching<fggl::gfx::Camera>();
if ( cameras.empty() ) {
return;
}
auto camera = cameras[0];
// get the models
auto renderables = ecs.findMatching<fggl::gfx::GlRenderToken>();
for ( auto renderable : renderables ) {
mod->renderer.render(ecs, camera, dt);
}
}
}
#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/>.
*/
#include <fggl/gfx/ogl/renderer.hpp>
#include <fggl/data/model.hpp>
#include "fggl/debug/logging.hpp"
#include "fggl/debug/pragmas.hpp"
#include "fggl/gfx/ogl/common.hpp"
#include "fggl/display/glfw/window.hpp"
#include <fggl/gfx/camera.hpp>
#include <fggl/gfx/ogl/renderer.hpp>
#include <fggl/gfx/paint.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>
extern "C" {
/**
* Convert an OpenGL source enum to a string.
*
* list of sources taken from table 20.1, GL Spec 4.5
* @param source
* @return string representing the source
*/
constexpr auto fggl_ogl_source(GLenum source) -> const char * {
switch (source) {
case GL_DEBUG_SOURCE_API: return "GL";
case GL_DEBUG_SOURCE_SHADER_COMPILER: return "GLSL compiler";
case GL_DEBUG_SOURCE_WINDOW_SYSTEM: return "Windowing System";
case GL_DEBUG_SOURCE_THIRD_PARTY: return "Third Party";
case GL_DEBUG_SOURCE_APPLICATION: return "Application";
default:
case GL_DEBUG_SOURCE_OTHER: return "Other";
}
assert(false);
return "unknown";
}
/**
* Convert an OpenGL type enum to a string.
*
* list of sources taken from table 20.2, GL Spec 4.5
* @param type
* @return string representing the type
*/
constexpr auto static fggl_ogl_type(GLenum type) -> const char * {
switch (type) {
case GL_DEBUG_TYPE_ERROR: return "Error";
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "Deprecated Behaviour";
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "Undefined Behaviour";
case GL_DEBUG_TYPE_PERFORMANCE: return "Performance";
case GL_DEBUG_TYPE_PORTABILITY: return "Portability";
case GL_DEBUG_TYPE_MARKER: return "Marker";
case GL_DEBUG_TYPE_PUSH_GROUP: return "Push Group";
case GL_DEBUG_TYPE_POP_GROUP: return "Pop Group";
default:
case GL_DEBUG_TYPE_OTHER: return "Other";
}
assert(false);
return "unknown";
}
PRAGMA_DIAGNOSTIC_PUSH
#pragma ide diagnostic ignored "bugprone-easily-swappable-parameters"
constexpr const char *OGL_LOG_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: fggl::debug::error(OGL_LOG_FMT, sourceStr, typeStr, msgId, message);
break;
default:
case GL_DEBUG_SEVERITY_MEDIUM: fggl::debug::warning(OGL_LOG_FMT, sourceStr, typeStr, msgId, message);
break;
case GL_DEBUG_SEVERITY_LOW: fggl::debug::info(OGL_LOG_FMT, sourceStr, typeStr, msgId, message);
break;
case GL_DEBUG_SEVERITY_NOTIFICATION: fggl::debug::debug(OGL_LOG_FMT, sourceStr, typeStr, msgId, message);
break;
}
}
PRAGMA_DIAGNOSTIC_POP
}
/**
* Future optimisations:
* recommended approach is to group stuff in to as few vao as possible - this will do one vao per mesh, aka bad.
* Add support for instanced rendering (particles)
* Look at packing vertex data in better ways (with profiling)
* recommended approach is to group stuff in to as few vao as possible - this
* will do one vao per mesh, aka bad. Add support for instanced rendering
* (particles) Look at packing vertex data in better ways (with profiling)
* Support shader specialisation (ie, dynamic/streamed data)
* Follow best recommendations for Vertex attributes (ie normals not using floats, bytes for colour)
* Follow best recommendations for Vertex attributes (ie normals not using
* floats, bytes for colour)
*
* Future features:
* Add support for models with weights (for animations)
* OpenGL ES for the FOSS thinkpad users who can't run anything even remotely modern
* OpenGL ES for the FOSS thinkpad users who can't run anything even remotely
* modern
*/
using namespace fggl::gfx;
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;
}
namespace fggl::gfx {
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;
}
using data::Mesh2D;
using data::Vertex2D;
GlRenderToken MeshRenderer::upload(fggl::data::Mesh& mesh) {
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"));
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)));
// debug shaders
cache->load(ShaderConfig::named("normals", true));
cache->load(ShaderConfig::named("debug"));
}
token.buffs[1] = createIndexBuffer( mesh.indexList() );
token.idxOffset = 0;
token.idxSize = mesh.indexCount();
glBindVertexArray(0);
static void splat_checkerboard(GLuint* memory, unsigned int width = 128, unsigned int height = 128) {
int counter = 0;
auto colour = ogl4::TEX_CHECKER;
return token;
}
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;
}
}
}
void MeshRenderer::render(fggl::ecs3::World& ecs, ecs3::entity_t camera, float dt) {
if ( camera == ecs::NULL_ENTITY ){
std::cerr << "no camera" << std::endl;
return;
}
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;
}
auto entities = ecs.findMatching<GlRenderToken>();
if ( entities.empty() ) {
std::cerr << "no entities with render tokens" << std::endl;
return;
}
return ogl::Image(
ogl::ImageFormat::RGBA,
{width, height},
std::move(data)
);
}
total += dt;
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 );
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
// 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);
}
glEnable(GL_DEPTH_TEST);
{
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);
}
// camera logic
const auto camTransform = ecs.get<math::Transform>(camera);
const auto 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(20.0f, 20.0f, 15.0f);
OpenGL4Backend::OpenGL4Backend(data::Storage *storage, gui::FontLibrary *fonts, assets::AssetManager *assets, GlFunctionLoader loader)
: fggl::gfx::Graphics(), m_canvasPipeline(nullptr), m_storage(storage) {
// TODO better performance if grouped by vao first
// TODO 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);
// load OpenGL context, or fail.
int version = gladLoadGLLoader(loader);
if (version == 0) {
debug::error("Failed to initialize OpenGL context\n");
return;
}
glm::mat4 model = transform->model();
// model = glm::rotate(model, glm::radians(total/2048.0f * 360.0f), glm::vec3(0.0f,1.0f,0.0f));
// 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)
debug::info("enabling OpenGL debug output");
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(fggl_ogl_log, nullptr);
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
}
auto shader = mesh->pipeline;
glUseProgram( shader );
// setup the viewport based on the window's framebuffer
//auto fbSize = owner.frameSize();
//glViewport(0, 0, fbSize.x, fbSize.y);
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 ) );
// setup the shader cache
m_cache = std::make_unique<ShaderCache>(m_storage);
setup_shaders(m_cache.get());
// lighting
GLint lightID = glGetUniformLocation(shader, "lightPos");
if ( lightID != -1 )
glUniform3fv( lightID, 1, glm::value_ptr( lightPos ) );
// setup 2D rendering system
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);
}
glBindVertexArray( mesh->vao );
glDrawElements( GL_TRIANGLES, mesh->idxSize, GL_UNSIGNED_INT, reinterpret_cast<void*>(mesh->idxOffset) );
}
// fallback textures
setup_fallback_texture(assets);
glBindVertexArray(0);
// rendering helpers
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() {
glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void OpenGL4Backend::draw2D(const gfx::Paint &paint) {
if (!m_canvasRenderer) {
return;
}
m_canvasRenderer->render(*m_canvasPipeline, paint);
}
void OpenGL4Backend::drawScene(entity::EntityManager &world, bool debugMode) {
if (m_modelRenderer) {
m_modelRenderer->render(world, debugMode);
}
if (m_debugRenderer) {
auto cameras = world.find<gfx::Camera>();
if (cameras.empty()) {
return;
}
auto cameraEnt = cameras.front();
const auto &camTransform = world.get<math::Transform>(cameraEnt);
const auto &camComp = world.get<gfx::Camera>(cameraEnt);
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;
dd::flush();
}
}
void OpenGL4Backend::resize(int width, int height) {
glViewport(0, 0, width, height);
}
} // namespace fggl::gfx
#ifndef FGGL_GFX_RENDERER_H
#define FGGL_GFX_RENDERER_H
#include <vector>
#include <fggl/data/model.hpp>
#include <fggl/ecs3/ecs.hpp>
#include <fggl/gfx/ogl/backend.hpp>
namespace fggl::gfx {
struct GlRenderToken {
constexpr static const char name[] = "RenderToken";
GLuint vao;
GLuint buffs[2];
GLuint idxOffset;
GLuint idxSize;
GLuint pipeline;
};
class GlMeshRenderer {
public:
using token_t = GlRenderToken;
token_t upload(fggl::data::Mesh& mesh);
void render(ecs3::World& ecs, ecs3::entity_t camera, float dt);
float total;
};
// specialisation hooks
using Graphics = GlGraphics;
using MeshRenderer = GlMeshRenderer;
using MeshToken = GlRenderToken;
};
#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/>.
*/
#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 ) {
std::cerr << ">> Error loading file: " << fname << std::endl;
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);
std::cerr << "source for " << fname << " is " << source << std::endl;
// check it worked
GLint compiled = GL_FALSE;
glGetShaderiv(sid, GL_COMPILE_STATUS, &compiled);
if (compiled == GL_FALSE) {
// upload and compile shader
const auto *src_cstr = (const GLchar *)source.c_str();
glShaderSource(sid, 1, &src_cstr, 0);
glCompileShader(sid);
GLint maxLength = 0;
glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &maxLength);
char *infoLog = new char[maxLength];
glGetShaderInfoLog(sid, maxLength, &maxLength, infoLog);
// check it worked
GLint compiled = GL_FALSE;
glGetShaderiv(sid, GL_COMPILE_STATUS, &compiled);
if ( compiled == GL_FALSE ) {
spdlog::warn("could not compile shader source: {}", infoLog);
delete[] infoLog;
return false;
}
GLint maxLength = 0;
glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &maxLength);
char* infoLog = new char[maxLength];
glGetShaderInfoLog(sid, maxLength, &maxLength, infoLog);
return true;
}
std::cerr << "error compiling shader: " << infoLog << std::endl;
delete[] infoLog;
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 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 ( !GLEW_ARB_get_program_binary ) {
std::cerr << "shader caching disabled, no card support" << std::endl;
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 ) {
std::cerr << "loading shader from disk failed" << std::endl;
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;
}
GLuint ShaderCache::getOrLoad(const ShaderConfig& config) {
try {
return m_shaders.at(config.name);
} catch ( std::out_of_range& e) {
return load(config);
void ShaderCache::saveToDisk(GLuint pid, const std::string &pipelineName) {
BinaryCache cache;
cacheSave(pid, &cache);
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);
std::cerr << "Starting program generation: " << config.name << std::endl;
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);
}
std::cerr << ">> could not load cached shader, taking the long route" << std::endl;
}
// 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) {
GLint linked = GL_FALSE;
glGetProgramiv(pid, GL_LINK_STATUS, &linked);
if ( linked == GL_FALSE ) {
std::cerr << "linking failed" << std::endl;
// 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());
// get the error
char infoLog[512];
glGetProgramInfoLog(pid, 512, nullptr, infoLog);
std::cerr << infoLog << std::endl;
// cleanup
glDeleteProgram(pid);
glDeleteShader(vertShader);
glDeleteShader(fragShader);
if (config.hasGeom) {
glDeleteShader(geomShader);
}
// 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 = std::fopen( data.c_str(), "r");
if ( f == nullptr ) {
std::cerr << "fp was null..." << std::endl;
return false;
}
GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
compileShaderFromSource(sources.fragmentSource, fragShader);
glAttachShader(pid, fragShader);
int rsize = std::fread( &out->format, sizeof(GLenum), 1, f);
if ( rsize != 1 ) {
std::cerr << "invalid read of type";
std::fclose(f);
return false;
GLuint geomShader = INVALID_SHADER_ID;
if (!sources.geometrySource.empty()) {
geomShader = glCreateShader(GL_GEOMETRY_SHADER);
compileShaderFromSource(sources.geometrySource, geomShader);
glAttachShader(pid, geomShader);
}
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 ) {
std::cerr << "invalid read of size" << std::endl;
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);
int 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 ) {
std::cerr << "invalid read of data" << std::endl;
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 true;
}
#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 = std::fopen( data.c_str(), "w");
if ( f == nullptr ){
auto *f =
#ifdef _MSC_VER
_wfopen( data.c_str(), L"w");
#else
std::fopen(data.c_str(), "w");
#endif
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;
}
#ifndef FGGL_GFX_SHADER_H
#define FGGL_GFX_SHADER_H
#include <cstdio>
#include <fggl/gfx/ogl/backend.hpp>
#include <fggl/data/storage.hpp>
#include <filesystem>
#include <stdexcept>
#include <unordered_map>
namespace fggl::gfx {
struct ShaderConfig {
// required
std::string name;
std::string vertex;
std::string fragment;
// optional parts
std::string geometry;
bool hasGeom = false;
};
struct BinaryCache {
void* data = nullptr;
GLsizei size = 0;
GLenum format = GL_INVALID_ENUM;
};
class ShaderCache {
public:
ShaderCache(std::shared_ptr<fggl::data::Storage> storage);
~ShaderCache() = default;
GLuint load(const ShaderConfig& config);
GLuint getOrLoad(const ShaderConfig& config);
GLuint get(const std::string& name);
private:
std::shared_ptr<fggl::data::Storage> m_storage;
std::unordered_map<std::string, GLuint> m_shaders;
// opengl operations
bool compileShader(const std::string&, GLuint);
// file io operations
bool loadFromDisk(GLuint pid, const ShaderConfig& config);
void saveToDisk(GLuint pid, const ShaderConfig& config);
bool m_binary;
void cacheSave(GLuint pid, BinaryCache* cache);
bool cacheLoad(GLuint pid, const BinaryCache* cache);
};
}
#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/>.
*/
#include "fggl/gfx/ogl/types.hpp"
#include <cassert>
//
// special defines:
//
// FGGL_GL_I_BOUND will take responsibility away from fggl for ensuring buffers are bound before use.
// FGGL_GL_PARANOID will try to ensure that openGL state is managed correctly in method calls, but will be slower
namespace fggl::gfx::ogl {
template<> const BuffAttrF attr_type<float>::attr = BuffAttrF::FLOAT;
template<> const BuffAttrF attr_type<math::vec2>::attr = BuffAttrF::FLOAT;
template<> const BuffAttrF attr_type<math::vec3>::attr = BuffAttrF::FLOAT;
template<> const BuffAttrF attr_type<math::vec4>::attr = BuffAttrF::FLOAT;
template<> const GLint attr_type<float>::size = 1;
template<> const GLint attr_type<math::vec2>::size = 2;
template<> const GLint attr_type<math::vec3>::size = 3;
template<> const GLint attr_type<math::vec4>::size = 4;
VertexArray::VertexArray() {
glGenVertexArrays(1, &m_obj);
}
VertexArray::~VertexArray() {
release();
}
VertexArray::VertexArray(VertexArray &&other) noexcept: m_obj(other.m_obj) {
other.m_obj = 0;
}
auto VertexArray::operator=(VertexArray &&other) -> VertexArray & {
if (this != &other) {
release();
std::swap(m_obj, other.m_obj);
}
return *this;
}
void VertexArray::release() {
glDeleteVertexArrays(1, &m_obj);
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());
#ifndef FGGL_GL_I_BOUND
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);
#ifndef FGGL_GL_I_BOUND
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());
#ifndef FGGL_GL_I_BOUND
GLuint boundVertexArray = 0;
bind_buffer(&boundVertexArray, buff);
#endif
glVertexAttribPointer(idx,
attr.elmCount,
(GLenum) attr.attrType,
(GLboolean) normalized,
attr.stride,
(void *) attr.offset);
#ifndef FGGL_GL_I_BOUND
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());
#ifndef FGGL_GL_I_BOUND
GLuint boundVertexArray = 0;
bind_buffer(&boundVertexArray, buff);
#endif
glVertexAttribIPointer(idx, attr.elmCount, (GLenum) attr.attrType, attr.stride, (void *) attr.offset);
#ifndef FGGL_GL_I_BOUND
unbind_buffer(&boundVertexArray, buff);
#endif
}
void VertexArray::drawElements(const ElementBuffer &buff, Primitive drawType, std::size_t size) {
bind();
#ifndef FGGL_I_BOUND
GLuint boundElementArray = 0;
bind_buffer(&boundElementArray, buff);
#endif
glDrawElements((GLenum) drawType, (GLsizei) size, GL_UNSIGNED_INT, nullptr);
#ifndef FGGL_I_BOUND
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