From cb2d8ea6ac7293548096fc602ceb8fdb36cf7264 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Sat, 11 Apr 2026 08:37:30 +0800 Subject: [PATCH 01/10] add variable FPS & VSync --- gframe/client_field.cpp | 2 + gframe/drawing.cpp | 120 +++++++++++--------- gframe/duelclient.cpp | 24 ++-- gframe/game.cpp | 242 ++++++++++++++++++++++++++-------------- gframe/game.h | 18 ++- 5 files changed, 258 insertions(+), 148 deletions(-) diff --git a/gframe/client_field.cpp b/gframe/client_field.cpp index 5beaf5c769..e5a4e138f0 100644 --- a/gframe/client_field.cpp +++ b/gframe/client_field.cpp @@ -1075,6 +1075,7 @@ void ClientField::GetCardLocation(ClientCard* pcard, irr::core::vector3df* t, ir } } void ClientField::MoveCard(ClientCard * pcard, int frame) { + frame = mainGame->ScaleFrame(frame); irr::core::vector3df trans = pcard->curPos; irr::core::vector3df rot = pcard->curRot; GetCardLocation(pcard, &trans, &rot); @@ -1105,6 +1106,7 @@ void ClientField::MoveCard(ClientCard * pcard, int frame) { pcard->aniFrame = frame; } void ClientField::FadeCard(ClientCard * pcard, int alpha, int frame) { + frame = mainGame->ScaleFrame(frame); pcard->dAlpha = (alpha - pcard->curAlpha) / frame; pcard->is_fading = true; pcard->aniFrame = frame; diff --git a/gframe/drawing.cpp b/gframe/drawing.cpp index 678faa11fc..105fe331d4 100644 --- a/gframe/drawing.cpp +++ b/gframe/drawing.cpp @@ -141,8 +141,8 @@ void Game::DrawSelectionLine(irr::gui::IGUIElement* element, int width, irr::vid } } void Game::DrawBackGround() { - static int selFieldAlpha = 255; - static int selFieldDAlpha = -10; + static float selFieldAlpha = 255.0f; + static float selFieldDAlpha = -10.0f; //draw field spell card driver->setTransform(irr::video::ETS_WORLD, irr::core::IdentityMatrix); bool drawField = false; @@ -260,14 +260,14 @@ void Game::DrawBackGround() { vertex = matManager.vFieldRemove[dField.hovered_controler][rule]; else if (dField.hovered_location == LOCATION_EXTRA) vertex = matManager.vFieldExtra[dField.hovered_controler]; - selFieldAlpha += selFieldDAlpha; - if (selFieldAlpha <= 5) { - selFieldAlpha = 5; - selFieldDAlpha = 10; + selFieldAlpha += selFieldDAlpha / fpsScale; + if (selFieldAlpha <= 5.0f) { + selFieldAlpha = 5.0f; + selFieldDAlpha = 10.0f; } - if (selFieldAlpha >= 205) { - selFieldAlpha = 205; - selFieldDAlpha = -10; + if (selFieldAlpha >= 205.0f) { + selFieldAlpha = 205.0f; + selFieldDAlpha = -10.0f; } matManager.mSelField.AmbientColor = 0xffffffff; matManager.mSelField.DiffuseColor = (irr::u32)selFieldAlpha << 24; @@ -483,7 +483,7 @@ void Game::DrawMisc() { static irr::core::vector3df act_rot(0, 0, 0); int rule = (dInfo.duel_rule >= 4) ? 1 : 0; irr::core::matrix4 im, ic, it, ig; - act_rot.Z += 0.02f; + act_rot.Z += 0.02f / fpsScale; im.setRotationRadians(act_rot); matManager.mTexture.setTexture(0, imageManager.tAct); driver->setMaterial(matManager.mTexture); @@ -621,11 +621,13 @@ void Game::DrawMisc() { auto tLPFrameRect = irr::core::recti(0, 0, imageManager.tLPFrame->getOriginalSize().Width, imageManager.tLPFrame->getOriginalSize().Height); driver->draw2DImage(imageManager.tLPFrame, Resize(329, 10, 629, 30), tLPFrameRect, 0, 0, true); driver->draw2DImage(imageManager.tLPFrame, Resize(691, 10, 991, 30), tLPFrameRect, 0, 0, true); - if(lpframe) { - dInfo.lp[lpplayer] -= lpd; + if(lpFrame) { + lpFrame--; + float progress = (float)lpFrame / lpFrameCount; + dInfo.lp[lpplayer] = lpFinal + (lpInitial - lpFinal) * progress; myswprintf(dInfo.strLP[lpplayer], L"%d", dInfo.lp[lpplayer]); - lpccolor -= 0x19000000; - lpframe--; + irr::u32 alpha = 5 + 250 * progress; + lpccolor = ((alpha & 0xff) << 24) | (lpccolor & 0x00ffffff); } if(lpcstring.size()) { if(lpplayer == 0) { @@ -801,7 +803,7 @@ void Game::DrawGUI() { if(fu.fadingFrame) { fu.guiFading->setVisible(true); if(fu.isFadein) { - if(fu.fadingFrame > 5) { + if(fu.fadingFrame > fu.fadingHalf) { fu.fadingUL.X -= fu.fadingDiff.X; fu.fadingLR.X += fu.fadingDiff.X; fu.fadingFrame--; @@ -818,7 +820,7 @@ void Game::DrawGUI() { fu.guiFading->setRelativePosition(irr::core::recti(fu.fadingUL, fu.fadingLR)); } } else { - if(fu.fadingFrame > 5) { + if(fu.fadingFrame > fu.fadingHalf) { fu.fadingUL.Y += fu.fadingDiff.Y; fu.fadingLR.Y -= fu.fadingDiff.Y; fu.fadingFrame--; @@ -857,10 +859,12 @@ void Game::DrawSpec() { driver->draw2DImage(imageManager.GetTexture(showcardcode, true), ResizeCardHint(574, 150)); driver->draw2DImage(imageManager.tMask, ResizeCardMid(574, 150, 574 + (showcarddif > CARD_IMG_WIDTH ? CARD_IMG_WIDTH : showcarddif), 150 + CARD_IMG_HEIGHT, midx, midy), irr::core::recti(CARD_IMG_HEIGHT - showcarddif, 0, CARD_IMG_HEIGHT - (showcarddif > CARD_IMG_WIDTH ? showcarddif - CARD_IMG_WIDTH : 0), CARD_IMG_HEIGHT), 0, 0, true); - showcarddif += 15; - if(showcarddif >= CARD_IMG_HEIGHT) { - showcard = 2; - showcarddif = 0; + if(logicalTick) { + showcarddif += 15; + if(showcarddif >= CARD_IMG_HEIGHT) { + showcard = 2; + showcarddif = 0; + } } break; } @@ -868,16 +872,18 @@ void Game::DrawSpec() { driver->draw2DImage(imageManager.GetTexture(showcardcode, true), ResizeCardHint(574, 150)); driver->draw2DImage(imageManager.tMask, ResizeCardMid(574 + showcarddif, 150, 574 + CARD_IMG_WIDTH, 150 + CARD_IMG_HEIGHT, midx, midy), irr::core::recti(0, 0, CARD_IMG_WIDTH - showcarddif, CARD_IMG_HEIGHT), 0, 0, true); - showcarddif += 15; - if(showcarddif >= CARD_IMG_WIDTH) { - showcard = 0; + if(logicalTick) { + showcarddif += 15; + if(showcarddif >= CARD_IMG_WIDTH) { + showcard = 0; + } } break; } case 3: { driver->draw2DImage(imageManager.GetTexture(showcardcode, true), ResizeCardHint(574, 150)); driver->draw2DImage(imageManager.tNegated, ResizeCardMid(536 + showcarddif, 141 + showcarddif, 792 - showcarddif, 397 - showcarddif, midx, midy), irr::core::recti(0, 0, 128, 128), 0, 0, true); - if(showcarddif < 64) + if(logicalTick && showcarddif < 64) showcarddif += 4; break; } @@ -889,7 +895,7 @@ void Game::DrawSpec() { matManager.c2d[3] = acolor; driver->draw2DImage(imageManager.GetTexture(showcardcode, true), ResizeCardHint(574, 150, 574 + CARD_IMG_WIDTH, 150 + CARD_IMG_HEIGHT), ResizeFit(0, 0, CARD_IMG_WIDTH, CARD_IMG_HEIGHT), 0, matManager.c2d, true); - if(showcarddif < 255) + if(logicalTick && showcarddif < 255) showcarddif += 17; break; } @@ -901,7 +907,7 @@ void Game::DrawSpec() { matManager.c2d[3] = acolor; driver->draw2DImage(imageManager.GetTexture(showcardcode, true), ResizeCardMid(662 - showcarddif * 0.69685f, 277 - showcarddif, 662 + showcarddif * 0.69685f, 277 + showcarddif, midx, midy), ResizeFit(0, 0, CARD_IMG_WIDTH, CARD_IMG_HEIGHT), 0, matManager.c2d, true); - if(showcarddif < 127) + if(logicalTick && showcarddif < 127) showcarddif += 9; break; } @@ -909,7 +915,7 @@ void Game::DrawSpec() { driver->draw2DImage(imageManager.GetTexture(showcardcode, true), ResizeCardHint(574, 150)); driver->draw2DImage(imageManager.tNumber, ResizeCardMid(536 + showcarddif, 141 + showcarddif, 792 - showcarddif, 397 - showcarddif, midx, midy), irr::core::recti((showcardp % 5) * 64, (showcardp / 5) * 64, (showcardp % 5 + 1) * 64, (showcardp / 5 + 1) * 64), 0, 0, true); - if(showcarddif < 64) + if(logicalTick && showcarddif < 64) showcarddif += 4; break; } @@ -927,13 +933,15 @@ void Game::DrawSpec() { corner[2] = irr::core::vector2d(winx, winy); corner[3] = irr::core::vector2d(winx2, winy); Draw2DImageQuad(driver, imageManager.GetTexture(showcardcode, true), ResizeFit(0, 0, CARD_IMG_WIDTH, CARD_IMG_HEIGHT), corner); - showcardp++; - showcarddif += 9; - if(showcarddif >= 90) - showcarddif = 90; - if(showcardp == 60) { - showcardp = 0; - showcarddif = 0; + if(logicalTick) { + showcardp++; + showcarddif += 9; + if(showcarddif >= 90) + showcarddif = 90; + if(showcardp == 60) { + showcardp = 0; + showcarddif = 0; + } } break; } @@ -941,10 +949,12 @@ void Game::DrawSpec() { if(showcardp < 60) { driver->draw2DImage(imageManager.tHand[(showcardcode >> 16) & 0x3], irr::core::vector2di((615 + 44.5) * xScale - 44.5, (showcarddif + 64) * yScale - 64)); driver->draw2DImage(imageManager.tHand[showcardcode & 0x3], irr::core::vector2di((615 + 44.5) * xScale - 44.5, (540 - showcarddif + 64) * yScale - 64)); - float dy = -0.333333f * showcardp + 10; - showcardp++; - if(showcardp < 30) - showcarddif += (int)dy; + if(logicalTick) { + float dy = -0.333333f * showcardp + 10; + showcardp++; + if(showcardp < 30) + showcarddif += (int)dy; + } } else showcard = 0; break; @@ -1012,7 +1022,7 @@ void Game::DrawSpec() { irr::u32 alpha = (irr::u32)((showcarddif + 10 - showcardp) * 25) << 24; DrawShadowText(lpcFont, lstr, ResizePhaseHint(660 + (showcardp - showcarddif) * 40, 290, 960, 370, pos.Width), Resize(-1, -1, 0, 0), alpha | 0xffffff, alpha); } - showcardp++; + if(logicalTick) showcardp++; break; } } @@ -1024,9 +1034,11 @@ void Game::DrawSpec() { driver->setTransform(irr::video::ETS_WORLD, matk); driver->setMaterial(matManager.mATK); driver->drawVertexPrimitiveList(&matManager.vArrow[attack_sv], 12, matManager.iArrow, 10, irr::video::EVT_STANDARD, irr::scene::EPT_TRIANGLE_STRIP); - attack_sv += 4; - if (attack_sv > 28) - attack_sv = 0; + if(logicalTick) { + attack_sv += 4; + if (attack_sv > 28) + attack_sv = 0; + } } bool showChat = true; if(hideChat) { @@ -1034,14 +1046,14 @@ void Game::DrawSpec() { hideChatTimer = 10; } else if(hideChatTimer > 0) { showChat = false; - hideChatTimer--; + if(logicalTick) hideChatTimer--; } int chatRectY = 0; for(int i = 0; i < 8; ++i) { static unsigned int chatColor[] = {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff8080ff, 0xffff4040, 0xffff4040, 0xffff4040, 0xff40ff40, 0xff4040ff, 0xff40ffff, 0xffff40ff, 0xffffff40, 0xffffffff, 0xff808080, 0xff404040}; if(chatTiming[i]) { - chatTiming[i]--; + if(logicalTick) chatTiming[i]--; if(!is_building) { if(dInfo.isStarted && i >= 5) continue; @@ -1085,16 +1097,18 @@ void Game::ShowElement(irr::gui::IGUIElement * win, int autoframe) { if(win == fit->guiFading && win != wOptions && win != wANNumber) // the size of wOptions is always setted by ClientField::ShowSelectOption before showing it fu.fadingSize = fit->fadingSize; irr::core::vector2di center = fu.fadingSize.getCenter(); - fu.fadingDiff.X = fu.fadingSize.getWidth() / 10; - fu.fadingDiff.Y = (fu.fadingSize.getHeight() - 4) / 10; + int frame = ScaleFrame(10); + fu.fadingDiff.X = fu.fadingSize.getWidth() / frame; + fu.fadingDiff.Y = (fu.fadingSize.getHeight() - 4) / frame; fu.fadingUL = center; fu.fadingLR = center; fu.fadingUL.Y -= 2; fu.fadingLR.Y += 2; fu.guiFading = win; fu.isFadein = true; - fu.fadingFrame = 10; - fu.autoFadeoutFrame = autoframe; + fu.fadingFrame = frame; + fu.fadingHalf = frame / 2; + fu.autoFadeoutFrame = autoframe ? ScaleFrame(autoframe) : 0; fu.signalAction = 0; SetImageButtonDrawing(win, false); win->setRelativePosition(irr::core::recti(center.X, center.Y, 0, 0)); @@ -1109,13 +1123,15 @@ void Game::HideElement(irr::gui::IGUIElement * win, bool set_action) { for(auto fit = fadingList.begin(); fit != fadingList.end(); ++fit) if(win == fit->guiFading) fu.fadingSize = fit->fadingSize; - fu.fadingDiff.X = fu.fadingSize.getWidth() / 10; - fu.fadingDiff.Y = (fu.fadingSize.getHeight() - 4) / 10; + int frame = ScaleFrame(10); + fu.fadingDiff.X = fu.fadingSize.getWidth() / frame; + fu.fadingDiff.Y = (fu.fadingSize.getHeight() - 4) / frame; fu.fadingUL = fu.fadingSize.UpperLeftCorner; fu.fadingLR = fu.fadingSize.LowerRightCorner; fu.guiFading = win; fu.isFadein = false; - fu.fadingFrame = 10; + fu.fadingFrame = frame; + fu.fadingHalf = frame / 2; fu.autoFadeoutFrame = 0; fu.signalAction = set_action; SetImageButtonDrawing(win, false); @@ -1162,7 +1178,7 @@ void Game::SetImageButtonDrawing(irr::gui::IGUIElement* element, bool draw) { } void Game::WaitFrameSignal(int frame) { frameSignal.Reset(); - signalFrame = (gameConf.quick_animation && frame >= 12) ? 12 : frame; + signalFrame = ScaleFrame((gameConf.quick_animation && frame >= 12) ? 12 : frame); frameSignal.Wait(); } void Game::DrawThumb(const CardDataC* cp, irr::core::vector2di pos, const LFList* lflist, bool drag) { diff --git a/gframe/duelclient.cpp b/gframe/duelclient.cpp index f2c5a8513c..1ce45ef855 100644 --- a/gframe/duelclient.cpp +++ b/gframe/duelclient.cpp @@ -3180,7 +3180,8 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, int len) { return true; } soundManager.PlaySoundEffect(SOUND_DAMAGE); - mainGame->lpd = (mainGame->dInfo.lp[player] - final) / 10; + mainGame->lpInitial = mainGame->dInfo.lp[player]; + mainGame->lpFinal = final; if (player == 0) myswprintf(event_string, dataManager.GetSysString(1613), val); else @@ -3190,7 +3191,8 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, int len) { myswprintf(textBuffer, L"-%d", val); mainGame->lpcstring = textBuffer; mainGame->WaitFrameSignal(30); - mainGame->lpframe = 10; + mainGame->lpFrameCount = mainGame->ScaleFrame(10); + mainGame->lpFrame = mainGame->lpFrameCount; mainGame->WaitFrameSignal(11); mainGame->lpcstring = L""; mainGame->dInfo.lp[player] = final; @@ -3209,7 +3211,8 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, int len) { return true; } soundManager.PlaySoundEffect(SOUND_RECOVER); - mainGame->lpd = (mainGame->dInfo.lp[player] - final) / 10; + mainGame->lpInitial = mainGame->dInfo.lp[player]; + mainGame->lpFinal = final; if (player == 0) myswprintf(event_string, dataManager.GetSysString(1615), val); else @@ -3219,7 +3222,8 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, int len) { myswprintf(textBuffer, L"+%d", val); mainGame->lpcstring = textBuffer; mainGame->WaitFrameSignal(30); - mainGame->lpframe = 10; + mainGame->lpFrameCount = mainGame->ScaleFrame(10); + mainGame->lpFrame = mainGame->lpFrameCount; mainGame->WaitFrameSignal(11); mainGame->lpcstring = L""; mainGame->dInfo.lp[player] = final; @@ -3270,9 +3274,11 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, int len) { myswprintf(mainGame->dInfo.strLP[player], L"%d", mainGame->dInfo.lp[player]); return true; } - mainGame->lpd = (mainGame->dInfo.lp[player] - val) / 10; + mainGame->lpInitial = mainGame->dInfo.lp[player]; + mainGame->lpFinal = val; mainGame->lpplayer = player; - mainGame->lpframe = 10; + mainGame->lpFrameCount = mainGame->ScaleFrame(10); + mainGame->lpFrame = mainGame->lpFrameCount; mainGame->WaitFrameSignal(11); mainGame->dInfo.lp[player] = val; mainGame->gMutex.lock(); @@ -3365,13 +3371,15 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, int len) { return true; } soundManager.PlaySoundEffect(SOUND_DAMAGE); - mainGame->lpd = (mainGame->dInfo.lp[player] - final) / 10; + mainGame->lpInitial = mainGame->dInfo.lp[player]; + mainGame->lpFinal = final; mainGame->lpccolor = 0xff0000ff; mainGame->lpplayer = player; myswprintf(textBuffer, L"-%d", cost); mainGame->lpcstring = textBuffer; mainGame->WaitFrameSignal(30); - mainGame->lpframe = 10; + mainGame->lpFrameCount = mainGame->ScaleFrame(10); + mainGame->lpFrame = mainGame->lpFrameCount; mainGame->WaitFrameSignal(11); mainGame->lpcstring = L""; mainGame->dInfo.lp[player] = final; diff --git a/gframe/game.cpp b/gframe/game.cpp index 395be242df..a5f2f70875 100644 --- a/gframe/game.cpp +++ b/gframe/game.cpp @@ -15,6 +15,9 @@ #ifdef _WIN32 #include #endif +#ifdef __APPLE__ +#include +#endif #if defined(__SSE2__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2) || \ defined(__x86_64__) || defined(_M_X64) || defined(__x86_64) || defined(_M_AMD64) @@ -71,6 +74,7 @@ bool Game::Initialize() { LoadConfig(); irr::SIrrlichtCreationParameters params{}; params.AntiAlias = gameConf.antialias; + params.Vsync = gameConf.vsync; if(gameConf.use_d3d) params.DriverType = irr::video::EDT_DIRECT3D9; else @@ -81,6 +85,27 @@ bool Game::Initialize() { ErrorLog("Failed to create Irrlicht Engine device!"); return false; } + effectiveFps = gameConf.target_fps; + if(gameConf.vsync && effectiveFps <= 0) { + int detectedFps = 0; +#ifdef _WIN32 + DEVMODE dm{}; + dm.dmSize = sizeof(dm); + if(EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dm) && dm.dmDisplayFrequency > 0) + detectedFps = (int)dm.dmDisplayFrequency; +#elif defined(__APPLE__) + CGDisplayModeRef mode = CGDisplayCopyDisplayMode(CGMainDisplayID()); + if(mode) { + double rate = CGDisplayModeGetRefreshRate(mode); + if(rate > 0) detectedFps = (int)rate; + CGDisplayModeRelease(mode); + } +#endif + if(detectedFps > 0) effectiveFps = detectedFps; + } + if(effectiveFps < 60) effectiveFps = 60; + if(effectiveFps > 1000) effectiveFps = 1000; + fpsScale = (float)effectiveFps / 60.0f; #ifndef _DEBUG device->getLogger()->setLogLevel(irr::ELOG_LEVEL::ELL_ERROR); #endif @@ -987,13 +1012,18 @@ void Game::MainLoop() { int fps = 0; auto lastFpsTime = std::chrono::steady_clock::now(); #ifdef _WIN32 - HANDLE hWaitTimer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_MODIFY_STATE | SYNCHRONIZE); - bool useHighResTimer = (hWaitTimer != NULL); - if(!useHighResTimer) - timeBeginPeriod(1); + HANDLE hWaitTimer = NULL; + bool useHighResTimer = false; + if(!gameConf.vsync) { + hWaitTimer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_MODIFY_STATE | SYNCHRONIZE); + useHighResTimer = (hWaitTimer != NULL); + if(!useHighResTimer) + timeBeginPeriod(1); + } #endif auto lastFrameTime = std::chrono::steady_clock::now(); - constexpr auto targetFrameDuration = std::chrono::microseconds(16667); + auto targetFrameDuration = std::chrono::microseconds(1000000 / effectiveFps); + float lineAccum = 0; while(device->run()) { auto size = driver->getScreenSize(); if(window_size != size) { @@ -1004,97 +1034,122 @@ void Game::MainLoop() { OnResize(); gMutex.unlock(); } - linePattern = (linePattern + 1) % 30; - stippleMask = (stippleMask << 1) | (stippleMask >> 15); - atkframe += 0.1f; + logicalFrameAccum += 1.0f; + logicalTick = false; + if(logicalFrameAccum >= fpsScale) { + logicalFrameAccum -= fpsScale; + logicalTick = true; + } + lineAccum += 1.0f; + while(lineAccum >= fpsScale) { + lineAccum -= fpsScale; + linePattern = (linePattern + 1) % 30; + stippleMask = (stippleMask << 1) | (stippleMask >> 15); + } + atkframe += 0.1f / fpsScale; if(atkframe > 6.2832f) atkframe -= 6.2832f; atkdy = (float)sin(atkframe); - driver->beginScene(true, true, irr::video::SColor(0, 0, 0, 0)); - gMutex.lock(); - if(dInfo.isStarted) { - if(dInfo.isFinished && showcardcode == 1) - soundManager.PlayBGM(BGM_WIN); - else if(dInfo.isFinished && (showcardcode == 2 || showcardcode == 3)) - soundManager.PlayBGM(BGM_LOSE); - else if(dInfo.lp[0] > 0 && dInfo.lp[0] <= dInfo.lp[1] / 2) - soundManager.PlayBGM(BGM_DISADVANTAGE); - else if(dInfo.lp[0] > 0 && dInfo.lp[0] >= dInfo.lp[1] * 2) - soundManager.PlayBGM(BGM_ADVANTAGE); - else - soundManager.PlayBGM(BGM_DUEL); - DrawBackImage(imageManager.tBackGround); - DrawBackGround(); - DrawCards(); - DrawMisc(); - smgr->drawAll(); - driver->setMaterial(irr::video::IdentityMaterial); - driver->clearZBuffer(); - } else if(is_building) { - soundManager.PlayBGM(BGM_DECK); - DrawBackImage(imageManager.tBackGround_deck); - DrawDeckBd(); + bool isMinimized = device->isWindowMinimized(); + if(!isMinimized) { + driver->beginScene(true, true, irr::video::SColor(0, 0, 0, 0)); + gMutex.lock(); + if(dInfo.isStarted) { + if(dInfo.isFinished && showcardcode == 1) + soundManager.PlayBGM(BGM_WIN); + else if(dInfo.isFinished && (showcardcode == 2 || showcardcode == 3)) + soundManager.PlayBGM(BGM_LOSE); + else if(dInfo.lp[0] > 0 && dInfo.lp[0] <= dInfo.lp[1] / 2) + soundManager.PlayBGM(BGM_DISADVANTAGE); + else if(dInfo.lp[0] > 0 && dInfo.lp[0] >= dInfo.lp[1] * 2) + soundManager.PlayBGM(BGM_ADVANTAGE); + else + soundManager.PlayBGM(BGM_DUEL); + DrawBackImage(imageManager.tBackGround); + DrawBackGround(); + DrawCards(); + DrawMisc(); + smgr->drawAll(); + driver->setMaterial(irr::video::IdentityMaterial); + driver->clearZBuffer(); + } else if(is_building) { + soundManager.PlayBGM(BGM_DECK); + DrawBackImage(imageManager.tBackGround_deck); + DrawDeckBd(); + } else { + soundManager.PlayBGM(BGM_MENU); + DrawBackImage(imageManager.tBackGround_menu); + } + DrawGUI(); + DrawSpec(); + gMutex.unlock(); + if(signalFrame > 0) { + signalFrame--; + if(!signalFrame) + frameSignal.Set(); + } + if(waitFrame >= 0 && logicalTick) { + waitFrame++; + if(waitFrame % 90 == 0) { + stHintMsg->setText(dataManager.GetSysString(1390)); + } else if(waitFrame % 90 == 30) { + stHintMsg->setText(dataManager.GetSysString(1391)); + } else if(waitFrame % 90 == 60) { + stHintMsg->setText(dataManager.GetSysString(1392)); + } + } + driver->endScene(); } else { - soundManager.PlayBGM(BGM_MENU); - DrawBackImage(imageManager.tBackGround_menu); - } - DrawGUI(); - DrawSpec(); - gMutex.unlock(); - if(signalFrame > 0) { - signalFrame--; - if(!signalFrame) - frameSignal.Set(); - } - if(waitFrame >= 0) { - waitFrame++; - if(waitFrame % 90 == 0) { - stHintMsg->setText(dataManager.GetSysString(1390)); - } else if(waitFrame % 90 == 30) { - stHintMsg->setText(dataManager.GetSysString(1391)); - } else if(waitFrame % 90 == 60) { - stHintMsg->setText(dataManager.GetSysString(1392)); + if(signalFrame > 0) { + signalFrame--; + if(!signalFrame) + frameSignal.Set(); } } - driver->endScene(); if(closeSignal.TryWait()) CloseDuelWindow(); fps++; - auto targetTime = lastFrameTime + targetFrameDuration; - auto now = std::chrono::steady_clock::now(); - if(now < targetTime) { + if(gameConf.vsync && isMinimized) { + // downscale to 60fps, reduce CPU usage + std::this_thread::sleep_for(std::chrono::microseconds(16667)); + } else if(!gameConf.vsync) { + auto targetTime = lastFrameTime + targetFrameDuration; + auto now = std::chrono::steady_clock::now(); + if(now < targetTime) { #ifdef _WIN32 - if(useHighResTimer) - { - auto remaining = std::chrono::duration_cast(targetTime - now); - if(remaining.count() > 1500) { - LARGE_INTEGER dueTime; - dueTime.QuadPart = -(LONGLONG)(remaining.count() - 1000) * 10; - if(SetWaitableTimer(hWaitTimer, &dueTime, 0, NULL, NULL, FALSE)) - WaitForSingleObject(hWaitTimer, INFINITE); + if(useHighResTimer) + { + auto remaining = std::chrono::duration_cast(targetTime - now); + if(remaining.count() > 1500) { + LARGE_INTEGER dueTime; + dueTime.QuadPart = -(LONGLONG)(remaining.count() - 1000) * 10; + if(SetWaitableTimer(hWaitTimer, &dueTime, 0, NULL, NULL, FALSE)) + WaitForSingleObject(hWaitTimer, INFINITE); + } } - } - else + else #endif - { - auto sleepTime = targetTime - now - std::chrono::milliseconds(2); - if(sleepTime > std::chrono::milliseconds(0)) { - std::this_thread::sleep_for(sleepTime); + { + auto sleepTime = targetTime - now - std::chrono::milliseconds(2); + if(sleepTime > std::chrono::milliseconds(0)) { + std::this_thread::sleep_for(sleepTime); + } + } + // Spin-wait for sub-millisecond precision. + // If the window is inactive, sleep 1ms per iteration to avoid wasting CPU. + while(std::chrono::steady_clock::now() < targetTime) { + if(device->isWindowActive()) + CPU_PAUSE(); + else + std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } - // Spin-wait for sub-millisecond precision. - // If the window is inactive, sleep 1ms per iteration to avoid wasting CPU. - while(std::chrono::steady_clock::now() < targetTime) { - if(device->isWindowActive()) - CPU_PAUSE(); - else - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } + lastFrameTime = targetTime; + now = std::chrono::steady_clock::now(); + if(now - targetTime > targetFrameDuration) + lastFrameTime = now; } - lastFrameTime = targetTime; - now = std::chrono::steady_clock::now(); - if(now - targetTime > targetFrameDuration) - lastFrameTime = now; + auto now = std::chrono::steady_clock::now(); if(now - lastFpsTime >= std::chrono::milliseconds(1000)) { myswprintf(cap, L"YGOPro FPS: %d", fps); device->setWindowCaption(cap); @@ -1108,10 +1163,12 @@ void Game::MainLoop() { } } #ifdef _WIN32 - if(useHighResTimer) - CloseHandle(hWaitTimer); - else - timeEndPeriod(1); + if(!gameConf.vsync) { + if(useHighResTimer) + CloseHandle(hWaitTimer); + else + timeEndPeriod(1); + } #endif DuelClient::StopClient(true); if(dInfo.isSingleMode) @@ -1120,6 +1177,11 @@ void Game::MainLoop() { SaveConfig(); device->drop(); } +int Game::ScaleFrame(int frame60) const { + if(device->isWindowMinimized()) return frame60; + int scaled = (int)(frame60 * fpsScale + 0.5f); + return scaled < 1 ? 1 : scaled; +} void Game::BuildProjectionMatrix(irr::core::matrix4& mProjection, irr::f32 left, irr::f32 right, irr::f32 bottom, irr::f32 top, irr::f32 znear, irr::f32 zfar) { for(int i = 0; i < 16; ++i) mProjection[i] = 0; @@ -1388,6 +1450,10 @@ void Game::LoadConfig() { gameConf.antialias = std::strtol(valbuf, nullptr, 10); } else if(!std::strcmp(strbuf, "use_d3d")) { gameConf.use_d3d = std::strtol(valbuf, nullptr, 10) > 0; + } else if(!std::strcmp(strbuf, "vsync")) { + gameConf.vsync = std::strtol(valbuf, nullptr, 10) > 0; + } else if(!std::strcmp(strbuf, "target_fps")) { + gameConf.target_fps = std::strtol(valbuf, nullptr, 10); } else if (!std::strcmp(strbuf, "use_image_scale_multi_thread")) { gameConf.use_image_scale_multi_thread = std::strtol(valbuf, nullptr, 10) > 0; } else if (!std::strcmp(strbuf, "use_image_load_background_thread")) { @@ -1522,7 +1588,11 @@ void Game::SaveConfig() { std::fprintf(fp, "#config file\n#nickname & gamename should be less than 20 characters\n"); char linebuf[CONFIG_LINE_SIZE]; std::fprintf(fp, "use_d3d = %d\n", gameConf.use_d3d ? 1 : 0); - std::fprintf(fp, "use_image_scale_multi_thread = %d\n", gameConf.use_image_scale_multi_thread ? 1 : 0); + std::fprintf(fp, "vsync = %d\n", gameConf.vsync ? 1 : 0); + std::fprintf(fp, "#target_fps: Controls FPS and calculation of animation speed. Default: 0\n"); + std::fprintf(fp, "# Vsync on: 0 = auto-detect monitor refresh rate. If auto-detect fails, animation speed may be too fast, please try setting this manually\n"); + std::fprintf(fp, "# Vsync off: 0 = run at default 60 FPS. Can be manually set to any value from 60 to 1000\n"); + std::fprintf(fp, "target_fps = %d\n", gameConf.target_fps); std::fprintf(fp, "use_image_load_background_thread = %d\n", gameConf.use_image_load_background_thread ? 1 : 0); std::fprintf(fp, "antialias = %d\n", gameConf.antialias); std::fprintf(fp, "errorlog = %u\n", enable_log); diff --git a/gframe/game.h b/gframe/game.h index 8b63a8fbc2..0aa43731db 100644 --- a/gframe/game.h +++ b/gframe/game.h @@ -46,6 +46,12 @@ bool IsExtension(const char* filename, const char(&extension)[N]) { struct Config { bool use_d3d{ false }; +#if defined(_WIN32) + bool vsync{ true }; +#else + bool vsync{ false }; +#endif + int target_fps{ 0 }; bool use_image_scale_multi_thread{ true }; #ifdef _OPENMP bool use_image_load_background_thread{ false }; @@ -149,6 +155,7 @@ struct FadingUnit { bool signalAction{}; bool isFadein{}; int fadingFrame{}; + int fadingHalf{}; int autoFadeoutFrame{}; irr::gui::IGUIElement* guiFading{}; irr::core::recti fadingSize; @@ -162,6 +169,7 @@ class Game { public: bool Initialize(); void MainLoop(); + int ScaleFrame(int frame60) const; void BuildProjectionMatrix(irr::core::matrix4& mProjection, irr::f32 left, irr::f32 right, irr::f32 bottom, irr::f32 top, irr::f32 znear, irr::f32 zfar); void InitStaticText(irr::gui::IGUIStaticText* pControl, irr::u32 cWidth, irr::u32 cHeight, irr::gui::CGUITTFont* font, const wchar_t* text); std::wstring SetStaticText(irr::gui::IGUIStaticText* pControl, irr::u32 cWidth, irr::gui::CGUITTFont* font, const wchar_t* text, irr::u32 pos = 0); @@ -272,6 +280,10 @@ class Game { unsigned short stippleMask{ 0x0f0f }; int waitFrame{}; int signalFrame{}; + int effectiveFps{}; + float fpsScale{ 1.0f }; + bool logicalTick{}; + float logicalFrameAccum{}; int actionParam{}; int showingcode{}; const wchar_t* showingtext{}; @@ -284,8 +296,10 @@ class Game { irr::core::vector3df atk_r; irr::core::vector3df atk_t; float atkdy{}; - int lpframe{}; - int lpd{}; + int lpFrameCount{}; + int lpFrame{}; + int lpInitial{}; + int lpFinal{}; int lpplayer{}; irr::u32 lpccolor{}; std::wstring lpcstring; From 39cd4aa34d1886541ce308050a04de74476219a8 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Thu, 2 Jul 2026 14:47:15 +0800 Subject: [PATCH 02/10] revert skip draw on mininize was reducing signalFrame but not progressing anim in DrawCards etc. --- gframe/game.cpp | 99 ++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 54 deletions(-) diff --git a/gframe/game.cpp b/gframe/game.cpp index 58bdf69a05..27e5a9de59 100644 --- a/gframe/game.cpp +++ b/gframe/game.cpp @@ -1072,66 +1072,57 @@ void Game::MainLoop() { if(atkframe > 6.2832f) atkframe -= 6.2832f; atkdy = (float)sin(atkframe); - bool isMinimized = device->isWindowMinimized(); - if(!isMinimized) { - driver->beginScene(true, true, irr::video::SColor(0, 0, 0, 0)); - gMutex.lock(); - if(dInfo.isStarted) { - if(dInfo.isFinished && showcardcode == 1) - soundManager.PlayBGM(BGM_WIN); - else if(dInfo.isFinished && (showcardcode == 2 || showcardcode == 3)) - soundManager.PlayBGM(BGM_LOSE); - else if(dInfo.lp[0] > 0 && dInfo.lp[0] <= dInfo.lp[1] / 2) - soundManager.PlayBGM(BGM_DISADVANTAGE); - else if(dInfo.lp[0] > 0 && dInfo.lp[0] >= dInfo.lp[1] * 2) - soundManager.PlayBGM(BGM_ADVANTAGE); - else - soundManager.PlayBGM(BGM_DUEL); - DrawBackImage(imageManager.tBackGround); - DrawBackGround(); - DrawCards(); - DrawMisc(); - smgr->drawAll(); - driver->setMaterial(irr::video::IdentityMaterial); - driver->clearZBuffer(); - } else if(is_building) { - soundManager.PlayBGM(BGM_DECK); - DrawBackImage(imageManager.tBackGround_deck); - DrawDeckBd(); - } else { - soundManager.PlayBGM(BGM_MENU); - DrawBackImage(imageManager.tBackGround_menu); - } - DrawGUI(); - DrawSpec(); - gMutex.unlock(); - if(signalFrame > 0) { - signalFrame--; - if(!signalFrame) - frameSignal.Set(); - } - if(waitFrame >= 0 && logicalTick) { - waitFrame++; - if(waitFrame % 90 == 0) { - stHintMsg->setText(dataManager.GetSysString(1390)); - } else if(waitFrame % 90 == 30) { - stHintMsg->setText(dataManager.GetSysString(1391)); - } else if(waitFrame % 90 == 60) { - stHintMsg->setText(dataManager.GetSysString(1392)); - } - } - driver->endScene(); + driver->beginScene(true, true, irr::video::SColor(0, 0, 0, 0)); + gMutex.lock(); + if(dInfo.isStarted) { + if(dInfo.isFinished && showcardcode == 1) + soundManager.PlayBGM(BGM_WIN); + else if(dInfo.isFinished && (showcardcode == 2 || showcardcode == 3)) + soundManager.PlayBGM(BGM_LOSE); + else if(dInfo.lp[0] > 0 && dInfo.lp[0] <= dInfo.lp[1] / 2) + soundManager.PlayBGM(BGM_DISADVANTAGE); + else if(dInfo.lp[0] > 0 && dInfo.lp[0] >= dInfo.lp[1] * 2) + soundManager.PlayBGM(BGM_ADVANTAGE); + else + soundManager.PlayBGM(BGM_DUEL); + DrawBackImage(imageManager.tBackGround); + DrawBackGround(); + DrawCards(); + DrawMisc(); + smgr->drawAll(); + driver->setMaterial(irr::video::IdentityMaterial); + driver->clearZBuffer(); + } else if(is_building) { + soundManager.PlayBGM(BGM_DECK); + DrawBackImage(imageManager.tBackGround_deck); + DrawDeckBd(); } else { - if(signalFrame > 0) { - signalFrame--; - if(!signalFrame) - frameSignal.Set(); + soundManager.PlayBGM(BGM_MENU); + DrawBackImage(imageManager.tBackGround_menu); + } + DrawGUI(); + DrawSpec(); + gMutex.unlock(); + if(signalFrame > 0) { + signalFrame--; + if(!signalFrame) + frameSignal.Set(); + } + if(waitFrame >= 0 && logicalTick) { + waitFrame++; + if(waitFrame % 90 == 0) { + stHintMsg->setText(dataManager.GetSysString(1390)); + } else if(waitFrame % 90 == 30) { + stHintMsg->setText(dataManager.GetSysString(1391)); + } else if(waitFrame % 90 == 60) { + stHintMsg->setText(dataManager.GetSysString(1392)); } } + driver->endScene(); if(closeSignal.TryWait()) CloseDuelWindow(); fps++; - if(gameConf.vsync && isMinimized) { + if(gameConf.vsync && device->isWindowMinimized()) { // downscale to 60fps, reduce CPU usage std::this_thread::sleep_for(std::chrono::microseconds(16667)); } else if(!gameConf.vsync) { From 13cadb8746178774ee98d1a0b85666d8798204d4 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Thu, 2 Jul 2026 17:37:23 +0800 Subject: [PATCH 03/10] add SetCardMovement, fix aniFrame --- gframe/client_field.cpp | 18 +++++-- gframe/client_field.h | 5 +- gframe/duelclient.cpp | 104 +++++++++++----------------------------- 3 files changed, 46 insertions(+), 81 deletions(-) diff --git a/gframe/client_field.cpp b/gframe/client_field.cpp index 097af3482d..5c6c1d6721 100644 --- a/gframe/client_field.cpp +++ b/gframe/client_field.cpp @@ -1074,8 +1074,18 @@ void ClientField::GetCardLocation(ClientCard* pcard, irr::core::vector3df* t, ir pcard->mTransform.setRotationRadians(*r); } } -void ClientField::MoveCard(ClientCard * pcard, int frame) { - frame = mainGame->ScaleFrame(frame); +// Set the movement of a card over a specified number of frames. +void ClientField::SetCardMovement(ClientCard* pcard, int frame60, irr::core::vector3df dPos, irr::core::vector3df dRot) { + int frame = mainGame->ScaleFrame(frame60); + float frameScale = static_cast(frame60) / frame; + pcard->dPos = dPos * frameScale; + pcard->dRot = dRot * frameScale; + pcard->is_moving = true; + pcard->aniFrame = frame; +} +// Move (reset) a card to its current location over a specified number of frames. +void ClientField::MoveCard(ClientCard * pcard, int frame60) { + int frame = mainGame->ScaleFrame(frame60); irr::core::vector3df trans = pcard->curPos; irr::core::vector3df rot = pcard->curRot; GetCardLocation(pcard, &trans, &rot); @@ -1105,8 +1115,8 @@ void ClientField::MoveCard(ClientCard * pcard, int frame) { pcard->is_moving = true; pcard->aniFrame = frame; } -void ClientField::FadeCard(ClientCard * pcard, int alpha, int frame) { - frame = mainGame->ScaleFrame(frame); +void ClientField::FadeCard(ClientCard * pcard, int alpha, int frame60) { + int frame = mainGame->ScaleFrame(frame60); pcard->dAlpha = (alpha - pcard->curAlpha) / frame; pcard->is_fading = true; pcard->aniFrame = frame; diff --git a/gframe/client_field.h b/gframe/client_field.h index 1e2b34b8e7..178f552ffb 100644 --- a/gframe/client_field.h +++ b/gframe/client_field.h @@ -114,8 +114,9 @@ class ClientField: public irr::IEventReceiver { void GetChainLocation(int controler, int location, int sequence, irr::core::vector3df* t); void GetCardLocation(ClientCard* pcard, irr::core::vector3df* t, irr::core::vector3df* r, bool setTrans = false); - void MoveCard(ClientCard* pcard, int frame); - void FadeCard(ClientCard* pcard, int alpha, int frame); + void SetCardMovement(ClientCard* pcard, int frame60, irr::core::vector3df dPos, irr::core::vector3df dRot = irr::core::vector3df(0, 0, 0)); + void MoveCard(ClientCard* pcard, int frame60); + void FadeCard(ClientCard* pcard, int alpha, int frame60); bool ShowSelectSum(bool panelmode); // caller must hold gMutex bool CheckSelectSum(); bool CheckSelectTribute(); diff --git a/gframe/duelclient.cpp b/gframe/duelclient.cpp index f7069228ed..6f0d039de6 100644 --- a/gframe/duelclient.cpp +++ b/gframe/duelclient.cpp @@ -2114,12 +2114,11 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, size_t len) { mainGame->gMutex.unlock(); float shift = -0.15f; if (player == 1) shift = 0.15f; - pcard->dPos = irr::core::vector3df(shift, 0, 0); + irr::core::vector3df dPos(shift, 0, 0); + irr::core::vector3df dRot(0, 0, 0); if(!mainGame->dField.deck_reversed) - pcard->dRot = irr::core::vector3df(0, 3.14159f / 5.0f, 0); - else pcard->dRot = irr::core::vector3df(0, 0, 0); - pcard->is_moving = true; - pcard->aniFrame = 5; + dRot = irr::core::vector3df(0, 3.14159f / 5.0f, 0); + mainGame->dField.SetCardMovement(pcard, 5, dPos, dRot); mainGame->WaitFrameSignal(count > 5 ? 12 : 45); mainGame->dField.MoveCard(pcard, 5); mainGame->WaitFrameSignal(5); @@ -2150,13 +2149,8 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, size_t len) { myswprintf(textBuffer, L"*[%ls]", dataManager.GetName(pcard->code)); mainGame->AddLog(textBuffer, pcard->code); mainGame->gMutex.unlock(); - if (player == 0) - pcard->dPos = irr::core::vector3df(0, -0.20f, 0); - else - pcard->dPos = irr::core::vector3df(0.15f, 0, 0); - pcard->dRot = irr::core::vector3df(0, 3.14159f / 5.0f, 0); - pcard->is_moving = true; - pcard->aniFrame = 5; + irr::core::vector3df dPos = player == 0 ? irr::core::vector3df(0, -0.20f, 0) : irr::core::vector3df(0.15f, 0, 0); + mainGame->dField.SetCardMovement(pcard, 5, dPos, irr::core::vector3df(0, 3.14159f / 5.0f, 0)); mainGame->WaitFrameSignal(45); mainGame->dField.MoveCard(pcard, 5); mainGame->WaitFrameSignal(5); @@ -2195,12 +2189,11 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, size_t len) { if(count == 1) { float shift = -0.15f; if (c == 0 && l == 0x40) shift = 0.15f; - pcard->dPos = irr::core::vector3df(shift, 0, 0); - if((l == LOCATION_DECK) && mainGame->dField.deck_reversed) - pcard->dRot = irr::core::vector3df(0, 0, 0); - else pcard->dRot = irr::core::vector3df(0, 3.14159f / 5.0f, 0); - pcard->is_moving = true; - pcard->aniFrame = 5; + irr::core::vector3df dPos(shift, 0, 0); + irr::core::vector3df dRot(0, 0, 0); + if(!((l == LOCATION_DECK) && mainGame->dField.deck_reversed)) + dRot = irr::core::vector3df(0, 3.14159f / 5.0f, 0); + mainGame->dField.SetCardMovement(pcard, 5, dPos, dRot); mainGame->WaitFrameSignal(45); mainGame->dField.MoveCard(pcard, 5); mainGame->WaitFrameSignal(5); @@ -2225,20 +2218,16 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, size_t len) { } else if (l == LOCATION_MZONE) { if (pcard->position & POS_FACEUP) continue; - pcard->dPos = irr::core::vector3df(0, 0, 0); + irr::core::vector3df dRot(0, 0, 0); if (pcard->position == POS_FACEDOWN_ATTACK) - pcard->dRot = irr::core::vector3df(0, 3.14159f / 5.0f, 0); + dRot = irr::core::vector3df(0, 3.14159f / 5.0f, 0); else - pcard->dRot = irr::core::vector3df(3.14159f / 5.0f, 0, 0); - pcard->is_moving = true; - pcard->aniFrame = 5; + dRot = irr::core::vector3df(3.14159f / 5.0f, 0, 0); + mainGame->dField.SetCardMovement(pcard, 5, irr::core::vector3df(0, 0, 0), dRot); } else if (l == LOCATION_SZONE) { if (pcard->position & POS_FACEUP) continue; - pcard->dPos = irr::core::vector3df(0, 0, 0); - pcard->dRot = irr::core::vector3df(0, 3.14159f / 5.0f, 0); - pcard->is_moving = true; - pcard->aniFrame = 5; + mainGame->dField.SetCardMovement(pcard, 5, irr::core::vector3df(0, 0, 0), irr::core::vector3df(0, 3.14159f / 5.0f, 0)); } } if (mainGame->dInfo.isReplay) @@ -2286,10 +2275,7 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, size_t len) { soundManager.PlaySoundEffect(SOUND_SHUFFLE); for (int i = 0; i < 5; ++i) { for (auto cit = mainGame->dField.deck[player].begin(); cit != mainGame->dField.deck[player].end(); ++cit) { - (*cit)->dPos = irr::core::vector3df(real_dist(rnd) * 0.4f - 0.2f, 0, 0); - (*cit)->dRot = irr::core::vector3df(0, 0, 0); - (*cit)->is_moving = true; - (*cit)->aniFrame = 3; + mainGame->dField.SetCardMovement(*cit, 3, irr::core::vector3df(real_dist(rnd) * 0.4f - 0.2f, 0, 0)); } mainGame->WaitFrameSignal(3); for (auto cit = mainGame->dField.deck[player].begin(); cit != mainGame->dField.deck[player].end(); ++cit) @@ -2315,22 +2301,16 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, size_t len) { bool flip = false; for (auto cit = mainGame->dField.hand[player].begin(); cit != mainGame->dField.hand[player].end(); ++cit) if((*cit)->code) { - (*cit)->dPos = irr::core::vector3df(0, 0, 0); - (*cit)->dRot = irr::core::vector3df(1.322f / 5, 3.1415926f / 5, 0); - (*cit)->is_moving = true; (*cit)->is_hovered = false; - (*cit)->aniFrame = 5; + mainGame->dField.SetCardMovement(*cit, 5, irr::core::vector3df(0, 0, 0), irr::core::vector3df(1.322f / 5, 3.1415926f / 5, 0)); flip = true; } if(flip) mainGame->WaitFrameSignal(5); } for (auto cit = mainGame->dField.hand[player].begin(); cit != mainGame->dField.hand[player].end(); ++cit) { - (*cit)->dPos = irr::core::vector3df((3.9f - (*cit)->curPos.X) / 5, 0, 0); - (*cit)->dRot = irr::core::vector3df(0, 0, 0); - (*cit)->is_moving = true; (*cit)->is_hovered = false; - (*cit)->aniFrame = 5; + mainGame->dField.SetCardMovement(*cit, 5, irr::core::vector3df((3.9f - (*cit)->curPos.X) / 5, 0, 0)); } mainGame->WaitFrameSignal(11); } @@ -2358,10 +2338,7 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, size_t len) { for (int i = 0; i < 5; ++i) { for (auto cit = mainGame->dField.extra[player].begin(); cit != mainGame->dField.extra[player].end(); ++cit) { if(!((*cit)->position & POS_FACEUP)) { - (*cit)->dPos = irr::core::vector3df(real_dist(rnd) * 0.4f - 0.2f, 0, 0); - (*cit)->dRot = irr::core::vector3df(0, 0, 0); - (*cit)->is_moving = true; - (*cit)->aniFrame = 3; + mainGame->dField.SetCardMovement(*cit, 3, irr::core::vector3df(real_dist(rnd) * 0.4f - 0.2f, 0, 0)); } } mainGame->WaitFrameSignal(3); @@ -2467,10 +2444,7 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, size_t len) { mc[i] = lst[c][s]; mc[i]->SetCode(0); if(!mainGame->dInfo.isReplay || !mainGame->dInfo.isReplaySkiping) { - mc[i]->dPos = irr::core::vector3df((3.95f - mc[i]->curPos.X) / 10, 0, 0.05f); - mc[i]->dRot = irr::core::vector3df(0, 0, 0); - mc[i]->is_moving = true; - mc[i]->aniFrame = 10; + mainGame->dField.SetCardMovement(mc[i], 10, irr::core::vector3df((3.95f - mc[i]->curPos.X) / 10, 0, 0.05f)); } } if(!mainGame->dInfo.isReplay || !mainGame->dInfo.isReplaySkiping) @@ -2667,11 +2641,8 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, size_t len) { mainGame->dField.AddCard(pcard, cc, cl, cs); mainGame->gMutex.unlock(); if (pl == cl && pc == cc && (cl & 0x71)) { - pcard->dPos = irr::core::vector3df(-0.3f, 0, 0); - pcard->dRot = irr::core::vector3df(0, 0, 0); - if (pc == 1) pcard->dPos.X = 0.3f; - pcard->is_moving = true; - pcard->aniFrame = 5; + float shift = pc == 1 ? 0.3f : -0.3f; + mainGame->dField.SetCardMovement(pcard, 5, irr::core::vector3df(shift, 0, 0)); mainGame->WaitFrameSignal(5); mainGame->dField.MoveCard(pcard, 5); mainGame->WaitFrameSignal(5); @@ -2982,10 +2953,7 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, size_t len) { if(pcard->location & 0x30) { float shift = -0.15f; if(cc == 1) shift = 0.15f; - pcard->dPos = irr::core::vector3df(shift, 0, 0); - pcard->dRot = irr::core::vector3df(0, 0, 0); - pcard->is_moving = true; - pcard->aniFrame = 5; + mainGame->dField.SetCardMovement(pcard, 5, irr::core::vector3df(shift, 0, 0)); mainGame->WaitFrameSignal(30); mainGame->dField.MoveCard(pcard, 5); } else @@ -3125,10 +3093,7 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, size_t len) { } else if(pcard->location & 0x30) { float shift = -0.15f; if(c == 1) shift = 0.15f; - pcard->dPos = irr::core::vector3df(shift, 0, 0); - pcard->dRot = irr::core::vector3df(0, 0, 0); - pcard->is_moving = true; - pcard->aniFrame = 5; + mainGame->dField.SetCardMovement(pcard, 5, irr::core::vector3df(shift, 0, 0)); mainGame->WaitFrameSignal(30); mainGame->dField.MoveCard(pcard, 5); } else @@ -3794,26 +3759,15 @@ bool DuelClient::ClientAnalyze(unsigned char* msg, size_t len) { size_t hcount = (size_t)BufferIO::Read(pbuf); int topcode = BufferIO::Read(pbuf); if(!mainGame->dInfo.isReplay || !mainGame->dInfo.isReplaySkiping) { + float shift = player == 0 ? 0.4f : -0.6f; for (auto cit = mainGame->dField.deck[player].begin(); cit != mainGame->dField.deck[player].end(); ++cit) { - if(player == 0) (*cit)->dPos.Y = 0.4f; - else (*cit)->dPos.Y = -0.6f; - (*cit)->dRot = irr::core::vector3df(0, 0, 0); - (*cit)->is_moving = true; - (*cit)->aniFrame = 5; + mainGame->dField.SetCardMovement(*cit, 5, irr::core::vector3df(0, shift, 0)); } for (auto cit = mainGame->dField.hand[player].begin(); cit != mainGame->dField.hand[player].end(); ++cit) { - if(player == 0) (*cit)->dPos.Y = 0.4f; - else (*cit)->dPos.Y = -0.6f; - (*cit)->dRot = irr::core::vector3df(0, 0, 0); - (*cit)->is_moving = true; - (*cit)->aniFrame = 5; + mainGame->dField.SetCardMovement(*cit, 5, irr::core::vector3df(0, shift, 0)); } for (auto cit = mainGame->dField.extra[player].begin(); cit != mainGame->dField.extra[player].end(); ++cit) { - if(player == 0) (*cit)->dPos.Y = 0.4f; - else (*cit)->dPos.Y = -0.6f; - (*cit)->dRot = irr::core::vector3df(0, 0, 0); - (*cit)->is_moving = true; - (*cit)->aniFrame = 5; + mainGame->dField.SetCardMovement(*cit, 5, irr::core::vector3df(0, shift, 0)); } mainGame->WaitFrameSignal(5); } From 8b32c81b5b908e64ac4c97babc7ba2dd5be01d02 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Thu, 2 Jul 2026 20:27:23 +0800 Subject: [PATCH 04/10] fix FadeCard --- gframe/client_card.h | 5 +++-- gframe/client_field.cpp | 3 ++- gframe/drawing.cpp | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/gframe/client_card.h b/gframe/client_card.h index d4a9c7023e..d5aaa19e21 100644 --- a/gframe/client_card.h +++ b/gframe/client_card.h @@ -15,8 +15,9 @@ class ClientCard { irr::core::vector3df curRot; irr::core::vector3df dPos; irr::core::vector3df dRot; - irr::u32 curAlpha{ 255 }; - irr::u32 dAlpha{ 0 }; + float curAlpha{ 255.0f }; + float dAlpha{ 0.0f }; + int targetAlpha{ 255 }; irr::u32 aniFrame{ 0 }; bool is_moving{ false }; bool is_fading{ false }; diff --git a/gframe/client_field.cpp b/gframe/client_field.cpp index 5c6c1d6721..0b7eb475b6 100644 --- a/gframe/client_field.cpp +++ b/gframe/client_field.cpp @@ -1117,7 +1117,8 @@ void ClientField::MoveCard(ClientCard * pcard, int frame60) { } void ClientField::FadeCard(ClientCard * pcard, int alpha, int frame60) { int frame = mainGame->ScaleFrame(frame60); - pcard->dAlpha = (alpha - pcard->curAlpha) / frame; + pcard->targetAlpha = alpha; + pcard->dAlpha = (pcard->targetAlpha - pcard->curAlpha) / frame; pcard->is_fading = true; pcard->aniFrame = frame; } diff --git a/gframe/drawing.cpp b/gframe/drawing.cpp index ba2dc314dd..9c118bdb08 100644 --- a/gframe/drawing.cpp +++ b/gframe/drawing.cpp @@ -405,13 +405,16 @@ void Game::DrawCard(ClientCard* pcard) { pcard->curAlpha += pcard->dAlpha; pcard->aniFrame--; if(pcard->aniFrame == 0) { + if(pcard->is_fading) + pcard->curAlpha = pcard->targetAlpha; pcard->is_moving = false; pcard->is_fading = false; pcard->chain_code = 0; } } matManager.mCard.AmbientColor = 0xffffffff; - matManager.mCard.DiffuseColor = (pcard->curAlpha << 24) | 0xffffff; + irr::u32 alpha = myclamp(static_cast(pcard->curAlpha + 0.5f), 0, 255); + matManager.mCard.DiffuseColor = (alpha << 24) | 0xffffff; driver->setTransform(irr::video::ETS_WORLD, pcard->mTransform); auto m22 = pcard->mTransform(2, 2); if(m22 > -0.99 || pcard->is_moving) { From ae40c4a1efeb9c1578c9ceb75af904ac0b4e438d Mon Sep 17 00:00:00 2001 From: mercury233 Date: Fri, 3 Jul 2026 13:38:42 +0800 Subject: [PATCH 05/10] fix fading elements --- gframe/drawing.cpp | 79 ++++++++++++++++++++-------------------------- gframe/game.h | 6 ++-- 2 files changed, 38 insertions(+), 47 deletions(-) diff --git a/gframe/drawing.cpp b/gframe/drawing.cpp index 9c118bdb08..8b9b6df94d 100644 --- a/gframe/drawing.cpp +++ b/gframe/drawing.cpp @@ -804,43 +804,36 @@ void Game::DrawGUI() { for(auto fit = fadingList.begin(); fit != fadingList.end();) { auto fthis = fit++; FadingUnit& fu = *fthis; - if(fu.fadingFrame) { + if(fu.fadingFrame > 0) { fu.guiFading->setVisible(true); - if(fu.isFadein) { - if(fu.fadingFrame > fu.fadingHalf) { - fu.fadingUL.X -= fu.fadingDiff.X; - fu.fadingLR.X += fu.fadingDiff.X; - fu.fadingFrame--; - fu.guiFading->setRelativePosition(irr::core::recti(fu.fadingUL, fu.fadingLR)); - } else { - fu.fadingUL.Y -= fu.fadingDiff.Y; - fu.fadingLR.Y += fu.fadingDiff.Y; - fu.fadingFrame--; - if(!fu.fadingFrame) { - fu.guiFading->setRelativePosition(fu.fadingSize); - SetImageButtonDrawing(fu.guiFading, true); - env->setFocus(fu.guiFading); - } else - fu.guiFading->setRelativePosition(irr::core::recti(fu.fadingUL, fu.fadingLR)); - } + const bool isFirstHalf = fu.fadingFrame > fu.fadingHalf; + if(fu.isFadein == isFirstHalf) { + const auto fadingDiff = fu.fadingDiff.X * (fu.isFadein ? -1 : 1); + fu.fadingUL.X += fadingDiff; + fu.fadingLR.X -= fadingDiff; + } else { + const auto fadingDiff = fu.fadingDiff.Y * (fu.isFadein ? -1 : 1); + fu.fadingUL.Y += fadingDiff; + fu.fadingLR.Y -= fadingDiff; + } + fu.fadingFrame--; + if(fu.fadingFrame > 0) { + fu.guiFading->setRelativePosition(irr::core::recti( + static_cast(fu.fadingUL.X), + static_cast(fu.fadingUL.Y), + static_cast(fu.fadingLR.X), + static_cast(fu.fadingLR.Y) + )); } else { - if(fu.fadingFrame > fu.fadingHalf) { - fu.fadingUL.Y += fu.fadingDiff.Y; - fu.fadingLR.Y -= fu.fadingDiff.Y; - fu.fadingFrame--; - fu.guiFading->setRelativePosition(irr::core::recti(fu.fadingUL, fu.fadingLR)); + fu.guiFading->setRelativePosition(fu.fadingSize); + if(fu.isFadein) { + SetImageButtonDrawing(fu.guiFading, true); + env->setFocus(fu.guiFading); } else { - fu.fadingUL.X += fu.fadingDiff.X; - fu.fadingLR.X -= fu.fadingDiff.X; - fu.fadingFrame--; - if(!fu.fadingFrame) { - fu.guiFading->setVisible(false); - fu.guiFading->setRelativePosition(fu.fadingSize); - SetImageButtonDrawing(fu.guiFading, false); - } else - fu.guiFading->setRelativePosition(irr::core::recti(fu.fadingUL, fu.fadingLR)); + fu.guiFading->setVisible(false); + SetImageButtonDrawing(fu.guiFading, false); } - if(fu.signalAction && !fu.fadingFrame) { + if(fu.signalAction) { DuelClient::SendResponse(); fu.signalAction = false; } @@ -1102,18 +1095,16 @@ void Game::ShowElement(irr::gui::IGUIElement * win, int autoframe) { fu.fadingSize = fit->fadingSize; irr::core::vector2di center = fu.fadingSize.getCenter(); int frame = ScaleFrame(10); - fu.fadingDiff.X = fu.fadingSize.getWidth() / frame; - fu.fadingDiff.Y = (fu.fadingSize.getHeight() - 4) / frame; - fu.fadingUL = center; - fu.fadingLR = center; - fu.fadingUL.Y -= 2; - fu.fadingLR.Y += 2; + fu.fadingDiff.X = static_cast(fu.fadingSize.getWidth()) / frame; + fu.fadingDiff.Y = static_cast(fu.fadingSize.getHeight() - 4) / frame; + fu.fadingUL = irr::core::vector2df(center.X, center.Y - 2); + fu.fadingLR = irr::core::vector2df(center.X, center.Y + 2); fu.guiFading = win; fu.isFadein = true; fu.fadingFrame = frame; fu.fadingHalf = frame / 2; fu.autoFadeoutFrame = autoframe ? ScaleFrame(autoframe) : 0; - fu.signalAction = 0; + fu.signalAction = false; SetImageButtonDrawing(win, false); win->setRelativePosition(irr::core::recti(center.X, center.Y, 0, 0)); win->setVisible(true); @@ -1128,10 +1119,10 @@ void Game::HideElement(irr::gui::IGUIElement * win, bool set_action) { if(win == fit->guiFading) fu.fadingSize = fit->fadingSize; int frame = ScaleFrame(10); - fu.fadingDiff.X = fu.fadingSize.getWidth() / frame; - fu.fadingDiff.Y = (fu.fadingSize.getHeight() - 4) / frame; - fu.fadingUL = fu.fadingSize.UpperLeftCorner; - fu.fadingLR = fu.fadingSize.LowerRightCorner; + fu.fadingDiff.X = static_cast(fu.fadingSize.getWidth()) / frame; + fu.fadingDiff.Y = static_cast(fu.fadingSize.getHeight() - 4) / frame; + fu.fadingUL = irr::core::vector2df(fu.fadingSize.UpperLeftCorner.X, fu.fadingSize.UpperLeftCorner.Y); + fu.fadingLR = irr::core::vector2df(fu.fadingSize.LowerRightCorner.X, fu.fadingSize.LowerRightCorner.Y); fu.guiFading = win; fu.isFadein = false; fu.fadingFrame = frame; diff --git a/gframe/game.h b/gframe/game.h index f329d77361..cd7fe7ab81 100644 --- a/gframe/game.h +++ b/gframe/game.h @@ -158,9 +158,9 @@ struct FadingUnit { int autoFadeoutFrame{}; irr::gui::IGUIElement* guiFading{}; irr::core::recti fadingSize; - irr::core::vector2di fadingUL; - irr::core::vector2di fadingLR; - irr::core::vector2di fadingDiff; + irr::core::vector2df fadingUL; + irr::core::vector2df fadingLR; + irr::core::vector2df fadingDiff; }; class Game { From a50e8a154c0a6571ce3f1db5a578832ab047f540 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Fri, 3 Jul 2026 14:25:56 +0800 Subject: [PATCH 06/10] eliminate fu.fadingDiff.X numerical error --- gframe/drawing.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/gframe/drawing.cpp b/gframe/drawing.cpp index 8b9b6df94d..56755c19f1 100644 --- a/gframe/drawing.cpp +++ b/gframe/drawing.cpp @@ -816,6 +816,10 @@ void Game::DrawGUI() { fu.fadingUL.Y += fadingDiff; fu.fadingLR.Y -= fadingDiff; } + if(fu.isFadein && fu.fadingFrame == fu.fadingHalf) { + fu.fadingUL.X = fu.fadingSize.UpperLeftCorner.X; + fu.fadingLR.X = fu.fadingSize.LowerRightCorner.X; + } fu.fadingFrame--; if(fu.fadingFrame > 0) { fu.guiFading->setRelativePosition(irr::core::recti( @@ -1095,14 +1099,14 @@ void Game::ShowElement(irr::gui::IGUIElement * win, int autoframe) { fu.fadingSize = fit->fadingSize; irr::core::vector2di center = fu.fadingSize.getCenter(); int frame = ScaleFrame(10); - fu.fadingDiff.X = static_cast(fu.fadingSize.getWidth()) / frame; - fu.fadingDiff.Y = static_cast(fu.fadingSize.getHeight() - 4) / frame; + fu.fadingFrame = frame; + fu.fadingHalf = frame / 2; + fu.fadingDiff.X = static_cast(fu.fadingSize.getWidth()) / ((fu.fadingFrame - fu.fadingHalf) * 2); + fu.fadingDiff.Y = static_cast(fu.fadingSize.getHeight() - 4) / (fu.fadingHalf * 2); fu.fadingUL = irr::core::vector2df(center.X, center.Y - 2); fu.fadingLR = irr::core::vector2df(center.X, center.Y + 2); fu.guiFading = win; fu.isFadein = true; - fu.fadingFrame = frame; - fu.fadingHalf = frame / 2; fu.autoFadeoutFrame = autoframe ? ScaleFrame(autoframe) : 0; fu.signalAction = false; SetImageButtonDrawing(win, false); @@ -1119,14 +1123,14 @@ void Game::HideElement(irr::gui::IGUIElement * win, bool set_action) { if(win == fit->guiFading) fu.fadingSize = fit->fadingSize; int frame = ScaleFrame(10); - fu.fadingDiff.X = static_cast(fu.fadingSize.getWidth()) / frame; - fu.fadingDiff.Y = static_cast(fu.fadingSize.getHeight() - 4) / frame; + fu.fadingFrame = frame; + fu.fadingHalf = frame / 2; + fu.fadingDiff.X = static_cast(fu.fadingSize.getWidth()) / (fu.fadingHalf * 2); + fu.fadingDiff.Y = static_cast(fu.fadingSize.getHeight() - 4) / ((fu.fadingFrame - fu.fadingHalf) * 2); fu.fadingUL = irr::core::vector2df(fu.fadingSize.UpperLeftCorner.X, fu.fadingSize.UpperLeftCorner.Y); fu.fadingLR = irr::core::vector2df(fu.fadingSize.LowerRightCorner.X, fu.fadingSize.LowerRightCorner.Y); fu.guiFading = win; fu.isFadein = false; - fu.fadingFrame = frame; - fu.fadingHalf = frame / 2; fu.autoFadeoutFrame = 0; fu.signalAction = set_action; SetImageButtonDrawing(win, false); From 74198b494c0ff1d80fbe16444cd6efefa3df2967 Mon Sep 17 00:00:00 2001 From: Mercury233 Date: Fri, 3 Jul 2026 15:22:58 +0800 Subject: [PATCH 07/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- gframe/game.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gframe/game.cpp b/gframe/game.cpp index 27e5a9de59..55832c701a 100644 --- a/gframe/game.cpp +++ b/gframe/game.cpp @@ -1191,7 +1191,7 @@ void Game::MainLoop() { device->drop(); } int Game::ScaleFrame(int frame60) const { - if(device->isWindowMinimized()) return frame60; + if(gameConf.vsync && device->isWindowMinimized()) return frame60; int scaled = (int)(frame60 * fpsScale + 0.5f); return scaled < 1 ? 1 : scaled; } From 316fcac40ace95e9d5d4499edb3004a1a5eb8858 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Fri, 10 Jul 2026 21:19:10 +0800 Subject: [PATCH 08/10] add WaitUntilNextFrame, fix vsync minimized --- gframe/game.cpp | 122 +++++++++++++++++++++++++----------------------- 1 file changed, 64 insertions(+), 58 deletions(-) diff --git a/gframe/game.cpp b/gframe/game.cpp index 96b8aed9b0..d6b9e16d6c 100644 --- a/gframe/game.cpp +++ b/gframe/game.cpp @@ -46,6 +46,67 @@ extern char **environ; namespace ygo { +namespace { +using namespace std::chrono; + +#ifdef _WIN32 +struct HighResolutionTimer { + HighResolutionTimer() { + handle = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_MODIFY_STATE | SYNCHRONIZE); + if(!handle) + periodEnabled = timeBeginPeriod(1) == TIMERR_NOERROR; + } + ~HighResolutionTimer() { + if(handle) + CloseHandle(handle); + else if(periodEnabled) + timeEndPeriod(1); + } + HANDLE handle{}; + bool periodEnabled{}; +}; +#endif + +void PreciseWaitUntil(steady_clock::time_point targetTime, bool windowActive) { + auto now = steady_clock::now(); + if(now >= targetTime) + return; +#ifdef _WIN32 + static HighResolutionTimer highResolutionTimer; + if(highResolutionTimer.handle) { + auto remaining = duration_cast(targetTime - now); + if(remaining.count() > 1500) { + LARGE_INTEGER dueTime; + dueTime.QuadPart = -(LONGLONG)(remaining.count() - 1000) * 10; + if(SetWaitableTimer(highResolutionTimer.handle, &dueTime, 0, NULL, NULL, FALSE)) + WaitForSingleObject(highResolutionTimer.handle, INFINITE); + } + } else +#endif + { + auto sleepTime = targetTime - now - milliseconds(2); + if(sleepTime > milliseconds(0)) + std::this_thread::sleep_for(sleepTime); + } + while(steady_clock::now() < targetTime) { + if(windowActive) + CPU_PAUSE(); + else + std::this_thread::sleep_for(milliseconds(1)); + } +} + +void WaitUntilNextFrame(steady_clock::time_point& lastFrameTime, microseconds targetFrameDuration, bool windowActive) { + auto targetTime = lastFrameTime + targetFrameDuration; + PreciseWaitUntil(targetTime, windowActive); + lastFrameTime = targetTime; + auto now = steady_clock::now(); + if(now - targetTime > targetFrameDuration) + lastFrameTime = now; +} + +} + Game* mainGame; void DuelInfo::Clear() { @@ -1051,16 +1112,6 @@ void Game::MainLoop() { float atkframe = 0.1f; int fps = 0; auto lastFpsTime = std::chrono::steady_clock::now(); -#ifdef _WIN32 - HANDLE hWaitTimer = NULL; - bool useHighResTimer = false; - if(!gameConf.vsync) { - hWaitTimer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_MODIFY_STATE | SYNCHRONIZE); - useHighResTimer = (hWaitTimer != NULL); - if(!useHighResTimer) - timeBeginPeriod(1); - } -#endif auto lastFrameTime = std::chrono::steady_clock::now(); auto targetFrameDuration = std::chrono::microseconds(1000000 / effectiveFps); float lineAccum = 0; @@ -1074,6 +1125,7 @@ void Game::MainLoop() { OnResize(); gMutex.unlock(); } + const bool isVsyncMinimized = gameConf.vsync && device->isWindowMinimized(); logicalFrameAccum += 1.0f; logicalTick = false; if(logicalFrameAccum >= fpsScale) { @@ -1140,46 +1192,8 @@ void Game::MainLoop() { if(closeSignal.TryWait()) CloseDuelWindow(); fps++; - if(gameConf.vsync && device->isWindowMinimized()) { - // downscale to 60fps, reduce CPU usage - std::this_thread::sleep_for(std::chrono::microseconds(16667)); - } else if(!gameConf.vsync) { - auto targetTime = lastFrameTime + targetFrameDuration; - auto now = std::chrono::steady_clock::now(); - if(now < targetTime) { -#ifdef _WIN32 - if(useHighResTimer) - { - auto remaining = std::chrono::duration_cast(targetTime - now); - if(remaining.count() > 1500) { - LARGE_INTEGER dueTime; - dueTime.QuadPart = -(LONGLONG)(remaining.count() - 1000) * 10; - if(SetWaitableTimer(hWaitTimer, &dueTime, 0, NULL, NULL, FALSE)) - WaitForSingleObject(hWaitTimer, INFINITE); - } - } - else -#endif - { - auto sleepTime = targetTime - now - std::chrono::milliseconds(2); - if(sleepTime > std::chrono::milliseconds(0)) { - std::this_thread::sleep_for(sleepTime); - } - } - // Spin-wait for sub-millisecond precision. - // If the window is inactive, sleep 1ms per iteration to avoid wasting CPU. - while(std::chrono::steady_clock::now() < targetTime) { - if(device->isWindowActive()) - CPU_PAUSE(); - else - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - } - lastFrameTime = targetTime; - now = std::chrono::steady_clock::now(); - if(now - targetTime > targetFrameDuration) - lastFrameTime = now; - } + if(isVsyncMinimized || !gameConf.vsync) + WaitUntilNextFrame(lastFrameTime, isVsyncMinimized ? std::chrono::microseconds(16667) : targetFrameDuration, device->isWindowActive()); auto now = std::chrono::steady_clock::now(); if(now - lastFpsTime >= std::chrono::milliseconds(1000)) { myswprintf(cap, L"YGOPro FPS: %d", fps); @@ -1193,14 +1207,6 @@ void Game::MainLoop() { dInfo.time_left[dInfo.time_player]--; } } -#ifdef _WIN32 - if(!gameConf.vsync) { - if(useHighResTimer) - CloseHandle(hWaitTimer); - else - timeEndPeriod(1); - } -#endif DuelClient::StopClient(true); if(dInfo.isSingleMode) SingleMode::StopPlay(true); From 03ef1a77b49de26cf3f3fdc292a4b7a9c7a004dc Mon Sep 17 00:00:00 2001 From: mercury233 Date: Fri, 10 Jul 2026 21:19:52 +0800 Subject: [PATCH 09/10] fix VsyncMinimized logicalTick --- gframe/game.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/gframe/game.cpp b/gframe/game.cpp index d6b9e16d6c..4eda916ca3 100644 --- a/gframe/game.cpp +++ b/gframe/game.cpp @@ -1126,11 +1126,16 @@ void Game::MainLoop() { gMutex.unlock(); } const bool isVsyncMinimized = gameConf.vsync && device->isWindowMinimized(); - logicalFrameAccum += 1.0f; - logicalTick = false; - if(logicalFrameAccum >= fpsScale) { - logicalFrameAccum -= fpsScale; + if(isVsyncMinimized) { + // The minimized VSync path below is throttled to 60 FPS. logicalTick = true; + } else { + logicalFrameAccum += 1.0f; + logicalTick = false; + if(logicalFrameAccum >= fpsScale) { + logicalFrameAccum -= fpsScale; + logicalTick = true; + } } lineAccum += 1.0f; while(lineAccum >= fpsScale) { From 40b3155f3e4b802b976948eb1f547023d4d635e7 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Fri, 10 Jul 2026 21:22:46 +0800 Subject: [PATCH 10/10] comment --- gframe/client_field.cpp | 10 ++++++---- gframe/client_field.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/gframe/client_field.cpp b/gframe/client_field.cpp index 0b7eb475b6..b681abfa99 100644 --- a/gframe/client_field.cpp +++ b/gframe/client_field.cpp @@ -1075,15 +1075,17 @@ void ClientField::GetCardLocation(ClientCard* pcard, irr::core::vector3df* t, ir } } // Set the movement of a card over a specified number of frames. -void ClientField::SetCardMovement(ClientCard* pcard, int frame60, irr::core::vector3df dPos, irr::core::vector3df dRot) { +void ClientField::SetCardMovement(ClientCard* pcard, int frame60, irr::core::vector3df dPos60, irr::core::vector3df dRot60) { int frame = mainGame->ScaleFrame(frame60); float frameScale = static_cast(frame60) / frame; - pcard->dPos = dPos * frameScale; - pcard->dRot = dRot * frameScale; + pcard->dPos = dPos60 * frameScale; + pcard->dRot = dRot60 * frameScale; pcard->is_moving = true; pcard->aniFrame = frame; } -// Move (reset) a card to its current location over a specified number of frames. +// Move a card to its current location over a specified number of frames. +// Legacy: The name of this function is misleading, as it does not actually move the card to a new location, +// but rather resets its position and rotation to its current location over a specified number of frames. void ClientField::MoveCard(ClientCard * pcard, int frame60) { int frame = mainGame->ScaleFrame(frame60); irr::core::vector3df trans = pcard->curPos; diff --git a/gframe/client_field.h b/gframe/client_field.h index 178f552ffb..43f3cc15c7 100644 --- a/gframe/client_field.h +++ b/gframe/client_field.h @@ -114,7 +114,7 @@ class ClientField: public irr::IEventReceiver { void GetChainLocation(int controler, int location, int sequence, irr::core::vector3df* t); void GetCardLocation(ClientCard* pcard, irr::core::vector3df* t, irr::core::vector3df* r, bool setTrans = false); - void SetCardMovement(ClientCard* pcard, int frame60, irr::core::vector3df dPos, irr::core::vector3df dRot = irr::core::vector3df(0, 0, 0)); + void SetCardMovement(ClientCard* pcard, int frame60, irr::core::vector3df dPos60, irr::core::vector3df dRot60 = irr::core::vector3df(0, 0, 0)); void MoveCard(ClientCard* pcard, int frame60); void FadeCard(ClientCard* pcard, int alpha, int frame60); bool ShowSelectSum(bool panelmode); // caller must hold gMutex