fixed main screen
This commit is contained in:
363
src/main.cpp
363
src/main.cpp
@ -38,6 +38,8 @@
|
||||
#include "states/LevelSelectorState.h"
|
||||
#include "states/PlayingState.h"
|
||||
#include "audio/MenuWrappers.h"
|
||||
#include "app/AssetLoader.h"
|
||||
#include "states/LoadingManager.h"
|
||||
#include "utils/ImagePathResolver.h"
|
||||
#include "graphics/renderers/GameRenderer.h"
|
||||
#include "core/Config.h"
|
||||
@ -486,6 +488,11 @@ int main(int, char **)
|
||||
SDL_GetError());
|
||||
}
|
||||
|
||||
// Asset loader (creates SDL_Textures on the main thread)
|
||||
AssetLoader assetLoader;
|
||||
assetLoader.init(renderer);
|
||||
LoadingManager loadingManager(&assetLoader);
|
||||
|
||||
// Font and UI asset handles (actual loading deferred until Loading state)
|
||||
FontAtlas pixelFont;
|
||||
FontAtlas font;
|
||||
@ -544,108 +551,10 @@ int main(int, char **)
|
||||
std::atomic_bool g_loadingComplete{false};
|
||||
std::atomic<size_t> g_loadingStep{0};
|
||||
|
||||
// Define performLoadingStep to execute one load operation per frame on main thread
|
||||
auto performLoadingStep = [&]() -> bool {
|
||||
size_t step = g_loadingStep.fetch_add(1);
|
||||
|
||||
// Initialize counters on first step
|
||||
if (step == 0) {
|
||||
constexpr int baseTasks = 25; // 2 fonts + 2 logos + 1 main + 1 blocks + 3 panels + 16 SFX
|
||||
g_totalLoadingTasks.store(baseTasks);
|
||||
g_loadedTasks.store(0);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_assetLoadErrorsMutex);
|
||||
g_assetLoadErrors.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_currentLoadingMutex);
|
||||
g_currentLoadingFile.clear();
|
||||
}
|
||||
|
||||
// Initialize background music loading
|
||||
Audio::instance().init();
|
||||
for (int i = 1; i <= 100; ++i) {
|
||||
char base[128];
|
||||
std::snprintf(base, sizeof(base), "assets/music/music%03d", i);
|
||||
std::string path = AssetPath::resolveWithExtensions(base, { ".mp3" });
|
||||
if (path.empty()) break;
|
||||
Audio::instance().addTrackAsync(path);
|
||||
totalTracks++;
|
||||
}
|
||||
// Expand task budget to account for music tracks so the loading bar waits for them
|
||||
g_totalLoadingTasks.store(baseTasks + totalTracks);
|
||||
if (totalTracks > 0) {
|
||||
Audio::instance().startBackgroundLoading();
|
||||
musicLoadingStarted = true;
|
||||
} else {
|
||||
// No music files found, mark as loaded so game can continue
|
||||
musicLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute one load operation per step
|
||||
switch (step) {
|
||||
case 0: return false; // Init step
|
||||
case 1: pixelFont.init(AssetPath::resolveWithBase("assets/fonts/Orbitron.ttf"), 22); g_loadedTasks.fetch_add(1); break;
|
||||
case 2: font.init(AssetPath::resolveWithBase("assets/fonts/Exo2.ttf"), 20); g_loadedTasks.fetch_add(1); break;
|
||||
case 3: logoTex = loadTextureFromImage(renderer, "assets/images/spacetris.png"); break;
|
||||
case 4: logoSmallTex = loadTextureFromImage(renderer, "assets/images/spacetris.png", &logoSmallW, &logoSmallH); break;
|
||||
case 5: mainScreenTex = loadTextureFromImage(renderer, "assets/images/main_screen.png", &mainScreenW, &mainScreenH);
|
||||
if (mainScreenTex) SDL_SetTextureBlendMode(mainScreenTex, SDL_BLENDMODE_BLEND); break;
|
||||
case 6:
|
||||
blocksTex = loadTextureFromImage(renderer, "assets/images/blocks90px_001.bmp");
|
||||
if (!blocksTex) {
|
||||
blocksTex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 630, 90);
|
||||
SDL_SetRenderTarget(renderer, blocksTex);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
for (int i = 0; i < PIECE_COUNT; ++i) {
|
||||
SDL_Color c = COLORS[i + 1];
|
||||
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
|
||||
SDL_FRect rect{(float)(i * 90), 0, 90, 90};
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
}
|
||||
SDL_SetRenderTarget(renderer, nullptr);
|
||||
g_loadedTasks.fetch_add(1);
|
||||
}
|
||||
break;
|
||||
case 7: scorePanelTex = loadTextureFromImage(renderer, "assets/images/panel_score.png");
|
||||
if (scorePanelTex) SDL_SetTextureBlendMode(scorePanelTex, SDL_BLENDMODE_BLEND); break;
|
||||
case 8: statisticsPanelTex = loadTextureFromImage(renderer, "assets/images/statistics_panel.png");
|
||||
if (statisticsPanelTex) SDL_SetTextureBlendMode(statisticsPanelTex, SDL_BLENDMODE_BLEND); break;
|
||||
case 9: nextPanelTex = loadTextureFromImage(renderer, "assets/images/next_panel.png");
|
||||
if (nextPanelTex) SDL_SetTextureBlendMode(nextPanelTex, SDL_BLENDMODE_BLEND); break;
|
||||
case 10: SoundEffectManager::instance().init(); g_loadedTasks.fetch_add(1); break;
|
||||
|
||||
// Audio loading steps
|
||||
default: {
|
||||
const std::vector<std::string> audioIds = {"clear_line","nice_combo","you_fire","well_played","keep_that_ryhtm","great_move","smooth_clear","impressive","triple_strike","amazing","you_re_unstoppable","boom_tetris","wonderful","lets_go","hard_drop","new_level"};
|
||||
size_t audioIdx = step - 11;
|
||||
if (audioIdx < audioIds.size()) {
|
||||
std::string id = audioIds[audioIdx];
|
||||
std::string basePath = "assets/music/" + (id == "hard_drop" ? "hard_drop_001" : id);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_currentLoadingMutex);
|
||||
g_currentLoadingFile = basePath;
|
||||
}
|
||||
std::string resolved = AssetPath::resolveWithExtensions(basePath, { ".wav", ".mp3" });
|
||||
if (!resolved.empty()) {
|
||||
SoundEffectManager::instance().loadSound(id, resolved);
|
||||
}
|
||||
g_loadedTasks.fetch_add(1);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_currentLoadingMutex);
|
||||
g_currentLoadingFile.clear();
|
||||
}
|
||||
} else {
|
||||
// All done
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false; // More steps remaining
|
||||
};
|
||||
// Loading is now handled by AssetLoader + LoadingManager.
|
||||
// Old incremental lambda removed; use LoadingManager to queue texture loads and
|
||||
// perform a single step per frame. Non-texture initialization (fonts, SFX)
|
||||
// is performed on the first loading frame below when the loader is started.
|
||||
|
||||
Game game(startLevelSelection);
|
||||
// Apply global gravity speed multiplier from config
|
||||
@ -1261,9 +1170,10 @@ int main(int, char **)
|
||||
currentTrackLoading = Audio::instance().getLoadedTrackCount();
|
||||
if (Audio::instance().isLoadingComplete() || (totalTracks > 0 && currentTrackLoading >= totalTracks)) {
|
||||
Audio::instance().shuffle();
|
||||
if (musicEnabled) {
|
||||
Audio::instance().start();
|
||||
}
|
||||
// Defer starting playback until the app has entered the Menu/Playing state.
|
||||
// Actual playback is started below when `musicLoaded` is observed and
|
||||
// the state is Menu or Playing (so the user doesn't hear music while
|
||||
// still on the Loading screen).
|
||||
musicLoaded = true;
|
||||
}
|
||||
}
|
||||
@ -1300,10 +1210,154 @@ int main(int, char **)
|
||||
}
|
||||
else if (state == AppState::Loading)
|
||||
{
|
||||
// Execute one loading step per frame on main thread
|
||||
static int queuedTextureCount = 0;
|
||||
// Execute one loading step per frame on main thread via LoadingManager
|
||||
if (g_loadingStarted.load() && !g_loadingComplete.load()) {
|
||||
if (performLoadingStep()) {
|
||||
g_loadingComplete.store(true);
|
||||
static bool queuedTextures = false;
|
||||
static std::vector<std::string> queuedPaths;
|
||||
if (!queuedTextures) {
|
||||
queuedTextures = true;
|
||||
// Initialize counters and clear previous errors
|
||||
constexpr int baseTasks = 25; // keep same budget as before
|
||||
g_totalLoadingTasks.store(baseTasks);
|
||||
g_loadedTasks.store(0);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_assetLoadErrorsMutex);
|
||||
g_assetLoadErrors.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_currentLoadingMutex);
|
||||
g_currentLoadingFile.clear();
|
||||
}
|
||||
|
||||
// Initialize background music loading
|
||||
Audio::instance().init();
|
||||
totalTracks = 0;
|
||||
for (int i = 1; i <= 100; ++i) {
|
||||
char base[128];
|
||||
std::snprintf(base, sizeof(base), "assets/music/music%03d", i);
|
||||
std::string path = AssetPath::resolveWithExtensions(base, { ".mp3" });
|
||||
if (path.empty()) break;
|
||||
Audio::instance().addTrackAsync(path);
|
||||
totalTracks++;
|
||||
}
|
||||
g_totalLoadingTasks.store(baseTasks + totalTracks);
|
||||
if (totalTracks > 0) {
|
||||
Audio::instance().startBackgroundLoading();
|
||||
musicLoadingStarted = true;
|
||||
} else {
|
||||
musicLoaded = true;
|
||||
}
|
||||
|
||||
// Initialize fonts (synchronous, cheap)
|
||||
pixelFont.init(AssetPath::resolveWithBase("assets/fonts/Orbitron.ttf"), 22);
|
||||
g_loadedTasks.fetch_add(1);
|
||||
font.init(AssetPath::resolveWithBase("assets/fonts/Exo2.ttf"), 20);
|
||||
g_loadedTasks.fetch_add(1);
|
||||
|
||||
// Queue UI textures for incremental loading
|
||||
queuedPaths = {
|
||||
"assets/images/spacetris.png",
|
||||
"assets/images/spacetris.png", // small logo uses same source
|
||||
"assets/images/main_screen.png",
|
||||
"assets/images/blocks90px_001.bmp",
|
||||
"assets/images/panel_score.png",
|
||||
"assets/images/statistics_panel.png",
|
||||
"assets/images/next_panel.png"
|
||||
};
|
||||
for (auto &p : queuedPaths) {
|
||||
loadingManager.queueTexture(p);
|
||||
}
|
||||
queuedTextureCount = static_cast<int>(queuedPaths.size());
|
||||
|
||||
// Initialize sound effects manager (counts as a loaded task)
|
||||
SoundEffectManager::instance().init();
|
||||
g_loadedTasks.fetch_add(1);
|
||||
|
||||
// Load small set of voice/audio SFX synchronously for now (keeps behavior)
|
||||
const std::vector<std::string> audioIds = {"clear_line","nice_combo","you_fire","well_played","keep_that_ryhtm","great_move","smooth_clear","impressive","triple_strike","amazing","you_re_unstoppable","boom_tetris","wonderful","lets_go","hard_drop","new_level"};
|
||||
for (const auto &id : audioIds) {
|
||||
std::string basePath = "assets/music/" + (id == "hard_drop" ? "hard_drop_001" : id);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_currentLoadingMutex);
|
||||
g_currentLoadingFile = basePath;
|
||||
}
|
||||
std::string resolved = AssetPath::resolveWithExtensions(basePath, { ".wav", ".mp3" });
|
||||
if (!resolved.empty()) {
|
||||
SoundEffectManager::instance().loadSound(id, resolved);
|
||||
}
|
||||
g_loadedTasks.fetch_add(1);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_currentLoadingMutex);
|
||||
g_currentLoadingFile.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform a single texture loading step via LoadingManager
|
||||
bool texturesDone = loadingManager.update();
|
||||
if (texturesDone) {
|
||||
// Bind loaded textures into the runtime context
|
||||
logoTex = assetLoader.getTexture("assets/images/spacetris.png");
|
||||
logoSmallTex = assetLoader.getTexture("assets/images/spacetris.png");
|
||||
mainScreenTex = assetLoader.getTexture("assets/images/main_screen.png");
|
||||
blocksTex = assetLoader.getTexture("assets/images/blocks90px_001.bmp");
|
||||
scorePanelTex = assetLoader.getTexture("assets/images/panel_score.png");
|
||||
statisticsPanelTex = assetLoader.getTexture("assets/images/statistics_panel.png");
|
||||
nextPanelTex = assetLoader.getTexture("assets/images/next_panel.png");
|
||||
|
||||
auto ensureTextureSize = [&](SDL_Texture* tex, int& outW, int& outH) {
|
||||
if (!tex) return;
|
||||
if (outW > 0 && outH > 0) return;
|
||||
float w = 0.0f, h = 0.0f;
|
||||
if (SDL_GetTextureSize(tex, &w, &h)) {
|
||||
outW = static_cast<int>(std::lround(w));
|
||||
outH = static_cast<int>(std::lround(h));
|
||||
}
|
||||
};
|
||||
|
||||
// If a texture was created by AssetLoader (not legacy IMG_Load),
|
||||
// its stored width/height may still be 0. Query the real size.
|
||||
ensureTextureSize(logoSmallTex, logoSmallW, logoSmallH);
|
||||
ensureTextureSize(mainScreenTex, mainScreenW, mainScreenH);
|
||||
|
||||
// Fallback: if any critical UI texture failed to load via AssetLoader,
|
||||
// load synchronously using the legacy helper so the Menu can render.
|
||||
auto legacyLoad = [&](const std::string& p, SDL_Texture*& outTex, int* outW = nullptr, int* outH = nullptr) {
|
||||
if (!outTex) {
|
||||
outTex = loadTextureFromImage(renderer, p, outW, outH);
|
||||
}
|
||||
};
|
||||
|
||||
legacyLoad("assets/images/spacetris.png", logoTex);
|
||||
legacyLoad("assets/images/spacetris.png", logoSmallTex, &logoSmallW, &logoSmallH);
|
||||
legacyLoad("assets/images/main_screen.png", mainScreenTex, &mainScreenW, &mainScreenH);
|
||||
legacyLoad("assets/images/blocks90px_001.bmp", blocksTex);
|
||||
legacyLoad("assets/images/panel_score.png", scorePanelTex);
|
||||
legacyLoad("assets/images/statistics_panel.png", statisticsPanelTex);
|
||||
legacyLoad("assets/images/next_panel.png", nextPanelTex);
|
||||
|
||||
// If blocks texture failed, create fallback and count it as loaded
|
||||
if (!blocksTex) {
|
||||
blocksTex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 630, 90);
|
||||
SDL_SetRenderTarget(renderer, blocksTex);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
for (int i = 0; i < PIECE_COUNT; ++i) {
|
||||
SDL_Color c = COLORS[i + 1];
|
||||
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
|
||||
SDL_FRect rect{(float)(i * 90), 0, 90, 90};
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
}
|
||||
SDL_SetRenderTarget(renderer, nullptr);
|
||||
// Do not update global task counter here; textures are accounted
|
||||
// for via the LoadingManager/AssetLoader progress below.
|
||||
}
|
||||
|
||||
// Mark loading complete when music also loaded
|
||||
if (musicLoaded) {
|
||||
g_loadingComplete.store(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1311,6 +1365,13 @@ int main(int, char **)
|
||||
const int totalTasks = g_totalLoadingTasks.load(std::memory_order_acquire);
|
||||
const int musicDone = std::min(totalTracks, currentTrackLoading);
|
||||
int doneTasks = g_loadedTasks.load(std::memory_order_acquire) + musicDone;
|
||||
// Include texture progress reported by the LoadingManager/AssetLoader
|
||||
if (queuedTextureCount > 0) {
|
||||
float texProg = loadingManager.getProgress();
|
||||
int texDone = static_cast<int>(std::floor(texProg * queuedTextureCount + 0.5f));
|
||||
if (texDone > queuedTextureCount) texDone = queuedTextureCount;
|
||||
doneTasks += texDone;
|
||||
}
|
||||
if (doneTasks > totalTasks) doneTasks = totalTasks;
|
||||
if (totalTasks > 0) {
|
||||
loadingProgress = std::min(1.0, double(doneTasks) / double(totalTasks));
|
||||
@ -1696,8 +1757,53 @@ int main(int, char **)
|
||||
}
|
||||
break;
|
||||
case AppState::Menu:
|
||||
// Delegate full menu rendering to MenuState object now
|
||||
menuState->render(renderer, logicalScale, logicalVP);
|
||||
// Ensure overlay is loaded (drawn after highscores so it sits above that layer)
|
||||
if (!mainScreenTex) {
|
||||
mainScreenTex = loadTextureFromImage(renderer, "assets/images/main_screen.png", &mainScreenW, &mainScreenH);
|
||||
}
|
||||
|
||||
// Render menu content that should appear *behind* the overlay (highscores/logo).
|
||||
// Bottom buttons are drawn separately on top.
|
||||
if (menuState) {
|
||||
menuState->drawMainButtonNormally = false;
|
||||
menuState->render(renderer, logicalScale, logicalVP);
|
||||
}
|
||||
|
||||
// Draw main screen overlay above highscores
|
||||
if (mainScreenTex) {
|
||||
SDL_SetRenderViewport(renderer, nullptr);
|
||||
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
||||
float texW = mainScreenW > 0 ? static_cast<float>(mainScreenW) : 0.0f;
|
||||
float texH = mainScreenH > 0 ? static_cast<float>(mainScreenH) : 0.0f;
|
||||
if (texW <= 0.0f || texH <= 0.0f) {
|
||||
float iwf = 0.0f, ihf = 0.0f;
|
||||
if (!SDL_GetTextureSize(mainScreenTex, &iwf, &ihf)) {
|
||||
iwf = ihf = 0.0f;
|
||||
}
|
||||
texW = iwf;
|
||||
texH = ihf;
|
||||
}
|
||||
if (texW > 0.0f && texH > 0.0f) {
|
||||
const float drawH = static_cast<float>(winH);
|
||||
const float scale = drawH / texH;
|
||||
const float drawW = texW * scale;
|
||||
SDL_FRect dst{
|
||||
(winW - drawW) * 0.5f,
|
||||
0.0f,
|
||||
drawW,
|
||||
drawH
|
||||
};
|
||||
SDL_SetTextureBlendMode(mainScreenTex, SDL_BLENDMODE_BLEND);
|
||||
SDL_RenderTexture(renderer, mainScreenTex, nullptr, &dst);
|
||||
}
|
||||
SDL_SetRenderViewport(renderer, &logicalVP);
|
||||
SDL_SetRenderScale(renderer, logicalScale, logicalScale);
|
||||
}
|
||||
|
||||
// Draw bottom menu buttons above the overlay
|
||||
if (menuState) {
|
||||
menuState->renderMainButtonTop(renderer, logicalScale, logicalVP);
|
||||
}
|
||||
break;
|
||||
case AppState::Options:
|
||||
optionsState->render(renderer, logicalScale, logicalVP);
|
||||
@ -1924,43 +2030,6 @@ int main(int, char **)
|
||||
HelpOverlay::Render(renderer, pixelFont, LOGICAL_W, LOGICAL_H, contentOffsetX, contentOffsetY);
|
||||
}
|
||||
|
||||
// Top-layer overlay: render `mainScreenTex` above all other layers when in Menu
|
||||
if (state == AppState::Menu && mainScreenTex) {
|
||||
SDL_SetRenderViewport(renderer, nullptr);
|
||||
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
||||
float texW = mainScreenW > 0 ? static_cast<float>(mainScreenW) : 0.0f;
|
||||
float texH = mainScreenH > 0 ? static_cast<float>(mainScreenH) : 0.0f;
|
||||
if (texW <= 0.0f || texH <= 0.0f) {
|
||||
float iwf = 0.0f, ihf = 0.0f;
|
||||
if (SDL_GetTextureSize(mainScreenTex, &iwf, &ihf) != 0) {
|
||||
iwf = ihf = 0.0f;
|
||||
}
|
||||
texW = iwf;
|
||||
texH = ihf;
|
||||
}
|
||||
if (texW > 0.0f && texH > 0.0f) {
|
||||
const float drawH = static_cast<float>(winH);
|
||||
const float scale = drawH / texH;
|
||||
const float drawW = texW * scale;
|
||||
SDL_FRect dst{
|
||||
(winW - drawW) * 0.5f,
|
||||
0.0f,
|
||||
drawW,
|
||||
drawH
|
||||
};
|
||||
SDL_SetTextureBlendMode(mainScreenTex, SDL_BLENDMODE_BLEND);
|
||||
SDL_RenderTexture(renderer, mainScreenTex, nullptr, &dst);
|
||||
}
|
||||
// Restore logical viewport/scale and draw the main PLAY button above the overlay
|
||||
SDL_SetRenderViewport(renderer, &logicalVP);
|
||||
SDL_SetRenderScale(renderer, logicalScale, logicalScale);
|
||||
if (menuState) {
|
||||
menuState->drawMainButtonNormally = false; // ensure it isn't double-drawn
|
||||
menuState->renderMainButtonTop(renderer, logicalScale, logicalVP);
|
||||
menuState->drawMainButtonNormally = true;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_RenderPresent(renderer);
|
||||
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user