From 7ab53f1a2357e7c67839509f05834fa7e61164a9 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 9 Nov 2024 22:34:40 +0000 Subject: [PATCH] Add a threshold for skipping the loading screen --- Source/engine/palette.cpp | 24 +++---- Source/engine/palette.h | 6 +- Source/interfac.cpp | 129 ++++++++++++++++++++++---------------- Source/options.cpp | 2 + Source/options.h | 7 +++ 5 files changed, 99 insertions(+), 69 deletions(-) diff --git a/Source/engine/palette.cpp b/Source/engine/palette.cpp index f4c5bc31c6f..6ee05fd2fea 100644 --- a/Source/engine/palette.cpp +++ b/Source/engine/palette.cpp @@ -296,15 +296,15 @@ int UpdateGamma(int gamma) return 130 - *sgOptions.Graphics.gammaCorrection; } -void SetFadeLevel(int fadeval, bool updateHardwareCursor) +void SetFadeLevel(int fadeval, bool updateHardwareCursor, const std::array &srcPalette) { if (HeadlessMode) return; for (int i = 0; i < 256; i++) { - system_palette[i].r = (fadeval * logical_palette[i].r) / 256; - system_palette[i].g = (fadeval * logical_palette[i].g) / 256; - system_palette[i].b = (fadeval * logical_palette[i].b) / 256; + system_palette[i].r = (fadeval * srcPalette[i].r) / 256; + system_palette[i].g = (fadeval * srcPalette[i].g) / 256; + system_palette[i].b = (fadeval * srcPalette[i].b) / 256; #if SDL_VERSION_ATLEAST(2, 0, 0) system_palette[i].a = SDL_ALPHA_OPAQUE; #endif @@ -323,14 +323,14 @@ void BlackPalette() SetFadeLevel(0, /*updateHardwareCursor=*/false); } -void PaletteFadeIn(int fr) +void PaletteFadeIn(int fr, const std::array &srcPalette) { if (HeadlessMode) return; if (demo::IsRunning()) fr = 0; - ApplyGamma(logical_palette, orig_palette, 256); + ApplyGamma(logical_palette, srcPalette, 256); if (fr > 0) { const uint32_t tc = SDL_GetTicks(); @@ -339,7 +339,7 @@ void PaletteFadeIn(int fr) for (uint32_t i = 0; i < 256; i = fr * (SDL_GetTicks() - tc) / 50) { if (i != prevFadeValue) { // We can skip hardware cursor update for fade level 0 (everything is black). - SetFadeLevel(i, /*updateHardwareCursor=*/i != 0u); + SetFadeLevel(i, /*updateHardwareCursor=*/i != 0u, logical_palette); prevFadeValue = i; } BltFast(nullptr, nullptr); @@ -352,12 +352,12 @@ void PaletteFadeIn(int fr) RenderPresent(); } - logical_palette = orig_palette; + logical_palette = srcPalette; sgbFadedIn = true; } -void PaletteFadeOut(int fr) +void PaletteFadeOut(int fr, const std::array &srcPalette) { if (!sgbFadedIn || HeadlessMode) return; @@ -370,15 +370,15 @@ void PaletteFadeOut(int fr) uint32_t prevFadeValue = 0; for (uint32_t i = 0; i < 256; i = fr * (SDL_GetTicks() - tc) / 50) { if (i != prevFadeValue) { - SetFadeLevel(256 - i); + SetFadeLevel(256 - i, /*updateHardwareCursor=*/true, srcPalette); prevFadeValue = i; } BltFast(nullptr, nullptr); RenderPresent(); } - SetFadeLevel(0); + SetFadeLevel(0, /*updateHardwareCursor=*/true, srcPalette); } else { - SetFadeLevel(0); + SetFadeLevel(0, /*updateHardwareCursor=*/true, srcPalette); BltFast(nullptr, nullptr); RenderPresent(); } diff --git a/Source/engine/palette.h b/Source/engine/palette.h index 7989f3af10a..a9cc0959ac0 100644 --- a/Source/engine/palette.h +++ b/Source/engine/palette.h @@ -60,17 +60,17 @@ void ApplyGamma(std::array &dst, const std::array &srcPalette = logical_palette); /** * @brief Fade screen from black * @param fr Steps per 50ms */ -void PaletteFadeIn(int fr); +void PaletteFadeIn(int fr, const std::array &srcPalette = orig_palette); /** * @brief Fade screen to black * @param fr Steps per 50ms */ -void PaletteFadeOut(int fr); +void PaletteFadeOut(int fr, const std::array &srcPalette = logical_palette); void palette_update_caves(); void palette_update_crypt(); void palette_update_hive(); diff --git a/Source/interfac.cpp b/Source/interfac.cpp index a43cac91441..33777b571fb 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -25,6 +25,7 @@ #include "loadsave.h" #include "pfile.h" #include "plrmsg.h" +#include "utils/log.hpp" #include "utils/sdl_geometry.h" #include "utils/sdl_thread.h" @@ -403,59 +404,89 @@ void DoLoad(interface_mode uMsg) } struct { - uint32_t delayStart; + uint32_t loadStartedAt; EventHandler prevHandler; + bool skipRendering; bool done; + uint32_t drawnProgress; std::array palette; } ProgressEventHandlerState; +void InitRendering() +{ + // Blit the background once and then free it. + DrawCutsceneBackground(); + if (RenderDirectlyToOutputSurface && PalSurface != nullptr) { + // Render into all the backbuffers if there are multiple. + const void *initialPixels = PalSurface->pixels; + if (DiabloUiSurface() == PalSurface) + BltFast(nullptr, nullptr); + RenderPresent(); + while (PalSurface->pixels != initialPixels) { + DrawCutsceneBackground(); + if (DiabloUiSurface() == PalSurface) + BltFast(nullptr, nullptr); + RenderPresent(); + } + } + FreeCutsceneBackground(); + + // The loading thread sets `orig_palette`, so we make sure to use + // our own palette for the fade-in. + PaletteFadeIn(8, ProgressEventHandlerState.palette); +} + +void CheckShouldSkipRendering() +{ + if (!ProgressEventHandlerState.skipRendering) return; + const bool shouldSkip = ProgressEventHandlerState.loadStartedAt + *sgOptions.Gameplay.skipLoadingScreenThresholdMs > SDL_GetTicks(); + if (shouldSkip) return; + ProgressEventHandlerState.skipRendering = false; + if (!HeadlessMode) InitRendering(); +} + void ProgressEventHandler(const SDL_Event &event, uint16_t modState) { DisableInputEventHandler(event, modState); if (!IsCustomEvent(event.type)) return; - static uint32_t drawnProgress; - drawnProgress = 0; - const interface_mode customEvent = GetCustomEvent(event.type); switch (customEvent) { case WM_PROGRESS: - if (!HeadlessMode && drawnProgress != sgdwProgress) { + if (!HeadlessMode && ProgressEventHandlerState.drawnProgress != sgdwProgress && !ProgressEventHandlerState.skipRendering) { DrawCutsceneForeground(); - drawnProgress = sgdwProgress; + ProgressEventHandlerState.drawnProgress = sgdwProgress; } break; case WM_ERROR: app_fatal(*static_cast(event.user.data1)); break; case WM_DONE: { - // We may have loaded a new palette. - // Temporarily switch back to the load screen palette for fade out. - std::array prevPalette; - if (!HeadlessMode) { - prevPalette = orig_palette; - orig_palette = ProgressEventHandlerState.palette; - ApplyGamma(logical_palette, orig_palette, 256); - } - NewCursor(CURSOR_HAND); - - if (!HeadlessMode) { - assert(ghMainWnd); - - if (RenderDirectlyToOutputSurface && PalSurface != nullptr) { - // Ensure that all back buffers have the full progress bar. - const void *initialPixels = PalSurface->pixels; - do { - DrawCutsceneForeground(); - if (DiabloUiSurface() == PalSurface) - BltFast(nullptr, nullptr); - RenderPresent(); - } while (PalSurface->pixels != initialPixels); + if (!ProgressEventHandlerState.skipRendering) { + NewCursor(CURSOR_HAND); + + if (!HeadlessMode) { + assert(ghMainWnd); + + if (RenderDirectlyToOutputSurface && PalSurface != nullptr) { + // The loading thread sets `orig_palette`, so we make sure to use + // our own palette for drawing the foreground. + ApplyGamma(logical_palette, ProgressEventHandlerState.palette, 256); + + // Ensure that all back buffers have the full progress bar. + const void *initialPixels = PalSurface->pixels; + do { + DrawCutsceneForeground(); + if (DiabloUiSurface() == PalSurface) + BltFast(nullptr, nullptr); + RenderPresent(); + } while (PalSurface->pixels != initialPixels); + } + + // The loading thread sets `orig_palette`, so we make sure to use + // our own palette for the fade-out. + PaletteFadeOut(8, ProgressEventHandlerState.palette); } - - PaletteFadeOut(8); - orig_palette = prevPalette; - ApplyGamma(logical_palette, orig_palette, 256); } [[maybe_unused]] EventHandler prevHandler = SetEventHandler(ProgressEventHandlerState.prevHandler); @@ -465,7 +496,7 @@ void ProgressEventHandler(const SDL_Event &event, uint16_t modState) Player &myPlayer = *MyPlayer; NetSendCmdLocParam2(true, CMD_PLAYER_JOINLEVEL, myPlayer.position.tile, myPlayer.plrlevel, myPlayer.plrIsOnSetLevel ? 1 : 0); - DelayPlrMessages(SDL_GetTicks() - ProgressEventHandlerState.delayStart); + DelayPlrMessages(SDL_GetTicks() - ProgressEventHandlerState.loadStartedAt); if (gbSomebodyWonGameKludge && myPlayer.isOnLevel(16)) { PrepDoEnding(); @@ -552,9 +583,11 @@ void ShowProgress(interface_mode uMsg) IsProgress = true; gbSomebodyWonGameKludge = false; - ProgressEventHandlerState.delayStart = SDL_GetTicks(); + ProgressEventHandlerState.loadStartedAt = SDL_GetTicks(); ProgressEventHandlerState.prevHandler = SetEventHandler(ProgressEventHandler); + ProgressEventHandlerState.skipRendering = true; ProgressEventHandlerState.done = false; + ProgressEventHandlerState.drawnProgress = 0; #ifndef USE_SDL1 DeactivateVirtualGamepad(); @@ -573,42 +606,30 @@ void ShowProgress(interface_mode uMsg) BlackPalette(); - // Blit the background once and then free it. + // Always load the background (even if we end up skipping rendering it). + // This is because the MPQ archive can only be read by a single thread at a time. LoadCutsceneBackground(uMsg); - DrawCutsceneBackground(); + + // Save the palette at this point because the loading process may replace it. ProgressEventHandlerState.palette = orig_palette; - if (RenderDirectlyToOutputSurface && PalSurface != nullptr) { - // Render into all the backbuffers if there are multiple. - const void *initialPixels = PalSurface->pixels; - if (DiabloUiSurface() == PalSurface) - BltFast(nullptr, nullptr); - RenderPresent(); - while (PalSurface->pixels != initialPixels) { - DrawCutsceneBackground(); - if (DiabloUiSurface() == PalSurface) - BltFast(nullptr, nullptr); - RenderPresent(); - } - } - FreeCutsceneBackground(); } // Begin loading static interface_mode loadTarget; loadTarget = uMsg; SdlThread loadThread = SdlThread([]() { + const uint32_t start = SDL_GetTicks(); DoLoad(loadTarget); + LogVerbose("Load thread finished in {}ms", SDL_GetTicks() - start); }); - if (!HeadlessMode) { - PaletteFadeIn(8); - } - while (true) { + CheckShouldSkipRendering(); SDL_Event event; // We use the real `SDL_PollEvent` here instead of `FetchEvent` // to process real events rather than the recorded ones in demo mode. while (SDL_PollEvent(&event)) { + CheckShouldSkipRendering(); if (event.type != SDL_QUIT) { HandleMessage(event, SDL_GetModState()); } diff --git a/Source/options.cpp b/Source/options.cpp index 06e52eedd8d..8ef40efc168 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -966,6 +966,7 @@ GameplayOptions::GameplayOptions() { FloatingNumbers::Random, N_("Random Angles") }, { FloatingNumbers::Vertical, N_("Vertical Only") }, }) + , skipLoadingScreenThresholdMs("Skip loading screen threshold, ms", OptionEntryFlags::Invisible, "", "", 0) { grabInput.SetValueChangedCallback(OptionGrabInputChanged); experienceBar.SetValueChangedCallback(OptionExperienceBarChanged); @@ -1012,6 +1013,7 @@ std::vector GameplayOptions::GetEntries() &adriaRefillsMana, &grabInput, &pauseOnFocusLoss, + &skipLoadingScreenThresholdMs, }; } diff --git a/Source/options.h b/Source/options.h index 3ae6c1f51da..e1af62395ca 100644 --- a/Source/options.h +++ b/Source/options.h @@ -599,6 +599,13 @@ struct GameplayOptions : OptionCategoryBase { OptionEntryInt numFullRejuPotionPickup; /** @brief Enable floating numbers. */ OptionEntryEnum enableFloatingNumbers; + + /** + * @brief If loading takes less than this value, skips displaying the loading screen. + * + * Advanced option, not displayed in the UI. + */ + OptionEntryInt skipLoadingScreenThresholdMs; }; struct ControllerOptions : OptionCategoryBase {