Add Application / GuiApplication abstraction

A better way of handling initialization and updates

Still need to incorporate pigui handling into GuiApplication so we don't have to duplicate all of the pigui code
master
Webster Sheets 2020-03-27 17:03:48 -04:00
parent 0c67f400c9
commit 027d485e51
6 changed files with 481 additions and 0 deletions

View File

@ -252,7 +252,10 @@ if (NOT USE_SYSTEM_LIBLUA)
include_directories(contrib/lua)
endif (NOT USE_SYSTEM_LIBLUA)
add_subdirectory(src/core)
define_pioneer_library(pioneer-lib PIONEER_CXX_FILES PIONEER_HXX_FILES)
target_link_libraries(pioneer-lib PUBLIC pioneer-core)
add_subdirectory(src/lua)

160
src/core/Application.cpp Normal file
View File

@ -0,0 +1,160 @@
// Copyright © 2008-2020 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#include "Application.h"
#include "FileSystem.h"
#include "OS.h"
#include "profiler/Profiler.h"
#include "utils.h"
#include "SDL_timer.h"
#include <stdexcept>
void Application::QueueLifecycle(std::shared_ptr<Lifecycle> cycle)
{
if (!cycle)
throw std::runtime_error("Invalid Lifecycle object pushed to Application queue!");
m_queuedLifecycles.push(cycle);
}
void Application::Startup()
{
PROFILE_SCOPED()
OS::EnableBreakpad();
OS::NotifyLoadBegin();
FileSystem::Init();
// ensure the config directory exists
FileSystem::userFiles.MakeDirectory("");
#ifdef PIONEER_PROFILER
FileSystem::userFiles.MakeDirectory("profiler");
#endif
}
void Application::Shutdown()
{
FileSystem::Uninit();
}
bool Application::StartLifecycle()
{
// can't start a lifecycle if there are no more queued.
if (!m_priorityLifecycle && !m_queuedLifecycles.size())
return false;
// If we still have an active lifestyle, we don't need to queue any more.
if (m_activeLifecycle)
throw std::runtime_error("Attempt to start a new lifecycle object while one is still active!");
// Lifecycle objects returned from Lifecycle::End() take priority over queued objects
if (m_priorityLifecycle) {
m_activeLifecycle = m_priorityLifecycle;
m_priorityLifecycle = nullptr;
} else {
m_activeLifecycle = std::move(m_queuedLifecycles.front());
m_queuedLifecycles.pop();
}
m_activeLifecycle->m_application = this;
m_activeLifecycle->Start();
return true;
}
void Application::EndLifecycle()
{
// Can't end a lifecycle if we don't have one active.
if (!m_activeLifecycle)
return;
// The only time we should have a prioritized lifecycle is
// between EndLifecycle() and the next StartLifecycle().
// If it's still set, something has gone wrong.
if (m_priorityLifecycle)
throw std::runtime_error("Unable to prioritize lifecycle object due to already-prioritized lifecycle. Did you mess up your control flow?");
m_activeLifecycle->End();
m_activeLifecycle->m_application = nullptr;
m_activeLifecycle->m_endLifecycle = false;
// wait until we've finished the control flow for the lifecycle;
// the lifecycle may decide to set the next lifecycle in End()
m_priorityLifecycle = m_activeLifecycle->m_nextLifecycle;
m_activeLifecycle = nullptr;
}
void Application::ClearQueuedLifecycles()
{
// std::queue doesn't provide a clear() method
// so simply invoke the destructor by reinitializing the queue
m_queuedLifecycles = {};
}
void Application::Run()
{
Profiler::Clock m_runtime{};
m_runtime.Start();
Startup();
if (!m_queuedLifecycles.size())
throw std::runtime_error("Application::Run must have a queued lifecycle object (did you forget to queue one?)");
#ifdef PIONEER_PROFILER
// For good measure, reset the profiler at the start of the first frame
Profiler::reset();
#endif
// SoftStop updates the elapsed time measured by the clock, and continues to run the clock.
m_runtime.SoftStop();
m_totalTime = m_runtime.seconds();
while (m_applicationRunning) {
m_runtime.SoftStop();
double thisTime = m_runtime.seconds();
m_deltaTime = thisTime - m_totalTime;
m_totalTime = thisTime;
if (!m_activeLifecycle) {
if (!StartLifecycle())
break;
}
// If there is no lifecycle object to start, end now.
if (!m_activeLifecycle)
break;
BeginFrame();
// The PreUpdate hook should be used for setting up per-frame state, etc.
PreUpdate();
m_activeLifecycle->Update(m_deltaTime);
// The PostUpdate hook should be used for finalizing per-frame state, rendering, etc.
PostUpdate();
EndFrame();
if (m_activeLifecycle->m_endLifecycle) {
EndLifecycle();
}
// TODO: design a better profiling interface, cache results of slow frames so they can be inspected
// in pigui, etc.
// m_runtime.SoftStop();
// thisTime = m_runtime.seconds();
// if (thisTime - m_totalTime > 0.100) // profile frames taking longer than 100ms
// Profiler::dumphtml(FileSystem::JoinPathBelow(FileSystem::GetUserDir(), "profiler");
#ifdef PIONEER_PROFILER
// reset the profiler at the end of the frame
if (!m_activeLifecycle || !m_activeLifecycle->m_profilerAccumulate)
Profiler::reset();
#endif
}
Shutdown();
}

111
src/core/Application.h Normal file
View File

@ -0,0 +1,111 @@
// Copyright © 2008-2020 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#pragma once
#include <memory>
#include <queue>
class Application {
public:
Application(){};
virtual ~Application(){};
class Lifecycle {
public:
Lifecycle(){};
Lifecycle(bool profilerAccumulate) :
m_profilerAccumulate(profilerAccumulate){};
virtual ~Lifecycle(){};
// Once called, the lifecycle is terminated at the end of the current update.
void RequestEndLifecycle() { m_endLifecycle = true; }
protected:
friend class Application;
// Called when the Lifecycle begins execution
virtual void Start(){};
// Called in a continual loop and passed the time since the last invocation.
virtual void Update(float deltaTime) = 0;
// Called when the lifecycle is leaving execution
// If a valid Lifecycle pointer is returned, it is run before any queued lifecycles.
virtual void End() {}
// Set a lifecycle that should begin immediately after this lifecycle has finished execution
void SetNextLifecycle(std::shared_ptr<Lifecycle> l)
{
m_nextLifecycle = l;
}
Application *GetApp();
private:
// set to true when you want to accumulate all updates in a lifecycle into a single profile frame
bool m_profilerAccumulate = false;
bool m_endLifecycle = false;
std::shared_ptr<Lifecycle> m_nextLifecycle;
Application *m_application;
};
// Add a lifecycle object to the queue of pending lifecycles
// You must add at least one lifecycle before calling Run()
void QueueLifecycle(std::shared_ptr<Lifecycle> cycle);
// Use this function very sparingly (e.g. when the application is shutting down)
void ClearQueuedLifecycles();
// Runs the application as long as there is a valid lifecycle object
void Run();
// Get the time between the start of the last update and the current one
float DeltaTime() { return m_deltaTime; }
double GetTime() { return m_totalTime; }
protected:
// Hooks for inheriting classes to add their own behaviors to.
// Runs before the main loop begins
virtual void Startup();
// Runs after the main loop ends
virtual void Shutdown();
// Runs at the top of each frame.
virtual void BeginFrame() {}
// Runs before each Update() call
virtual void PreUpdate() {}
// Runs after each Update() call
virtual void PostUpdate() {}
// Runs at the bottom of each frame.
virtual void EndFrame(){};
// Request the application quit immediately at the end of the update,
// ignoring all queued lifecycles
void RequestQuit() { m_applicationRunning = false; }
Lifecycle *GetActiveLifecycle() { return m_activeLifecycle.get(); }
private:
bool StartLifecycle();
void EndLifecycle();
bool m_applicationRunning;
float m_deltaTime;
double m_totalTime;
// The lifecycle we're actually running right now
std::shared_ptr<Lifecycle> m_activeLifecycle;
// A lifecycle that should be run next before the rest of the queue.
std::shared_ptr<Lifecycle> m_priorityLifecycle;
// Lifecycles queued by QueueLifecycle()
std::queue<std::shared_ptr<Lifecycle>> m_queuedLifecycles;
};

11
src/core/CMakeLists.txt Normal file
View File

@ -0,0 +1,11 @@
list(APPEND CORE_SRC_FOLDERS
${CMAKE_CURRENT_SOURCE_DIR}
)
# Creates variables CORE_CXX_FILES and CORE_HXX_FILES
add_source_folders(CORE CORE_SRC_FOLDERS)
# Creates a library, adds it to the build, and sets C++ target properties on it
define_pioneer_library(pioneer-core CORE_CXX_FILES CORE_HXX_FILES)
target_include_directories(pioneer-core PRIVATE ${CMAKE_BINARY_DIR})

147
src/core/GuiApplication.cpp Normal file
View File

@ -0,0 +1,147 @@
// Copyright © 2008-2020 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#include "GuiApplication.h"
#include "GameConfig.h"
#include "OS.h"
#include "SDL.h"
#include "graphics/Drawables.h"
#include "graphics/Graphics.h"
#include "graphics/RenderState.h"
#include "graphics/RenderTarget.h"
#include "graphics/Renderer.h"
#include "graphics/Texture.h"
#include "utils.h"
#include "versioningInfo.h"
// FIXME: add support for offscreen rendertarget drawing and multisample RTs
#define RTT 0
void GuiApplication::BeginFrame()
{
#if RTT
m_renderer->SetRenderTarget(m_renderTarget);
#endif
// TODO: render target size
m_renderer->SetViewport(0, 0, Graphics::GetScreenWidth(), Graphics::GetScreenHeight());
m_renderer->BeginFrame();
}
void GuiApplication::DrawRenderTarget()
{
#if RTT
m_renderer->BeginFrame();
m_renderer->SetViewport(0, 0, Graphics::GetScreenWidth(), Graphics::GetScreenHeight());
m_renderer->SetTransform(matrix4x4f::Identity());
{
m_renderer->SetMatrixMode(Graphics::MatrixMode::PROJECTION);
m_renderer->PushMatrix();
m_renderer->SetOrthographicProjection(0, Graphics::GetScreenWidth(), Graphics::GetScreenHeight(), 0, -1, 1);
m_renderer->SetMatrixMode(Graphics::MatrixMode::MODELVIEW);
m_renderer->PushMatrix();
m_renderer->LoadIdentity();
}
m_renderQuad->Draw(m_renderer);
{
m_renderer->SetMatrixMode(Graphics::MatrixMode::PROJECTION);
m_renderer->PopMatrix();
m_renderer->SetMatrixMode(Graphics::MatrixMode::MODELVIEW);
m_renderer->PopMatrix();
}
m_renderer->EndFrame();
#endif
}
void GuiApplication::EndFrame()
{
#if RTT
m_renderer->SetRenderTarget(nullptr);
DrawRenderTarget();
#endif
m_renderer->EndFrame();
m_renderer->SwapBuffers();
}
Graphics::RenderTarget *GuiApplication::CreateRenderTarget(const Graphics::Settings &settings)
{
/* @fluffyfreak here's a rendertarget implementation you can use for oculusing and other things. It's pretty simple:
- fill out a RenderTargetDesc struct and call Renderer::CreateRenderTarget
- pass target to Renderer::SetRenderTarget to start rendering to texture
- set up viewport, clear etc, then draw as usual
- SetRenderTarget(0) to resume render to screen
- you can access the attached texture with GetColorTexture to use it with a material
You can reuse the same target with multiple textures.
In that case, leave the color format to NONE so the initial texture is not created, then use SetColorTexture to attach your own.
*/
#if RTT
Graphics::RenderStateDesc rsd;
rsd.depthTest = false;
rsd.depthWrite = false;
rsd.blendMode = Graphics::BLEND_SOLID;
m_renderState.reset(m_renderer->CreateRenderState(rsd));
// Complete the RT description so we can request a buffer.
Graphics::RenderTargetDesc rtDesc(
width,
height,
Graphics::TEXTURE_RGB_888, // don't create a texture
Graphics::TEXTURE_DEPTH,
false, settings.requestedSamples);
m_renderTarget.reset(m_renderer->CreateRenderTarget(rtDesc));
m_renderTarget->SetColorTexture(*m_renderTexture);
#endif
return nullptr;
}
Graphics::Renderer *GuiApplication::StartupRenderer(const GameConfig *config, bool hidden)
{
PROFILE_SCOPED()
// Initialize SDL
Uint32 sdlInitFlags = SDL_INIT_VIDEO | SDL_INIT_JOYSTICK;
if (SDL_Init(sdlInitFlags) < 0) {
Error("SDL initialization failed: %s\n", SDL_GetError());
}
OutputVersioningInfo();
// determine what renderer we should use, default to Opengl 3.x
const std::string rendererName = config->String("RendererName", Graphics::RendererNameFromType(Graphics::RENDERER_OPENGL_3x));
// if we add new renderer types, make sure to update this logic
Graphics::RendererType rType = Graphics::RENDERER_OPENGL_3x;
Graphics::Settings videoSettings = {};
videoSettings.rendererType = rType;
videoSettings.width = config->Int("ScrWidth");
videoSettings.height = config->Int("ScrHeight");
videoSettings.fullscreen = (config->Int("StartFullscreen") != 0);
videoSettings.hidden = hidden;
videoSettings.requestedSamples = config->Int("AntiAliasingMode");
videoSettings.vsync = (config->Int("VSync") != 0);
videoSettings.useTextureCompression = (config->Int("UseTextureCompression") != 0);
videoSettings.useAnisotropicFiltering = (config->Int("UseAnisotropicFiltering") != 0);
videoSettings.enableDebugMessages = (config->Int("EnableGLDebug") != 0);
videoSettings.gl3ForwardCompatible = (config->Int("GL3ForwardCompatible") != 0);
videoSettings.iconFile = OS::GetIconFilename();
videoSettings.title = m_applicationTitle.c_str();
m_renderer.reset(Graphics::Init(videoSettings));
m_renderTarget.reset(CreateRenderTarget(videoSettings));
return m_renderer.get();
}
void GuiApplication::ShutdownRenderer()
{
m_renderTarget.reset();
m_renderState.reset();
m_renderer.reset();
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}

49
src/core/GuiApplication.h Normal file
View File

@ -0,0 +1,49 @@
// Copyright © 2008-2020 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#pragma once
#include "Application.h"
#include "GameConfig.h"
#include "RefCounted.h"
#include "graphics/RenderState.h"
#include "graphics/RenderTarget.h"
#include "graphics/Renderer.h"
class GuiApplication : public Application {
public:
GuiApplication(std::string title) :
Application(), m_applicationTitle(title)
{}
protected:
Graphics::Renderer *GetRenderer() { return m_renderer.get(); }
// Called at the end of the frame automatically, blits the RT onto the application
// framebuffer
void DrawRenderTarget();
// Call this from your Startup() method
Graphics::Renderer *StartupRenderer(const GameConfig *config, bool hidden = false);
// Call this from your Shutdown() method
void ShutdownRenderer();
// Hook to bind the RT and clear the screen.
// If you override BeginFrame, make sure you call this.
void BeginFrame() override;
// Hook to end the frame and draw to the application framebuffer.
// If you override EndFrame, make sure you call this.
void EndFrame() override;
private:
Graphics::RenderTarget *CreateRenderTarget(const Graphics::Settings &settings);
std::string m_applicationTitle;
std::unique_ptr<Graphics::Renderer> m_renderer;
std::unique_ptr<Graphics::RenderTarget> m_renderTarget;
std::unique_ptr<Graphics::RenderState> m_renderState;
};