Commit c9a48655 authored by Philip Trettner's avatar Philip Trettner

Added GlfwApp first version

parent a1ea0c22
......@@ -47,3 +47,12 @@ if (TARGET assimp)
else()
message(STATUS "target 'assimp' not found, disabling glow-extras-assimp")
endif()
# GLFW App
# requires 'aion'
if (TARGET aion)
add_subdirectory(glfw)
target_link_libraries(glow-extras PUBLIC glow-extras-glfw)
else()
message(STATUS "target 'aion' not found, disabling glow-extras-glfw")
endif()
cmake_minimum_required(VERSION 3.0)
file(GLOB_RECURSE SOURCE_FILES "*.cc")
file(GLOB_RECURSE HEADER_FILES "*.hh")
add_library(glow-extras-glfw ${GLOW_LINK_TYPE} ${SOURCE_FILES} ${HEADER_FILES})
target_include_directories(glow-extras-glfw PUBLIC ./)
target_compile_options(glow-extras-glfw PRIVATE ${GLOW_EXTRAS_DEF_OPTIONS})
target_link_libraries(glow-extras-glfw PUBLIC
glow
aion
AntTweakBar
fmt
glow-extras-timing
glow-extras-material
glow-extras-pipeline
glow-extras-camera
)
#include "GlfwApp.hh"
#include <AntTweakBar.h>
#include <cassert>
#include <iostream>
#include <glow/gl.hh>
#include <chrono>
#include <GLFW/glfw3.h>
#include <fmt/format.hh>
#include <aion/ActionAnalyzer.hh>
#include <glow/glow.hh>
#include <glow/common/log.hh>
#include <glow/common/str_utils.hh>
#include <glow/util/DefaultShaderParser.hh>
#include <glow/objects/PrimitiveQuery.hh>
#include <glow/objects/OcclusionQuery.hh>
#include <glow/objects/TimerQuery.hh>
#include <glow-extras/camera/GenericCamera.hh>
#include <glow-extras/pipeline/RenderingPipeline.hh>
using namespace glow;
using namespace glow::glfw;
void GlfwApp::setTitle(const std::string &title)
{
mTitle = title;
if (mWindow)
glfwSetWindowTitle(mWindow, title.c_str());
}
void GlfwApp::setClipboardString(const std::string &s)
{
glfwSetClipboardString(mWindow, s.c_str());
}
std::string GlfwApp::getClipboardString() const
{
auto s = glfwGetClipboardString(mWindow);
return s ? s : "";
}
void GlfwApp::init()
{
if (mUseDefaultRendering)
{
// include paths (BEFORE pipeline init)
DefaultShaderParser::addIncludePath(util::pathOf(__FILE__) + "/../../../pipeline/shader");
DefaultShaderParser::addIncludePath(util::pathOf(__FILE__) + "/../../../material/shader");
mCamera = std::make_shared<camera::GenericCamera>();
mPipeline = pipeline::RenderingPipeline::create(mCamera);
}
mPrimitiveQuery = std::make_shared<PrimitiveQuery>();
mOcclusionQuery = std::make_shared<OcclusionQuery>();
mRenderStartQuery = std::make_shared<TimerQuery>();
mRenderEndQuery = std::make_shared<TimerQuery>();
}
void GlfwApp::update(float elapsedSeconds)
{
}
void GlfwApp::render(float elapsedSeconds)
{
if (mUseDefaultRendering)
{
mPipeline->render([this, elapsedSeconds](const pipeline::RenderPass &pass)
{
renderPass(pass, elapsedSeconds);
});
}
}
void GlfwApp::renderPass(const pipeline::RenderPass &pass, float elapsedSeconds)
{
}
void GlfwApp::onResize(int w, int h)
{
if (mUseDefaultRendering)
{
mCamera->resize(w, h);
mPipeline->resize(w, h);
}
}
void GlfwApp::onClose()
{
}
bool GlfwApp::onKey(int key, int scancode, int action, int mods)
{
if (TwEventKeyGLFW(mWindow, key, scancode, action, mods))
return true;
return false;
}
bool GlfwApp::onChar(unsigned int codepoint, int mods)
{
if (TwEventCharGLFW(mWindow, codepoint))
return true;
return false;
}
bool GlfwApp::onMousePosition(double x, double y)
{
if (TwEventMousePosGLFW(mWindow, x, y))
return true;
return false;
}
bool GlfwApp::onMouseButton(double x, double y, int button, int action, int mods)
{
if (TwEventMouseButtonGLFW(mWindow, button, action, mods))
return true;
return false;
}
bool GlfwApp::onMouseScroll(double sx, double sy)
{
if (TwEventMouseWheelGLFW(mWindow, sx, sy))
return true;
return false;
}
bool GlfwApp::onMouseEnter()
{
return false;
}
bool GlfwApp::onMouseExit()
{
return false;
}
bool GlfwApp::onFocusGain()
{
return false;
}
bool GlfwApp::onFocusLost()
{
return false;
}
bool GlfwApp::onFileDrop(const std::vector<std::string> &files)
{
return false;
}
static std::string thousandSep(size_t val)
{
auto s = std::to_string(val);
auto l = s.size();
while (l > 3)
{
s = s.substr(0, l - 3) + "'" + s.substr(l - 3);
l -= 3;
}
return s;
}
int GlfwApp::run(int argc, char *argv[])
{
static GlfwApp *currApp = nullptr;
assert(currApp == nullptr && "cannot run multiple apps simulatenously");
currApp = this;
assert(mWindow == nullptr);
assert(mTweakbar == nullptr);
// Taken from http://www.glfw.org/documentation.html
// Initialize the library
if (!glfwInit())
{
std::cerr << "Unable to initialize GLFW" << std::endl;
return -1;
}
// Request debug context
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
// Create a windowed mode window and its OpenGL context
mWindow = glfwCreateWindow(mWindowWidth, mWindowHeight, mTitle.c_str(), NULL, NULL);
if (!mWindow)
{
std::cerr << "Unable to create a GLFW window" << std::endl;
glfwTerminate();
return -1;
}
// Make the window's context current
glfwMakeContextCurrent(mWindow);
// Initialize GLOW
if (!glow::initGLOW())
{
std::cerr << "Unable to initialize GLOW" << std::endl;
return -1;
}
// restore ogl state
glow::restoreDefaultOpenGLState();
// anttweakbar
TwInit(TW_OPENGL_CORE, NULL); // for core profile
TwWindowSize(mWindowWidth, mWindowHeight);
mTweakbar = TwNewBar("Tweakbar");
// input callbacks
{
glfwSetKeyCallback(mWindow, [](GLFWwindow *win, int key, int scancode, int action, int mods)
{
currApp->onKey(key, scancode, action, mods);
});
glfwSetCharModsCallback(mWindow, [](GLFWwindow *win, unsigned int codepoint, int mods)
{
currApp->onChar(codepoint, mods);
});
glfwSetMouseButtonCallback(mWindow, [](GLFWwindow *win, int button, int action, int mods)
{
currApp->onMouseButton(currApp->mMouseX, currApp->mMouseY, button, action, mods);
});
glfwSetCursorEnterCallback(mWindow, [](GLFWwindow *win, int entered)
{
if (entered)
currApp->onMouseEnter();
else
currApp->onMouseExit();
});
glfwSetCursorPosCallback(mWindow, [](GLFWwindow *win, double x, double y)
{
currApp->mMouseX = x;
currApp->mMouseY = y;
currApp->onMousePosition(x, y);
});
glfwSetScrollCallback(mWindow, [](GLFWwindow *win, double sx, double sy)
{
currApp->onMouseScroll(sx, sy);
});
glfwSetFramebufferSizeCallback(mWindow, [](GLFWwindow *win, int w, int h)
{
currApp->mWindowWidth = w;
currApp->mWindowHeight = h;
currApp->onResize(w, h);
TwWindowSize(w, h);
});
glfwSetWindowFocusCallback(mWindow, [](GLFWwindow *win, int focused)
{
if (focused)
currApp->onFocusGain();
else
currApp->onFocusLost();
});
glfwSetDropCallback(mWindow, [](GLFWwindow *win, int count, const char **paths)
{
std::vector<std::string> files;
for (auto i = 0; i < count; ++i)
files.push_back(paths[i]);
currApp->onFileDrop(files);
});
}
// init app
init();
glfwGetFramebufferSize(mWindow, &mWindowWidth, &mWindowHeight);
onResize(mWindowWidth, mWindowHeight);
// Loop until the user closes the window
int frames = 0;
double lastTime = glfwGetTime();
double lastStatsTime = lastTime;
double timeAccum = 0.000001;
double renderTimestep = 1.0 / mUpdateRate;
mCurrentTime = 0.0;
size_t primitives = 0;
size_t fragments = 0;
double gpuTime = 0;
double cpuTime = 0;
while (!glfwWindowShouldClose(mWindow))
{
// update cursor mode
switch (mCursorMode)
{
case CursorMode::Normal:
glfwSetInputMode(mWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
break;
case CursorMode::Hidden:
glfwSetInputMode(mWindow, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
break;
case CursorMode::Disabled:
glfwSetInputMode(mWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
break;
}
// vsync
glfwSwapInterval(mVSync ? 1 : 0);
// Poll for and process events
glfwPollEvents();
// Check for resize
int width, height;
glfwGetFramebufferSize(mWindow, &width, &height);
glViewport(0, 0, width, height);
// Update
auto maxTicks = mMaxFrameSkip;
while (timeAccum > 0)
{
if (maxTicks-- < 0)
{
timeAccum = 0.0;
glow::warning() << "Too many updates queued, frame skip of " << timeAccum << " secs";
break;
}
auto dt = 1.0 / mUpdateRate;
auto cpuStart = glfwGetTime();
update(dt);
cpuTime += glfwGetTime() - cpuStart;
timeAccum -= dt;
mCurrentTime += dt;
}
// Render here
{
if (mQueryStats)
{
mPrimitiveQuery->begin();
mOcclusionQuery->begin();
mRenderStartQuery->saveTimestamp();
}
render(renderTimestep);
if (mQueryStats)
{
mPrimitiveQuery->end();
mOcclusionQuery->end();
mRenderEndQuery->saveTimestamp();
}
}
// draw the tweak bar(s)
if (mDrawTweakbars)
TwDraw();
// Swap front and back buffers
glfwSwapBuffers(mWindow);
// timing
auto now = glfwGetTime();
renderTimestep = now - lastTime;
timeAccum += now - lastTime;
lastTime = now;
++frames;
if (mQueryStats)
{
primitives += mPrimitiveQuery->getResult64();
fragments += mOcclusionQuery->getResult64();
gpuTime += TimerQuery::toSeconds(mRenderEndQuery->getSavedTimestamp() - mRenderStartQuery->getSavedTimestamp());
}
if (mOutputStatsInterval > 0 && lastTime > lastStatsTime + mOutputStatsInterval)
{
double fps = frames / (lastTime - lastStatsTime);
if (mQueryStats)
glow::info() << fmt::format("FPS: {:.1f}, CPU: {:.1f} ms, GPU: {:.1f} ms, Primitives: {}, Frags: {}",
fps, cpuTime / frames * 1000., gpuTime / frames * 1000.,
thousandSep(primitives / frames), thousandSep(fragments / frames));
else
glow::info() << fmt::format("FPS: {:.1f}, CPU: {:.1f} ms", fps, cpuTime / frames * 1000.);
lastStatsTime = lastTime;
frames = 0;
primitives = 0;
fragments = 0;
gpuTime = 0;
cpuTime = 0;
}
}
// cleanup
{
onClose();
TwTerminate();
glfwTerminate();
// aion dump
if (mDumpTimingsOnShutdown)
aion::ActionAnalyzer::dumpSummary(std::cout, false);
}
return 0;
}
#pragma once
#include <string>
#include <vector>
#include <glm/vec2.hpp>
#include <glow/common/property.hh>
#include <glow/common/shared.hh>
#include <glow/fwd.hh>
struct GLFWwindow;
struct CTwBar;
typedef struct CTwBar TwBar; // structure CTwBar is not exposed.
namespace glow
{
namespace pipeline
{
struct RenderPass;
GLOW_SHARED(class, RenderingPipeline);
}
namespace camera
{
GLOW_SHARED(class, GenericCamera);
}
namespace glfw
{
enum class CursorMode
{
/// normal behavior
Normal,
/// normal behavior but hardware cursor is hidden
Hidden,
/// virtual unrestricted cursor, real cursor is hidden and locked to center
Disabled,
};
/**
* @brief The GlfwApp can be used to efficiently build small sample applications based on glfw
*
* Derive your own class from GlfwApp and override the functions you need:
* - init(...): initialize and allocate all your resources and objects
* - update(...): called with a constant rate (default 60 Hz, configurable) before rendering
* - render(...): called as fast as possible (affected by vsync)
* - renderPass(...): if rendering pipeline enabled (default), default render(...) will call this (RECOMMENDED)
* - onResize(...): called when window is resized
* - onClose(...): called when app is closed
* - input: see onKey/onChar/onMouseXYZ/onFileDrop (return true if you handled the event, if base returned true, you
* should probably return as well)
* be sure to call base function unless you know what you do!
*
* Additional important functions:
* - setUpdateRate(...): set the update rate
* - window(): get the GLFW window
* - tweakbar(): get the AntTweakBar instance
* - setWindowWidth/Height(...): set initial window size before run(...)
*
* Notes:
* - if you use primitive/occlusion queries, use setQueryStats(false);
*
* Usage:
* int main(int argc, char *argv[])
* {
* MyGlfwApp app;
* return app.run(argc, argv); // automatically sets up GLOW and GLFW and everything
* }
*/
class GlfwApp
{
private:
std::string mTitle = "GLFW/GLOW Application"; ///< window title
int mUpdateRate = 60; ///< rate at which update(...) is called
int mMaxFrameSkip = 4; ///< maximum number of update(...) steps that are performed without rendering
GLFWwindow* mWindow = nullptr; ///< current GLFW window
TwBar* mTweakbar = nullptr; ///< main tweakbar window
int mWindowWidth = 640; ///< window width, only set before run!
int mWindowHeight = 480; ///< window height, only set before run!
bool mDumpTimingsOnShutdown = true; ///< if true, dumps AION timings on shutdown
double mMouseX = -1.0; ///< cursor X in pixels
double mMouseY = -1.0; ///< cursor Y in pixels
CursorMode mCursorMode = CursorMode::Normal; ///< cursor mode
bool mDrawTweakbars = true; ///< if true, draws tweakbars
bool mVSync = true; ///< if true, enables vsync
double mOutputStatsInterval = 5.0; ///< number of seconds between stats output (0.0 for never)
bool mQueryStats = true; ///< if true, queries stats (vertices, fragments, ...)
bool mUseDefaultRendering = true; ///< if true, uses default rendering pipeline setup
double mCurrentTime = 0.0; ///< current frame time (starts with 0)
// Default graphics
private:
camera::SharedGenericCamera mCamera; ///< default camera
pipeline::SharedRenderingPipeline mPipeline; ///< default pipeline
SharedPrimitiveQuery mPrimitiveQuery; ///< nr of primitives per frame
SharedOcclusionQuery mOcclusionQuery; ///< nr of pixels per frame
SharedTimerQuery mRenderStartQuery; ///< start timestamp
SharedTimerQuery mRenderEndQuery; ///< end timestamp
public:
GLOW_PROPERTY(UpdateRate);
GLOW_PROPERTY(MaxFrameSkip);
GLOW_GETTER(Title);
GLOW_PROPERTY(WindowWidth);
GLOW_PROPERTY(WindowHeight);
GLOW_PROPERTY(DumpTimingsOnShutdown);
GLOW_PROPERTY(CursorMode);
GLOW_PROPERTY(DrawTweakbars);
GLOW_PROPERTY(VSync);
GLOW_PROPERTY(OutputStatsInterval);
GLOW_PROPERTY(QueryStats);
GLOW_PROPERTY(UseDefaultRendering);
float getCurrentTime() const { return mCurrentTime; }
double getCurrentTimeD() const { return mCurrentTime; }
GLOW_GETTER(Camera);
GLOW_GETTER(Pipeline);
void setTitle(std::string const& title);
glm::vec2 getMousePosition() const { return {mMouseX, mMouseY}; }
GLFWwindow* window() const { return mWindow; }
TwBar* tweakbar() const { return mTweakbar; }
public:
/// sets the current clipboard content
void setClipboardString(std::string const& s);
/// gets the current clipboard content
std::string getClipboardString() const;
protected:
/// Called once GLOW is initialized. Allocated your resources and init your logic here.
virtual void init();
/// Called with at 1 / getUpdateRate() Hz (timestep)
virtual void update(float elapsedSeconds);
/// Called as fast as possible for rendering (elapsedSeconds is not fixed here)
virtual void render(float elapsedSeconds);
/// When using the builtin rendering pipeline, this is called for every pass in every render step
virtual void renderPass(pipeline::RenderPass const& pass, float elapsedSeconds);
/// Called once in the beginning after (init) and whenever the window size changed
virtual void onResize(int w, int h);
/// Called at the end, when application is closed
virtual void onClose();
/// Called whenever a key is pressed
virtual bool onKey(int key, int scancode, int action, int mods);
/// Called whenever a character is entered (unicode)
virtual bool onChar(unsigned int codepoint, int mods);
/// Called whenever the mouse position changes
virtual bool onMousePosition(double x, double y);
/// Called whenever a mouse button is pressed
virtual bool onMouseButton(double x, double y, int button, int action, int mods);
/// Called whenever the mouse is scrolled
virtual bool onMouseScroll(double sx, double sy);
/// Called whenever the mouse enters the window
virtual bool onMouseEnter();
/// Called whenever the mouse leaves the window
virtual bool onMouseExit();
/// Called whenever the window gets focus
virtual bool onFocusGain();
/// Called whenever the window loses focus
virtual bool onFocusLost();
/// Called whenever files are dropped (drag'n'drop), parameters is file paths
virtual bool onFileDrop(std::vector<std::string> const& files);
public:
/// Initializes GLFW and GLOW, and runs until window is closed
int run(int argc, char* argv[]);
};
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment