diff --git a/files.cmake b/files.cmake index 6fc4762be3..675ba70fdc 100644 --- a/files.cmake +++ b/files.cmake @@ -1436,6 +1436,7 @@ set(DUSK_FILES src/dusk/gamepad_color.cpp src/dusk/globals.cpp src/dusk/gyro.cpp + src/dusk/hq_minimap.cpp src/dusk/imgui/ImGuiActorSpawner.cpp src/dusk/imgui/ImGuiBloomWindow.cpp src/dusk/imgui/ImGuiBloomWindow.hpp diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 0c26e77a53..466335eae9 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -38,7 +38,7 @@ void JFWDisplay::ctor_subroutine(bool enableAlpha) { mTickRate = 0; mCombinationRatio = 0.0f; field_0x30 = 0; - field_0x2c = OSGetTick(); + field_0x2c = DUSK_IF_ELSE(static_cast(OSGetNativeTime()), OSGetTick()); field_0x34 = 0; field_0x48 = 0; field_0x4a = 0; @@ -258,7 +258,7 @@ void JFWDisplay::beginRender() { waitForTick(mTickRate, mFrameRate); JUTVideo::getManager()->waitRetraceIfNeed(); - OSTick tick = OSGetTick(); + OSTick tick = DUSK_IF_ELSE(static_cast(OSGetNativeTime()), OSGetTick()); field_0x30 = tick - field_0x2c; field_0x2c = tick; field_0x34 = field_0x2c - JUTVideo::getVideoLastTick(); @@ -371,7 +371,7 @@ constexpr auto FRAME_PERIOD = std::chrono::duration_cast(sleepTime) / static_cast(targetNs)); } @@ -381,15 +381,12 @@ static void waitForTick(u32 p1, u16 p2) { #if TARGET_PC static Limiter limiter; - if (dusk::frame_interp::is_enabled() && !dusk::getTransientSettings().skipFrameRateLimit) { - dusk::frameUsagePct = 0.f; - return; + if (dusk::frame_interp::is_enabled() || dusk::getTransientSettings().turboMode) { + limiter.Reset(); + dusk::frameUsagePct = 0.f; + return; } - if (dusk::getTransientSettings().skipFrameRateLimit) { - p1 = OS_TIMER_CLOCK / 120; - } - if (fopOvlpM_IsPeek() && dusk::getTransientSettings().stateShareLoadActive) { return; } @@ -399,7 +396,6 @@ static void waitForTick(u32 p1, u16 p2) { if (p1 != 0) { #if TARGET_PC - static Limiter limiter; waitPrecise(limiter, static_cast(OSTicksToMicroseconds(p1)) * 1000ULL); #else static OSTime nextTick = OSGetTime(); @@ -413,7 +409,6 @@ static void waitForTick(u32 p1, u16 p2) { } else { u32 uVar1 = (p2 == 0) ? 1 : p2; #if TARGET_PC - static Limiter limiter; waitPrecise(limiter, static_cast((RETRACE_PERIOD * uVar1).count())); #else static u32 nextCount = VIGetRetraceCount(); diff --git a/libs/JSystem/src/JUtility/JUTVideo.cpp b/libs/JSystem/src/JUtility/JUTVideo.cpp index 2135c8fc22..b9a51ab6f3 100644 --- a/libs/JSystem/src/JUtility/JUTVideo.cpp +++ b/libs/JSystem/src/JUtility/JUTVideo.cpp @@ -42,7 +42,7 @@ JUTVideo::JUTVideo(GXRenderModeObj const* param_0) { mRetraceCount = VIGetRetraceCount(); field_0x10 = 1; field_0x18 = 0; - sVideoLastTick = OSGetTick(); + sVideoLastTick = DUSK_IF_ELSE(static_cast(OSGetNativeTime()), OSGetTick()); sVideoInterval = 670000; mPreRetraceCallback = VISetPreRetraceCallback(preRetraceProc); mPostRetraceCallback = VISetPostRetraceCallback(postRetraceProc); @@ -68,7 +68,7 @@ void JUTVideo::preRetraceProc(u32 retrace_count) { (*sManager->mPreCallback)(retrace_count); } - OSTick tick = OSGetTick(); + OSTick tick = DUSK_IF_ELSE(static_cast(OSGetNativeTime()), OSGetTick()); sVideoInterval = tick - sVideoLastTick; sVideoLastTick = tick; diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index e6f155a989..f9d77837f9 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -2346,7 +2346,7 @@ void dScnKy_env_light_c::setLight() { u8 next_pal_end_id; #if TARGET_PC const f32 deltaTime = dusk::game_clock::consume_interval(this); - timeScale = deltaTime / dusk::game_clock::period_for_original_frames(1.0f); + timeScale = deltaTime / dusk::game_clock::kSimPeriod; #endif setLight_palno_get(&g_env_light.PrevCol, &g_env_light.UseCol, &g_env_light.wether_pat0, &g_env_light.wether_pat1, &prev_pal_start_id, &prev_pal_end_id, diff --git a/src/d/d_kyeff.cpp b/src/d/d_kyeff.cpp index 984478f74d..1ba4ffa96f 100644 --- a/src/d/d_kyeff.cpp +++ b/src/d/d_kyeff.cpp @@ -118,7 +118,7 @@ static int dKyeff_Create(kankyo_class* i_this) { if (strcmp(dComIfGp_getStartStageName(), "Name") == 0) { camera_process_class* camera = dComIfGp_getCamera(0); - OSTime time = OSGetTime(); + OSTime time = DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()); OSTicksToCalendarTime(time, &calendar); g_env_light.global_wind_influence.vec.x = 1.0f; diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index 08591007ec..41d5108e32 100644 --- a/src/d/d_map_path.cpp +++ b/src/d/d_map_path.cpp @@ -16,23 +16,14 @@ #ifdef TARGET_PC #include "dusk/settings.h" +#include "dusk/hq_minimap.hpp" #include "m_Do/m_Do_graphic.h" #include #include #include -#include #include -#include #include -#include -#include - -constexpr u16 kMapIconResolutionMultiplier = 4; -constexpr u16 kMapImageSide = 16 * kMapIconResolutionMultiplier; -constexpr u32 kMapImageTotalPixels = kMapImageSide * kMapImageSide; - -typedef std::function PaintI8Fn; u16 scaled_map_axis(u16 value, f32 scale) { const auto scaledValue = @@ -59,27 +50,6 @@ aurora::Vec2 map_render_size_for(u16 width, u16 height) { scaled_map_axis(height, irScaleY * hudScale), }; } - -void paint_i8(std::span dst, size_t width, PaintI8Fn paint) { - const auto blocksAcross = width >> 3; - - for (size_t i = 0; i < dst.size(); i++) { - // 8x4 block swizzling for I8 - const auto blockIdx = i >> 5; - const auto localIdx = i & 31; - - const auto blockY = blockIdx / blocksAcross; - const auto blockX = blockIdx % blocksAcross; - - const auto localY = localIdx >> 3; - const auto localX = localIdx & 7; - - const auto x = (blockX << 3) + localX; - const auto y = (blockY << 2) + localY; - - dst[i] = paint(x, y); - } -} #endif void dMpath_n::dTexObjAggregate_c::create() { @@ -100,106 +70,11 @@ void dMpath_n::dTexObjAggregate_c::create() { JUT_ASSERT(72, image != NULL); JUT_ASSERT(73, image->minFilter == GX_NEAR); JUT_ASSERT(74, image->magFilter == GX_NEAR); + IF_DUSK(dusk::hq_minimap::register_pointer(data[lp1], reinterpret_cast(image) + image->imageOffset)); mDoLib_setResTimgObj(image, mp_texObj[lp1], 0, NULL); } -#if TARGET_PC - static bool hqTexsDrawn = false; - - static u8 hqCircleData[kMapImageTotalPixels]; - static u8 hqCircleAltData[kMapImageTotalPixels]; - static u8 hqNijumaruData[kMapImageTotalPixels]; - static u8 hqEnterData[kMapImageTotalPixels]; - static u8 hqTryForceData[kMapImageTotalPixels]; - - if (!hqTexsDrawn) { - constexpr auto center = kMapImageSide / 2.0f; - constexpr auto radiusSq = center * center; - - // 6: map_icon_circle16x16_4i.bti - simple circle - paint_i8(std::span{hqCircleData}, kMapImageSide, [=](auto x, auto y) { - const auto dx = (x + 0.5f) - center; - const auto dy = (y + 0.5f) - center; - return (dx * dx + dy * dy < radiusSq) ? 0x11 : 0; - }); - - // 4: im_map_icon_circle_4i.bti - outlined circle - paint_i8(std::span{hqCircleAltData}, kMapImageSide, [=](auto x, auto y) { - constexpr auto innerRadius = kMapImageSide * 3.0f / 8.0f; - constexpr auto innerRadiusSq = innerRadius * innerRadius; - - const auto dx = (x + 0.5f) - center; - const auto dy = (y + 0.5f) - center; - const auto dSq = dx * dx + dy * dy; - - return dSq < radiusSq ? (dSq < innerRadiusSq ? 0x22 : 0x11) : 0; - }); - - // 3: im_map_icon_nijumaru_4i.bti - concentric rings - paint_i8(std::span{hqNijumaruData}, kMapImageSide, [=](auto x, auto y) { - constexpr u8 nijumaruRings[] = {0x11, 0x22, 0x11, 0x11, 0x22, 0x22}; - - const auto dx = (x + 0.5f) - center; - const auto dy = (y + 0.5f) - center; - const auto dSq = dx * dx + dy * dy; - - if (dSq < radiusSq) { - const auto ringIndex = - static_cast(std::trunc(std::sqrt(dSq) / kMapImageSide * 12)); - return nijumaruRings[ringIndex]; - } - return u8{0}; - }); - - // 2: im_map_icon_enter_4i.bti - outlined octagram - paint_i8(std::span{hqEnterData}, kMapImageSide, [=](auto x, auto y) { - constexpr auto outlineWidth = kMapImageSide / 6.0f; - - const auto adx = std::abs((x + 0.5f) - center); - const auto ady = std::abs((y + 0.5f) - center); - const auto dist = - std::min(adx + ady, std::max(adx, ady) * std::numbers::sqrt2_v) - - kMapImageSide / 2.0f; - - return dist > 0.0f ? 0 : (dist > -outlineWidth ? 0x22 : 0x33); - }); - - // 5: im_map_icon_try_force_4i.bti - outlined circle with triangle - paint_i8(std::span{hqTryForceData}, kMapImageSide, [=](auto x, auto y) { - constexpr auto innerRadiusNorm = 5.0f / 12.0f; - constexpr auto innerRadius = kMapImageSide * innerRadiusNorm; - constexpr auto innerRadiusSq = innerRadius * innerRadius; - constexpr auto triRadius = kMapImageSide * innerRadiusNorm / 2.0f; - - const auto dx = (x + 0.5f) - center; - const auto dy = (y + 0.5f) - center; - const auto dSq = dx * dx + dy * dy; - const auto triSideDist = (std::numbers::sqrt3_v * std::abs(dx) - dy) * 0.5f; - const auto insideTri = std::max(dy, triSideDist) < triRadius; - - return insideTri ? 0x22 : (dSq < radiusSq ? (dSq < innerRadiusSq ? 0x33 : 0x22) : 0); - }); - - hqTexsDrawn = true; - } - - constexpr auto replacements = std::to_array >({ - {2, hqEnterData}, - {3, hqNijumaruData}, - {4, hqCircleAltData}, - {5, hqTryForceData}, - {6, hqCircleData}, - }); - - for (const auto& [idx, data] : replacements) { - JKR_DELETE(mp_texObj[idx]); - const auto texobj = JKR_NEW TGXTexObj(); - GXInitTexObj( - texobj, data, kMapImageSide, kMapImageSide, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE); - GXInitTexObjLOD(texobj, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); - mp_texObj[idx] = texobj; - } -#endif + IF_DUSK(dusk::hq_minimap::initialize_if_needed()); } void dMpath_n::dTexObjAggregate_c::remove() { diff --git a/src/d/d_menu_ring.cpp b/src/d/d_menu_ring.cpp index 3fe55c2b6c..f0f44db166 100644 --- a/src/d/d_menu_ring.cpp +++ b/src/d/d_menu_ring.cpp @@ -1798,7 +1798,7 @@ void dMenu_Ring_c::drawSelectItem() { if (field_0x674[i] != 0) { #if TARGET_PC mSelectItemSlideElapsed[i] += dusk::game_clock::consume_interval(this); - const f32 u = std::min(mSelectItemSlideElapsed[i] / dusk::game_clock::period_for_original_frames(10.0f), 1.0f); + const f32 u = std::min(mSelectItemSlideElapsed[i] / (dusk::game_clock::kSimPeriod * 10.0f), 1.0f); if (u >= 1.0f) { setSelectItemForce(i); } else { diff --git a/src/d/d_menu_window.cpp b/src/d/d_menu_window.cpp index 7873632265..eb67822899 100644 --- a/src/d/d_menu_window.cpp +++ b/src/d/d_menu_window.cpp @@ -123,7 +123,13 @@ public: #endif } - void setCaptureFlag() { mFlag = 1; } + void setCaptureFlag() { + mFlag = 1; + #ifdef TARGET_PC + dusk::frame_interp::request_presentation_sync(); + #endif + } + bool checkDraw() { return mFlag; } u8 getAlpha() { return mAlpha; } u8 getTopFlag() { return mTopFlag; } @@ -1092,10 +1098,6 @@ void dMw_c::dMw_ring_create(u8 i_origin) { } mpCapture->setCaptureFlag(); - -#ifdef TARGET_PC - dusk::frame_interp::request_presentation_sync(); -#endif } bool dMw_c::dMw_ring_delete() { diff --git a/src/d/d_save.cpp b/src/d/d_save.cpp index cdd5c467ef..d634c7b21d 100644 --- a/src/d/d_save.cpp +++ b/src/d/d_save.cpp @@ -1828,7 +1828,7 @@ int dSv_info_c::memory_to_card(char* card_ptr, int dataNum) { savedata->getPlayer().getPlayerInfo().setTotalTime(play_time); } - savedata->getPlayer().getPlayerStatusB().setDateIpl(OSGetTime()); + savedata->getPlayer().getPlayerStatusB().setDateIpl(DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime())); memcpy(card_ptr, savedata, sizeof(dSv_save_c)); card_ptr += 0x958; @@ -2035,7 +2035,7 @@ void flagFile_c::listenPropertyEvent(const JORPropertyEvent* i_event) { } case 102: { OSCalendarTime time; - OSTicksToCalendarTime(OSGetTime(), &time); + OSTicksToCalendarTime(DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()), &time); const char* start_stage_name = dComIfGp_getStartStageName(); char filename[64]; diff --git a/src/dusk/action_bindings.cpp b/src/dusk/action_bindings.cpp index 68b07d6c0a..c919c9d4fe 100644 --- a/src/dusk/action_bindings.cpp +++ b/src/dusk/action_bindings.cpp @@ -41,6 +41,15 @@ bool isActionBound(ActionBinds action, u32 port) { return getActionBindButton(action, port) != PAD_NATIVE_BUTTON_INVALID; } +bool isActionBoundAnyPort(ActionBinds action) { + for (u32 port = 0; port < PAD_CHANMAX; ++port) { + if (isActionBound(action, port)) { + return true; + } + } + return false; +} + void updateActionBindings() { for (u32 port = 0; port < PAD_CHANMAX; ++port) { // Move the current press to the previous frame diff --git a/src/dusk/action_bindings.h b/src/dusk/action_bindings.h index a71dac5dfe..77ce9cb815 100644 --- a/src/dusk/action_bindings.h +++ b/src/dusk/action_bindings.h @@ -31,6 +31,7 @@ using ActionBindsMap = std::unordered_map; ActionBindsMap& getActionBinds(); bool isActionBound(ActionBinds action, u32 port); +bool isActionBoundAnyPort(ActionBinds action); void updateActionBindings(); diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp index be03d51e96..ca30ec0db5 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -147,6 +147,10 @@ void begin_frame(FrameInterpMode mode, bool is_sim_frame, float step) { g_enabled = mode != FrameInterpMode::Off; g_is_sim_frame = is_sim_frame; g_step = std::clamp(step, 0.0f, 1.0f); + if (!g_enabled) { + g_interpolating = false; + clear_replacements(); + } } bool is_enabled() { diff --git a/src/dusk/game_clock.cpp b/src/dusk/game_clock.cpp index 1dc7fbf322..18ee24d542 100644 --- a/src/dusk/game_clock.cpp +++ b/src/dusk/game_clock.cpp @@ -1,104 +1,135 @@ #include "dusk/game_clock.h" #include +#include #include #include -#include #include +#include namespace dusk::game_clock { -using clock = std::chrono::steady_clock; +using native_clock = aurora::time::native_clock; +using game_clock = aurora::time::game_clock; +FrameTiming g_frameTiming; + +namespace { bool s_initialized = false; -clock::time_point s_previous_sample{}; -clock::time_point s_current_snapshot_time{}; +bool s_fixedStepActive = false; +bool s_simTickActive = false; +native_clock::time_point s_previousNativeSample{}; +game_clock::time_point s_latestGameSample{}; +game_clock::time_point s_currentSnapshotTime{}; +game_clock::time_point s_pendingSimTime{}; -std::unordered_map s_interval_last_sample; +std::unordered_map s_intervalLastSample; -constexpr clock::duration kSimPeriodDuration = - std::chrono::duration_cast(std::chrono::duration(sim_pace())); -constexpr clock::duration kAbnormalGapResetThreshold = std::chrono::milliseconds(250); -constexpr int kMaxSimTicksPerFrame = 2; +constexpr game_clock::duration kSimPeriodDuration = + std::chrono::duration_cast(std::chrono::duration(kSimPeriod)); +constexpr native_clock::duration kAbnormalGapResetThreshold = std::chrono::milliseconds(250); +constexpr int kMaxSimTicksPerFrame = static_cast(aurora::time::kMaximumTimeScale) * 4; +} // namespace -void ensure_initialized() { +void initialize() { if (s_initialized) { return; } - s_previous_sample = clock::now(); - s_current_snapshot_time = s_previous_sample; + s_previousNativeSample = native_clock::now(); + s_latestGameSample = game_clock::now(); + s_currentSnapshotTime = s_latestGameSample; + s_pendingSimTime = s_latestGameSample; s_initialized = true; } -void reset_frame_timer() { - s_previous_sample = clock::now(); - s_current_snapshot_time = s_previous_sample - kSimPeriodDuration; +void reset() { + s_previousNativeSample = native_clock::now(); + s_latestGameSample = game_clock::now(); + s_currentSnapshotTime = s_latestGameSample - kSimPeriodDuration; + s_pendingSimTime = s_currentSnapshotTime; + s_simTickActive = false; } -MainLoopPacer advance_main_loop() { - ensure_initialized(); +const FrameTiming& advance() { + const auto nativeNow = native_clock::now(); + const auto gameNow = game_clock::now(); + const auto nativeFrameGap = nativeNow - s_previousNativeSample; + s_previousNativeSample = nativeNow; + s_latestGameSample = gameNow; - const clock::time_point now = clock::now(); - const clock::duration frame_gap = now - s_previous_sample; - const float presentation_dt = std::chrono::duration(frame_gap).count(); - s_previous_sample = now; + auto& out = g_frameTiming; + out = {.dt = std::chrono::duration().count()}; - MainLoopPacer out{}; - out.presentation_dt_seconds = presentation_dt; + const float timeScale = aurora::time::scale(); + const bool interpolating = + getSettings().game.enableFrameInterpolation.getValue() != FrameInterpMode::Off; + const bool separatePresentation = interpolating || timeScale != 1.0f; + out.interpolating = interpolating; + out.separatePresentation = separatePresentation; + s_fixedStepActive = separatePresentation; - const bool should_interpolate = dusk::getSettings().game.enableFrameInterpolation.getValue() != - dusk::FrameInterpMode::Off && - !dusk::getTransientSettings().skipFrameRateLimit; - out.is_interpolating = should_interpolate; - out.sim_pace = sim_pace(); - - if (!should_interpolate) { - s_current_snapshot_time = now; - out.sim_ticks_to_run = 1; + if (!separatePresentation) { + s_currentSnapshotTime = gameNow; + out.numSimTicks = 1; return out; } - if (frame_gap > kAbnormalGapResetThreshold) { - s_current_snapshot_time = now - kSimPeriodDuration; - out.sim_ticks_to_run = 0; + const auto simulationTarget = interpolating ? gameNow - kSimPeriodDuration : gameNow; + if (timeScale == 0.f || nativeFrameGap > kAbnormalGapResetThreshold) { + s_currentSnapshotTime = simulationTarget; + out.numSimTicks = 0; return out; } - int sim_ticks_to_run = 0; - clock::time_point projected_snapshot_time = s_current_snapshot_time; - const clock::time_point render_time = now - kSimPeriodDuration; - while (sim_ticks_to_run < kMaxSimTicksPerFrame && projected_snapshot_time < render_time) { - projected_snapshot_time += kSimPeriodDuration; - sim_ticks_to_run++; + int numSimTicks = 0; + auto projectedSnapshotTime = s_currentSnapshotTime; + while (numSimTicks < kMaxSimTicksPerFrame) { + const bool tickDue = interpolating ? + projectedSnapshotTime < simulationTarget : + projectedSnapshotTime + kSimPeriodDuration <= simulationTarget; + if (!tickDue) { + break; + } + projectedSnapshotTime += kSimPeriodDuration; + numSimTicks++; } - out.sim_ticks_to_run = sim_ticks_to_run; + out.numSimTicks = numSimTicks; return out; } +void begin_sim_tick() { + s_pendingSimTime = + s_fixedStepActive ? s_currentSnapshotTime + kSimPeriodDuration : s_latestGameSample; + s_simTickActive = true; +} + void commit_sim_tick() { - ensure_initialized(); - s_current_snapshot_time += kSimPeriodDuration; + if (s_simTickActive) { + s_currentSnapshotTime = s_pendingSimTime; + s_simTickActive = false; + } else { + s_currentSnapshotTime += kSimPeriodDuration; + } } float sample_interpolation_step() { - ensure_initialized(); const float step = - std::chrono::duration(clock::now() - s_current_snapshot_time).count() / sim_pace(); + std::chrono::duration(game_clock::now() - s_currentSnapshotTime).count() / + kSimPeriod; return std::clamp(step, 0.0f, 1.0f); } float consume_interval(const void* consumer) { - ensure_initialized(); - const uintptr_t key = reinterpret_cast(consumer); - const clock::time_point now = clock::now(); - - float dt = ui_initial_dt(); - const auto it = s_interval_last_sample.find(key); - if (it != s_interval_last_sample.end()) { + const auto key = reinterpret_cast(consumer); + const auto now = s_simTickActive ? s_pendingSimTime : game_clock::now(); + const float timeScale = aurora::time::scale(); + float dt = kUiInitialDt * timeScale; + if (const auto it = s_intervalLastSample.find(key); it != s_intervalLastSample.end()) { dt = std::chrono::duration(now - it->second).count(); - dt = std::min(dt, ui_maximum_dt()); + const float maximumDt = std::max(kUiMaximumDt * timeScale, kSimPeriod); + dt = std::min(dt, maximumDt); } - s_interval_last_sample[key] = now; + s_intervalLastSample[key] = now; return dt; } diff --git a/src/dusk/game_clock.h b/src/dusk/game_clock.h index 8bb277e070..b300cf28d7 100644 --- a/src/dusk/game_clock.h +++ b/src/dusk/game_clock.h @@ -2,22 +2,27 @@ namespace dusk::game_clock { -void ensure_initialized(); -void reset_frame_timer(); +// Amount of time that a simulation tick advances +constexpr float kSimPeriod = 1.0f / 30.0f; +constexpr float kUiMaximumDt = 0.05f; +constexpr float kUiInitialDt = 1.0f / 60.0f; -constexpr float sim_pace() { return 1.0f / 30.0f; } -constexpr float period_for_original_frames(float frame_count) { return frame_count * sim_pace(); } -constexpr float ui_maximum_dt() { return 0.05f; } -constexpr float ui_initial_dt() { return 1.0f / 60.0f; } - -struct MainLoopPacer { - float presentation_dt_seconds; - bool is_interpolating; - int sim_ticks_to_run; - float sim_pace; +struct FrameTiming { + // Amount of time elapsed in seconds since the last advance + float dt; + // Whether interpolation is active + bool interpolating; + // Run simulation and presentation separately (for interpolation or time scaling) + bool separatePresentation; + // Number of simulation ticks to run + int numSimTicks; }; +extern FrameTiming g_frameTiming; -MainLoopPacer advance_main_loop(); +void initialize(); +void reset(); +const FrameTiming& advance(); +void begin_sim_tick(); void commit_sim_tick(); float sample_interpolation_step(); diff --git a/src/dusk/hq_minimap.cpp b/src/dusk/hq_minimap.cpp new file mode 100644 index 0000000000..a4d73cbcf1 --- /dev/null +++ b/src/dusk/hq_minimap.cpp @@ -0,0 +1,210 @@ +#include "aurora/texture.hpp" +#include "dusk/texture_replacements.hpp" +#include "fmt/format.h" + +#include +#include +#include +#include + +namespace { + +constexpr u16 kMapIconResolutionMultiplier = 4; +constexpr u16 kMapImageSide = 16 * kMapIconResolutionMultiplier; +constexpr u32 kMapImageTotalPixels = kMapImageSide * kMapImageSide; + +// give higher priority to user and mod replacements +constexpr auto kInternalTextureReplacementPriority = dusk::texture_replacements::kUserTextureReplacementPriority - 1; + +typedef std::function PaintI8Fn; + +enum class ArcIndex : int { + Circle16 = 82, // map_icon_circle16x16_4i.bti - simple circle + Circle = 76, // im_map_icon_circle_4i.bti - outlined circle + Nijumaru = 78, // im_map_icon_nijumaru_4i.bti - concentric rings + Enter = 77, // im_map_icon_enter_4i.bti - outlined octagram + TryForce = 81, // im_map_icon_try_force_4i.bti - outlined circle with triangle +}; + +struct Replacement { + ArcIndex index; + PaintI8Fn painter; +}; + +struct Icon { + u8* origData = nullptr; + std::unique_ptr newData; + std::string label; + std::optional reg; +}; + +bool s_initialized = false; +bool s_active = false; +std::unordered_map s_icons; + +void paint_i8(std::span dst, size_t width, PaintI8Fn paint) { + assert(width % 8 == 0 && dst.size() % 32 == 0); + + const auto blocksAcross = width >> 3; + + for (size_t i = 0; i < dst.size(); i++) { + // 8x4 block swizzling for I8 + const auto blockIdx = i >> 5; + const auto localIdx = i & 31; + + const auto blockY = blockIdx / blocksAcross; + const auto blockX = blockIdx % blocksAcross; + + const auto localY = localIdx >> 3; + const auto localX = localIdx & 7; + + const auto x = (blockX << 3) + localX; + const auto y = (blockY << 2) + localY; + + dst[i] = paint(x, y); + } +} + +void draw_all_replacements() { + constexpr auto center = kMapImageSide / 2.0f; + constexpr auto radiusSq = center * center; + + // clang-format off + const auto replacements = std::to_array({ + { + ArcIndex::Circle16, + [=](auto x, auto y) { + const auto dx = (x + 0.5f) - center; + const auto dy = (y + 0.5f) - center; + return (dx * dx + dy * dy < radiusSq) ? 0x11 : 0; + } + }, + { + ArcIndex::Circle, + [=](auto x, auto y) { + constexpr auto innerRadius = kMapImageSide * 3.0f / 8.0f; + constexpr auto innerRadiusSq = innerRadius * innerRadius; + + const auto dx = (x + 0.5f) - center; + const auto dy = (y + 0.5f) - center; + const auto dSq = dx * dx + dy * dy; + + return dSq < radiusSq ? (dSq < innerRadiusSq ? 0x22 : 0x11) : 0; + } + }, + { + ArcIndex::Nijumaru, + [=](auto x, auto y) { + constexpr u8 nijumaruRings[] = {0x11, 0x22, 0x11, 0x11, 0x22, 0x22}; + + const auto dx = (x + 0.5f) - center; + const auto dy = (y + 0.5f) - center; + const auto dSq = dx * dx + dy * dy; + + if (dSq < radiusSq) { + auto ringIndex = static_cast(std::trunc(std::sqrt(dSq) / kMapImageSide * 12)); + ringIndex = std::min(ringIndex, sizeof(nijumaruRings) - 1); + return nijumaruRings[ringIndex]; + } + return u8{0}; + } + }, + { + ArcIndex::Enter, + [=](auto x, auto y) { + constexpr auto outlineWidth = kMapImageSide / 6.0f; + + const auto adx = std::abs((x + 0.5f) - center); + const auto ady = std::abs((y + 0.5f) - center); + const auto dist = + std::min(adx + ady, std::max(adx, ady) * std::numbers::sqrt2_v) - + kMapImageSide / 2.0f; + + return dist > 0.0f ? 0 : (dist > -outlineWidth ? 0x22 : 0x33); + } + }, + { + ArcIndex::TryForce, + [=](auto x, auto y) { + constexpr auto innerRadiusNorm = 5.0f / 12.0f; + constexpr auto innerRadius = kMapImageSide * innerRadiusNorm; + constexpr auto innerRadiusSq = innerRadius * innerRadius; + constexpr auto triRadius = kMapImageSide * innerRadiusNorm / 2.0f; + + const auto dx = (x + 0.5f) - center; + const auto dy = (y + 0.5f) - center; + const auto dSq = dx * dx + dy * dy; + const auto triSideDist = (std::numbers::sqrt3_v * std::abs(dx) - dy) * 0.5f; + const auto insideTri = std::max(dy, triSideDist) < triRadius; + + return insideTri ? 0x22 : (dSq < radiusSq ? (dSq < innerRadiusSq ? 0x33 : 0x22) : 0); + } + } + }); + // clang-format on + + for (const auto r : replacements) { + auto pixels = std::make_unique_for_overwrite(kMapImageTotalPixels); + paint_i8(std::span{pixels.get(), kMapImageTotalPixels}, kMapImageSide, r.painter); + + auto& icon = s_icons[static_cast(r.index)]; + icon.newData = std::move(pixels); + icon.label = fmt::format("hq minimap icon {}", static_cast(r.index)); + } +} + +} // namespace + +namespace dusk::hq_minimap { + +void register_pointer(int idx, u8* ptr) { + if (s_initialized) { + return; + } + + s_icons[idx].origData = ptr; +} + +void set_active(bool active) { + s_active = active; +} + +void update() { + if (!s_initialized) { + return; + } + + for (auto& [idx, icon] : s_icons) { + const bool shouldBeRegistered = s_active && icon.origData && icon.newData; + + if (shouldBeRegistered && !icon.reg) { + aurora::texture::ReplacementKey key{aurora::texture::TexturePointerKey{icon.origData}}; + aurora::texture::RawTextureReplacement repl{ + .bytes = std::span{icon.newData.get(), kMapImageTotalPixels}, + .width = kMapImageSide, + .height = kMapImageSide, + .mipCount = 1, + .gxFormat = GX_TF_I8, + .label = icon.label, + }; + icon.reg = aurora::texture::register_replacement( + key, repl, {.priority = kInternalTextureReplacementPriority}); + } else if (!shouldBeRegistered && icon.reg) { + aurora::texture::unregister_replacement(*icon.reg); + icon.reg.reset(); + } + } +} + +void initialize_if_needed() { + if (s_initialized) { + return; + } + + draw_all_replacements(); + s_initialized = true; + + update(); +} + +} // namespace dusk::hq_minimap diff --git a/src/dusk/hq_minimap.hpp b/src/dusk/hq_minimap.hpp new file mode 100644 index 0000000000..5ca89ac7d1 --- /dev/null +++ b/src/dusk/hq_minimap.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace dusk::hq_minimap { + +/// Adds a mapping of an image resource index (into `Always.arc`) to the pointer to its image data. +/// If `initialize_if_needed` has been called, this is a no-op. Pointers are expected to be stable +/// and valid for the program's entire lifetime. +void register_pointer(int idx, u8* ptr); + +/// Sets whether HQ minimap texture replacements should be active or not. Does not manage +/// replacement registrations itself; see `update`. +void set_active(bool active); + +/// Registers or unregisters texture replacements depending on active state. +void update(); + +/// Called once after registering image pointers, in which their HQ replacements are procedurally +/// drawn and `update` is called. Further calls are no-ops. +void initialize_if_needed(); + +} diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 3c0131c729..a102b0ca11 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -1,8 +1,9 @@ #include #include +#include +#include #include #include -#include #define IMGUI_DEFINE_MATH_OPERATORS #include "imgui.h" @@ -20,6 +21,7 @@ #include "dusk/frame_interpolation.h" #include "dusk/livesplit.h" #include "dusk/main.h" +#include "dusk/presentation.hpp" #include "dusk/settings.h" #include "dusk/ui/ui.hpp" #include "f_pc/f_pc_manager.h" @@ -37,6 +39,8 @@ using namespace std::string_literals; using namespace std::string_view_literals; namespace { +constexpr float kTurboTimeScale = 4.f; + ImGuiWindow* FindDragScrollWindow(ImGuiWindow* window) { while (window != nullptr) { const bool canScrollX = window->ScrollMax.x > 0.0f; @@ -235,10 +239,32 @@ namespace dusk { } void ImGuiConsole::UpdateSettings() { - getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && - (ImGui::IsKeyDown(ImGuiKey_Tab) || getActionBindHoldAnyPort(ActionBinds::TURBO_SPEED_BUTTON)); + static bool previousTurboActive = false; + static bool previousSlowActive = false; + static float previousTimeScale = 1.0f; - if (dusk::frame_interp::get_ui_tick_pending() && mDoMain::developmentMode == 1 && (mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == (PAD_TRIGGER_R | PAD_TRIGGER_L) && mDoCPd_c::getTrigY(PAD_1)) { + const bool turboBound = isActionBoundAnyPort(ActionBinds::TURBO_SPEED_BUTTON); + const bool turboActive = + getSettings().game.enableTurboKeybind && + (turboBound ? getActionBindHoldAnyPort(ActionBinds::TURBO_SPEED_BUTTON) : + ImGui::IsKeyDown(ImGuiKey_Tab)); + const bool slowDown = turboActive && ImGui::GetIO().KeyShift; + if (turboActive != previousTurboActive) { + getTransientSettings().turboMode = turboActive; + presentation::update_frame_rate_preference(); + if (turboActive) { + previousTimeScale = aurora_get_timescale(); + aurora_set_timescale(slowDown ? 1.f / kTurboTimeScale : kTurboTimeScale); + } else { + aurora_set_timescale(previousTimeScale); + } + } else if (turboActive && slowDown != previousSlowActive) { + aurora_set_timescale(slowDown ? 1.f / kTurboTimeScale : kTurboTimeScale); + } + previousTurboActive = turboActive; + previousSlowActive = slowDown; + + if (frame_interp::get_ui_tick_pending() && mDoMain::developmentMode == 1 && (mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == (PAD_TRIGGER_R | PAD_TRIGGER_L) && mDoCPd_c::getTrigY(PAD_1)) { getTransientSettings().moveLinkActive = !getTransientSettings().moveLinkActive; } if (mDoMain::developmentMode != 1) { @@ -270,7 +296,7 @@ namespace dusk { m_isHidden = true; } } - + bool showMenu = !m_isHidden; // The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg, diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index b9ad5eb021..c2f5e45754 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -25,10 +25,6 @@ #include #endif -namespace aurora::gx { -extern bool enableLodBias; -} - namespace dusk { ImGuiMenuTools::ImGuiMenuTools() {} @@ -76,7 +72,6 @@ namespace dusk { getSettings().game.disableWaterRefraction.setValue(disableWaterRefraction); config::save(); } - ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias); ImGui::EndMenu(); } diff --git a/src/dusk/presentation.cpp b/src/dusk/presentation.cpp index b55c11d93b..1443ab85f9 100644 --- a/src/dusk/presentation.cpp +++ b/src/dusk/presentation.cpp @@ -10,6 +10,10 @@ namespace dusk::presentation { namespace { float preferred_frame_rate() { + if (getTransientSettings().turboMode) { + return 0.0f; + } + switch (getSettings().game.enableFrameInterpolation.getValue()) { case FrameInterpMode::Off: return 30.0f; diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 00191c3015..1af01faa81 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -76,6 +76,7 @@ UserSettings g_userSettings = { .resampler {"game.resampler", Resampler::Bilinear}, .enableMapBackground {"game.enableMapBackground", true}, .disableCutscenePillarboxing {"game.disableCutscenePillarboxing", false}, + .enableHighQualityMinimapTextures {"game.enableHighQualityMinimapTextures", true}, // Audio .noLowHpSound {"game.noLowHpSound", false}, @@ -283,6 +284,7 @@ void registerSettings() { Register(g_userSettings.game.shadowResolutionMultiplier); Register(g_userSettings.game.enableMapBackground); Register(g_userSettings.game.disableCutscenePillarboxing); + Register(g_userSettings.game.enableHighQualityMinimapTextures); Register(g_userSettings.game.enableFastIronBoots); Register(g_userSettings.game.canTransformAnywhere); Register(g_userSettings.game.fastRoll); @@ -394,7 +396,7 @@ static TransientSettings g_transientSettings = { .colliderViewOpacity = 50.0f, .drawRange = 100.0f, }, - .skipFrameRateLimit = false, + .turboMode = false, }; TransientSettings& getTransientSettings() { diff --git a/src/dusk/settings.h b/src/dusk/settings.h index 19aad6ee09..914ef37efc 100644 --- a/src/dusk/settings.h +++ b/src/dusk/settings.h @@ -208,6 +208,7 @@ struct UserSettings { ConfigVar resampler; ConfigVar enableMapBackground; ConfigVar disableCutscenePillarboxing; + ConfigVar enableHighQualityMinimapTextures; // Audio ConfigVar noLowHpSound; @@ -326,7 +327,7 @@ struct CollisionViewSettings { struct TransientSettings { CollisionViewSettings collisionView; - bool skipFrameRateLimit; + bool turboMode; bool moveLinkActive; bool stateShareLoadActive; }; diff --git a/src/dusk/speedrun.h b/src/dusk/speedrun.h index d9e43c3ed7..887d9e6842 100644 --- a/src/dusk/speedrun.h +++ b/src/dusk/speedrun.h @@ -6,18 +6,23 @@ namespace dusk { struct SpeedrunInfo { void startRun() { m_isRunStarted = true; - m_startTimestamp = OSGetTime(); + m_rtaStartTimestamp = OSGetNativeTime(); + m_igtStartTimestamp = OSGetTime(); } void stopRun() { m_isRunStarted = false; - m_endTimestamp = OSGetTime() - m_startTimestamp; + m_rtaTimer = OSGetNativeTime() - m_rtaStartTimestamp; + if (!m_isPauseIGT) { + m_igtTimer = OSGetTime() - m_igtStartTimestamp - m_totalLoadTime; + } } void reset() { m_isRunStarted = false; - m_startTimestamp = 0; - m_endTimestamp = 0; + m_rtaStartTimestamp = 0; + m_rtaTimer = 0; + m_igtStartTimestamp = 0; m_isPauseIGT = false; m_loadStartTimestamp = 0; m_totalLoadTime = 0; @@ -25,8 +30,9 @@ struct SpeedrunInfo { } bool m_isRunStarted = false; - OSTime m_startTimestamp = 0; - OSTime m_endTimestamp = 0; + OSTime m_rtaStartTimestamp = 0; + OSTime m_rtaTimer = 0; + OSTime m_igtStartTimestamp = 0; bool m_isPauseIGT = false; OSTime m_loadStartTimestamp = 0; diff --git a/src/dusk/ui/overlay.cpp b/src/dusk/ui/overlay.cpp index 740b4febf0..c271cccfdb 100644 --- a/src/dusk/ui/overlay.cpp +++ b/src/dusk/ui/overlay.cpp @@ -203,10 +203,19 @@ void remove_element(Rml::Element*& elem) noexcept { } // namespace -static std::string FormatTime(OSTime ticks) { - OSCalendarTime t; - OSTicksToCalendarTime(ticks, &t); - return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", t.hour, t.min, t.sec, t.msec); +static std::string FormatElapsedTime(OSTime ticksElapsed) { + using namespace std::chrono; + + milliseconds ms{OSTicksToMilliseconds(ticksElapsed)}; + + const hours hr = duration_cast(ms); + ms -= hr; + const minutes min = duration_cast(ms); + ms -= min; + const seconds sec = duration_cast(ms); + ms -= sec; + + return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", hr.count(), min.count(), sec.count(), ms.count()); } Overlay::Overlay() : Document(kDocumentSource, true, DocumentScope::Overlay) { @@ -317,33 +326,33 @@ void Overlay::update() { mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigY(PAD_1)) { if (m_speedrunInfo.m_isRunStarted) { - m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp; - m_speedrunInfo.m_isRunStarted = false; + m_speedrunInfo.stopRun(); } } - OSTime elapsedTime = 0; + OSTime rtaElapsedTime = 0; if (m_speedrunInfo.m_isRunStarted) { - elapsedTime = OSGetTime() - m_speedrunInfo.m_startTimestamp; - } else if (m_speedrunInfo.m_endTimestamp != 0) { - elapsedTime = m_speedrunInfo.m_endTimestamp; + rtaElapsedTime = OSGetNativeTime() - m_speedrunInfo.m_rtaStartTimestamp; + } else if (m_speedrunInfo.m_rtaTimer != 0) { + rtaElapsedTime = m_speedrunInfo.m_rtaTimer; } - if (!m_speedrunInfo.m_isPauseIGT) { - m_speedrunInfo.m_igtTimer = elapsedTime - m_speedrunInfo.m_totalLoadTime; + if (m_speedrunInfo.m_isRunStarted && !m_speedrunInfo.m_isPauseIGT) { + m_speedrunInfo.m_igtTimer = OSGetTime() - m_speedrunInfo.m_igtStartTimestamp - + m_speedrunInfo.m_totalLoadTime; } mSpeedrunTimer->SetAttribute("open", ""); if (getSettings().game.showSpeedrunRTATimer) { mSpeedrunRta->SetAttribute("open", ""); - mSpeedrunRta->SetInnerRML(escape(fmt::format("RTA {}", FormatTime(elapsedTime)))); + mSpeedrunRta->SetInnerRML(escape(fmt::format("RTA {}", FormatElapsedTime(rtaElapsedTime)))); } else { mSpeedrunRta->RemoveAttribute("open"); } mSpeedrunIgt->SetInnerRML( - escape(fmt::format("IGT {}", FormatTime(m_speedrunInfo.m_igtTimer)))); + escape(fmt::format("IGT {}", FormatElapsedTime(m_speedrunInfo.m_igtTimer)))); } else { mSpeedrunTimer->RemoveAttribute("open"); } diff --git a/src/f_op/f_op_overlap_req.cpp b/src/f_op/f_op_overlap_req.cpp index 37b901c706..7d7f855c26 100644 --- a/src/f_op/f_op_overlap_req.cpp +++ b/src/f_op/f_op_overlap_req.cpp @@ -19,15 +19,16 @@ static int fopOvlpReq_phase_Done(overlap_request_class* i_overlapReq) { i_overlapReq->field_0x8 = 0; i_overlapReq->field_0xc = 0; - #if TARGET_PC +#if TARGET_PC if (dusk::getSettings().game.speedrunMode) { if (dusk::m_speedrunInfo.m_isRunStarted) { dusk::m_speedrunInfo.m_isPauseIGT = false; - dusk::m_speedrunInfo.m_totalLoadTime += OSGetTime() - dusk::m_speedrunInfo.m_loadStartTimestamp; + dusk::m_speedrunInfo.m_totalLoadTime += + OSGetTime() - dusk::m_speedrunInfo.m_loadStartTimestamp; dusk::m_speedrunInfo.m_loadStartTimestamp = OSGetTime(); } } - #endif +#endif return cPhs_NEXT_e; } diff --git a/src/f_pc/f_pc_manager.cpp b/src/f_pc/f_pc_manager.cpp index 3cda826854..22c705c720 100644 --- a/src/f_pc/f_pc_manager.cpp +++ b/src/f_pc/f_pc_manager.cpp @@ -22,7 +22,7 @@ #include "m_Do/m_Do_controller_pad.h" #if TARGET_PC -#include "dusk/frame_interpolation.h" +#include "dusk/game_clock.h" #endif #include "tracy/Tracy.hpp" @@ -68,8 +68,8 @@ void fpcM_Management(fpcM_ManagementFunc i_preExecuteFn, fpcM_ManagementFunc i_p } #ifdef TARGET_PC - // FRAME INTERP NOTE: Called in m_Do_main when interp is enabled - if (!dusk::frame_interp::is_enabled()) + // The main loop manages painting when simulation and presentation are separated. + if (!dusk::game_clock::g_frameTiming.separatePresentation) #endif { cAPIGph_Painter(); diff --git a/src/m_Do/m_Do_MemCardRWmng.cpp b/src/m_Do/m_Do_MemCardRWmng.cpp index 59688043a5..bb0326f623 100644 --- a/src/m_Do/m_Do_MemCardRWmng.cpp +++ b/src/m_Do/m_Do_MemCardRWmng.cpp @@ -320,7 +320,7 @@ static void mDoMemCdRWm_BuildHeader(mDoMemCdRWm_HeaderData* header) { #endif OSCalendarTime time; - OSTicksToCalendarTime(OSGetTime(), &time); + OSTicksToCalendarTime(DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()), &time); #if TARGET_PC if (dusk::version::isRegionPal()) { diff --git a/src/m_Do/m_Do_machine.cpp b/src/m_Do/m_Do_machine.cpp index a18657a2ad..3d0acca1a9 100644 --- a/src/m_Do/m_Do_machine.cpp +++ b/src/m_Do/m_Do_machine.cpp @@ -527,7 +527,7 @@ void myExceptionCallback(u16, OSContext*, u32, u32) { u32 btnHold; u32 btnTrig; - mDoMain::sHungUpTime = OSGetTime(); + mDoMain::sHungUpTime = DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()); OSReportEnable(); cAPICPad_recalibrate(); // "Vibration stopping & resetting to default\n" diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index ccb6c35c1c..d169a7ad98 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -65,6 +65,7 @@ #include "dusk/iso_validate.hpp" #include "dusk/logging.h" #include "dusk/main.h" +#include "dusk/hq_minimap.hpp" #include "dusk/mod_loader.hpp" #include "dusk/mods/svc/window.hpp" #include "dusk/mouse.h" @@ -232,7 +233,7 @@ void main01(void) { OSReport("Entering Main Loop (main01)...\n"); - dusk::game_clock::ensure_initialized(); + dusk::game_clock::initialize(); do { // 1. Update Window Events @@ -247,7 +248,7 @@ void main01(void) { break; case AURORA_UNPAUSED: dusk::audio::SetPaused(false); - dusk::game_clock::reset_frame_timer(); + dusk::game_clock::reset(); dusk::mouse::on_focus_gained(); break; case AURORA_SDL_EVENT: @@ -289,46 +290,56 @@ void main01(void) { dusk::ui::update(); - const auto pacing = dusk::game_clock::advance_main_loop(); - if (pacing.is_interpolating) { - if (pacing.sim_ticks_to_run > 0) { - dusk::frame_interp::begin_frame(dusk::getSettings().game.enableFrameInterpolation, true, 0.0f); + const auto timing = dusk::game_clock::advance(); + const auto interpolationMode = dusk::getSettings().game.enableFrameInterpolation.getValue(); + if (timing.separatePresentation) { + if (timing.numSimTicks > 0) { + dusk::frame_interp::begin_frame(interpolationMode, true, 0.0f); dusk::frame_interp::set_ui_tick_pending(true); - - for (int sim_tick = 0; sim_tick < pacing.sim_ticks_to_run; ++sim_tick) { - dusk::frame_interp::begin_sim_tick(); + for (int i = 0; i < timing.numSimTicks; ++i) { + if (timing.interpolating) { + dusk::frame_interp::begin_sim_tick(); + } + dusk::game_clock::begin_sim_tick(); mDoCPd_c::read(); dusk::mouse::read(); - dusk::gyro::read(pacing.sim_pace); + dusk::gyro::read(dusk::game_clock::kSimPeriod); fapGm_Execute(); mDoAud_Execute(); dusk::game_clock::commit_sim_tick(); } } - dusk::frame_interp::begin_frame(dusk::getSettings().game.enableFrameInterpolation, false, - dusk::game_clock::sample_interpolation_step()); - dusk::frame_interp::interpolate(); - dusk::frame_interp::begin_presentation_camera(); - // run draw functions for anything specially marked to handle interp + const float interpolationStep = + timing.interpolating ? dusk::game_clock::sample_interpolation_step() : 1.0f; + dusk::frame_interp::begin_frame(interpolationMode, false, interpolationStep); + if (timing.interpolating) { + dusk::frame_interp::interpolate(); + dusk::frame_interp::begin_presentation_camera(); + } + fpcM_DrawIterater((fpcM_DrawIteraterFunc)fpcM_Draw); cAPIGph_Painter(); - dusk::frame_interp::end_presentation_camera(); + if (timing.interpolating) { + dusk::frame_interp::end_presentation_camera(); + } dusk::frame_interp::set_ui_tick_pending(false); } else { dusk::frame_interp::begin_frame(dusk::FrameInterpMode::Off, true, 0.0f); dusk::frame_interp::set_ui_tick_pending(true); + dusk::game_clock::begin_sim_tick(); // Game Inputs mDoCPd_c::read(); dusk::mouse::read(); - dusk::gyro::read(pacing.presentation_dt_seconds); + dusk::gyro::read(timing.dt); // EXECUTE GAME LOGIC & RENDER // This calls mDoGph_Painter -> JFWDisplay -> GX Functions fapGm_Execute(); mDoAud_Execute(); + dusk::game_clock::commit_sim_tick(); } aurora_end_frame(); @@ -344,7 +355,10 @@ void main01(void) { static double last_fps_setting = 0.0; static Limiter::duration_t target_ns = 0; - if (dusk::getSettings().game.enableFrameInterpolation.getValue() == dusk::FrameInterpMode::Capped && !dusk::getTransientSettings().skipFrameRateLimit) { + if (dusk::getSettings().game.enableFrameInterpolation.getValue() == + dusk::FrameInterpMode::Capped && + !dusk::getTransientSettings().turboMode) + { ZoneScopedN("Frame limiter"); double current_fps = dusk::getSettings().video.maxFrameRate.getValue(); if (current_fps != last_fps_setting) { @@ -353,7 +367,8 @@ void main01(void) { } Limiter::duration_t sleepTime = main_loop_limiter.Sleep(target_ns); - dusk::frameUsagePct = 100.0f * (1.0f - static_cast(sleepTime) / static_cast(target_ns)); + dusk::frameUsagePct = + 100.0f * (1.0f - static_cast(sleepTime) / static_cast(target_ns)); } else { main_loop_limiter.Reset(); } @@ -711,6 +726,10 @@ int game_main(int argc, char* argv[]) { return 0; } + if (dusk::getSettings().game.enableHighQualityMinimapTextures.getValue()) { + dusk::hq_minimap::set_active(true); + } + dusk::texture_replacements::reload(); dusk::ui::initialize(); dusk::ui::push_document(std::make_unique(), true, true); @@ -840,7 +859,7 @@ int game_main(int argc, char* argv[]) { OSInit(); - mDoMain::sPowerOnTime = OSGetTime(); + mDoMain::sPowerOnTime = DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()); // Reset Data static mDoRstData sResetData = {0};