| 201 | } |
| 202 | |
| 203 | static void vSyncInfoCalc(vSyncTimingInfo* info, double framesPerSecond, u32 scansPerFrame) |
| 204 | { |
| 205 | constexpr double clock = static_cast<double>(PS2CLK); |
| 206 | |
| 207 | const u64 Frame = clock * 10000ULL / framesPerSecond; |
| 208 | const u64 Scanline = Frame / scansPerFrame; |
| 209 | |
| 210 | // There are two renders and blanks per frame. This matches the PS2 test results. |
| 211 | // The PAL and NTSC VBlank periods respectively lasts for approximately 22 and 26 scanlines. |
| 212 | // An older test suggests that these periods are actually the periods that VBlank is off, but |
| 213 | // Legendz Gekitou! Saga Battle runs very slowly if the VBlank period is inverted. |
| 214 | // Some of the more timing sensitive games and their symptoms when things aren't right: |
| 215 | // Dynasty Warriors 3 Xtreme Legends - fake save corruption when loading save |
| 216 | // Jak II - random speedups |
| 217 | // Shadow of Rome - FMV audio issues |
| 218 | const bool ntsc_hblank = gsVideoMode != GS_VideoMode::PAL && gsVideoMode != GS_VideoMode::DVD_PAL; |
| 219 | const u64 HalfFrame = Frame / 2; |
| 220 | const float extra_scanlines = static_cast<float>(IsProgressiveVideoMode()) * (ntsc_hblank ? 0.5f : 1.5f); |
| 221 | const u64 Blank = Scanline * ((ntsc_hblank ? 22.5f : 24.5f) + extra_scanlines); |
| 222 | const u64 Render = HalfFrame - Blank; |
| 223 | const u64 GSBlank = Scanline * ((ntsc_hblank ? 3.5 : 3) + extra_scanlines); // GS VBlank/CSR Swap happens roughly 3.5(NTSC) and 3(PAL) Scanlines after VBlank Start |
| 224 | |
| 225 | // Important! The hRender/hBlank timer ratio below is set according to PS2 tests. |
| 226 | // in EE Cycles taken from PAL system: |
| 227 | // 18876 cycles for hsync |
| 228 | // 15796 cycles for hsync are low (render) |
| 229 | // Ratio: 83.68298368298368 |
| 230 | u64 hRender = Scanline * 0.8368298368298368f; |
| 231 | u64 hBlank = Scanline - hRender; |
| 232 | |
| 233 | if (!IsInterlacedVideoMode()) |
| 234 | { |
| 235 | hBlank /= 2; |
| 236 | hRender /= 2; |
| 237 | } |
| 238 | |
| 239 | //TODO: Carry fixed-point math all the way through the entire vsync and hsync counting processes, and continually apply rounding |
| 240 | //as needed for each scheduled v/hsync related event. Much better to handle than this messed state. |
| 241 | info->Framerate = framesPerSecond; |
| 242 | info->GSBlank = (u32)(GSBlank / 10000); |
| 243 | info->Render = (u32)(Render / 10000); |
| 244 | info->Blank = (u32)(Blank / 10000); |
| 245 | const u64 accumilated_vrender = (Render % 10000) + (Blank % 10000); |
| 246 | info->Render += (u32)(accumilated_vrender / 10000); |
| 247 | |
| 248 | info->hRender = (u32)(hRender / 10000); |
| 249 | info->hBlank = (u32)(hBlank / 10000); |
| 250 | info->hScanlinesPerFrame = scansPerFrame; |
| 251 | |
| 252 | const u64 accumilatedHRenderError = (hRender % 10000) + (hBlank % 10000); |
| 253 | const u64 accumilatedHFractional = accumilatedHRenderError % 10000; |
| 254 | info->hRender += (u32)(accumilatedHRenderError / 10000); |
| 255 | info->hSyncError = (u32)((accumilatedHFractional * (scansPerFrame / (IsInterlacedVideoMode() ? 2 : 1))) / 10000); |
| 256 | |
| 257 | // Note: In NTSC modes there is some small rounding error in the vsync too, |
| 258 | // however it would take thousands of frames for it to amount to anything and |
| 259 | // is thus not worth the effort at this time. |
| 260 | } |
no test coverage detected