ramapcsx2 / gbs-control

GNU General Public License v3.0
771 stars 110 forks source link

Fixing no image on some sensitive monitors #494

Open obibann opened 10 months ago

obibann commented 10 months ago

Hi everyone,

I was encountering a problem with my TV, and I've patched my own device so that I works fine. I would like to share with all what was my problem and how I've fixed it. If other people are having this kind of problem, why not merging in the project :)

1. Problem

No image on my TV for some NTSC sources (I have PAL consoles but I play NTSC imports on them). I had the problem with a PSOne and a n64.

The TV is compatible with NTSC, but for some reason, I had no image with a message telling me that the signal is not supported. My GBS-C is plugged to the TV using HDMI. But if I directly plug the console to the TV through SCART (Yes I have SCART on my TV, it is 15 years old ^^) I have a signal. So the TV is somehow more sensitive through HDMI than using SCART.

2. Investigation and spotting the issue

I have the force 60Hz option enabled so that all Pal games are converted to 60Hz. And when I run a Pal game, I have a perfect image ! So I looked at the output console and I could observe this:

So my guess was that the TV was quite sensitive to that and considers that a signal below 59.80 for instance is invalid.

In the developer mode, I tried the "60/50Hz HDMI" option, and guess what, it fixes it ! This feature tries to adjust the output frequency as close as possible as 60Hz.

3. My modifications

Having to enable developer options and click on this feature everytime was not an option for me (I can be lazy sometimes ;)). So I decided to patch the source code.

float getOutputFrameRateWithRetries() {

  // First try
  float ofr = getOutputFrameRate();

  // If read failed, wait and retry
  if (ofr < 1.0f) {
      delay(1);
      ofr = getOutputFrameRate();
  }

  return ofr;

}

boolean snapToIntegralFrameRate(void)
{
    // Fetch the current output frame rate
    float ofr = getOutputFrameRateWithRetries();
    if (ofr < 1.0f) {
      return false;
    }

    // Calculating target
    float target;
    if (ofr > 56.5f && ofr < 64.5f) {
        target = 60.0f; // NTSC like
    } else if (ofr > 46.5f && ofr < 54.5f) {
        target = 50.0f; // PAL like
    } else {
        // too far out of spec for an auto adjust
        SerialM.println(F("out of bounds"));
        return false;
    }

    if(target == ofr) {
      SerialM.println(F("Target already reached, no need to snap"));
      return false;
    }

    SerialM.print(F("Snap to "));
    SerialM.print(target, 1); // precission 1
    SerialM.println("Hz");

    // We'll be adjusting the htotal incrementally, so store current and best match.
    uint16_t currentHTotal = GBS::VDS_HSYNC_RST::read();

    // What's the closest we've been to the frame rate?
    float closestDifference = fabs(target - ofr);
    float newDifference = 0;

    // Experimental perf patch
    // On the use cases I've tested it takes around 5 loops per 0.1Hz
    // Let's directly start with a closer value
    if (target > ofr) {
        currentHTotal = currentHTotal - (floor(closestDifference * 10) * 5);
    }
    if (target < ofr) {
        currentHTotal = currentHTotal + (floor(closestDifference * 10) * 5);
    }

    if(!applyBestHTotal(currentHTotal)) {
      currentHTotal = GBS::VDS_HSYNC_RST::read();
    }
    else {
      ofr = getOutputFrameRateWithRetries();
      closestDifference = fabs(target - ofr);
    }

    uint16_t closestHTotal = currentHTotal;
    float currentDifference = target - ofr;
    float lastDifference = currentDifference;

    // Repeatedly adjust htotals until we find the closest match.
    for (;;) {

        delay(0);

        // Try to move closer to the desired framerate.
        if (target > ofr) {
            if (currentHTotal > 0 && applyBestHTotal(currentHTotal - 1)) {
                --currentHTotal;
            } else {
                return false;
            }
        } else if (target < ofr) {
            if (currentHTotal < 4095 && applyBestHTotal(currentHTotal + 1)) {
                ++currentHTotal;
            } else {
                return false;
            }
        } else {
            return true;
        }

        // Are we closer?
        ofr = getOutputFrameRateWithRetries();
        if (ofr < 1.0f) {
            return false;
        }

        // If we're getting closer, continue trying, otherwise break out of the test loop.
        currentDifference = target - ofr;
        newDifference = fabs(currentDifference);
        if (newDifference < closestDifference) {
            closestDifference = newDifference;
            closestHTotal = currentHTotal;
            // Difference changed sign, means we found it
            if((lastDifference * currentDifference) < 0) {
              break;
            }
            lastDifference = currentDifference;
        } else {
            break;
        }
    }

    // Reapply the closest htotal if needed.
    if (closestHTotal != currentHTotal) {
        applyBestHTotal(closestHTotal);
    }

    return true;
}

4. Results

Works like a charm for my setup. I hope this will help others. Maybe a cleaner way to proceed would be to add an option to execute a "snapToIntegralFrameRate" when a preset is applied so people that do not need it could let this disabled.

Have a nice day.