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 2388 additions and 125 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
# OpenGL Backend
find_package( OpenGL REQUIRED )
include_directories( ${OPENGL_INCLUDE_DIR} )
# Sources
find_package(glfw3 REQUIRED)
# GLEW
find_package( GLEW REQUIRED )
include_directories( ${GLEW_INCLUDE_DIRS} )
include(CMakePrintHelpers)
cmake_print_variables(GLFW_TARGETS)
target_link_libraries(fggl PUBLIC glfw fggl-glad)
target_sources(fggl
PRIVATE
window.cpp
input.cpp
atlas.cpp
)
# OpenGL backend
add_subdirectory(ogl)
add_subdirectory(ogl4)
find_package( glm 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 <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
/*
* 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/input.hpp>
using fggl::gfx::Input;
Input::Input() : m_mouse_curr(), m_mouse_last(), m_joysticks(), m_joydata(), m_pad_curr(), m_pad_last() {
clear();
}
void Input::clear() {
// reset mouse data
m_mouse_curr.cursor[0] = 0.0;
m_mouse_curr.cursor[1] = 0.0;
m_mouse_curr.scroll[0] = 0.0;
m_mouse_curr.scroll[1] = 0.0;
m_mouse_curr.buttons = 0x00;
m_mouse_last = m_mouse_curr;
// reset pad data
for (auto &pad : m_pad_curr) {
pad.present = false;
pad.buttons.reset();
for (float &axe : pad.axes) {
axe = 0.0F;
}
}
m_pad_last = m_pad_curr;
}
void Input::frame() {
m_mouse_last = m_mouse_curr;
m_mouse_curr.scroll[0] = 0.0;
m_mouse_curr.scroll[1] = 0.0;
m_pad_last = m_pad_curr;
}
void Input::mousePos(double x, double y) {
m_mouse_curr.cursor[0] = x;
m_mouse_curr.cursor[1] = y;
}
auto Input::cursorDeltaX() const -> double {
return m_mouse_last.cursor[0] - m_mouse_curr.cursor[0];
}
auto Input::cursorDeltaY() const -> double {
return m_mouse_last.cursor[1] - m_mouse_curr.cursor[1];
}
auto Input::mousePos() const -> const double * {
return m_mouse_curr.scroll.data();
}
void Input::mouseScroll(double deltaX, double deltaY) {
m_mouse_curr.scroll[0] = deltaX;
m_mouse_curr.scroll[1] = deltaY;
}
auto Input::mouseScroll() const -> const double * {
return m_mouse_curr.scroll.data();
}
auto Input::scrollDeltaX() const -> double {
return m_mouse_curr.scroll[0];
}
auto Input::scrollDeltaY() const -> double {
return m_mouse_curr.scroll[1];
}
void Input::mouseBtn(const MouseButton btn, bool state) {
if (state) {
m_mouse_curr.buttons |= btn;
} else {
m_mouse_curr.buttons &= ~btn;
}
}
auto Input::mouseDown(const MouseButton btn) const -> bool {
return m_mouse_curr.buttons & btn;
}
auto Input::mousePressed(const MouseButton btn) const -> bool {
return (m_mouse_curr.buttons & btn) && !(m_mouse_last.buttons & btn);
}
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) {
// 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) {
// reset to empty joystick
m_joysticks[id] = false;
m_joydata[id] = Joystick();
}
auto Input::hasJoystick(int id) const -> bool {
return m_joysticks[id];
}
auto Input::joystick(int id) const -> const fggl::gfx::Joystick & {
return m_joydata[id];
}
void Input::padState(int id, const PadState &state) {
m_pad_curr[id] = state;
}
auto Input::padDown(int id, PadButton btn) -> bool {
return m_pad_curr[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];
}
auto Input::padReleased(int id, PadButton btn) -> bool {
return !m_pad_curr[id].buttons[(int) btn] && m_pad_last[id].buttons[(int) btn];
}
auto Input::padAxis(int id, PadAxis axis) -> float {
return 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];
}
#include <fggl/gfx/ogl.hpp>
#include <stdexcept>
using namespace fggl::gfx;
Graphics::Graphics(Window& window) : m_window(window) {
window.activate();
GLenum err = glewInit();
if ( GLEW_OK != err ) {
throw std::runtime_error("couldn't init glew");
}
}
Graphics::~Graphics() {
}
void Graphics::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_H
#define FGGL_GFX_OGL_H
#include "window.hpp"
#include "rendering.hpp"
#include <stdexcept>
/**
* FGGL OpenGL 4.x rendering backend.
*/
namespace fggl::gfx {
class Graphics {
public:
Graphics(Window& window);
~Graphics();
void clear();
private:
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
# Sources
target_sources(fggl
PRIVATE
backend.cpp
shader.cpp
renderer.cpp
types.cpp
)
# OpenGL Backend
find_package(OpenGL REQUIRED)
if (MSVC)
target_link_libraries(${PROJECT_NAME} PUBLIC OpenGL::GL)
else ()
target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::OpenGL)
endif ()
# FreeType
#find_package(Freetype REQUIRED )
#target_link_libraries(fggl PUBLIC Freetype::Freetype)
/*
* 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;
/*
* 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/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 <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)
* Support shader specialisation (ie, dynamic/streamed data)
* 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
*/
namespace fggl::gfx {
using data::Mesh2D;
using data::Vertex2D;
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;
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;
}
}
}
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;
}
return ogl::Image(
ogl::ImageFormat::RGBA,
{width, height},
std::move(data)
);
}
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) {
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)
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);
}
// setup the viewport based on the window's framebuffer
//auto fbSize = owner.frameSize();
//glViewport(0, 0, fbSize.x, fbSize.y);
// setup the shader cache
m_cache = std::make_unique<ShaderCache>(m_storage);
setup_shaders(m_cache.get());
// 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);
}
// fallback textures
setup_fallback_texture(assets);
// 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
/*
* 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>
namespace fggl::gfx {
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);
// 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);
spdlog::warn("could not compile shader source: {}", infoLog);
delete[] infoLog;
return false;
}
return true;
}
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);
}
ShaderCache::ShaderCache(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_shading_language_include) {
setupIncludes();
}
initFallbackPipelines();
}
void ShaderCache::setupIncludes() {
auto root = m_storage->resolvePath(data::StorageType::Data, "include");
auto paths = m_storage->findResources(root, ".glsl");
for (auto &path : paths) {
std::string source;
m_storage->load(fggl::data::Data, path.string(), &source);
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());
}
}
auto ShaderCache::loadFromDisk(GLuint pid, const std::string &pipelineName) -> bool {
BinaryCache cache;
auto fname = "shader_" + pipelineName + ".bin";
bool status = m_storage->load(fggl::data::Cache, fname, &cache);
if (!status) {
spdlog::info("cached shader '{}' could not be loaded from disk", pipelineName);
return false;
}
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);
auto fname = "shader_" + pipelineName + ".bin";
m_storage->save(fggl::data::Cache, fname, &cache);
}
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;
}
auto ShaderCache::load(const ShaderConfig &config) -> ShaderCache::ShaderPtr {
spdlog::debug("starting shader program generation for {}", config.name);
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;
}
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);
GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
readAndCompileShader(config.fragment, fragShader);
glAttachShader(pid, fragShader);
GLuint geomShader = 0;
if (config.hasGeom) {
geomShader = glCreateShader(GL_GEOMETRY_SHADER);
readAndCompileShader(config.geometry, geomShader);
glAttachShader(pid, geomShader);
}
glLinkProgram(pid);
glDetachShader(pid, vertShader);
glDetachShader(pid, fragShader);
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);
}
return nullptr;
}
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;
}
auto ShaderCache::load(const ShaderSources &sources, bool allowBinaryCache) -> ShaderCache::ShaderPtr {
spdlog::debug("starting shader program generation for {}", sources.name);
GLuint pid = glCreateProgram();
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;
}
spdlog::debug("could not use cached shader for '{}', doing full compile.", sources.name);
}
// TODO actual shader loading
GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
compileShaderFromSource(sources.vertexSource, vertShader);
glAttachShader(pid, vertShader);
GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
compileShaderFromSource(sources.fragmentSource, fragShader);
glAttachShader(pid, fragShader);
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];
}
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);
}
auto ShaderCache::cacheLoad(GLuint pid, const BinaryCache *cache) -> bool {
if (!m_binary) {
return false;
}
glProgramBinary(pid, cache->format, cache->data, cache->size);
// 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);
}
}
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<>
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>()));
return true;
}
template<>
auto fggl::data::fggl_serialize(std::filesystem::path &data, const fggl::gfx::BinaryCache *out) -> bool {
// try and write
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::fclose(f);
std::free(out->data);
return true;
}
/*
* 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
# Sources
target_sources(fggl
PRIVATE
setup.cpp
canvas.cpp
models.cpp
debug.cpp
meshes.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/>.
*/
#include "fggl/gfx/ogl/common.hpp"
#include "fggl/gfx/ogl4/canvas.hpp"
#include "fggl/data/model.hpp"
#include "fggl/math/types.hpp"
#include <glm/gtc/type_ptr.hpp>
#include "fggl/math/triangulation.hpp"
#include "fggl/gui/fonts.hpp"
#define FGGL_OPENGL_CORRECTNESS
namespace fggl::gfx::ogl4 {
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) {
for (const auto &cmd : paint.cmds()) {
auto path = cmd.path;
std::vector<data::Vertex2D> verts;
math::vec3 colour{1.0F, 1.0F, 1.0F};
auto idx = 0;
auto colourIdx = 0;
for (auto &type : path.m_types) {
if (type == PathType::PATH) {
verts.push_back({path.m_points[idx++], colour});
} else if (type == PathType::MOVE) {
// polygon finished
if (verts.size() < 3) {
// empty, point, or line
// TODO deal with whatever I'm meant to do with this...
} else if (verts.size() == 3) {
// triangle
add_mesh_triangle(mesh, verts);
} else {
// polygon
math::fan_triangulation(verts, mesh);
}
verts.clear();
verts.push_back({path.m_points[idx++], colour});
} else if (type == PathType::COLOUR) {
colour = path.m_colours[colourIdx++];
} else {
// unsupported type
}
}
if (!verts.empty() && verts.size() > 3) {
math::fan_triangulation(verts, mesh);
}
}
}
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 );
#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 );
#endif
glBindVertexArray(0);
}
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()) {
return;
}
// update data
m_vao.bind();
m_vertexList.replace(mesh.vertexList.size(), mesh.vertexList.data());
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);
// 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,
glm::value_ptr(projMat));
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
/*
* 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/ogl4/meshes.hpp"
#include "fggl/gfx/phong.hpp"
#include "fggl/data/texture.hpp"
//
// Created by webpigeon on 22/10/22.
//
namespace fggl::gfx::ogl4 {
static auto setup_array_buffer(std::shared_ptr<ogl::VertexArray> &vao,
const std::vector<mesh::Vertex3D> &data) -> std::shared_ptr<ogl::ArrayBuffer> {
// upload the data to the GPU
auto buff = std::make_shared<ogl::ArrayBuffer>();
buff->write(data.size() * sizeof(mesh::Vertex3D), data.data(), ogl::BufUsage::STATIC_DRAW);
// set up the vertex attributes
auto posAttr = ogl::attribute<mesh::Vertex3D, math::vec3>(offsetof(mesh::Vertex3D, position));
auto normalAttr = ogl::attribute<mesh::Vertex3D, math::vec3>(offsetof(mesh::Vertex3D, normal));
auto colAttr = ogl::attribute<mesh::Vertex3D, math::vec3>(offsetof(mesh::Vertex3D, colour));
auto texAttr = ogl::attribute<mesh::Vertex3D, math::vec2>(offsetof(mesh::Vertex3D, texPos));
vao->setAttribute(*buff, 0, posAttr);
vao->setAttribute(*buff, 1, normalAttr);
vao->setAttribute(*buff, 2, colAttr);
vao->setAttribute(*buff, 3, texAttr);
return buff;
}
static auto setup_index_buffer(const std::vector<uint32_t>& data) -> std::shared_ptr<ogl::ElementBuffer> {
auto elementBuffer = std::make_shared<ogl::ElementBuffer>();
elementBuffer->write(data.size() * sizeof(uint32_t),
data.data(), ogl::BufUsage::STATIC_DRAW);
return elementBuffer;
}
void setup_material(const std::shared_ptr<ogl::Shader>& shader, const PhongMaterial* material) {
if ( !shader->hasUniform("material.ambient") ) {
return;
}
// setup material block
shader->setUniformF(shader->uniform("material.emission"), material->emission);
shader->setUniformF(shader->uniform("material.ambient"), material->ambient);
shader->setUniformF(shader->uniform("material.diffuse"), material->diffuse);
shader->setUniformF(shader->uniform("material.specular"), material->specular);
//shader->setUniformF(shader->uniform("material.shininess"), material->shininess);
}
void setup_material(const std::shared_ptr<ogl::Shader>& shader, const Material* material) {
if ( shader->hasUniform("material.diffuse") ) {
// setup material block
// shader->setUniformF(shader->uniform("material.emission"), material->emission);
// shader->setUniformF(shader->uniform("material.ambient"), material->ambient);
shader->setUniformF(shader->uniform("material.diffuse"), material->m_diffCol);
shader->setUniformF(shader->uniform("material.specular"), material->m_specCol);
}
// setup diffuse texture
if (material->m_diffuse != nullptr) {
material->m_diffuse->bind(0);
if (shader->hasUniform("diffuseTexture")) {
shader->setUniformI(shader->uniform("diffuseTexture"), 0);
}
}
// setup specular texture
if (material->m_specular != nullptr) {
material->m_specular->bind(1);
if (shader->hasUniform("specularTexture")) {
shader->setUniformI(shader->uniform("specularTexture"), 1);
}
}
//shader->setUniformF(shader->uniform("material.shininess"), material->shininess);
}
void MeshData::draw(std::shared_ptr<ogl::Shader> shader) const {
vao->bind();
vertexData->bind();
if ( material != nullptr ) {
setup_material(shader, material);
} else {
debug::info("no material is active, cannot bind textures!");
}
if ( drawInfo.restartIndex != ogl::NO_RESTART_IDX) {
glEnable(GL_PRIMITIVE_RESTART);
glPrimitiveRestartIndex( drawInfo.restartIndex );
}
vao->drawElements(*elements, drawInfo.mode, elementCount);
if ( drawInfo.restartIndex != ogl::NO_RESTART_IDX ) {
glDisable(GL_PRIMITIVE_RESTART);
}
}
void StaticMultiMesh::draw() const {
for ( const auto& mesh : meshes ) {
mesh.draw(pipeline);
}
}
void setup_lighting(const std::shared_ptr<ogl::Shader>& shader, const DirectionalLight* light) {
assert( light != nullptr );
if ( !shader->hasUniform("light.direction") ) {
fggl::debug::warning("asked for directional lighting, but shader does not support!");
return;
}
shader->setUniformF( shader->uniform("light.direction"), light->position);
shader->setUniformF( shader->uniform("light.ambient"), light->ambient);
shader->setUniformF( shader->uniform("light.diffuse"), light->diffuse);
shader->setUniformF( shader->uniform("light.specular"), light->specular);
}
void setup_lighting(const std::shared_ptr<ogl::Shader>& shader, const math::mat4& viewMatrix, const math::Transform& camTransform, const math::Transform& transform, math::vec3 lightPos) {
if (shader->hasUniform("lightPos")) {
shader->setUniformF(shader->uniform("lightPos"), lightPos);
}
if ( !shader->hasUniform("lights[0].isEnabled") ) {
return;
}
bool local = true;
shader->setUniformI(shader->uniform("lights[0].isEnabled"), 1);
shader->setUniformI(shader->uniform("lights[0].isLocal"), local);
shader->setUniformI(shader->uniform("lights[0].isSpot"), 0);
shader->setUniformF(shader->uniform("lights[0].constantAttenuation"), 5.0F);
shader->setUniformF(shader->uniform("lights[0].linearAttenuation"), 0.0F);
shader->setUniformF(shader->uniform("lights[0].quadraticAttenuation"), 0.0F);
shader->setUniformF(shader->uniform("Strength"), 0.6F);
if (!local) {
lightPos = glm::normalize(lightPos);
auto viewDir = glm::normalize(camTransform.origin() - transform.origin());
auto halfVector = glm::normalize(lightPos + viewDir);
shader->setUniformF(shader->uniform("lights[0].halfVector"), halfVector);
shader->setUniformF(shader->uniform("EyeDirection"), viewDir);
shader->setUniformF(shader->uniform("lights[0].position"), lightPos);
} else {
auto camModelView = (viewMatrix * camTransform.model() * math::vec4(0.0F, 0.0F, 0.0F, 1.0F));
auto modelModelView = (viewMatrix * transform.model() * math::vec4(0.0F, 0.0F, 0.0F, 1.0F));
math::vec3 viewDir = glm::normalize(camModelView - modelModelView);
shader->setUniformF(shader->uniform("EyeDirection"), viewDir);
shader->setUniformF(shader->uniform("lights[0].position"),
math::vec3(viewMatrix * math::vec4(lightPos, 1.0F)));
}
shader->setUniformF(shader->uniform("lights[0].ambient"), {0.0F, 0.5F, 0.0F});
shader->setUniformF(shader->uniform("lights[0].colour"), {0.5F, 0.5F, 0.5F});
}
static auto upload_texture( assets::AssetID name, assets::AssetManager* manager ) -> ogl::Texture* {
debug::info("loading texture: {}", name.get());
auto uploadedTex = assets::make_asset_id_rt("ogl", std::to_string(name.get()) );
auto* texture = manager->get<ogl::Texture>( uploadedTex );
if ( texture != nullptr ) {
return texture;
}
// get the texture data we plan to load
auto *textureData = manager->get<data::Texture2D>(name);
// create a texture
texture = new ogl::Texture(ogl::TextureType::Tex2D);
texture->setData( ogl::InternalImageFormat::RedGreenBlue, textureData);
return manager->set(uploadedTex, texture);
}
static auto get_fallback_material(assets::AssetManager* manager) -> Material* {
auto* fallback = manager->get<Material>(FALLBACK_MAT);
if ( fallback != nullptr ) {
return fallback;
}
auto* mat = new Material();
mat->m_diffCol = FALLBACK_COLOUR;
mat->m_specCol= FALLBACK_COLOUR;
mat->m_diffuse = manager->get<ogl::Texture>(FALLBACK_TEX);
mat->m_specular = manager->get<ogl::Texture>(FALLBACK_TEX);
mat->m_normals = manager->get<ogl::Texture>(SOLID_TEX);
return manager->set(FALLBACK_MAT, mat);
}
static auto upload_material( assets::AssetID name, assets::AssetManager* manager ) -> Material* {
auto* meshMaterial = manager->get<mesh::Material>(name);
if ( meshMaterial == nullptr ){
debug::error("attempted to load material {}, but did not exist!", name.get());
return get_fallback_material(manager);
}
auto uploadedMat = assets::make_asset_id_rt("ogl", std::to_string(name.get()) );
auto* material = manager->get<Material>( uploadedMat );
if ( material != nullptr ) {
return material;
}
material = new Material();
material->m_diffCol = meshMaterial->diffuse;
material->m_specCol = meshMaterial->specular;
// do we have a diffuse texture?
if ( !meshMaterial->diffuseTextures.empty() ) {
material->m_diffuse = upload_texture(meshMaterial->getPrimaryDiffuse(), manager);
} else {
material->m_diffuse = manager->get<ogl::Texture>( ogl4::SOLID_TEX );
}
// do we have a normal Texture?
if ( !meshMaterial->normalTextures.empty() ) {
material->m_normals = upload_texture(meshMaterial->getPrimaryNormals(), manager);
} else {
material->m_normals = manager->get<ogl::Texture>( ogl4::SOLID_TEX );
}
// do we have a specular texture?
if ( !meshMaterial->specularTextures.empty() ) {
material->m_specular = upload_texture(meshMaterial->getPrimarySpecular(), manager);
} else {
material->m_specular = manager->get<ogl::Texture>( ogl4::SOLID_TEX );
}
return manager->set( uploadedMat, material);
}
auto upload_mesh(const mesh::Mesh3D& rawMesh, assets::AssetManager* manager) -> MeshData {
auto vao = std::make_shared<ogl::VertexArray>();
return {
.vao = vao,
.elements = setup_index_buffer( rawMesh.indices ),
.vertexData = setup_array_buffer(vao, rawMesh.data ),
.elementCount = rawMesh.indices.size(),
.drawInfo = { .mode = ogl::Primitive::TRIANGLE },
.material = upload_material( rawMesh.material, manager )
};
}
auto upload_multi_mesh(const mesh::MultiMesh3D& rawMesh, assets::AssetManager* manager) -> std::vector<MeshData> {
std::vector<MeshData> gpuMeshes;
for (const auto& mesh : rawMesh.meshes) {
gpuMeshes.push_back( upload_mesh(mesh, manager) );
}
return gpuMeshes;
}
}
/*
* 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 18/04/22.
//
#include "fggl/gfx/ogl4/models.hpp"
#include "fggl/gfx/ogl4/meshes.hpp"
#include "fggl/data/heightmap.hpp"
#include "fggl/data/texture.hpp"
#include "fggl/debug/logging.hpp"
#include "fggl/gfx/camera.hpp"
#include "fggl/gfx/phong.hpp"
namespace fggl::gfx::ogl4 {
static auto setup_array_buffer(std::shared_ptr<ogl::VertexArray> &vao,
const std::vector<data::Vertex> &data) -> std::shared_ptr<ogl::ArrayBuffer> {
auto buff = std::make_shared<ogl::ArrayBuffer>();
buff->write(data.size() * sizeof(data::Vertex), data.data(), ogl::BufUsage::STATIC_DRAW);
// set up the vertex attributes
auto posAttr = ogl::attribute<data::Vertex, math::vec3>(offsetof(data::Vertex, posititon));
auto normalAttr = ogl::attribute<data::Vertex, math::vec3>(offsetof(data::Vertex, normal));
auto colAttr = ogl::attribute<data::Vertex, math::vec3>(offsetof(data::Vertex, colour));
vao->setAttribute(*buff, 0, posAttr);
vao->setAttribute(*buff, 1, normalAttr);
vao->setAttribute(*buff, 2, colAttr);
return buff;
}
static auto setup_index_buffer(std::shared_ptr<ogl::VertexArray> &vao,
const std::vector<uint32_t> &data) -> std::shared_ptr<ogl::ElementBuffer> {
auto elementBuffer = std::make_shared<ogl::ElementBuffer>();
elementBuffer->write(data.size() * sizeof(uint32_t),
data.data(), ogl::BufUsage::STATIC_DRAW);
return elementBuffer;
}
static void setup_component(StaticModel &modelComp, std::shared_ptr<ogl::Shader> &shader, data::Mesh &mesh) {
auto vao = std::make_shared<ogl::VertexArray>();
auto meshBuffer = setup_array_buffer(vao, mesh.vertexList());
auto elementBuffer = setup_index_buffer(vao, mesh.indexList());
// set up the element attributes
modelComp.vao = vao;
modelComp.vertexData = meshBuffer;
modelComp.elements = elementBuffer;
modelComp.pipeline = shader;
modelComp.elementCount = mesh.indexCount();
modelComp.drawType = ogl::Primitive::TRIANGLE;
}
auto StaticModelRenderer::uploadMesh(assets::AssetID guid, const data::Mesh &mesh, bool allowCache) -> StaticModel* {
assert( m_assets != nullptr );
// if the asset has already been uploaded, we don't need to do anything
if ( allowCache && m_assets->has(guid) ) {
m_assets->require(guid);
return m_assets->get<StaticModel>(guid);
}
// the asset does not exist, we need to upload it
auto* modelAsset = new StaticModel();
modelAsset->vao = std::make_shared<ogl::VertexArray>();
modelAsset->vertexData = setup_array_buffer(modelAsset->vao, mesh.vertexList());
modelAsset->elements = setup_index_buffer(modelAsset->vao, mesh.indexList());
modelAsset->elementCount = mesh.indexCount();
modelAsset->drawType = ogl::Primitive::TRIANGLE;
// if caching is enabled, then use the cache
if ( allowCache ) {
m_assets->set(guid, modelAsset);
}
return modelAsset;
}
/*MeshData* StaticModelRenderer::uploadMesh(assets::AssetGUID guid, const mesh::Mesh3D &mesh, bool allowCache) {
assert( m_assets != nullptr );
// if the asset has already been uploaded, we don't need to do anything
if ( allowCache && m_assets->has(guid) ) {
m_assets->require(guid);
return m_assets->get<MeshData>(guid);
}
// the asset does not exist, we need to upload it
auto* modelAsset = new MeshData();
modelAsset->vao = std::make_shared<ogl::VertexArray>();
modelAsset->vertexData = setupArrayBuffer(modelAsset->vao, mesh.data);
modelAsset->elements = setupIndexBuffer(modelAsset->vao, mesh.indices);
modelAsset->elementCount = mesh.indices.size();
modelAsset->drawInfo.mode = ogl::Primitive::TRIANGLE;
// if caching is enabled, then use the cache
if ( allowCache ) {
m_assets->set(guid, modelAsset);
}
return modelAsset;
}*/
auto StaticModelRenderer::uploadMesh2(const assets::AssetID& meshName, const data::Mesh &mesh) -> StaticModelGPU* {
assert( m_assets != nullptr );
if ( m_assets->has(meshName) ) {
// we've already uploaded it...
return m_assets->get<StaticModelGPU>(meshName);
}
auto* modelAsset = new StaticModelGPU();
modelAsset->vao = std::make_shared<ogl::VertexArray>();
modelAsset->vertices = setup_array_buffer(modelAsset->vao, mesh.vertexList());
modelAsset->elements = setup_index_buffer(modelAsset->vao, mesh.indexList());
modelAsset->elementCount = mesh.indexCount();
modelAsset->drawType = ogl::Primitive::TRIANGLE;
return m_assets->set(meshName, modelAsset);
}
#ifdef FGGL_ALLOW_DEFERRED_UPLOAD
static void setup_meshes(entity::EntityManager& world, ShaderCache* shaders, assets::AssetManager* manager) {
auto entsWithModels = world.find<mesh::StaticMesh3D>();
for ( const auto& mesher : entsWithModels ) {
// check if this entity already has a mesh...
const auto* currModel = world.tryGet<StaticMesh>(mesher);
if ( currModel != nullptr ) {
continue;
}
// figure out the requirements
auto& rawMesh = world.get<mesh::StaticMesh3D>(mesher);
if ( rawMesh.guid != util::make_guid("__NO_CACHE__") ){
// TODO support fetching of loaded meshes
debug::warning("multiple entities sharing a mesh has not been implemented yet...");
}
auto& entityMesh = world.add<StaticMesh>(mesher);
entityMesh.pipeline = shaders->get( rawMesh.pipeline );
entityMesh.mesh = upload_mesh( rawMesh.mesh, manager );
}
}
static void setup_multi_meshes(entity::EntityManager& world, ShaderCache* shaders, assets::AssetManager* manager) {
auto entsWithModels = world.find<mesh::StaticMultiMesh3D>();
for ( const auto& mesher : entsWithModels ) {
// check if this entity already has a mesh...
const auto* currModel = world.tryGet<StaticMultiMesh>(mesher);
if ( currModel != nullptr ) {
continue;
}
// figure out the requirements
auto& multiMesh = world.get<mesh::StaticMultiMesh3D>(mesher);
if ( multiMesh.guid != util::make_guid("__NO_CACHE__") ){
// TODO support fetching of loaded meshes
debug::warning("multiple entities sharing a mesh has not been implemented yet...");
}
auto& entityMesh = world.add<StaticMultiMesh>(mesher);
entityMesh.pipeline = shaders->get( multiMesh.pipeline );
entityMesh.meshes = upload_multi_mesh(multiMesh.mesh, manager);
//entityMesh.material = setup_material( multiMesh.mesh.materials[0], manager);
}
}
void StaticModelRenderer::resolveModels(entity::EntityManager &world) {
// new mesh formats
setup_meshes(world, m_shaders, m_assets);
setup_multi_meshes(world, m_shaders, m_assets);
// terrain
auto terrain = world.find<data::HeightMap>();
for (const auto &renderable : terrain) {
auto *currModel = world.tryGet<StaticModel>(renderable);
if (currModel != nullptr) {
continue;
}
auto &heightmap = world.get<data::HeightMap>(renderable);
data::Mesh heightMapMesh{};
data::generateHeightMesh(heightmap, heightMapMesh);
auto &modelComp = world.add<StaticModel>(renderable);
setup_component(modelComp, m_phong, heightMapMesh);
// we know this is a triangle strip with a restart vertex...
// FIXME the model should be telling us this...
modelComp.drawType = ogl::Primitive::TRIANGLE_STRIP;
modelComp.restartIndex = heightMapMesh.restartVertex;
// no active model, we need to resolve/load one.
debug::info("looks like {} needs a static mesh", (uint64_t)renderable);
}
}
#endif
void StaticModelRenderer::renderModelsForward(const entity::EntityManager &world, bool debugMode) {
// fetch cameras we will need to render with
auto cameras = world.find<gfx::Camera>();
// if there are no cameras, we can't do anything...
if (cameras.empty()) {
debug::warning("asked to render static models, but there were no cameras");
return;
}
// perform a rendering pass for each camera (will usually only be one...)
for (const auto &cameraEnt : cameras) {
//TODO should be clipping this to only visible objects
//forward_camera_pass(cameraEnt, world);
forward_pass<ogl4::StaticMesh>(cameraEnt, world, m_assets);
forward_pass<ogl4::StaticMultiMesh>(cameraEnt, world, m_assets);
// enable rendering normals
if ( debugMode ) {
auto normalShader = m_shaders->get("normals");
forward_pass_normals<StaticMesh>(cameraEnt, world, normalShader);
forward_pass_normals<StaticMultiMesh>(cameraEnt, world, normalShader);
//forward_normal_pass(cameraEnt, world, normalShader);
}
}
}
}
/*
* 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/gfx/ogl4/module.hpp"
#include "fggl/gfx/phong.hpp"
#include "fggl/mesh/components.hpp"
#include "fggl/data/procedural.hpp"
#include "fggl/assets/loader.hpp"
#include <string>
namespace fggl::gfx {
constexpr uint32_t DEFAULT_STACKS = 16;
constexpr uint32_t DEFAULT_SLICES = 16;
constexpr const char *SHAPE_SPHERE{"sphere"};
constexpr const char *SHAPE_BOX{"box"};
namespace {
void process_shape(const YAML::Node &node, mesh::Mesh3D &mesh) {
auto transform = data::OFFSET_NONE;
auto offset = node["offset"].as<math::vec3>(math::VEC3_ZERO);
transform = glm::translate(transform, offset);
auto scale = node["scale"].as<math::vec3>(math::VEC3_ONES);
transform = glm::scale(transform, scale);
debug::debug("scale: {}, {}, {}", scale.x, scale.y, scale.z);
// now the shape itself
auto type = node["type"].as<std::string>();
if (type == SHAPE_BOX) {
data::make_cube(mesh, transform);
} else if (type == SHAPE_SPHERE) {
auto stacks = node["stacks"].as<uint32_t>(DEFAULT_STACKS);
auto slices = node["slices"].as<uint32_t>(DEFAULT_SLICES);
data::make_sphere(mesh, transform, stacks, slices);
} else {
debug::log(debug::Level::warning, "unknown shape type requested: {}", type);
}
}
}
void attach_mesh(const entity::ComponentSpec &spec, entity::EntityManager &manager, const entity::EntityID &id, modules::Services &services) {
// check for the asset service
auto* assetService = services.get<assets::AssetManager>();
auto* assetLoader = services.get<assets::Loader>();
if ( assetService == nullptr || assetLoader == nullptr ) {
// no asset service, give up
return;
}
// asset is a procedural mesh description
if (spec.has("shape")) {
auto pipeline = spec.get<std::string>("pipeline", "");
// check if we had previously loaded this asset
/*const auto shapeName = spec.get<std::string>("shape_id", "");
if ( !shapeName.empty() ) {
meshAsset = assetService->get<data::Mesh>(shapeName);
}*/
// we've not loaded this before - generate mesh
if ( true ) {
// procedural meshes are build as static meshes first
if (spec.config["shape"].IsSequence()) {
mesh::MultiMesh3D* multiMesh;
for (const auto &node : spec.config["shape"]) {
auto* meshAsset = new mesh::Mesh3D();
process_shape(node, *meshAsset);
multiMesh->meshes.push_back(*meshAsset);
delete meshAsset;
}
#ifdef FGGL_ALLOW_DEFERRED_UPLOAD
// the graphics stack can detect static meshes without a rendering proxy at runtime and fix it but this
// requires loading the whole model into the ECS (and should be removed in the future). instead we should
// be triggering the upload at this point (if needed).
auto &entityMesh = manager.add<mesh::StaticMultiMesh3D>(id);
entityMesh.mesh = *multiMesh;
entityMesh.pipeline = pipeline;
debug::warning("HACKY: Triggered proc mesh - using deferred upload");
#endif
} else {
auto* meshAsset = new mesh::Mesh3D();
process_shape(spec.config["shape"], *meshAsset);
#ifdef FGGL_ALLOW_DEFERRED_UPLOAD
// the graphics stack can detect static meshes without a rendering proxy at runtime and fix it but this
// requires loading the whole model into the ECS (and should be removed in the future). instead we should
// be triggering the upload at this point (if needed).
auto &entityMesh = manager.add<mesh::StaticMesh3D>(id);
entityMesh.mesh = *meshAsset;
entityMesh.pipeline = pipeline;
debug::warning("HACKY: Triggered proc mesh - using deferred upload");
#endif
}
//assetService->set(shapeName, meshTmp);
}
// TODO we need to trigger an upload to the GPU (somehow)
return;
}
// asset is a model from the resource cache
if ( spec.has("model") ) {
// figure out what model we want
auto assetStr = spec.get<std::string>("model", "");
auto assetId = assets::asset_from_user( assetStr );
auto* asset = assetService->get<ogl4::StaticModel>(assetId);
if ( asset == nullptr ) {
// the asset is not loaded/does not exist
debug::error("requested model {} but it was not loaded.", assetStr);
return;
}
// copy the asset to the model
auto& model = manager.add<ogl4::StaticModel>(id);
model = *asset;
}
}
void attach_material(const entity::ComponentSpec &spec,
entity::EntityManager &manager,
const entity::EntityID &id,
modules::Services& /*services*/ ) {
auto &mat = manager.add<gfx::PhongMaterial>(id);
mat.ambient = spec.get<math::vec3>("ambient", gfx::DEFAULT_AMBIENT);
mat.diffuse = spec.get<math::vec3>("diffuse", gfx::DEFAULT_DIFFUSE);
mat.specular = spec.get<math::vec3>("ambient", gfx::DEFAULT_SPECULAR);
mat.shininess = spec.get<float>("ambient", gfx::DEFAULT_SHININESS);
}
void attach_light_directional(const entity::ComponentSpec &spec, entity::EntityManager &manager, const entity::EntityID &id, modules::Services& /*services*/) {
auto &light = manager.add<gfx::DirectionalLight>(id);
light.position = spec.get<math::vec3>("direction", -math::UP);
light.ambient = spec.get<math::vec3>("ambient", gfx::colours::WHITE);
light.specular = spec.get<math::vec3>("specular", gfx::colours::WHITE);
light.diffuse = spec.get<math::vec3>("diffuse", gfx::colours::WHITE);
}
void attach_light_point(const entity::ComponentSpec &spec, entity::EntityManager &manager, const entity::EntityID &id, modules::Services& /*services*/) {
auto &light = manager.add<gfx::PointLight>(id);
light.position = spec.get<math::vec3>("position", math::VEC3_ZERO);
light.constant = spec.get<float>("constant", 1.0F);
light.linear = spec.get<float>("linear", 0.0014F);
light.quadratic = spec.get<float>("quadratic", 0.000007F);
}
auto OpenGL4::factory(modules::ServiceName service, modules::Services &services) -> bool {
if (service == WindowGraphics::service) {
// setup the thing responsible for graphics
auto *storage = services.get<data::Storage>();
auto *fontLibrary = services.get<gui::FontLibrary>();
auto *assets = services.get<assets::AssetManager>();
services.bind<WindowGraphics, ogl4::WindowGraphics>(storage, fontLibrary, assets);
// register as responsible for creating rendering components
auto *entityFactory = services.get<entity::EntityFactory>();
entityFactory->bind(data::StaticMesh::guid, attach_mesh);
entityFactory->bind(mesh::StaticMesh3D::guid, attach_mesh);
entityFactory->bind(mesh::StaticMultiMesh3D::guid, attach_mesh);
entityFactory->bind(gfx::PhongMaterial::guid, attach_material);
entityFactory->bind(gfx::DirectionalLight::guid, attach_light_directional);
entityFactory->bind(gfx::PointLight::guid, attach_light_point);
return true;
}
return false;
}
} // namespace fggl::gfX::ogl4
\ No newline at end of file