From ffde2e592af7e76ae6e1d7b28c370f6ff8179076 Mon Sep 17 00:00:00 2001 From: Thomas Jentzsch Date: Thu, 14 Jul 2022 20:03:45 +0200 Subject: [PATCH] enhanced scanline based frame layout detection to fractional values, this allows combining results with color based layout overruling --- .../tia/frame-manager/FrameLayoutDetector.cxx | 154 ++++++++---------- .../tia/frame-manager/FrameLayoutDetector.hxx | 10 +- 2 files changed, 72 insertions(+), 92 deletions(-) diff --git a/src/emucore/tia/frame-manager/FrameLayoutDetector.cxx b/src/emucore/tia/frame-manager/FrameLayoutDetector.cxx index 5bce1774c..8c1fb81db 100644 --- a/src/emucore/tia/frame-manager/FrameLayoutDetector.cxx +++ b/src/emucore/tia/frame-manager/FrameLayoutDetector.cxx @@ -72,62 +72,71 @@ FrameLayout FrameLayoutDetector::detectedLayout(bool detectPal60, bool detectNts } #endif - // Multiply each hue's count with its NTSC and PAL stats and aggregate results - // If NTSC/PAL results differ significantly, overrule frame result - FrameLayout layout = myPalFrames > myNtscFrames ? FrameLayout::pal : FrameLayout::ntsc; + // Init the layout based on scanline analysis. + FrameLayout layout = myPalFrameSum > myNtscFrameSum ? FrameLayout::pal : FrameLayout::ntsc; - constexpr std::array ntscColorFactor{ - 0.00000, 0.05683, 0.06220, 0.05505, 0.06162, 0.02874, 0.03532, 0.03716, - 0.15568, 0.06471, 0.02886, 0.03224, 0.06903, 0.11478, 0.02632, 0.01675 - }; // ignore black = 0x00! - constexpr std::array palColorFactor{ - 0.00000, 0.00450, 0.09962, 0.07603, 0.06978, 0.13023, 0.09638, 0.02268, - 0.02871, 0.04700, 0.02950, 0.11974, 0.03474, 0.08025, 0.00642, 0.00167 - }; // ignore black = 0x00! - // Calculation weights and params (optimum based on sampled ROMs optimized for PAL-60) - constexpr double POWER_FACTOR = 0.17; // Level the color counts (large values become less relevant) - constexpr uInt32 SUM_DIV = 20; // Skip too small counts - constexpr uInt32 MIN_VALID = 3; // Minimum number of different hues with significant counts - constexpr double SUM_FACTOR = 2.0; // Minimum sum difference which triggers a layout change + if(detectPal60 || detectNtsc50) + { + // Multiply each hue's count with its NTSC and PAL stats and aggregate results + // If NTSC/PAL results differ significantly, overrule frame result + constexpr std::array ntscColorFactor{ + 0.00000, 0.05683, 0.06220, 0.05505, 0.06162, 0.02874, 0.03532, 0.03716, + 0.15568, 0.06471, 0.02886, 0.03224, 0.06903, 0.11478, 0.02632, 0.01675 + }; // ignore black = 0x00! + constexpr std::array palColorFactor{ + 0.00000, 0.00450, 0.09962, 0.07603, 0.06978, 0.13023, 0.09638, 0.02268, + 0.02871, 0.04700, 0.02950, 0.11974, 0.03474, 0.08025, 0.00642, 0.00167 + }; // ignore black = 0x00! + // Calculation weights and params (optimum based on sampled ROMs, optimized for PAL-60) + constexpr double POWER_FACTOR = 0.17; // Level the color counts (large values become less relevant) + constexpr uInt32 SUM_DIV = 20; // Skip too small counts (could be removed) + constexpr uInt32 MIN_VALID = 3; // Minimum number of different hues with significant counts + constexpr double OVERRULE_FACTOR = 2.0; // Minimum sum difference which triggers a layout change - double ntscSum{0}, palSum{0}; - std::array hueSum{0}; - double totalHueSum = 0; - uInt32 validHues = 0; + double ntscColSum{ 0 }, paCollSum{ 0 }; + std::array hueSum{ 0 }; + double totalHueSum = 0; + uInt32 validHues = 0; - // Aggregate hues - for(int hue = 0; hue < NUM_HUES; ++hue) - { - for(int lum = 0; lum < NUM_LUMS; ++lum) - if(hue || lum) // skip 0x00 - hueSum[hue] += myColorCount[hue * NUM_LUMS + lum]; - hueSum[hue] = std::pow(hueSum[hue], POWER_FACTOR); - totalHueSum += hueSum[hue]; - } - // Calculate hue sums - for(int hue = 0; hue < NUM_HUES; ++hue) - { - if(hueSum[hue] > totalHueSum / SUM_DIV) - validHues++; - ntscSum += hueSum[hue] * ntscColorFactor[hue]; - palSum += hueSum[hue] * palColorFactor[hue]; - } - // Correct layout if enough valid hues and significant sum difference - // TODO: Use fractional scanline counts for intermediate values around 285 scanlines, e.g. - // Desert Falcon, Dumbo's Flying Circus, Dungeon, Firefox, Millipede, Popeye, RS Basketball, Star Trek, Stunt Cycle - if(validHues >= MIN_VALID) - { - if(detectPal60 && layout == FrameLayout::ntsc && ntscSum * SUM_FACTOR < palSum) + // Aggregate hues + for(int hue = 0; hue < NUM_HUES; ++hue) { - layout = FrameLayout::pal60; - Logger::debug("Changed layout from NTSC into PAL-60"); + for(int lum = 0; lum < NUM_LUMS; ++lum) + if(hue || lum) // skip 0x00 + hueSum[hue] += myColorCount[hue * NUM_LUMS + lum]; + hueSum[hue] = std::pow(hueSum[hue], POWER_FACTOR); + totalHueSum += hueSum[hue]; } - // Note: three false positives (Berzerk, Canyon Bomber, Jawbreaker) for NTSC-50 after - // optimizing for PAL-60 - else if(detectNtsc50 && layout == FrameLayout::pal && palSum * SUM_FACTOR < ntscSum) + // Calculate hue sums + for(int hue = 0; hue < NUM_HUES; ++hue) { - layout = FrameLayout::ntsc50; - Logger::debug("Changed layout from PAL into NTSC-50"); + if(hueSum[hue] > totalHueSum / SUM_DIV) + validHues++; + ntscColSum += hueSum[hue] * ntscColorFactor[hue]; + paCollSum += hueSum[hue] * palColorFactor[hue]; + } + + // Correct the layout if there are enough valid hues and a significant color sum difference. + // The required difference depends on the significance of the scanline analyis. + if(validHues >= MIN_VALID) + { + // Use frame analysis results to scale color overrule factor from 1.0 .. OVERRULE_FACTOR + const double overRuleFactor = 1.0 + (OVERRULE_FACTOR - 1.0) * 2 + * (std::max(myNtscFrameSum, myPalFrameSum) / (myNtscFrameSum + myPalFrameSum) - 0.5); // 1.0 .. OVERRULE_FACTOR + + //cerr << overRuleFactor << " * PAL:" << paCollSum << "/NTSC:" << ntscColSum << endl; + if(detectPal60 && layout == FrameLayout::ntsc && ntscColSum * overRuleFactor < paCollSum) + { + layout = FrameLayout::pal60; + Logger::debug("TV format changed from NTSC to PAL-60"); + } + // Note: Three false positives (Adventure, Berzerk, Canyon Bomber) for NTSC-50 after + // optimizing for PAL-60 + else if(detectNtsc50 && layout == FrameLayout::pal && paCollSum * overRuleFactor < ntscColSum) + { + layout = FrameLayout::ntsc50; + Logger::debug("TV format changed from PAL to NTSC-50"); + } } } return layout; @@ -143,7 +152,7 @@ FrameLayoutDetector::FrameLayoutDetector() void FrameLayoutDetector::onReset() { myState = State::waitForVsyncStart; - myNtscFrames = myPalFrames = 0; + myNtscFrameSum = myPalFrameSum = 0; myLinesWaitingForVsyncToStart = 0; myColorCount.fill(0); myIsRendering = true; @@ -223,37 +232,14 @@ void FrameLayoutDetector::finalizeFrame() if (myTotalFrames <= Metrics::initialGarbageFrames) return; - // Calculate the delta between scanline count and the sweet spot for the respective - // frame layouts - const uInt32 - deltaNTSC = abs(static_cast(myCurrentFrameFinalLines) - static_cast(frameLinesNTSC)), - deltaPAL = abs(static_cast(myCurrentFrameFinalLines) - static_cast(frameLinesPAL)); - - // Does the scanline count fall into one of our tolerance windows? -> use it - if (std::min(deltaNTSC, deltaPAL) <= Metrics::tvModeDetectionTolerance) - layout(deltaNTSC <= deltaPAL ? FrameLayout::ntsc : FrameLayout::pal); - else if ( - // If scanline count is odd and lies between the PAL and NTSC windows we assume - // it is NTSC (it would cause color loss on PAL CRTs) - (myCurrentFrameFinalLines < frameLinesPAL) && - (myCurrentFrameFinalLines > frameLinesNTSC) && - (myCurrentFrameFinalLines % 2) - ) - layout(FrameLayout::ntsc); - else - // Take the nearest layout if all else fails - layout(deltaNTSC <= deltaPAL ? FrameLayout::ntsc : FrameLayout::pal); - - switch (layout()) { - case FrameLayout::ntsc: - ++myNtscFrames; - break; - - case FrameLayout::pal: - ++myPalFrames; - break; - - default: - throw runtime_error("cannot happen"); - } + // Calculate how close a frame is to PAL and NTSC based on scanlines. An odd scanline count + // results into a penalty of 0.5 for PAL. The result is between 0.0 (<=262 scanlines) and + // 1.0 (>=312) and added to PAL and (inverted) NTSC sums. + constexpr double ODD_PENALTY = 0.5; // guessed value :) + const double palFrame = BSPF::clamp(((myCurrentFrameFinalLines % 2) ? ODD_PENALTY : 1.0) + * static_cast(myCurrentFrameFinalLines - frameLinesNTSC) + / static_cast(frameLinesPAL - frameLinesNTSC), 0.0, 1.0); + myPalFrameSum += palFrame; + myNtscFrameSum += 1.0 - palFrame; + //cerr << myCurrentFrameFinalLines << ", " << palFrame << ", " << myPalFrameSum << ", " << myNtscFrameSum << endl; } diff --git a/src/emucore/tia/frame-manager/FrameLayoutDetector.hxx b/src/emucore/tia/frame-manager/FrameLayoutDetector.hxx index bdb8ad391..368086709 100644 --- a/src/emucore/tia/frame-manager/FrameLayoutDetector.hxx +++ b/src/emucore/tia/frame-manager/FrameLayoutDetector.hxx @@ -95,9 +95,6 @@ class FrameLayoutDetector: public AbstractFrameManager // (exceeding ideal frame height) waitForVsync = 100, - // tolerance window around ideal frame size for TV mode detection - tvModeDetectionTolerance = 20, - // these frames will not be considered for detection initialGarbageFrames = TIAConstants::initialGarbageFrames }; @@ -121,10 +118,8 @@ class FrameLayoutDetector: public AbstractFrameManager */ State myState{State::waitForVsyncStart}; - /** - * The total number of frames detected as the respective frame layout. - */ - uInt32 myNtscFrames{0}, myPalFrames{0}; + // The aggregated likelynesses of respective two frame layouts. + double myNtscFrameSum{0}, myPalFrameSum{0}; /** * We count the number of scanlines we spend waiting for vsync to be @@ -139,7 +134,6 @@ class FrameLayoutDetector: public AbstractFrameManager */ static constexpr int NUM_HUES = 16; static constexpr int NUM_LUMS = 8; - std::array myColorCount{0}; private: