Skip to content

Commit

Permalink
Add engine loading / splash screen for content pre-loading
Browse files Browse the repository at this point in the history
Add engine loading / splash screen for content pre-loading onto
the GPU etc.

TODO:
- Explorer the content classes and load stuff on the GPU
- Add a way to select an UI for the loading screen.

Issue #176
  • Loading branch information
ensisoft committed Dec 19, 2023
1 parent a84fd4c commit 6bbcee7
Show file tree
Hide file tree
Showing 11 changed files with 358 additions and 26 deletions.
8 changes: 8 additions & 0 deletions editor/app/workspace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2050,6 +2050,7 @@ bool Workspace::SaveProperties(const QString& filename) const
JsonWrite(project, "application_version" , mSettings.application_version);
JsonWrite(project, "application_library_win" , mSettings.application_library_win);
JsonWrite(project, "application_library_lin" , mSettings.application_library_lin);
JsonWrite(project, "loading_screen_font" , mSettings.loading_font);
JsonWrite(project, "debug_font" , mSettings.debug_font);
JsonWrite(project, "debug_show_fps" , mSettings.debug_show_fps);
JsonWrite(project, "debug_show_msg" , mSettings.debug_show_msg);
Expand Down Expand Up @@ -2174,6 +2175,7 @@ bool Workspace::LoadProperties(const QString& filename)
JsonReadSafe(project, "application_version", &mSettings.application_version);
JsonReadSafe(project, "application_library_win", &mSettings.application_library_win);
JsonReadSafe(project, "application_library_lin", &mSettings.application_library_lin);
JsonReadSafe(project, "loading_screen_font" , &mSettings.loading_font);
JsonReadSafe(project, "debug_font" , &mSettings.debug_font);
JsonReadSafe(project, "debug_show_fps" , &mSettings.debug_show_fps);
JsonReadSafe(project, "debug_show_msg" , &mSettings.debug_show_msg);
Expand Down Expand Up @@ -3384,6 +3386,11 @@ bool Workspace::BuildReleasePackage(const std::vector<const Resource*>& resource
file_packer.CopyFile(app::ToUtf8(mSettings.debug_font), "fonts/");
}

if (!mSettings.loading_font.isEmpty())
{
file_packer.CopyFile(app::ToUtf8(mSettings.loading_font), "fonts/");
}

// write content file ?
if (options.write_content_file)
{
Expand Down Expand Up @@ -3484,6 +3491,7 @@ bool Workspace::BuildReleasePackage(const std::vector<const Resource*>& resource
base::JsonWrite(json["application"], "game_script", ToUtf8(mSettings.game_script));
base::JsonWrite(json["application"], "save_window_geometry", mSettings.save_window_geometry);
base::JsonWrite(json["desktop"], "audio_io_strategy", mSettings.desktop_audio_io_strategy);
base::JsonWrite(json["loading_screen"], "font", ToUtf8(mSettings.loading_font));
base::JsonWrite(json["debug"], "font", ToUtf8(mSettings.debug_font));
base::JsonWrite(json["debug"], "show_msg", mSettings.debug_show_msg);
base::JsonWrite(json["debug"], "show_fps", mSettings.debug_show_fps);
Expand Down
3 changes: 3 additions & 0 deletions editor/app/workspace.h
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,9 @@ namespace app
application_library_win = library;
#endif
}
// Loading screen font
QString loading_font = "app://fonts/orbitron-bold.otf";

// Debug font (if any) used by the engine to print debug messages.
QString debug_font;
bool debug_show_fps = false;
Expand Down
3 changes: 3 additions & 0 deletions editor/gui/dlgproject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ DlgProject::DlgProject(QWidget* parent, app::Workspace& workspace, app::Workspac
PopulateFromEnum<app::Workspace::ProjectSettings::DefaultAudioIOStrategy>(mUI.cmbWasmAudioIO);
PopulateFromEnum<audio::SampleType>(mUI.audioFormat);
PopulateFromEnum<audio::Channels>(mUI.audioChannels);
PopulateFontNames(mUI.cmbLoadingFont);
PopulateFontNames(mUI.cmbDebugFont);
SetList(mUI.mouseDrawable, workspace.ListCursors());
SetList(mUI.mouseMaterial, workspace.ListAllMaterials());
SetUIValue(mUI.cmbLoadingFont, mSettings.loading_font);
SetUIValue(mUI.edtAppIdentifier, mSettings.application_identifier);
SetUIValue(mUI.cmbMSAA, mSettings.multisample_sample_count);
SetUIValue(mUI.cmbMinFilter, mSettings.default_min_filter);
Expand Down Expand Up @@ -124,6 +126,7 @@ DlgProject::DlgProject(QWidget* parent, app::Workspace& workspace, app::Workspac

void DlgProject::on_btnAccept_clicked()
{
GetUIValue(mUI.cmbLoadingFont, &mSettings.loading_font);
GetUIValue(mUI.cmbMSAA, &mSettings.multisample_sample_count);
GetUIValue(mUI.cmbMinFilter, &mSettings.default_min_filter);
GetUIValue(mUI.cmbMagFilter, &mSettings.default_mag_filter);
Expand Down
37 changes: 37 additions & 0 deletions editor/gui/dlgproject.ui
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,42 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_20">
<property name="title">
<string>Loading screen settings</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout_22">
<item row="0" column="0">
<widget class="QLabel" name="label_40">
<property name="text">
<string>Font</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_14">
<item>
<widget class="QComboBox" name="cmbLoadingFont">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="placeholderText">
<string>Select Font</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_10">
<property name="title">
Expand Down Expand Up @@ -1790,6 +1826,7 @@
<tabstop>edtAppName</tabstop>
<tabstop>edtAppVersion</tabstop>
<tabstop>edtGameScript</tabstop>
<tabstop>cmbLoadingFont</tabstop>
<tabstop>cmbDebugFont</tabstop>
<tabstop>btnResetDebugFont</tabstop>
<tabstop>chkDebugShowFps</tabstop>
Expand Down
113 changes: 90 additions & 23 deletions emscripten/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,27 @@ class Application
mEngine->SetEngineConfig(config);
// doesn't exist here.
mEngine->SetTracer(nullptr);
mEngine->Load();
mEngine->Start();


// We're no longer doing this here because the loading screen must
// run first and it requires rendering which means it must be done
// in the animation frame callback.
//mEngine->Load();
//mEngine->Start();

{
engine::Engine::LoadingScreenSettings settings;
if (json.contains("loading_screen"))
{
const auto& splash = json["loading_screen"];
base::JsonReadSafe(splash, "font", &settings.font_uri);
}

mLoadingScreen = std::make_unique<LoadingScreen>();
mLoadingScreen->classes = mContentLoader->ListClasses();
mLoadingScreen->screen = mEngine->CreateLoadingScreen(settings);
}


mRenderTargetWidth = canvas_render_width;
mRenderTargetHeight = canvas_render_height;
Expand Down Expand Up @@ -442,6 +461,43 @@ class Application

EM_BOOL OnAnimationFrame()
{
if (mLoadingScreen)
{
// we're pumping the event queue here too, but only processing
// the resize events since it seems these are important for
// the correct rendering in the engine (correct surface size)
for (const auto& event : mEventQueue)
{
if (const auto* ptr = std::get_if<wdk::WindowEventResize>(&event))
{
PostResize(ptr);
}
}
mEventQueue.clear();

const auto& classes = mLoadingScreen->classes;
auto& screen = mLoadingScreen->screen;
auto& counter = mLoadingScreen->counter;

DEBUG("Loading %1 class. [name='%2', id=%3]", classes[counter].type,
classes[counter].name, classes[counter].id);

engine::Engine::ContentClass klass;
klass.type = classes[counter].type;
klass.name = classes[counter].name;
klass.id = classes[counter].id;
mEngine->PreloadClass(klass, counter, classes.size()-1, screen.get());

if (++counter < classes.size())
return EM_TRUE;

DEBUG("Class loading is done!");
mLoadingScreen.reset();

mEngine->Load();
mEngine->Start();
}

// make sure the tracing state only changes before
// any trace macros are called. otherwise, there'll
// be an assert.
Expand Down Expand Up @@ -513,27 +569,7 @@ class Application
else if (const auto* ptr = std::get_if<wdk::WindowEventChar>(&event))
mListener->OnChar(*ptr);
else if (const auto* ptr = std::get_if<wdk::WindowEventResize>(&event))
{
// filter out superfluous event notifications when the render target
// hasn't actually changed.
if ((mRenderTargetHeight != ptr->height) || (mRenderTargetWidth != ptr->width))
{
// for consistency's sake call the window resize event handler.
mListener->OnResize(*ptr);
// this is the main engine rendering surface callback which is important.
mEngine->OnRenderingSurfaceResized(ptr->width, ptr->height);

mRenderTargetWidth = ptr->width;
mRenderTargetHeight = ptr->height;
DEBUG("Canvas render target size changed. [width=%1, height=%2]", ptr->width, ptr->height);
}
// obtain the new (if changed) canvas display width and height.
// we need these for mapping the mouse coordinates from CSS display
// units to render target units.
emscripten_get_element_css_size("canvas", &mCanvasDisplayWidth, &mCanvasDisplayHeight);
DEBUG("Canvas display (CSS logical pixel) size changed. [width=%1, height=%2]",
mCanvasDisplayWidth, mCanvasDisplayHeight);
}
PostResize(ptr);
else BUG("Unhandled window event.");
}
mEventQueue.clear();
Expand Down Expand Up @@ -614,6 +650,30 @@ class Application
mContext.reset();
return EM_FALSE;
}
void PostResize(const wdk::WindowEventResize* ptr)
{
// filter out superfluous event notifications when the render target
// hasn't actually changed.
if ((mRenderTargetHeight != ptr->height) || (mRenderTargetWidth != ptr->width))
{
// for consistency's sake call the window resize event handler.
mListener->OnResize(*ptr);
// this is the main engine rendering surface callback which is important.
mEngine->OnRenderingSurfaceResized(ptr->width, ptr->height);

mRenderTargetWidth = ptr->width;
mRenderTargetHeight = ptr->height;
DEBUG("Canvas render target size changed. [width=%1, height=%2]", ptr->width, ptr->height);
}
// obtain the new (if changed) canvas display width and height.
// we need these for mapping the mouse coordinates from CSS display
// units to render target units.
emscripten_get_element_css_size("canvas", &mCanvasDisplayWidth, &mCanvasDisplayHeight);
DEBUG("Canvas display (CSS logical pixel) size changed. [width=%1, height=%2]",
mCanvasDisplayWidth, mCanvasDisplayHeight);
}


void ToggleTracing(bool enable)
{
if (enable && mTraceLogger)
Expand Down Expand Up @@ -993,6 +1053,13 @@ class Application
// Not necessarily the same as the render target size.
double mCanvasDisplayWidth = 0.0f;
double mCanvasDisplayHeight = 0.0f;

struct LoadingScreen {
std::unique_ptr<engine::Engine::LoadingScreen> screen;
std::vector<engine::JsonFileClassLoader::Class> classes;
std::size_t counter = 0;
};
std::unique_ptr<LoadingScreen> mLoadingScreen;
};

EM_BOOL OnWindowSizeChanged(int emsc_type, const EmscriptenUiEvent* emsc_event, void* user_data)
Expand Down
4 changes: 4 additions & 0 deletions engine/classlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ namespace engine
class ClassLibrary
{
public:
enum class ClassType {
Entity, Scene, AudioGraph, Material, Drawable, Tilemap, UI
};

template<typename T>
using ClassHandle = std::shared_ptr<const T>;

Expand Down
86 changes: 86 additions & 0 deletions engine/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,92 @@ class GameStudioEngine final : public engine::Engine,
mFlags.set(Flags::EnableBloom, true);
mFlags.set(Flags::EnablePhysics, true);
}

struct LoadingScreen : public Engine::LoadingScreen {
std::unique_ptr<uik::Window> splash;
uik::TransientState state;
engine::UIStyle style;
engine::UIPainter painter;
std::vector<uik::Animation> animations;
std::string font;
};
virtual std::unique_ptr<Engine::LoadingScreen> CreateLoadingScreen(const LoadingScreenSettings& settings) override
{
auto state = std::make_unique<LoadingScreen>();
state->font = settings.font_uri;
return state;
}

virtual void PreloadClass(const Engine::ContentClass& klass, size_t index, size_t last, Engine::LoadingScreen* screen) override
{
// todo: explore the content class and load content on the GPU


auto* my_screen = static_cast<LoadingScreen*>(screen);
const float surf_width = mSurfaceWidth;
const float surf_height = mSurfaceHeight;

mDevice->BeginFrame();
mDevice->ClearColor(mClearColor);
mDevice->ClearDepth(1.0f);

if (my_screen->splash)
{
auto& ui_splash = *my_screen->splash;
auto& ui_painter = my_screen->painter;
auto& ui_state = my_screen->state;
auto& ui_style = my_screen->style;

// the viewport retains the UI's aspect ratio and is centered in the middle
// of the rendering surface.
const auto& window_rect = ui_splash.GetBoundingRect();
const float width = window_rect.GetWidth();
const float height = window_rect.GetHeight();
const float scale = std::min(surf_width / width, surf_height / height);
const float device_viewport_width = width * scale;
const float device_viewport_height = height * scale;

gfx::IRect device_viewport;
device_viewport.Move((surf_width - device_viewport_width) * 0.5,
(surf_height - device_viewport_height) * 0.5);
device_viewport.Resize(device_viewport_width, device_viewport_height);

gfx::Painter painter(mDevice);
painter.SetSurfaceSize(mSurfaceWidth, mSurfaceHeight);
painter.SetPixelRatio(glm::vec2(1.0f, 1.0f));
painter.SetProjectionMatrix(gfx::MakeOrthographicProjection(0, 0, width, height));
painter.ResetViewMatrix();
painter.SetViewport(device_viewport);
painter.SetEditingMode(mFlags.test(Flags::EditingMode));

ui_painter.SetPainter(&painter);
ui_painter.SetStyle(&ui_style);
ui_splash.Paint(ui_state, ui_painter, base::GetTime(), nullptr);
ui_painter.SetPainter(nullptr);
}
else if (!my_screen->font.empty())
{
gfx::Painter painter;
painter.SetDevice(mDevice);
painter.SetSurfaceSize(mSurfaceWidth, mSurfaceHeight);
painter.SetPixelRatio(glm::vec2(1.0f, 1.0f));
painter.SetViewport(0, 0, surf_width, surf_height);
painter.SetProjectionMatrix(gfx::MakeOrthographicProjection(0, 0, surf_width, surf_height));
painter.SetEditingMode(mFlags.test(Flags::EditingMode));

gfx::FillRect(painter, gfx::FRect(0.0f, 0.0f, mSurfaceWidth, mSurfaceHeight), gfx::Color::Black);
gfx::DrawTextRect(painter, base::FormatString("DETONATOR 2D\n\nLoading ... %1/%2 ", index, last),
my_screen->font, 26, gfx::FRect(0.0f, 0.0f, mSurfaceWidth, mSurfaceHeight),
gfx::Color::Silver,
gfx::TextAlign::AlignVCenter | gfx::TextAlign::AlignVCenter);
}

// this is for debugging so we can see what happens...
//std::this_thread::sleep_for(std::chrono::milliseconds(100));

mDevice->EndFrame(true);
}

virtual bool Load() override
{
DEBUG("Loading game state.");
Expand Down
Loading

0 comments on commit 6bbcee7

Please sign in to comment.