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 1148 additions and 589 deletions
......@@ -20,7 +20,7 @@
namespace fggl::data {
bool LocalStorage::factory(modules::ModuleService service, modules::Services &data) {
auto LocalStorage::factory(modules::ServiceName service, modules::Services &data) -> bool {
if (service == SERVICE_STORAGE) {
// FIXME: no easy way to set the application name
auto pathConfig = fggl::platform::calc_engine_paths("fggl-demo");
......
......@@ -17,64 +17,63 @@
#include <fggl/data/model.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/quaternion.hpp>
#include <iostream>
#include <array>
#include <glm/geometric.hpp>
#include "fggl/mesh/mesh.hpp"
using namespace fggl::data;
// from https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
static glm::vec3 calcSurfaceNormal(glm::vec3 vert1, glm::vec3 vert2, glm::vec3 vert3) {
static auto calcSurfaceNormal(glm::vec3 vert1, glm::vec3 vert2, glm::vec3 vert3) -> glm::vec3 {
const glm::vec3 edge1 = vert2 - vert1;
const glm::vec3 edge2 = vert3 - vert1;
return glm::normalize(glm::cross(edge1, edge2));
}
static void computeNormalsDirect(fggl::data::Mesh &mesh, const fggl::data::Mesh::IndexType *colIdx, int nPoints) {
static void computeNormalsDirect(fggl::mesh::Mesh3D &mesh, const fggl::data::Mesh::IndexType *colIdx, int nPoints) {
// we're assuming all the normals are zero...
for (int i = 0; i < nPoints; i++) {
auto &vertex = mesh.vertex(colIdx[i]);
auto &vertex = mesh.data[colIdx[i]];
vertex.normal = glm::vec3(0.0F);
}
// We're assuming each vertex appears only once (because we're not indexed)
for (int i = 0; i < nPoints; i += 3) {
auto &v1 = mesh.vertex(colIdx[i]);
auto &v2 = mesh.vertex(colIdx[i + 1]);
auto &v3 = mesh.vertex(colIdx[i + 2]);
auto &v1 = mesh.data[colIdx[i]];
auto &v2 = mesh.data[colIdx[i + 1]];
auto &v3 = mesh.data[colIdx[i + 2]];
const glm::vec3 normal = glm::normalize(calcSurfaceNormal(v1.posititon, v2.posititon, v3.posititon));
const glm::vec3 normal = glm::normalize(calcSurfaceNormal(v1.position, v2.position, v3.position));
v1.normal = normal;
v2.normal = normal;
v3.normal = normal;
}
}
static void compute_normals(fggl::data::Mesh &mesh,
static void compute_normals(fggl::mesh::Mesh3D &mesh,
const std::vector<Mesh::IndexType> &idxList, // source index
const std::vector<Mesh::IndexType> &idxMapping // source-to-mesh lookup
) {
// clear the normals, so the summation below works correctly
for (auto vertexIndex : idxMapping) {
auto &vertex = mesh.vertex(vertexIndex);
auto &vertex = mesh.data[vertexIndex];
vertex.normal = ILLEGAL_NORMAL;
}
// we need to calculate the contribution for each vertex
// this assumes IDXList describes a raw triangle list (ie, not quads and not a strip)
for (std::size_t i = 0; i < idxList.size(); i += 3) {
auto &v1 = mesh.vertex(idxMapping[idxList[i]]);
auto &v2 = mesh.vertex(idxMapping[idxList[i + 1]]);
auto &v3 = mesh.vertex(idxMapping[idxList[i + 2]]);
auto &v1 = mesh.data[ idxMapping[idxList[i]] ];
auto &v2 = mesh.data[ idxMapping[idxList[i + 1]] ];
auto &v3 = mesh.data[ idxMapping[idxList[i + 2]] ];
// calculate the normal and area (formula for area the math textbook)
float area = glm::length(glm::cross(v3.posititon - v2.posititon, v1.posititon - v3.posititon)) / 2;
auto faceNormal = calcSurfaceNormal(v1.posititon, v2.posititon, v3.posititon);
float area = glm::length(glm::cross(v3.position - v2.position, v1.position - v3.position)) / 2;
auto faceNormal = calcSurfaceNormal(v1.position, v2.position, v3.position);
// weight the normal according to the area of the surface (bigger area = more impact)
v1.normal += area * faceNormal;
......@@ -84,12 +83,12 @@ static void compute_normals(fggl::data::Mesh &mesh,
// re-normalise the normals
for (unsigned int vertexIndex : idxMapping) {
auto &vertex = mesh.vertex(vertexIndex);
auto &vertex = mesh.data[vertexIndex];
vertex.normal = glm::normalize(vertex.normal);
}
}
static void populateMesh(fggl::data::Mesh &mesh, const fggl::math::mat4 transform,
static void populateMesh(fggl::mesh::Mesh3D &mesh, const fggl::math::mat4 transform,
const int nIdx, const fggl::math::vec3 *pos, const Mesh::IndexType *idx) {
auto *colIdx = new fggl::data::Mesh::IndexType[nIdx];
......@@ -97,8 +96,8 @@ static void populateMesh(fggl::data::Mesh &mesh, const fggl::math::mat4 transfor
// generate mesh
for (int i = 0; i < nIdx; i++) {
glm::vec3 rawPos = transform * glm::vec4(pos[idx[i]], 1.0);
colIdx[i] = mesh.pushVertex(Vertex::from_pos(rawPos));
mesh.pushIndex(colIdx[i]);
colIdx[i] = mesh.append(fggl::mesh::Vertex3D::from_pos(rawPos));
mesh.indices.push_back(colIdx[i]);
}
computeNormalsDirect(mesh, colIdx, nIdx);
......@@ -106,7 +105,7 @@ static void populateMesh(fggl::data::Mesh &mesh, const fggl::math::mat4 transfor
delete[] colIdx;
}
static void populateMesh(fggl::data::Mesh &mesh,
static void populateMesh(fggl::mesh::Mesh3D &mesh,
const fggl::math::mat4 transform,
const std::vector<fggl::math::vec3> &posList,
const std::vector<fggl::data::Mesh::IndexType> &idxList) {
......@@ -118,12 +117,13 @@ static void populateMesh(fggl::data::Mesh &mesh,
// clion this thinks this loop is infinite, my assumption is it's gone bananas
for (std::size_t i = 0; i < posList.size(); ++i) {
glm::vec3 position = transform * fggl::math::vec4(posList[i], 1.0F);
colIdx[i] = mesh.pushVertex(Vertex::from_pos(position));
auto vert = fggl::mesh::Vertex3D::from_pos(position);
colIdx[i] = mesh.append(vert);
}
// use the remapped indexes for the mesh
for (auto idx : idxList) {
mesh.pushIndex(colIdx[idx]);
mesh.indices.push_back(colIdx[idx]);
}
compute_normals(mesh, idxList, colIdx);
......@@ -152,7 +152,7 @@ namespace fggl::data {
}
}
void make_sphere(Mesh &mesh, const math::mat4 &offset, uint32_t slices, uint32_t stacks) {
void make_sphere(fggl::mesh::Mesh3D &mesh, const math::mat4 &offset, uint32_t slices, uint32_t stacks) {
std::vector<math::vec3> positions;
......@@ -183,7 +183,7 @@ namespace fggl::data {
} // namespace fggl::data
fggl::data::Mesh fggl::data::make_triangle() {
auto fggl::data::make_triangle() -> fggl::data::Mesh {
constexpr fggl::math::vec3 pos[]{
{-0.5F, -0.5F, 0.0F},
{0.5F, -0.5F, 0.0F},
......@@ -204,7 +204,7 @@ fggl::data::Mesh fggl::data::make_triangle() {
return mesh;
}
fggl::data::Mesh fggl::data::make_quad_xy() {
auto fggl::data::make_quad_xy() -> fggl::data::Mesh {
constexpr fggl::math::vec3 pos[]{
{-0.5F, -0.5F, 0.0F},
{0.5F, -0.5F, 0.0F},
......@@ -229,7 +229,7 @@ fggl::data::Mesh fggl::data::make_quad_xy() {
return mesh;
}
fggl::data::Mesh fggl::data::make_quad_xz() {
auto fggl::data::make_quad_xz() -> fggl::data::Mesh {
constexpr std::array<fggl::math::vec3, 4> pos{{
{-0.5F, 0.0F, -0.5F},
{0.5F, 0.0F, -0.5F},
......@@ -254,7 +254,7 @@ fggl::data::Mesh fggl::data::make_quad_xz() {
return mesh;
}
fggl::data::Mesh fggl::data::make_cube(fggl::data::Mesh &mesh, const fggl::math::mat4 &transform) {
auto fggl::data::make_cube(fggl::mesh::Mesh3D &mesh, const fggl::math::mat4 &transform) -> fggl::mesh::Mesh3D {
// done as two loops, top loop is 0,1,2,3, bottom loop is 4,5,6,7
constexpr std::array<fggl::math::vec3, 8> pos{{
{-0.5, 0.5, -0.5}, // 0 TOP LOOP
......@@ -285,7 +285,7 @@ fggl::data::Mesh fggl::data::make_cube(fggl::data::Mesh &mesh, const fggl::math:
return mesh;
}
fggl::data::Mesh fggl::data::make_slope(fggl::data::Mesh &mesh, const fggl::math::mat4 &transform) {
auto fggl::data::make_slope(fggl::mesh::Mesh3D &mesh, const fggl::math::mat4 &transform) -> fggl::mesh::Mesh3D {
// done as two loops, top loop is 0,1,2,3, bottom loop is 4,5,6,7
// FIXME remove 2 and 3 and renumber the index list accordingly
......@@ -316,7 +316,7 @@ fggl::data::Mesh fggl::data::make_slope(fggl::data::Mesh &mesh, const fggl::math
return mesh;
}
fggl::data::Mesh fggl::data::make_ditch(fggl::data::Mesh &mesh, const fggl::math::mat4 &transform) {
auto fggl::data::make_ditch(fggl::mesh::Mesh3D &mesh, const fggl::math::mat4 &transform) -> fggl::mesh::Mesh3D {
// done as two loops, top loop is 0,1,2,3, bottom loop is 4,5,6,7
// FIXME remove 2 and renumber the index list accordingly
......@@ -349,7 +349,7 @@ fggl::data::Mesh fggl::data::make_ditch(fggl::data::Mesh &mesh, const fggl::math
return mesh;
}
fggl::data::Mesh fggl::data::make_point(fggl::data::Mesh &mesh, const fggl::math::mat4 &transform) {
auto fggl::data::make_point(fggl::mesh::Mesh3D &mesh, const fggl::math::mat4 &transform) -> fggl::mesh::Mesh3D {
// done as two loops, top loop is 0,1,2,3, bottom loop is 4,5,6,7
constexpr fggl::math::vec3 pos[]{
......
target_sources(fggl
PRIVATE
debug.cpp
debug_draw.cpp
debug.cpp
debug_draw.cpp
logging.cpp
)
# spdlog for cleaner logging
......
......@@ -21,9 +21,9 @@
struct GLFWwindow;
IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow *window, bool install_callbacks);
IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow *window, bool install_callbacks);
IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOther(GLFWwindow *window, bool install_callbacks);
IMGUI_IMPL_API auto ImGui_ImplGlfw_InitForOpenGL(GLFWwindow *window, bool installCallbacks) -> bool;
IMGUI_IMPL_API auto ImGui_ImplGlfw_InitForVulkan(GLFWwindow *window, bool installCallbacks) -> bool;
IMGUI_IMPL_API auto ImGui_ImplGlfw_InitForOther(GLFWwindow *window, bool installCallbacks) -> bool;
IMGUI_IMPL_API void ImGui_ImplGlfw_Shutdown();
IMGUI_IMPL_API void ImGui_ImplGlfw_NewFrame();
......
......@@ -25,15 +25,15 @@
#include "imgui.h" // IMGUI_IMPL_API
// Backend API
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char *glsl_version = NULL);
IMGUI_IMPL_API auto ImGui_ImplOpenGL3_Init(const char *glsl_version = nullptr) -> bool;
IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData *draw_data);
// (Optional) Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture();
IMGUI_IMPL_API auto ImGui_ImplOpenGL3_CreateFontsTexture() -> bool;
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture();
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects();
IMGUI_IMPL_API auto ImGui_ImplOpenGL3_CreateDeviceObjects() -> bool;
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects();
// Specific OpenGL ES versions
......
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 11/09/22.
//
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>
namespace fggl::debug {
auto demangle(const char *name) -> std::string {
int status = -4;
std::unique_ptr<char, decltype(&std::free)> res{
abi::__cxa_demangle(name, nullptr, nullptr, &status),
std::free
};
return (status == 0) ? res.get() : name;
}
}
#else
namespace fggl::debug {
std::string demangle(const char* name) {
return name;
}
}
#endif
......@@ -18,23 +18,76 @@
#include "fggl/entity/loader/loader.hpp"
#include "fggl/debug/logging.hpp"
#include "fggl/scenes/game.hpp"
namespace fggl::entity {
assets::AssetRefRaw load_prototype(EntityFactory *factory, const assets::AssetGUID &guid, assets::AssetData data) {
auto *filePath = std::get<assets::AssetPath *>(data);
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());
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>();
#ifndef NDEBUG
debug::info("found prefab: {}", fggl::util::guidToString(name));
#endif
debug::info("found prefab: {}", name);
// set up the components
EntitySpec entity{};
......@@ -48,11 +101,13 @@ namespace fggl::entity {
entity.components[compId] = compSpec;
entity.ordering.push_back(compId);
#ifndef NDEBUG
debug::trace("{} has component {}",
fggl::util::guidToString(name),
fggl::util::guidToString(compId));
#endif
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);
......
......@@ -18,11 +18,14 @@
#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) {
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));
......@@ -34,16 +37,54 @@ namespace fggl::entity {
factory->bind(math::Transform::guid, make_transform);
}
bool ECS::factory(modules::ModuleService service, modules::Services &services) {
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>();
auto *factory = services.create<EntityFactory>(services);
install_component_factories(factory);
// we are responsible for prefabs...
auto *assetLoader = services.get<assets::Loader>();
assetLoader->setFactory(PROTOTYPE_ASSET, [factory](const assets::AssetGUID &a, assets::AssetData b) {
return load_prototype(factory, a, b);
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;
}
......
......@@ -14,10 +14,8 @@
#include <fggl/gfx/atlas.hpp>
#include <fggl/math/types.hpp>
#include <array>
#include <vector>
#include <string>
#define STBRP_STATIC
#define STB_RECT_PACK_IMPLEMENTATION
......@@ -44,8 +42,8 @@ static void unpack_stbrp_query(Query &query, std::vector<stbrp_rect> &data) {
}
}
bool pack_iter(int width, int height, std::vector<stbrp_rect> &query) {
stbrp_node *tmp = new stbrp_node[width];
auto pack_iter(int width, int height, std::vector<stbrp_rect> &query) -> bool {
auto *tmp = new stbrp_node[width];
// setup context
stbrp_context context;
......@@ -58,7 +56,7 @@ bool pack_iter(int width, int height, std::vector<stbrp_rect> &query) {
namespace fggl::gfx {
bool pack(std::vector<Bounds2D> &pack) {
auto pack(std::vector<Bounds2D> &pack) -> bool {
// setup query structure
std::vector<stbrp_rect> query;
query.reserve(pack.size());
......
......@@ -13,10 +13,6 @@
*/
#include <fggl/gfx/input.hpp>
#include <cassert>
#include <bitset>
#include <iostream>
using fggl::gfx::Input;
......@@ -57,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();
}
......@@ -74,15 +70,15 @@ 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];
}
......@@ -94,15 +90,15 @@ void Input::mouseBtn(const MouseButton btn, bool state) {
}
}
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);
}
......@@ -118,11 +114,11 @@ void Input::joystickDisconnect(int id) {
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];
}
......@@ -130,22 +126,22 @@ void Input::padState(int id, const PadState &state) {
m_pad_curr[id] = state;
}
bool Input::padDown(int id, PadButton btn) {
auto Input::padDown(int id, PadButton btn) -> bool {
return m_pad_curr[id].buttons[(int) btn];
}
bool Input::padPressed(int id, PadButton 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) {
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) {
auto Input::padAxis(int id, PadAxis axis) -> float {
return m_pad_curr[id].axes[(int) axis];
}
float Input::padAxisDelta(int id, PadAxis axis) {
auto Input::padAxisDelta(int id, PadAxis axis) -> float {
return m_pad_last[id].axes[(int) axis] - m_pad_curr[id].axes[(int) axis];
}
......@@ -27,7 +27,6 @@
#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>
......@@ -64,24 +63,15 @@ constexpr auto fggl_ogl_source(GLenum source) -> const char * {
constexpr auto static fggl_ogl_type(GLenum type) -> const char * {
switch (type) {
case GL_DEBUG_TYPE_ERROR: return "Error";
break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "Deprecated Behaviour";
break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "Undefined Behaviour";
break;
case GL_DEBUG_TYPE_PERFORMANCE: return "Performance";
break;
case GL_DEBUG_TYPE_PORTABILITY: return "Portability";
break;
case GL_DEBUG_TYPE_MARKER: return "Marker";
break;
case GL_DEBUG_TYPE_PUSH_GROUP: return "Push Group";
break;
case GL_DEBUG_TYPE_POP_GROUP: return "Pop Group";
break;
default:
case GL_DEBUG_TYPE_OTHER: return "Other";
break;
}
assert(false);
return "unknown";
......@@ -92,7 +82,7 @@ PRAGMA_DIAGNOSTIC_PUSH
constexpr const char *OGL_LOG_FMT{"[GL] {}, {}: [{}]: {}"};
void static GLAPIENTRY fggl_ogl_to_spdlog(GLenum source,
void static GLAPIENTRY fggl_ogl_log(GLenum source,
GLenum type,
unsigned int msgId,
GLenum severity,
......@@ -139,13 +129,73 @@ namespace fggl::gfx {
using data::Mesh2D;
using data::Vertex2D;
OpenGL4Backend::OpenGL4Backend(data::Storage *storage, gui::FontLibrary *fonts)
: fggl::gfx::Graphics(), m_canvasPipeline(INVALID_SHADER_ID), m_storage(storage) {
// initialise GLEW, or fail
// FIXME this binds the graphics stack to GLFW :'(
int version = gladLoadGLLoader((GLADloadproc) glfwGetProcAddress);
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) {
printf("Failed to initialize OpenGL context\n");
debug::error("Failed to initialize OpenGL context\n");
return;
}
// OpenGL debug Support
......@@ -155,7 +205,7 @@ namespace fggl::gfx {
debug::info("enabling OpenGL debug output");
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(fggl_ogl_to_spdlog, nullptr);
glDebugMessageCallback(fggl_ogl_log, nullptr);
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
}
......@@ -165,23 +215,22 @@ namespace fggl::gfx {
// 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 == INVALID_SHADER_ID) {
if (m_canvasPipeline == nullptr) {
debug::error("failed to load shader2D - using fallback");
m_canvasPipeline = m_cache->get(ogl4::FALLBACK_CANVAS_PIPELINE);
}
// FIXME this should not be hard-coded, it should be part of the scene loading process
m_cache->load(ShaderConfig::named("phong"));
m_cache->load(ShaderConfig::named("redbook/lighting"));
m_cache->load(ShaderConfig::named("redbook/debug"));
// 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());
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) {
......@@ -199,12 +248,12 @@ namespace fggl::gfx {
return;
}
m_canvasRenderer->render(m_canvasPipeline, paint);
m_canvasRenderer->render(*m_canvasPipeline, paint);
}
void OpenGL4Backend::drawScene(entity::EntityManager &world) {
void OpenGL4Backend::drawScene(entity::EntityManager &world, bool debugMode) {
if (m_modelRenderer) {
m_modelRenderer->render(world);
m_modelRenderer->render(world, debugMode);
}
if (m_debugRenderer) {
......
......@@ -17,333 +17,340 @@
#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::compileShaderFromSource(const std::string &source, GLuint sid) {
// upload and compile shader
const char *src = source.c_str();
glShaderSource(sid, 1, &src, nullptr);
glCompileShader(sid);
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) {
// check it worked
GLint compiled = GL_FALSE;
glGetShaderiv(sid, GL_COMPILE_STATUS, &compiled);
if (compiled == GL_FALSE) {
GLint maxLength = 0;
glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &maxLength);
char *infoLog = new char[maxLength];
glGetShaderInfoLog(sid, maxLength, &maxLength, infoLog);
GLint maxLength = 0;
glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &maxLength);
char *infoLog = new char[maxLength];
glGetShaderInfoLog(sid, maxLength, &maxLength, infoLog);
spdlog::warn("could not compile shader source: {}", infoLog);
delete[] infoLog;
return false;
spdlog::warn("could not compile shader source: {}", infoLog);
delete[] infoLog;
return false;
}
return true;
}
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;
}
bool ShaderCache::readAndCompileShader(const std::string &filename, GLuint shader) {
std::string source;
bool result = m_storage->load(fggl::data::Data, filename, &source);
if (!result) {
spdlog::warn("could not load shader source from disk: {}", filename.c_str());
return false;
return compileShaderFromSource(source, shader);
}
return compileShaderFromSource(source, shader);
}
ShaderCache::ShaderCache(fggl::data::Storage *storage) : m_storage(storage), m_shaders(), m_binary(true) {
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_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();
}
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");
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);
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 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 {
bool ShaderCache::loadFromDisk(GLuint pid, const std::string &pipelineName) {
BinaryCache cache;
auto fname = "shader_" + pipelineName + ".bin";
bool status = m_storage->load(fggl::data::Cache, fname, &cache);
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;
}
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;
}
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);
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 fname = "shader_" + pipelineName + ".bin";
m_storage->save(fggl::data::Cache, fname, &cache);
}
GLuint ShaderCache::getOrLoad(const ShaderConfig &config) {
try {
return m_shaders.at(config.name);
} catch (std::out_of_range &e) {
return load(config);
auto ShaderCache::getOrLoad(const ShaderConfig &config) -> ShaderCache::ShaderPtr {
try {
return m_shaders.at(config.name);
} catch (std::out_of_range &e) {
return load(config);
}
}
}
GLuint ShaderCache::get(const std::string &name) {
return m_shaders.at(name);
}
auto ShaderCache::get(const std::string &name) -> ShaderCache::ShaderPtr {
auto itr = m_shaders.find(name);
if ( itr != m_shaders.end() ){
return itr->second;
}
return nullptr;
}
GLuint ShaderCache::load(const ShaderConfig &config) {
auto ShaderCache::load(const ShaderConfig &config) -> ShaderCache::ShaderPtr {
spdlog::debug("starting shader program generation for {}", config.name);
spdlog::debug("starting shader program generation for {}", config.name);
GLuint pid = glCreateProgram();
GLuint pid = glCreateProgram();
if (m_binary) {
// if we have support for shader cache, give that a go
bool worked = loadFromDisk(pid, config.name);
if (worked) {
auto shader = std::make_shared<ogl::Shader>(pid);
m_shaders[config.name] = shader;
return shader;
}
if (m_binary) {
// if we have support for shader cache, give that a go
bool worked = loadFromDisk(pid, config.name);
if (worked) {
m_shaders[config.name] = pid;
return pid;
spdlog::debug("could not use cached shader for '{}', doing full compile.", config.name);
}
spdlog::debug("could not use cached shader for '{}', doing full compile.", config.name);
}
// TODO actual shader loading
GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
readAndCompileShader(config.vertex, vertShader);
glAttachShader(pid, vertShader);
// TODO actual shader loading
GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
readAndCompileShader(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);
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);
}
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);
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) {
// 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
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);
}
// cleanup
glDeleteProgram(pid);
glDeleteShader(vertShader);
glDeleteShader(fragShader);
if (config.hasGeom) {
glDeleteShader(geomShader);
return nullptr;
}
return INVALID_SHADER_ID;
}
if (m_binary) {
saveToDisk(pid, config.name);
}
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 {
GLuint ShaderCache::load(const ShaderSources &sources, bool allowBinaryCache) {
spdlog::debug("starting shader program generation for {}", sources.name);
spdlog::debug("starting shader program generation for {}", sources.name);
GLuint pid = glCreateProgram();
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;
}
if (m_binary && allowBinaryCache) {
// if we have support for shader cache, give that a go
bool worked = loadFromDisk(pid, sources.name);
if (worked) {
m_shaders[sources.name] = pid;
return pid;
spdlog::debug("could not use cached shader for '{}', doing full compile.", sources.name);
}
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);
// 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 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);
}
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);
glLinkProgram(pid);
glDetachShader(pid, vertShader);
glDetachShader(pid, fragShader);
if (geomShader != INVALID_SHADER_ID) {
glDetachShader(pid, geomShader);
}
if (geomShader != INVALID_SHADER_ID) {
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) {
// 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());
// 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);
}
// cleanup
glDeleteProgram(pid);
glDeleteShader(vertShader);
glDeleteShader(fragShader);
if (geomShader != INVALID_SHADER_ID) {
glDeleteShader(geomShader);
return nullptr;
}
return INVALID_SHADER_ID;
}
if (m_binary && allowBinaryCache) {
saveToDisk(pid, sources.name);
}
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];
}
// update the cache and return
m_shaders[sources.name] = pid;
return pid;
}
void ShaderCache::cacheSave(GLuint pid, BinaryCache *cache) {
GLsizei length;
glGetProgramiv(pid, GL_PROGRAM_BINARY_LENGTH, &length);
void ShaderCache::cacheSave(GLuint pid, BinaryCache *cache) {
GLsizei length;
glGetProgramiv(pid, GL_PROGRAM_BINARY_LENGTH, &length);
cache->data = std::malloc(length);
cache->size = length;
cache->data = std::malloc(length);
cache->size = length;
glGetProgramBinary(pid, length, &cache->size, &cache->format, cache->data);
}
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);
bool ShaderCache::cacheLoad(GLuint pid, const BinaryCache *cache) {
if (!m_binary) {
return false;
// check it loaded correctly
GLint status = GL_FALSE;
glGetProgramiv(pid, GL_LINK_STATUS, &status);
return status == GL_TRUE;
}
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);
}
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<>
bool fggl::data::fggl_deserialize(std::filesystem::path &data, fggl::gfx::BinaryCache *out) {
auto f =
#ifdef _MSC_VER
_wfopen(data.c_str(), L"r");
#else
std::fopen(data.c_str(), "r");
#endif
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;
}
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;
}
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->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);
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;
}
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;
}
std::fclose(f);
return result;
}
#include <iostream>
#include <fstream>
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>()));
......@@ -351,10 +358,10 @@ bool fggl::data::fggl_deserialize(std::filesystem::path &data, std::string *out)
}
template<>
bool fggl::data::fggl_serialize(std::filesystem::path &data, const fggl::gfx::BinaryCache *out) {
auto fggl::data::fggl_serialize(std::filesystem::path &data, const fggl::gfx::BinaryCache *out) -> bool {
// try and write
auto f =
auto *f =
#ifdef _MSC_VER
_wfopen( data.c_str(), L"w");
#else
......
......@@ -44,7 +44,7 @@ namespace fggl::gfx::ogl {
other.m_obj = 0;
}
VertexArray &VertexArray::operator=(VertexArray &&other) {
auto VertexArray::operator=(VertexArray &&other) -> VertexArray & {
if (this != &other) {
release();
std::swap(m_obj, other.m_obj);
......@@ -115,7 +115,7 @@ namespace fggl::gfx::ogl {
#endif
}
void VertexArray::drawElements(const ElementBuffer &buff, Primative drawType, std::size_t size) {
void VertexArray::drawElements(const ElementBuffer &buff, Primitive drawType, std::size_t size) {
bind();
#ifndef FGGL_I_BOUND
......@@ -130,7 +130,7 @@ namespace fggl::gfx::ogl {
#endif
}
void VertexArray::draw(Primative drawType, int first, std::size_t count) {
void VertexArray::draw(Primitive drawType, int first, std::size_t count) {
glDrawArrays((GLenum) drawType, first, count);
}
......
......@@ -6,5 +6,6 @@ target_sources(fggl
canvas.cpp
models.cpp
debug.cpp
meshes.cpp
module.cpp
)
......@@ -114,7 +114,7 @@ namespace fggl::gfx::ogl4 {
glBindVertexArray(0);
}
void CanvasRenderer::renderShapes(const gfx::Paint &paint, GLuint shader) {
void CanvasRenderer::renderShapes(const gfx::Paint &paint, ogl::Shader& shader) {
data::Mesh2D mesh;
convert_to_mesh(paint, mesh);
......@@ -132,13 +132,12 @@ namespace fggl::gfx::ogl4 {
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
// FIXME: this should be abstracted into the shader class
glUseProgram(shader);
auto projMat = glm::ortho(m_bounds.left, m_bounds.right, m_bounds.bottom, m_bounds.top);
glUniformMatrix4fv(glGetUniformLocation(shader, "projection"), 1, GL_FALSE,
glm::value_ptr(projMat));
shader.use();
shader.setUniformMtx(shader.uniform("projection"), projMat);
m_vao.drawElements(m_indexList, ogl::Primative::TRIANGLE, mesh.indexList.size());
// draw elements
m_vao.drawElements(m_indexList, ogl::Primitive::TRIANGLE, mesh.indexList.size());
// cleanup
glUseProgram(0);
......@@ -147,29 +146,28 @@ namespace fggl::gfx::ogl4 {
}
// slow version
void CanvasRenderer::renderText(const Paint &paint, GLuint shader) {
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->getFont("LiberationSans-Regular.ttf");
std::shared_ptr<gui::FontFace> face = m_fonts->getDefaultFont();
if (face == nullptr) {
// we don't know about that font...
return;
}
// setup the shader
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));
// 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();
// setup the openGL state we expect for rendering
// set up the openGL state we expect for rendering
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
......@@ -181,14 +179,15 @@ namespace fggl::gfx::ogl4 {
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) {
ogl::Image img{};
img.format = ogl::ImageFormat::R;
img.type = ogl::PixelFormat::UNSIGNED_BYTE;
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
face->texture(letter, img.size.x, img.size.y, &img.data);
m_fontTex.setData(ogl::InternalImageFormat::Red, img);
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);
......@@ -196,32 +195,33 @@ namespace fggl::gfx::ogl4 {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
m_fontTex.bind(1);
glUniform1i(glGetUniformLocation(shader, "tex"), 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);
float xPos = penPos.x + metrics.bearing.x;
float yPos = (penPos.y - metrics.bearing.y);
float w = metrics.size.x;
float h = metrics.size.y;
const math::vec3 texCol{1.0f, 1.0f, 1.0f};
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}, texCol, {0.0F, 1.0F}},
{{xPos, yPos}, texCol, {0.0F, 0.0F}},
{{xPos + w, yPos}, texCol, {1.0F, 0.0F}},
{{xPos, yPos + h}, texCol, {0.0F, 1.0F}},
{{xPos + w, yPos}, texCol, {1.0F, 0.0F}},
{{xPos + w, yPos + h}, texCol, {1.0F, 1.0F}},
{{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::Primative::TRIANGLE, 0, verts.size());
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);
......@@ -279,7 +279,7 @@ namespace fggl::gfx::ogl4 {
glUseProgram( 0 );
}*/
void CanvasRenderer::render(GLuint shader, const gfx::Paint &paint) {
void CanvasRenderer::render(ogl::Shader& shader, const gfx::Paint &paint) {
renderShapes(paint, shader);
renderText(paint, shader);
}
......
......@@ -21,13 +21,14 @@
#include "fggl/gfx/ogl4/debug.hpp"
#include <cassert>
#include <utility>
namespace fggl::gfx::ogl4 {
DebugRenderer::DebugRenderer(GLuint shader) :
mvpMatrix(1.0f),
m_lineShader(shader),
m_lineShaderMVP(m_lineShader.uniform("u_MvpMatrix")) {
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);
......@@ -42,8 +43,8 @@ namespace fggl::gfx::ogl4 {
assert(count > 0 && count <= DEBUG_DRAW_VERTEX_BUFFER_SIZE);
m_lineVao.bind();
m_lineShader.use();
m_lineShader.setUniformMtx(m_lineShaderMVP, mvpMatrix);
m_lineShader->use();
m_lineShader->setUniformMtx(m_lineShaderMVP, mvpMatrix);
if (depthEnabled) {
glEnable(GL_DEPTH_TEST);
......@@ -54,7 +55,7 @@ namespace fggl::gfx::ogl4 {
m_lineVbo.bind();
m_lineVbo.replace<dd::DrawVertex>(count, lines);
m_lineVao.draw(ogl::Primative::LINE, 0, count);
m_lineVao.draw(ogl::Primitive::LINE, 0, count);
glUseProgram(0);
glBindVertexArray(0);
......
/*
* 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;
}
}
......@@ -17,17 +17,19 @@
//
#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"
#include <spdlog/spdlog.h>
namespace fggl::gfx::ogl4 {
static std::shared_ptr<ogl::ArrayBuffer> setupArrayBuffer(std::shared_ptr<ogl::VertexArray> &vao,
std::vector<data::Vertex> &data) {
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);
......@@ -42,18 +44,19 @@ namespace fggl::gfx::ogl4 {
return buff;
}
static std::shared_ptr<ogl::ElementBuffer> setupIndexBuffer(std::shared_ptr<ogl::VertexArray> &vao,
std::vector<uint32_t> &data) {
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 setupComponent(StaticModel &modelComp, std::shared_ptr<ogl::Shader> &shader, data::Mesh &mesh) {
static void setup_component(StaticModel &modelComp, std::shared_ptr<ogl::Shader> &shader, data::Mesh &mesh) {
auto vao = std::make_shared<ogl::VertexArray>();
auto meshBuffer = setupArrayBuffer(vao, mesh.vertexList());
auto elementBuffer = setupIndexBuffer(vao, mesh.indexList());
auto meshBuffer = setup_array_buffer(vao, mesh.vertexList());
auto elementBuffer = setup_index_buffer(vao, mesh.indexList());
// set up the element attributes
modelComp.vao = vao;
......@@ -61,186 +64,184 @@ namespace fggl::gfx::ogl4 {
modelComp.elements = elementBuffer;
modelComp.pipeline = shader;
modelComp.elementCount = mesh.indexCount();
modelComp.drawType = ogl::Primative::TRIANGLE;
modelComp.drawType = ogl::Primitive::TRIANGLE;
}
void StaticModelRenderer::resolveModels(entity::EntityManager &world) {
// FIXME: this needs something reactive or performance will suck.
auto renderables = world.find<data::StaticMesh>();
for (const auto &renderable : renderables) {
auto *currModel = world.tryGet<StaticModel>(renderable);
if (currModel != nullptr) {
continue;
}
auto &meshComp = world.get<data::StaticMesh>(renderable);
auto &modelComp = world.add<StaticModel>(renderable);
auto shader = m_phong;
try {
shader = std::make_shared<ogl::Shader>(m_shaders->get(meshComp.pipeline));
} catch (std::out_of_range &e) {
debug::log(debug::Level::warning, "Could not find shader: {}", meshComp.pipeline);
}
auto StaticModelRenderer::uploadMesh(assets::AssetID guid, const data::Mesh &mesh, bool allowCache) -> StaticModel* {
assert( m_assets != nullptr );
setupComponent(modelComp, shader, meshComp.mesh);
debug::log(debug::Level::info, "Added static mesh to {}", (uint64_t) renderable);
// 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);
}
// terrain
auto terrain = world.find<data::HeightMap>();
for (auto &renderable : terrain) {
auto currModel = world.tryGet<StaticModel>(renderable);
if (currModel != nullptr) {
continue;
}
// 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);
}
auto &heightmap = world.get<data::HeightMap>(renderable);
data::Mesh heightMapMesh{};
data::generateHeightMesh(heightmap, heightMapMesh);
return modelAsset;
}
auto &modelComp = world.add<StaticModel>(renderable);
setupComponent(modelComp, m_phong, heightMapMesh);
/*MeshData* StaticModelRenderer::uploadMesh(assets::AssetGUID guid, const mesh::Mesh3D &mesh, bool allowCache) {
assert( m_assets != nullptr );
// we know this is a triangle strip with a restart vertex...
// FIXME the model should be telling us this...
modelComp.drawType = ogl::Primative::TRIANGLE_STRIP;
modelComp.restartIndex = heightMapMesh.restartVertex;
// 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);
}
// no active model, we need to resolve/load one.
//spdlog::info("looks like heightmap, {} needs a static mesh", renderable);
// 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);
}
}
void StaticModelRenderer::renderModelsForward(const entity::EntityManager &world) {
return modelAsset;
}*/
// fetch cameras we will need to render with
auto cameras = world.find<gfx::Camera>();
auto StaticModelRenderer::uploadMesh2(const assets::AssetID& meshName, const data::Mesh &mesh) -> StaticModelGPU* {
assert( m_assets != nullptr );
// if there are no cameras, we can't do anything...
if (cameras.empty()) {
spdlog::warn("asked to render static models, but there were no cameras");
return;
if ( m_assets->has(meshName) ) {
// we've already uploaded it...
return m_assets->get<StaticModelGPU>(meshName);
}
// 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
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;
// enable required OpenGL state
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
return m_assets->set(meshName, modelAsset);
}
// enable depth testing
glEnable(GL_DEPTH_TEST);
#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 ) {
// set-up camera matrices
const auto &camTransform = world.get<math::Transform>(cameraEnt);
const auto &camComp = world.get<gfx::Camera>(cameraEnt);
// check if this entity already has a mesh...
const auto* currModel = world.tryGet<StaticMesh>(mesher);
if ( currModel != nullptr ) {
continue;
}
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());
// 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...");
}
// TODO lighting needs to not be this...
math::vec3 lightPos{0.0f, 10.0f, 0.0f};
std::shared_ptr<ogl::Shader> shader = nullptr;
auto& entityMesh = world.add<StaticMesh>(mesher);
entityMesh.pipeline = shaders->get( rawMesh.pipeline );
entityMesh.mesh = upload_mesh( rawMesh.mesh, manager );
auto renderables = world.find<StaticModel>();
for (const auto &entity : renderables) {
}
}
// ensure that the model pipeline actually exists...
const auto &model = world.get<StaticModel>(entity);
if (model.pipeline == nullptr) {
spdlog::warn("shader was null, aborting render");
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;
}
// check if we switched shaders
if (shader != model.pipeline) {
// new shader - need to re-send the view and projection matrices
shader = model.pipeline;
shader->use();
if (shader->hasUniform("projection")) {
shader->setUniformMtx(shader->uniform("view"), viewMatrix);
shader->setUniformMtx(shader->uniform("projection"), projectionMatrix);
}
// 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...");
}
// set model transform
const auto &transform = world.get<math::Transform>(entity);
shader->setUniformMtx(shader->uniform("MVPMatrix"), projectionMatrix * viewMatrix * transform.model());
shader->setUniformMtx(shader->uniform("MVMatrix"), viewMatrix * transform.model());
auto normalMatrix = glm::mat3(glm::transpose(inverse(transform.model())));
shader->setUniformMtx(shader->uniform("NormalMatrix"), normalMatrix);
// setup lighting mode
if (shader->hasUniform("lights[0].isEnabled")) {
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});
}
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);
}
}
// material detection with fallback
const auto &material = world.get<PhongMaterial>(entity);
void StaticModelRenderer::resolveModels(entity::EntityManager &world) {
// new mesh formats
setup_meshes(world, m_shaders, m_assets);
setup_multi_meshes(world, m_shaders, m_assets);
if (shader->hasUniform("materials[0].ambient")) {
shader->setUniformF(shader->uniform("materials[0].emission"), material.emission);
shader->setUniformF(shader->uniform("materials[0].ambient"), material.ambient);
shader->setUniformF(shader->uniform("materials[0].diffuse"), material.diffuse);
shader->setUniformF(shader->uniform("materials[0].specular"), material.specular);
shader->setUniformF(shader->uniform("materials[0].shininess"), material.shininess);
// terrain
auto terrain = world.find<data::HeightMap>();
for (const auto &renderable : terrain) {
auto *currModel = world.tryGet<StaticModel>(renderable);
if (currModel != nullptr) {
continue;
}
if (shader->hasUniform("lightPos")) {
shader->setUniformF(shader->uniform("lightPos"), lightPos);
}
auto &heightmap = world.get<data::HeightMap>(renderable);
data::Mesh heightMapMesh{};
data::generateHeightMesh(heightmap, heightMapMesh);
auto vao = model.vao;
vao->bind();
auto &modelComp = world.add<StaticModel>(renderable);
setup_component(modelComp, m_phong, heightMapMesh);
model.vertexData->bind();
if (model.restartIndex != NO_RESTART_IDX) {
glEnable(GL_PRIMITIVE_RESTART);
glPrimitiveRestartIndex(model.restartIndex);
}
// 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;
auto *elements = model.elements.get();
vao->drawElements(*elements, model.drawType, model.elementCount);
if (model.restartIndex != NO_RESTART_IDX) {
glDisable(GL_PRIMITIVE_RESTART);
}
// 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);
}
}
}
......
......@@ -18,8 +18,10 @@
#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 {
......@@ -29,51 +31,123 @@ namespace fggl::gfx {
constexpr const char *SHAPE_SPHERE{"sphere"};
constexpr const char *SHAPE_BOX{"box"};
static void process_shape(const YAML::Node &node, data::Mesh &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);
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) {
auto &meshComp = manager.add<data::StaticMesh>(id);
meshComp.pipeline = spec.get<std::string>("pipeline", "");
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")) {
// procedural mesh
data::Mesh mesh;
if (spec.config["shape"].IsSequence()) {
for (const auto &node : spec.config["shape"]) {
process_shape(node, mesh);
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
}
} else {
process_shape(spec.config["shape"], mesh);
//assetService->set(shapeName, meshTmp);
}
mesh.removeDups();
meshComp.mesh = mesh;
// 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) {
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);
......@@ -81,22 +155,40 @@ namespace fggl::gfx {
mat.shininess = spec.get<float>("ambient", gfx::DEFAULT_SHININESS);
}
void attach_light(const entity::ComponentSpec &spec, entity::EntityManager &manager, const entity::EntityID &id) {
auto &light = manager.add<gfx::Light>(id);
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);
}
bool OpenGL4::factory(modules::ModuleService service, modules::Services &services) {
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>();
services.bind<WindowGraphics, ogl4::WindowGraphics>(storage, 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::Light::guid, attach_light);
entityFactory->bind(gfx::DirectionalLight::guid, attach_light_directional);
entityFactory->bind(gfx::PointLight::guid, attach_light_point);
return true;
}
......
......@@ -20,8 +20,8 @@
namespace fggl::gfx::ogl4 {
Graphics *WindowGraphics::create(display::Window &window) {
return new OpenGL4Backend(m_storage, m_fonts);
auto WindowGraphics::create(display::Window &window) -> Graphics * {
return new OpenGL4Backend(m_storage, m_fonts, m_assets, (GlFunctionLoader)glfwGetProcAddress);
}
}
\ No newline at end of file