From 709029f22c5ec45b92d5c70403c0831bf7560297 Mon Sep 17 00:00:00 2001 From: Attila Uygun Date: Wed, 21 Jun 2023 23:24:29 +0200 Subject: [PATCH] Async-load support for assets --- src/demo/demo.cc | 50 +++++--- src/demo/demo.h | 6 +- src/demo/enemy.cc | 111 +++++++---------- src/demo/enemy.h | 13 +- src/demo/menu.cc | 111 +++++++++-------- src/demo/menu.h | 5 +- src/demo/player.cc | 44 +++---- src/demo/player.h | 8 +- src/demo/sky_quad.cc | 4 +- src/engine/engine.cc | 170 +++++++++++++++++--------- src/engine/engine.h | 24 +++- src/engine/game.h | 4 + src/engine/image_quad.cc | 2 +- src/engine/platform/platform_linux.cc | 2 + src/engine/sound_player.cc | 4 + src/engine/sound_player.h | 2 + 16 files changed, 304 insertions(+), 256 deletions(-) diff --git a/src/demo/demo.cc b/src/demo/demo.cc index 6ac88dd..ed6818b 100644 --- a/src/demo/demo.cc +++ b/src/demo/demo.cc @@ -13,7 +13,6 @@ #include "engine/engine.h" #include "engine/game_factory.h" #include "engine/input_event.h" -#include "engine/sound.h" DECLARE_GAME_BEGIN DECLARE_GAME(Demo) @@ -47,16 +46,38 @@ Demo::~Demo() { saved_data_.Save(); } -bool Demo::Initialize() { - saved_data_.Load(kSaveFileName); - - Engine::Get().LoadCustomShader("sky_without_nebula", - "sky_without_nebula.glsl"); - Engine::Get().LoadCustomShader("sky", "sky.glsl"); - +bool Demo::PreInitialize() { if (!font_.Load("PixelCaps!.ttf")) return false; + Engine::Get().SetShaderSource("sky_without_nebula", + "sky_without_nebula.glsl"); + Engine::Get().SetShaderSource("sky", "sky.glsl"); + + Engine::Get().AsyncLoadSound("music", "Game_2_Main.mp3"); + Engine::Get().AsyncLoadSound("boss_music", "Game_2_Boss.mp3"); + + if (!enemy_.PreInitialize()) { + LOG << "Failed to create the enemy."; + return false; + } + + if (!player_.PreInitialize()) { + LOG << "Failed to create the enemy."; + return false; + } + + if (!menu_.PreInitialize()) { + LOG << "Failed to create the menu."; + return false; + } + + return true; +} + +bool Demo::Initialize() { + saved_data_.Load(kSaveFileName); + if (!sky_.Create(false)) { LOG << "Could not create the sky."; return false; @@ -87,18 +108,10 @@ bool Demo::Initialize() { return false; } - auto sound = std::make_unique(); - if (!sound->Load("Game_2_Main.mp3", true)) - return false; - - auto boss_sound = std::make_unique(); - if (!boss_sound->Load("Game_2_Boss.mp3", true)) - return false; - - music_.SetSound(std::move(sound)); + music_.SetSound("music"); music_.SetMaxAplitude(0.5f); - boss_music_.SetSound(std::move(boss_sound)); + boss_music_.SetSound("boss_music"); boss_music_.SetMaxAplitude(0.5f); if (!saved_data_.root().get("audio", Json::Value(true)).asBool()) @@ -172,6 +185,7 @@ void Demo::ContextLost() { num_benchmark_samples_ = 0; avarage_fps_ = 0; } + menu_.SetRendererType(); } void Demo::LostFocus() {} diff --git a/src/demo/demo.h b/src/demo/demo.h index b3ec1ed..89ed04f 100644 --- a/src/demo/demo.h +++ b/src/demo/demo.h @@ -23,14 +23,12 @@ class Demo final : public eng::Game { Demo(); ~Demo() final; + // Game interface + bool PreInitialize() final; bool Initialize() final; - void Update(float delta_time) final; - void ContextLost() final; - void LostFocus() final; - void GainedFocus(bool from_interstitial_ad) final; void AddScore(size_t score); diff --git a/src/demo/enemy.cc b/src/demo/enemy.cc index dfa2f6f..46c2144 100644 --- a/src/demo/enemy.cc +++ b/src/demo/enemy.cc @@ -13,7 +13,6 @@ #include "engine/font.h" #include "engine/image.h" #include "engine/renderer/geometry.h" -#include "engine/sound.h" #include "demo/demo.h" @@ -78,46 +77,44 @@ Enemy::Enemy() = default; Enemy::~Enemy() = default; +bool Enemy::PreInitialize() { + Engine::Get().SetImageSource("skull_tex", "enemy_anims_01_frames_ok.png", + true); + Engine::Get().SetImageSource("bug_tex", "enemy_anims_02_frames_ok.png", true); + Engine::Get().SetImageSource("boss_tex1", "Boss_ok.png", true); + Engine::Get().SetImageSource("boss_tex2", "Boss_ok_lvl2.png", true); + Engine::Get().SetImageSource("boss_tex3", "Boss_ok_lvl3.png", true); + Engine::Get().SetImageSource("target_tex", "enemy_target_single_ok.png", + true); + Engine::Get().SetImageSource("blast_tex", "enemy_anims_blast_ok.png", true); + Engine::Get().SetImageSource("shield_tex", "woom_enemy_shield.png", true); + Engine::Get().SetImageSource("crate_tex", "nuke_pack_OK.png", true); + + for (int i = 0; i < kEnemyType_Max; ++i) + Engine::Get().SetImageSource( + "score_tex"s + std::to_string(i), + std::bind(&Enemy::GetScoreImage, this, (EnemyType)i), true); + + Engine::Get().SetShaderSource("chromatic_aberration", + "chromatic_aberration.glsl"); + + Engine::Get().AsyncLoadSound("boss_intro", "boss_intro.mp3"); + Engine::Get().AsyncLoadSound("boss_explosion", "boss_explosion.mp3"); + Engine::Get().AsyncLoadSound("explosion", "explosion.mp3"); + Engine::Get().AsyncLoadSound("stealth", "stealth.mp3"); + Engine::Get().AsyncLoadSound("shield", "shield.mp3"); + Engine::Get().AsyncLoadSound("hit", "hit.mp3"); + Engine::Get().AsyncLoadSound("powerup-spawn", "powerup-spawn.mp3"); + Engine::Get().AsyncLoadSound("powerup-pick", "powerup-pick.mp3"); + + return true; +} + bool Enemy::Initialize() { - boss_intro_sound_ = std::make_shared(); - if (!boss_intro_sound_->Load("boss_intro.mp3", false)) - return false; - - boss_explosion_sound_ = std::make_shared(); - if (!boss_explosion_sound_->Load("boss_explosion.mp3", false)) - return false; - - explosion_sound_ = std::make_shared(); - if (!explosion_sound_->Load("explosion.mp3", false)) - return false; - - stealth_sound_ = std::make_shared(); - if (!stealth_sound_->Load("stealth.mp3", false)) - return false; - - shield_on_sound_ = std::make_shared(); - if (!shield_on_sound_->Load("shield.mp3", false)) - return false; - - hit_sound_ = std::make_shared(); - if (!hit_sound_->Load("hit.mp3", false)) - return false; - - power_up_spawn_sound_ = std::make_shared(); - if (!power_up_spawn_sound_->Load("powerup-spawn.mp3", false)) - return false; - - power_up_pick_sound_ = std::make_shared(); - if (!power_up_pick_sound_->Load("powerup-pick.mp3", false)) - return false; - - if (!CreateRenderResources()) - return false; - boss_.SetZOrder(10); boss_animator_.Attach(&boss_); - boss_intro_.SetSound(boss_intro_sound_); + boss_intro_.SetSound("boss_intro"); boss_intro_.SetVariate(false); boss_intro_.SetSimulateStereo(false); @@ -729,29 +726,29 @@ void Enemy::SpawnUnit(EnemyType enemy_type, e.movement_animator.Play(Animator::kMovement, false); if (e.enemy_type == kEnemyType_PowerUp) { - e.explosion.SetSound(power_up_pick_sound_); + e.explosion.SetSound("powerup-pick"); - e.spawn.SetSound(power_up_spawn_sound_); + e.spawn.SetSound("powerup-spawn"); e.spawn.SetMaxAplitude(2.0f); e.spawn.Play(false); } else { - e.explosion.SetSound(explosion_sound_); + e.explosion.SetSound("explosion"); e.explosion.SetVariate(true); e.explosion.SetSimulateStereo(true); e.explosion.SetMaxAplitude(0.9f); } - e.stealth.SetSound(stealth_sound_); + e.stealth.SetSound("stealth"); e.stealth.SetVariate(false); e.stealth.SetSimulateStereo(false); e.stealth.SetMaxAplitude(0.7f); - e.shield_on.SetSound(shield_on_sound_); + e.shield_on.SetSound("shield"); e.shield_on.SetVariate(false); e.shield_on.SetSimulateStereo(false); e.shield_on.SetMaxAplitude(0.5f); - e.hit.SetSound(hit_sound_); + e.hit.SetSound("hit"); e.hit.SetVariate(true); e.hit.SetSimulateStereo(false); e.hit.SetMaxAplitude(0.5f); @@ -825,11 +822,11 @@ void Enemy::SpawnBoss() { Animator::kMovement, [&]() -> void { e.marked_for_removal = true; }); e.score_animator.Attach(&e.score); - e.explosion.SetSound(boss_explosion_sound_); + e.explosion.SetSound("boss_explosion"); e.explosion.SetVariate(false); e.explosion.SetSimulateStereo(false); - e.hit.SetSound(hit_sound_); + e.hit.SetSound("hit"); e.hit.SetVariate(true); e.hit.SetSimulateStereo(false); e.hit.SetMaxAplitude(0.5f); @@ -1185,30 +1182,6 @@ std::unique_ptr Enemy::GetScoreImage(EnemyType enemy_type) { return image; } -bool Enemy::CreateRenderResources() { - Engine::Get().SetImageSource("skull_tex", "enemy_anims_01_frames_ok.png", - true); - Engine::Get().SetImageSource("bug_tex", "enemy_anims_02_frames_ok.png", true); - Engine::Get().SetImageSource("boss_tex1", "Boss_ok.png", true); - Engine::Get().SetImageSource("boss_tex2", "Boss_ok_lvl2.png", true); - Engine::Get().SetImageSource("boss_tex3", "Boss_ok_lvl3.png", true); - Engine::Get().SetImageSource("target_tex", "enemy_target_single_ok.png", - true); - Engine::Get().SetImageSource("blast_tex", "enemy_anims_blast_ok.png", true); - Engine::Get().SetImageSource("shield_tex", "woom_enemy_shield.png", true); - Engine::Get().SetImageSource("crate_tex", "nuke_pack_OK.png", true); - - for (int i = 0; i < kEnemyType_Max; ++i) - Engine::Get().SetImageSource( - "score_tex"s + std::to_string(i), - std::bind(&Enemy::GetScoreImage, this, (EnemyType)i), true); - - Engine::Get().LoadCustomShader("chromatic_aberration", - "chromatic_aberration.glsl"); - - return true; -} - void Enemy::TranslateEnemyUnit(EnemyUnit& e, const Vector2f& delta) { e.sprite.Translate(delta); e.target.Translate(delta); diff --git a/src/demo/enemy.h b/src/demo/enemy.h index f8d4c85..0adb1c3 100644 --- a/src/demo/enemy.h +++ b/src/demo/enemy.h @@ -15,7 +15,6 @@ namespace eng { class Image; -class Sound; } // namespace eng class Enemy { @@ -23,6 +22,7 @@ class Enemy { Enemy(); ~Enemy(); + bool PreInitialize(); bool Initialize(); void Update(float delta_time); @@ -109,15 +109,6 @@ class Enemy { eng::Animator boss_animator_; eng::SoundPlayer boss_intro_; - std::shared_ptr boss_intro_sound_; - std::shared_ptr boss_explosion_sound_; - std::shared_ptr explosion_sound_; - std::shared_ptr stealth_sound_; - std::shared_ptr shield_on_sound_; - std::shared_ptr hit_sound_; - std::shared_ptr power_up_spawn_sound_; - std::shared_ptr power_up_pick_sound_; - std::list enemies_; int num_enemies_killed_in_current_wave_ = 0; @@ -164,8 +155,6 @@ class Enemy { std::unique_ptr GetScoreImage(EnemyType enemy_type); - bool CreateRenderResources(); - void TranslateEnemyUnit(EnemyUnit& e, const base::Vector2f& delta); }; diff --git a/src/demo/menu.cc b/src/demo/menu.cc index 02efe6b..afe15c8 100644 --- a/src/demo/menu.cc +++ b/src/demo/menu.cc @@ -53,7 +53,7 @@ Menu::Menu() = default; Menu::~Menu() = default; -bool Menu::Initialize() { +bool Menu::PreInitialize() { click_sound_ = std::make_shared(); if (!click_sound_->Load("menu_click.mp3", false)) return false; @@ -70,8 +70,59 @@ bool Menu::Initialize() { max_text_width_ = width; } - if (!CreateRenderResources()) - return false; + Engine::Get().SetImageSource("menu_tex", + std::bind(&Menu::CreateMenuImage, this), true); + Engine::Get().SetImageSource("logo_tex0", "woom_logo_start_frames_01.png", + true); + Engine::Get().SetImageSource("logo_tex1", "woom_logo_start_frames_02-03.png", + true); + Engine::Get().SetImageSource("buttons_tex", "menu_icons.png", true); + Engine::Get().SetImageSource("renderer_logo", "renderer_logo.png", true); + + Engine::Get().SetImageSource( + "version_tex", + []() -> std::unique_ptr { + const Font* font = Engine::Get().GetSystemFont(); + + int w, h; + font->CalculateBoundingBox(kVersionStr, w, h); + + auto image = std::make_unique(); + image->Create(w, font->GetLineHeight()); + image->Clear({1, 1, 1, 0}); + + font->Print(0, 0, kVersionStr, image->GetBuffer(), image->GetWidth()); + + image->Compress(); + return image; + }, + true); + + Engine::Get().SetImageSource("high_score_tex", + std::bind(&Menu::CreateHighScoreImage, this)); + Engine::Get().SetImageSource("wave_up_tex", []() -> std::unique_ptr { + const Font& font = static_cast(Engine::Get().GetGame())->GetFont(); + + constexpr char btn_text[] = "[ ]"; + + int w, h; + font.CalculateBoundingBox(btn_text, w, h); + + auto image = std::make_unique(); + image->Create(w, h); + image->Clear({1, 1, 1, 0}); + + font.Print(0, 0, btn_text, image->GetBuffer(), image->GetWidth()); + + image->Compress(); + return image; + }); + + return true; +} + +bool Menu::Initialize() { + Demo* game = static_cast(Engine::Get().GetGame()); for (int i = 0; i < kOption_Max; ++i) { items_[i].text.Create("menu_tex", {1, 4}); @@ -199,8 +250,6 @@ bool Menu::Initialize() { Engine::Get().CreateRenderer(renderer_type_.enabled() ? RendererType::kVulkan : RendererType::kOpenGL); - renderer_type_.SetEnabled( - (Engine::Get().GetRendererType() == RendererType::kVulkan)); }, true, Engine::Get().GetRendererType() == RendererType::kVulkan, kColorFadeOut, {Vector4f{1, 1, 1, 1}, Vector4f{1, 1, 1, 1}}); @@ -320,6 +369,11 @@ void Menu::SetOptionEnabled(Option o, bool enable) { } } +void Menu::SetRendererType() { + renderer_type_.SetEnabled( + (Engine::Get().GetRendererType() == RendererType::kVulkan)); +} + void Menu::Show() { logo_[1].SetColor(kColorNormal); logo_animator_[0].SetVisible(true); @@ -440,53 +494,6 @@ void Menu::Hide(Closure cb) { } } -bool Menu::CreateRenderResources() { - Engine::Get().SetImageSource("menu_tex", - std::bind(&Menu::CreateMenuImage, this)); - Engine::Get().SetImageSource("logo_tex0", "woom_logo_start_frames_01.png"); - Engine::Get().SetImageSource("logo_tex1", "woom_logo_start_frames_02-03.png"); - Engine::Get().SetImageSource("buttons_tex", "menu_icons.png"); - Engine::Get().SetImageSource("high_score_tex", - std::bind(&Menu::CreateHighScoreImage, this)); - Engine::Get().SetImageSource("renderer_logo", "renderer_logo.png"); - - Engine::Get().SetImageSource("wave_up_tex", []() -> std::unique_ptr { - const Font& font = static_cast(Engine::Get().GetGame())->GetFont(); - - constexpr char btn_text[] = "[ ]"; - - int w, h; - font.CalculateBoundingBox(btn_text, w, h); - - auto image = std::make_unique(); - image->Create(w, h); - image->Clear({1, 1, 1, 0}); - - font.Print(0, 0, btn_text, image->GetBuffer(), image->GetWidth()); - - image->Compress(); - return image; - }); - - Engine::Get().SetImageSource("version_tex", []() -> std::unique_ptr { - const Font* font = Engine::Get().GetSystemFont(); - - int w, h; - font->CalculateBoundingBox(kVersionStr, w, h); - - auto image = std::make_unique(); - image->Create(w, font->GetLineHeight()); - image->Clear({1, 1, 1, 0}); - - font->Print(0, 0, kVersionStr, image->GetBuffer(), image->GetWidth()); - - image->Compress(); - return image; - }); - - return true; -} - std::unique_ptr Menu::CreateMenuImage() { const Font& font = static_cast(Engine::Get().GetGame())->GetFont(); diff --git a/src/demo/menu.h b/src/demo/menu.h index 9a42216..5909475 100644 --- a/src/demo/menu.h +++ b/src/demo/menu.h @@ -30,11 +30,13 @@ class Menu { Menu(); ~Menu(); + bool PreInitialize(); bool Initialize(); void OnInputEvent(std::unique_ptr event); void SetOptionEnabled(Option o, bool enable); + void SetRendererType(); void Show(); void Hide(base::Closure cb = nullptr); @@ -145,9 +147,6 @@ class Menu { Radio starting_wave_; Button wave_up_; - Button wave_down_; - - bool CreateRenderResources(); std::unique_ptr CreateMenuImage(); std::unique_ptr CreateHighScoreImage(); diff --git a/src/demo/player.cc b/src/demo/player.cc index a78f71e..2aeb1d2 100644 --- a/src/demo/player.cc +++ b/src/demo/player.cc @@ -5,7 +5,6 @@ #include "engine/engine.h" #include "engine/font.h" #include "engine/input_event.h" -#include "engine/sound.h" #include "demo/demo.h" @@ -29,22 +28,20 @@ Player::Player() = default; Player::~Player() = default; +bool Player::PreInitialize() { + Engine::Get().SetImageSource("weapon_tex", "enemy_anims_flare_ok.png", true); + Engine::Get().SetImageSource("beam_tex", "enemy_ray_ok.png", true); + Engine::Get().SetImageSource("nuke_symbol_tex", "nuke_frames.png", true); + Engine::Get().SetImageSource("health_bead", "bead.png", true); + + Engine::Get().AsyncLoadSound("laser", "laser.mp3"); + Engine::Get().AsyncLoadSound("nuke", "nuke.mp3"); + Engine::Get().AsyncLoadSound("no_nuke", "no_nuke.mp3"); + + return true; +} + bool Player::Initialize() { - if (!CreateRenderResources()) - return false; - - laser_shot_sound_ = std::make_shared(); - if (!laser_shot_sound_->Load("laser.mp3", false)) - return false; - - nuke_explosion_sound_ = std::make_shared(); - if (!nuke_explosion_sound_->Load("nuke.mp3", false)) - return false; - - no_nuke_sound_ = std::make_shared(); - if (!no_nuke_sound_->Load("no_nuke.mp3", false)) - return false; - SetupWeapons(); Vector2f hb_pos = Engine::Get().GetScreenSize() / Vector2f(2, -2) + @@ -75,12 +72,12 @@ bool Player::Initialize() { nuke_symbol_animator_.Attach(&nuke_symbol_); - nuke_explosion_.SetSound(nuke_explosion_sound_); + nuke_explosion_.SetSound("nuke"); nuke_explosion_.SetVariate(false); nuke_explosion_.SetSimulateStereo(false); nuke_explosion_.SetMaxAplitude(0.8f); - no_nuke_.SetSound(no_nuke_sound_); + no_nuke_.SetSound("no_nuke"); return true; } @@ -306,7 +303,7 @@ void Player::SetupWeapons() { beam_animator_[i].SetBlending({1, 1, 1, 0}, 0.16f); beam_animator_[i].Attach(&beam_[i]); - laser_shot_[i].SetSound(laser_shot_sound_); + laser_shot_[i].SetSound("laser"); laser_shot_[i].SetVariate(true); laser_shot_[i].SetSimulateStereo(false); laser_shot_[i].SetMaxAplitude(0.4f); @@ -485,12 +482,3 @@ void Player::NavigateBack() { Engine& engine = Engine::Get(); static_cast(engine.GetGame())->EnterMenuState(); } - -bool Player::CreateRenderResources() { - Engine::Get().SetImageSource("weapon_tex", "enemy_anims_flare_ok.png", true); - Engine::Get().SetImageSource("beam_tex", "enemy_ray_ok.png", true); - Engine::Get().SetImageSource("nuke_symbol_tex", "nuke_frames.png", true); - Engine::Get().SetImageSource("health_bead", "bead.png", true); - - return true; -} diff --git a/src/demo/player.h b/src/demo/player.h index d29a223..f920172 100644 --- a/src/demo/player.h +++ b/src/demo/player.h @@ -13,7 +13,6 @@ namespace eng { class InputEvent; -class Sound; } // namespace eng class Player { @@ -21,6 +20,7 @@ class Player { Player(); ~Player(); + bool PreInitialize(); bool Initialize(); void Update(float delta_time); @@ -41,10 +41,6 @@ class Player { int nuke_count() { return nuke_count_; } private: - std::shared_ptr nuke_explosion_sound_; - std::shared_ptr no_nuke_sound_; - std::shared_ptr laser_shot_sound_; - eng::ImageQuad drag_sign_[2]; eng::ImageQuad weapon_[2]; eng::ImageQuad beam_[2]; @@ -101,8 +97,6 @@ class Player { bool ValidateDrag(int i); void NavigateBack(); - - bool CreateRenderResources(); }; #endif // DEMO_PLAYER_H diff --git a/src/demo/sky_quad.cc b/src/demo/sky_quad.cc index 01a1424..6c2b868 100644 --- a/src/demo/sky_quad.cc +++ b/src/demo/sky_quad.cc @@ -21,8 +21,8 @@ SkyQuad::~SkyQuad() = default; bool SkyQuad::Create(bool without_nebula) { without_nebula_ = without_nebula; scale_ = Engine::Get().GetScreenSize(); - shader_ = Engine::Get().GetCustomShader( - without_nebula ? "sky_without_nebula" : "sky"); + shader_ = + Engine::Get().GetShader(without_nebula ? "sky_without_nebula" : "sky"); color_animator_.Attach(this); diff --git a/src/engine/engine.cc b/src/engine/engine.cc index 932356c..21c01d9 100644 --- a/src/engine/engine.cc +++ b/src/engine/engine.cc @@ -20,6 +20,7 @@ #include "engine/renderer/texture.h" #include "engine/renderer/vulkan/renderer_vulkan.h" #include "engine/shader_source.h" +#include "engine/sound.h" #include "third_party/texture_compressor/texture_compressor.h" using namespace base; @@ -84,6 +85,7 @@ void Engine::Run() { if (!renderer_->IsInitialzed()) { timer_.Reset(); + input_queue_.clear(); continue; } @@ -109,7 +111,7 @@ void Engine::Initialize() { thread_pool_.Initialize(); - CreateRenderer(RendererType::kVulkan); + CreateRendererInternal(RendererType::kVulkan); // Normalize viewport. if (GetScreenWidth() > GetScreenHeight()) { @@ -134,7 +136,11 @@ void Engine::Initialize() { game_ = GameFactoryBase::CreateGame(""); CHECK(game_) << "No game found to run."; + CHECK(game_->PreInitialize()) << "Failed to pre-initialize the game."; + // Create resources and let the game finalize initialization. + CreateRenderResources(); + WaitForAsyncWork(); CHECK(game_->Initialize()) << "Failed to initialize the game."; } @@ -205,33 +211,12 @@ void Engine::RemoveAnimator(Animator* animator) { } void Engine::CreateRenderer(RendererType type) { - if ((dynamic_cast(renderer_.get()) && - type == RendererType::kVulkan) || - (dynamic_cast(renderer_.get()) && - type == RendererType::kOpenGL)) - return; - - if (type == RendererType::kVulkan) - renderer_ = - std::make_unique(std::bind(&Engine::ContextLost, this)); - else if (type == RendererType::kOpenGL) - renderer_ = - std::make_unique(std::bind(&Engine::ContextLost, this)); - else - NOTREACHED; - - bool result = renderer_->Initialize(platform_); - if (!result && type == RendererType::kVulkan) { - LOG << "Failed to initialize " << renderer_->GetDebugName() << " renderer."; - LOG << "Fallback to OpenGL renderer."; - CreateRenderer(RendererType::kOpenGL); - return; - } - CHECK(result) << "Failed to initialize " << renderer_->GetDebugName() - << " renderer."; - - CreateTextureCompressors(); - ContextLost(); + // Create a new renderer next cycle. + TaskRunner::TaskRunner::GetThreadLocalTaskRunner()->PostTask( + HERE, std::bind(&Engine::CreateRendererInternal, this, type)); + TaskRunner::TaskRunner::GetThreadLocalTaskRunner()->PostTask( + HERE, std::bind(&Engine::ContextLost, this)); + input_queue_.clear(); } RendererType Engine::GetRendererType() { @@ -323,41 +308,58 @@ void Engine::ReleaseTexture(const std::string& asset_name) { it->second.texture->Destroy(); } -void Engine::LoadCustomShader(const std::string& asset_name, - const std::string& file_name) { +void Engine::SetShaderSource(const std::string& asset_name, + const std::string& file_name) { if (shaders_.contains(asset_name)) { DLOG << "Shader already exists: " << asset_name; return; } - auto& s = shaders_[asset_name] = {std::make_unique(renderer_.get()), - file_name}; - - auto source = std::make_unique(); - if (!source->Load(file_name)) - return; - s.shader->Create(std::move(source), quad_->vertex_description(), - quad_->primitive(), false); + shaders_[asset_name] = {std::make_unique(renderer_.get()), file_name}; } -Shader* Engine::GetCustomShader(const std::string& asset_name) { +Shader* Engine::GetShader(const std::string& asset_name) { auto it = shaders_.find(asset_name); if (it == shaders_.end()) { DLOG << "Shader not found: " << asset_name; return nullptr; } + if (!it->second.shader->IsValid()) { + auto source = std::make_unique(); + if (source->Load(it->second.file_name)) + it->second.shader->Create(std::move(source), quad_->vertex_description(), + quad_->primitive(), false); + } + return it->second.shader.get(); } -void Engine::RemoveCustomShader(const std::string& asset_name) { - auto it = shaders_.find(asset_name); - if (it == shaders_.end()) { - DLOG << "Shader not found: " << asset_name; +void Engine::AsyncLoadSound(const std::string& asset_name, + const std::string& file_name, + bool stream) { + if (audio_buses_.contains(asset_name)) { + DLOG << "AudioBus already exists: " << asset_name; return; } - shaders_.erase(it); + auto sound = std::make_shared(); + audio_buses_[asset_name] = sound; + + ++async_work_count_; + thread_pool_.PostTaskAndReply( + HERE, std::bind(&Sound::Load, sound, file_name, stream), + [&]() -> void { --async_work_count_; }); +} + +std::shared_ptr Engine::GetAudioBus(const std::string& asset_name) { + auto it = audio_buses_.find(asset_name); + if (it == audio_buses_.end()) { + DLOG << "AudioBus not found: " << asset_name; + return nullptr; + } + + return it->second; } std::unique_ptr Engine::GetNextInputEvent() { @@ -560,6 +562,35 @@ void Engine::AddInputEvent(std::unique_ptr event) { input_queue_.push_back(std::move(event)); } +void Engine::CreateRendererInternal(RendererType type) { + if ((dynamic_cast(renderer_.get()) && + type == RendererType::kVulkan) || + (dynamic_cast(renderer_.get()) && + type == RendererType::kOpenGL)) + return; + + if (type == RendererType::kVulkan) + renderer_ = + std::make_unique(std::bind(&Engine::ContextLost, this)); + else if (type == RendererType::kOpenGL) + renderer_ = + std::make_unique(std::bind(&Engine::ContextLost, this)); + else + NOTREACHED; + + bool result = renderer_->Initialize(platform_); + if (!result && type == RendererType::kVulkan) { + LOG << "Failed to initialize " << renderer_->GetDebugName() << " renderer."; + LOG << "Fallback to OpenGL renderer."; + CreateRendererInternal(RendererType::kOpenGL); + return; + } + CHECK(result) << "Failed to initialize " << renderer_->GetDebugName() + << " renderer."; + + CreateTextureCompressors(); +} + void Engine::CreateTextureCompressors() { tex_comp_alpha_.reset(); tex_comp_opaque_.reset(); @@ -583,6 +614,15 @@ void Engine::CreateTextureCompressors() { } void Engine::ContextLost() { + CreateRenderResources(); + WaitForAsyncWork(); + input_queue_.clear(); + + if (game_) + game_->ContextLost(); +} + +void Engine::CreateRenderResources() { quad_->SetRenderer(renderer_.get()); pass_through_shader_->SetRenderer(renderer_.get()); solid_shader_->SetRenderer(renderer_.get()); @@ -620,24 +660,44 @@ void Engine::ContextLost() { for (auto& t : textures_) { t.second.texture->SetRenderer(renderer_.get()); if (t.second.persistent || t.second.use_count > 0) { - auto image = t.second.create_image(); - if (image) - t.second.texture->Update(std::move(image)); + ++async_work_count_; + thread_pool_.PostTaskAndReplyWithResult>( + HERE, t.second.create_image, + [&, + ptr = t.second.texture.get()](std::unique_ptr image) -> void { + --async_work_count_; + if (image) + ptr->Update(std::move(image)); + }); } } for (auto& s : shaders_) { s.second.shader->SetRenderer(renderer_.get()); - auto source = std::make_unique(); - if (source->Load(s.second.file_name)) - s.second.shader->Create(std::move(source), quad_->vertex_description(), - quad_->primitive(), false); + ++async_work_count_; + thread_pool_.PostTaskAndReplyWithResult>( + HERE, + [file_name = s.second.file_name]() -> std::unique_ptr { + auto source = std::make_unique(); + if (!source->Load(file_name)) + return nullptr; + return source; + }, + [&, ptr = s.second.shader.get()]( + std::unique_ptr source) -> void { + --async_work_count_; + if (source) + ptr->Create(std::move(source), quad_->vertex_description(), + quad_->primitive(), false); + }); } +} - if (game_) - game_->ContextLost(); - - input_queue_.clear(); +void Engine::WaitForAsyncWork() { + while (async_work_count_ > 0) { + TaskRunner::GetThreadLocalTaskRunner()->RunTasks(); + platform_->Update(); + } } void Engine::SetStatsVisible(bool visible) { diff --git a/src/engine/engine.h b/src/engine/engine.h index 78e1991..525a20b 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -19,6 +19,7 @@ class TextureCompressor; namespace eng { class Animator; +class AudioBus; class AudioMixer; class Drawable; class Font; @@ -73,10 +74,14 @@ class Engine : public PlatformObserver { Texture* AcquireTexture(const std::string& asset_name); void ReleaseTexture(const std::string& asset_name); - void LoadCustomShader(const std::string& asset_name, - const std::string& file_name); - Shader* GetCustomShader(const std::string& asset_name); - void RemoveCustomShader(const std::string& asset_name); + void SetShaderSource(const std::string& asset_name, + const std::string& file_name); + Shader* GetShader(const std::string& asset_name); + + void AsyncLoadSound(const std::string& asset_name, + const std::string& file_name, + bool stream = false); + std::shared_ptr GetAudioBus(const std::string& asset_name); std::unique_ptr GetNextInputEvent(); @@ -186,9 +191,12 @@ class Engine : public PlatformObserver { std::list animators_; - // Managed render resources mapped by asset name. + // Resources mapped by asset name. std::unordered_map textures_; std::unordered_map shaders_; + std::unordered_map> audio_buses_; + + size_t async_work_count_ = 0; std::unique_ptr stats_; @@ -227,10 +235,16 @@ class Engine : public PlatformObserver { void GainedFocus(bool from_interstitial_ad) final; void AddInputEvent(std::unique_ptr event) final; + void CreateRendererInternal(RendererType type); + void CreateTextureCompressors(); void ContextLost(); + void CreateRenderResources(); + + void WaitForAsyncWork(); + void SetStatsVisible(bool visible); std::unique_ptr PrintStats(); diff --git a/src/engine/game.h b/src/engine/game.h index 54429a7..93ef9e5 100644 --- a/src/engine/game.h +++ b/src/engine/game.h @@ -8,6 +8,10 @@ class Game { Game() = default; virtual ~Game() = default; + // Called before async-loading assets. + virtual bool PreInitialize() = 0; + + // Called after resources are created. virtual bool Initialize() = 0; virtual void Update(float delta_time) = 0; diff --git a/src/engine/image_quad.cc b/src/engine/image_quad.cc index c0c78af..6656da0 100644 --- a/src/engine/image_quad.cc +++ b/src/engine/image_quad.cc @@ -50,7 +50,7 @@ void ImageQuad::AutoScale() { } void ImageQuad::SetCustomShader(const std::string& asset_name) { - custom_shader_ = Engine::Get().GetCustomShader(asset_name); + custom_shader_ = Engine::Get().GetShader(asset_name); custom_uniforms_.clear(); } diff --git a/src/engine/platform/platform_linux.cc b/src/engine/platform/platform_linux.cc index d911257..6d04399 100644 --- a/src/engine/platform/platform_linux.cc +++ b/src/engine/platform/platform_linux.cc @@ -25,6 +25,8 @@ Platform::Platform() { shared_data_path_ = "./"; LOG << "Shared data path: " << shared_data_path_.c_str(); + XInitThreads(); + bool res = CreateWindow(800, 1205); CHECK(res) << "Failed to create window."; diff --git a/src/engine/sound_player.cc b/src/engine/sound_player.cc index b7e16ca..06f37ae 100644 --- a/src/engine/sound_player.cc +++ b/src/engine/sound_player.cc @@ -17,6 +17,10 @@ SoundPlayer::~SoundPlayer() { Engine::Get().GetAudioMixer()->DestroyResource(resource_id_); } +void SoundPlayer::SetSound(const std::string& asset_name) { + sound_ = Engine::Get().GetAudioBus(asset_name); +} + void SoundPlayer::SetSound(std::shared_ptr sound) { sound_ = sound; } diff --git a/src/engine/sound_player.h b/src/engine/sound_player.h index 3380827..a7aa246 100644 --- a/src/engine/sound_player.h +++ b/src/engine/sound_player.h @@ -2,6 +2,7 @@ #define ENGINE_AUDIO_PLAYER_H #include +#include #include "base/closure.h" @@ -14,6 +15,7 @@ class SoundPlayer { SoundPlayer(); ~SoundPlayer(); + void SetSound(const std::string& asset_name); void SetSound(std::shared_ptr sound); void Play(bool loop, float fade_in_duration = 0);