diff --git a/Source/Core/Core/HW/VideoInterface.cpp b/Source/Core/Core/HW/VideoInterface.cpp index 3dd8e065e3..38a87b1ecd 100644 --- a/Source/Core/Core/HW/VideoInterface.cpp +++ b/Source/Core/Core/HW/VideoInterface.cpp @@ -472,55 +472,90 @@ static u32 GetTicksPerOddField() return GetTicksPerHalfLine() * GetHalfLinesPerOddField(); } -float GetAspectRatio(bool wide) +// Get the aspect ratio of VI's active area. +float GetAspectRatio() { - u32 multiplier = static_cast(m_PictureConfiguration.STD / m_PictureConfiguration.WPL); - int height = (multiplier * m_VerticalTimingRegister.ACV); - int width = ((2 * m_HTiming0.HLW) - (m_HTiming0.HLW - m_HTiming1.HBS640) - - m_HTiming1.HBE640); - float pixelAR; - if (m_DisplayControlRegister.FMT == 1) + // The picture of a PAL/NTSC TV signal is defined to have a 4:3 aspect ratio, + // but it's only 4:3 if the picture fill the entire active area. + // All games configure VideoInterface to add padding in both the horizontal and vertical + // directions and most games also do a slight horizontal scale. + // This means that XFB never fills the entire active area and is therefor almost never 4:3 + + // To work out the correct aspect ratio of the XFB, we need to know how VideoInterface's + // currently configured active area compares to the active area of a stock PAL or NTSC + // signal (which would be 4:3) + + // This function only deals with standard aspect ratios. For widescreen aspect ratios, + // multiply the result by 1.33333.. + + // 1. Get our active area in BT.601 samples (more or less pixels) + int active_lines = m_VerticalTimingRegister.ACV; + int active_width_samples = (m_HTiming0.HLW + m_HTiming1.HBS640 - m_HTiming1.HBE640); + + // 2. TVs are analog and don't have pixels. So we convert to seconds. + float tick_length = (1.0f / SystemTimers::GetTicksPerSecond()); + float vertical_period = tick_length * GetTicksPerField(); + float horizontal_period= tick_length * GetTicksPerHalfLine() * 2; + float vertical_active_area = active_lines * horizontal_period; + float horizontal_active_area = tick_length * GetTicksPerSample() * active_width_samples; + + // We are approximating the horizontal/vertical flyback transformers that control the + // position of the election beam on the screen. Our flyback transformers create a + // perfect Sawtooth wave, with a smooth rise and a fall that takes zero time. + // For more accurate emulation of video signals out of the 525 or 625 line standards, + // it might be necessary to emulate a less precise flyback transformer with more flaws. + // But those modes aren't officially supported by TVs anyway and could behave differently + // on different TVs. + + // 3. Calculate the ratio of active time to total time for VI's active area + float vertical_active_ratio = vertical_active_area / vertical_period; + float horizontal_active_ratio = horizontal_active_area / horizontal_period; + + // 4. And then scale the ratios to typical PAL/NTSC signals. + // NOTE: With the exception of selecting between PAL-M and NTSC color encoding on Brazilian + // GameCubes, the FMT field doesn't actually do anything on real hardware. But + // Nintendo's SDK always sets it appropriately to match the number of lines. + if (m_DisplayControlRegister.FMT == 1) // 625 line TV (PAL) { - //PAL active frame is 702*576 - //In square pixels, 1024*576 is 16:9, and 768*576 is 4:3 - //Therefore a 16:9 TV would have a "pixel" aspect ratio of 1024/702 - //Similarly a 4:3 TV would have a ratio of 768/702 - if (wide) - { - pixelAR = 1024.0f / 702.0f; - } - else - { - pixelAR = 768.0f / 702.0f; - } + // PAL defines the horizontal active area as 52us of the 64us line. + // BT.470-6 defines the blanking period as 12.0us +0.0 -0.3 [table on page 5] + horizontal_active_ratio *= 64.0f / 52.0f; + // PAL defines the vertical active area as 576 of 625 lines. + vertical_active_ratio *= 625.0f / 576.0f; + // TODO: Should PAL60 games go through the 625 or 525 line codepath? + // The resulting aspect ratio is close, but not identical. } + else // 525 line TV (NTSC or PAL-M) + { + // The NTSC standard doesn't define it's active area very well. + // The line is 63.55555..us long, which is derived from 1.001 / (30 * 525) + // but the blanking area is defined with a large amount of slack in the SMPTE 170M-2004 + // standard, 10.7us +0.3 -0.2 [derived from table on page 9] + // The BT.470-6 standard provides a different number of 10.9us +/- 0.2 [table on page 5] + // This results in an active area between 52.5555us and 53.05555us + // Lots of different numbers float around the Internet including: + // * 52.655555.. us -- http://web.archive.org/web/20140218044518/http://lipas.uwasa.fi/~f76998/video/conversion/ + // * 52.66 us -- http://www.ni.com/white-paper/4750/en/ + // * 52.6 us -- http://web.mit.edu/6.111/www/f2008/handouts/L12.pdf + // + // None of these website provide primary sources for their numbers, back in the days of + // analog, TV signal timings were not that precise to start with and it never got standardized + // during the move to digital. + // We are just going to use 52.655555.. as most other numbers on the Internet appear to be a + // simplification of it. 53.655555.. is a blanking period of 10.9us, matching the BT.470-6 standard + // and within tolerance of the SMPTE 170M-2004 standard. + horizontal_active_ratio *= 63.555555f / 52.655555f; + // Even 486 active lines isn't completely agreed upon. + // Depending on how you count the two half lines you could get 485 or 484 + vertical_active_ratio *= 525.0f / 486.0f; + } + + // 5. Calculate the final ratio and scale to 4:3 + float ratio = horizontal_active_ratio / vertical_active_ratio; + if (std::isnormal(ratio)) // Check we have a sane ratio and haven't propagated any infs/nans/zeros + return ratio * (4.0f / 3.0f); // Scale to 4:3 else - { - //NTSC active frame is 710.85*486 - //In square pixels, 864*486 is 16:9, and 648*486 is 4:3 - //Therefore a 16:9 TV would have a "pixel" aspect ratio of 864/710.85 - //Similarly a 4:3 TV would have a ratio of 648/710.85 - if (wide) - { - pixelAR = 864.0f / 710.85f; - } - else - { - pixelAR = 648.0f / 710.85f; - } - } - if (width == 0 || height == 0) - { - if (wide) - { - return 16.0f / 9.0f; - } - else - { - return 4.0f / 3.0f; - } - } - return ((float)width / (float)height) * pixelAR; + return (4.0f / 3.0f); // VI isn't initialized correctly, just return 4:3 instead } // This function updates: @@ -582,9 +617,14 @@ void UpdateParameters() TargetRefreshRate = lround(2.0 * SystemTimers::GetTicksPerSecond() / (GetTicksPerEvenField() + GetTicksPerOddField())); } +u32 GetTicksPerSample() +{ + return 2 * SystemTimers::GetTicksPerSecond() / s_clock_freqs[m_Clock]; +} + u32 GetTicksPerHalfLine() { - return 2 * SystemTimers::GetTicksPerSecond() / s_clock_freqs[m_Clock] * m_HTiming0.HLW; + return GetTicksPerSample() * m_HTiming0.HLW; } diff --git a/Source/Core/Core/HW/VideoInterface.h b/Source/Core/Core/HW/VideoInterface.h index 45942e04f7..4253a75695 100644 --- a/Source/Core/Core/HW/VideoInterface.h +++ b/Source/Core/Core/HW/VideoInterface.h @@ -330,9 +330,11 @@ union UVIHorizontalStepping // Change values pertaining to video mode void UpdateParameters(); + u32 GetTicksPerSample(); u32 GetTicksPerHalfLine(); u32 GetTicksPerField(); - //For VI Scaling and Aspect Ratio Correction - float GetAspectRatio(bool); + // Get the aspect ratio of VI's active area. + // This function only deals with standard aspect ratios. For widescreen aspect ratios, multiply the result by 1.33333.. + float GetAspectRatio(); } diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 5054fd61d5..76136c88aa 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -80,6 +80,7 @@ unsigned int Renderer::efb_scale_numeratorY = 1; unsigned int Renderer::efb_scale_denominatorX = 1; unsigned int Renderer::efb_scale_denominatorY = 1; +static float AspectToWidescreen(float aspect) { return aspect * ((16.0f / 9.0f) / (4.0f / 3.0f)); } Renderer::Renderer() : frame_data() @@ -427,7 +428,9 @@ void Renderer::UpdateDrawRectangle(int backbuffer_width, int backbuffer_height) // Don't know if there is a better place for this code so there isn't a 1 frame delay if (g_ActiveConfig.bWidescreenHack) { - float source_aspect = VideoInterface::GetAspectRatio(Core::g_aspect_wide); + float source_aspect = VideoInterface::GetAspectRatio(); + if (Core::g_aspect_wide) + source_aspect = AspectToWidescreen(source_aspect); float target_aspect; switch (g_ActiveConfig.iAspectRatio) @@ -436,10 +439,10 @@ void Renderer::UpdateDrawRectangle(int backbuffer_width, int backbuffer_height) target_aspect = WinWidth / WinHeight; break; case ASPECT_ANALOG: - target_aspect = VideoInterface::GetAspectRatio(false); + target_aspect = VideoInterface::GetAspectRatio(); break; case ASPECT_ANALOG_WIDE: - target_aspect = VideoInterface::GetAspectRatio(true); + target_aspect = AspectToWidescreen(VideoInterface::GetAspectRatio()); break; default: // ASPECT_AUTO @@ -472,17 +475,13 @@ void Renderer::UpdateDrawRectangle(int backbuffer_width, int backbuffer_height) // The rendering window aspect ratio as a proportion of the 4:3 or 16:9 ratio float Ratio; - switch (g_ActiveConfig.iAspectRatio) + if (g_ActiveConfig.iAspectRatio == ASPECT_ANALOG_WIDE || (g_ActiveConfig.iAspectRatio != ASPECT_ANALOG && Core::g_aspect_wide)) { - case ASPECT_ANALOG_WIDE: - Ratio = (WinWidth / WinHeight) / VideoInterface::GetAspectRatio(true); - break; - case ASPECT_ANALOG: - Ratio = (WinWidth / WinHeight) / VideoInterface::GetAspectRatio(false); - break; - default: - Ratio = (WinWidth / WinHeight) / VideoInterface::GetAspectRatio(Core::g_aspect_wide); - break; + Ratio = (WinWidth / WinHeight) / AspectToWidescreen(VideoInterface::GetAspectRatio()); + } + else + { + Ratio = (WinWidth / WinHeight) / VideoInterface::GetAspectRatio(); } if (g_ActiveConfig.iAspectRatio != ASPECT_STRETCH) @@ -508,18 +507,7 @@ void Renderer::UpdateDrawRectangle(int backbuffer_width, int backbuffer_height) // ------------------ if (g_ActiveConfig.iAspectRatio != ASPECT_STRETCH && g_ActiveConfig.bCrop) { - switch (g_ActiveConfig.iAspectRatio) - { - case ASPECT_ANALOG_WIDE: - Ratio = (16.0f / 9.0f) / VideoInterface::GetAspectRatio(true); - break; - case ASPECT_ANALOG: - Ratio = (4.0f / 3.0f) / VideoInterface::GetAspectRatio(false); - break; - default: - Ratio = (!Core::g_aspect_wide ? (4.0f / 3.0f) : (16.0f / 9.0f)) / VideoInterface::GetAspectRatio(Core::g_aspect_wide); - break; - } + Ratio = (4.0f / 3.0f) / VideoInterface::GetAspectRatio(); if (Ratio <= 1.0f) { Ratio = 1.0f / Ratio;