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 codemaster
parent
0c67f400c9
commit
027d485e51
|
@ -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)
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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})
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
Loading…
Reference in New Issue