-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
True 50 Hz support #46
Comments
I would personally recommend using the V-total reg for this. V-total is PDC regs + 0x24. I had heard that a few people had issues with H-total. Here is another example. In this case i'm adjusting the value every frame to perfectly synchronize with GBA output. |
The holy grail! That would be amazing. I thought it was impossible on the 3DS hardware. VB doomed to be locked to 50fps on a 60Hz refresh. Framepacing in games like Jack Bros is stuttery compared to when I play it on a 50Hz panel. Galactic Pinball is even more stuttery compared to how smooth it should look. But proper 50Hz would be so nice, it's a lot more apparent if you're already used to the buttery smooth motion of native refresh. It's one of the, once you notice you never can unnotice, type of things. |
Also see the VTotal reg below for a formula to calculate the refresh rate with the current settings: |
@asiekierka told me about this a couple days after the initial release. I tried implementing it, but couldn't get it to work: it still ran at 60Hz, and exiting the emulator crashed my 3DS. |
Does the emu synchronize with VBlank or with audio? If audio then of course it will still run at whatever the audio rate is. And a little tip. With the whole apt hook cookie stuff shown in the commit vaguerant linked i would just backup the whole register instead of assuming what the register was set to on app launch. edit: #include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <3ds.h>
int main(int argc, char* argv[])
{
gfxInitDefault();
consoleInit(GFX_TOP, NULL);
u32 vtotal;
Result res = GSPGPU_ReadHWRegs(0x400424, &vtotal, 4);
printf("VTotal test: 0x%" PRIX32 "\nUp: + 1, Left: + 10, Down: - 1, Right: - 10\n", res);
u64 startTicks = 0;
u32 frameCount = 0;
bool update = true;
while (aptMainLoop())
{
if(frameCount == 0) startTicks = svcGetSystemTick();
gspWaitForVBlank();
frameCount++;
if(frameCount == 60)
{
const u64 ticks = svcGetSystemTick() - startTicks;
printf("\x1b[5;1H%f fps ", (double)268111856 * 60u / ticks);
frameCount = 0;
}
gfxFlushBuffers();
gfxSwapBuffers();
hidScanInput();
// Your code goes here
u32 kDown = hidKeysDown();
if (kDown & KEY_START)
break; // break in order to return to hbmenu
else if(kDown & KEY_DUP)
{
vtotal++;
update = true;
}
else if(kDown & KEY_DLEFT)
{
vtotal += 10;
update = true;
}
else if(kDown & KEY_DDOWN)
{
vtotal--;
update = true;
}
else if(kDown & KEY_DRIGHT)
{
vtotal -= 10;
update = true;
}
if(update)
{
update = false;
vtotal &= 0xFFFu; // VTotal is 12 bits.
GSPGPU_WriteHWRegs(0x400424, &vtotal, 4);
printf("\x1b[4;1HVTotal: %" PRIu32 " ", vtotal);
}
}
gfxExit();
return 0;
} |
It's currently on a hardware 20ms timer, but when I tested this I largely copied the atari800 implementation, including syncing on vblank and storing the initial register value. |
As mentioned above you probably also have to backup/restore the old value each time the app is going into background. Keywords sleep mode, HOME menu, app close, power button press. I assume this can all be done via apt hooks. Most of the software expects 59.8 fps but i don't think it would cause crashes anywhere. I don't know the exact frame rate of the Virtual Boy. It's unlikely that you will get a perfect match with VTotal alone meaning you could always try the approach open_agb_firm uses picking the 2 VTotal values that give the closest fps to VB and then switching between them on a frame by frame basis. This is absolutely invisible to your eyes and never failed me so far. |
My backlight PR #48 does some APT hook stuff if you need an example that already works in Red Viper. |
Found out why things started glitching out in my previous attempt. All the existing calculations work when 3D is disabled, in which case the resting VTotal is 413. When 3D is enabled (i.e. |
On 2DS 3D and wide modes don't work at all. They will display incorrectly. Also yeah, that value doubles. I should have mentioned it: https://github.com/profi200/libn3ds/blob/master/include/arm11/drivers/pdc_presets.h#L109 |
disabled until i know how it runs on n3ds related to #46
disabled until i know how it runs on 2ds related to #46
I wonder how this interacts with capture cards. I see the most recent commit in libn3ds relates to those, but that at least is still roughly 60Hz. |
Got someone to test this on a 2DS and made interesting observations:
|
From what i gather the capture cards use a lazy approach by synchronizing to one LCD only and then capturing both frames at the same time. The way i did it previously made the LCDs not run at the same speed so they were not in sync. Certain capture cards didn't like that and showed garbage frames. The HOME menu slowdown you mentioned comes from HOME menu not handling out of sync LCDs correctly i have been told. It will get VBlank events at entirely different times.
That's because setting v-total higher makes it slower. And gsp likely refuses to turn on the pixel doubling mode on 2DS. So you effectively only slow down the LCD. |
I'll try to clear up any confusion. 2DS is the only known 3DS with 1:1 pixels (as opposed to the 2:1 pixels, where two pixels on top of eachother form a 1:1 pseudo-pixel in 2D mode), so that's why the image appears stretched when 3D is enabled. In fact, it's non-2DS that stretches the image, so it doesn't appear squished. It does actually matter when and how you change VTotal. Not only do SDK programs expect both VSync to happen at the same time, but 2DS screen actually glitches out if the two screens are way too desynchronized. Not only that, but when the VTotal write happens also matters, as I heard that IPS screens are notoriously sensitive for timing "glitches". DO NOT use clock doubling on 2DS, even if you manage to somehow bypass gsp in this regard. If you really need fake 3D mode, you can use AB interlacing, use a stride size that is double of the real stide size, adjust the 2nd buffer's framebuffer pointer by one real stride's worth, and you've got 2D mode interlacing. So basically what gsp does in 3D mode, except we have to do the stride and pointer nonsense, so it works on 2DS in particular. Although not sure you want to do this, but this is an option. If there are any questions, please ping me with the questions, I'm more than happy to answer 😃 |
I implemented the thing where you wait for VCount to decrease, but it seems to break when you press the power button and return to home menu. For some reason, when closing the software in this way, VCount never changes. I made it abort if it takes longer than 20ms as a workaround, but is there a better way to deal with that? |
Not sure why your aptHookCookie doesn't work as expected ( As for LCDs getting corrupted, there should be a way to force gsp to re-synchronize the LCDs. Alternatively, you could synchronize twice after toggling VTotal, just for good luck, to avoid any race conditions possible in gsp and such. |
Found out that if I write to VTotal lazily (i.e. don't write when the state doesn't change), the previously mentioned bug is fixed. I will do the bottom screen thing though, it makes sense. |
Changing the vcount wait to the other screen doesn't seem to have made any difference. Screens still get out of sync sometimes. Is gspWaitForVBlank() not sufficient for what you're trying to accomplish? Simply removing the vcount wait makes it work 100% of the time for me. With that fixed, lazy vtotal write isn't really necessary either (though harmless). I'd suggest getting rid of both things. In other words, waitForVblank was all you needed to do. |
Thanks for getting in touch! I'll probably keep the lazy write, but if removing the vcount wait helps I'll do that. |
Is this about capture cards? Because i had to make changes recently as well. None of the capture cards on the market handle the situation well when the LCDs don't stay synchronized down to the exact same output cycle. The solution for me was to set the LCD timings of both LCDs at the same time. |
Consider this a "nice to have" backburner issue.
Apparently it is technically possible to fudge the refresh rate of the 3DS screen. This is done in the (closed-source) emulator ZXDS, which emulates the ZX Spectrum--a computer that was really only successful in PAL50 territories, where most of the software is designed around 50 Hz. As far as I know, there are no open-source implementations of the refresh delay implementation used by ZXDS, so unfortunately there's not much I can point to as far as how this can be achieved.
The ZXDS author did write down a lot of their process when developing the original DS version of their emulator which you can read here, but that page has not been updated since the emulator was ported to run on the 3DS. Forcing the original DS screen into refreshing at 50 Hz involved (ab)using the
VCOUNT
register, which is able to delay refreshes for the purpose of synchronizing wireless multiplayer games. I don't know whether the 3DS has something equivalent.The ZXDS changelog is behind a Patreon subscription, but the version 2.0.1 changelog can be found online and implies there are at least two possible methods of delaying refreshes, one of which was problematic on certain 3DS models:
Speaking for myself, I really can't tell the difference while playing games, 50 FPS displaying with duped frames at 60 Hz is fine, especially considering the more advanced games like Red Alarm don't run at a full 50 anyway. Still, it would probably be even better to match the refresh rate to the original console for greater accuracy.
EDIT: Talking to asiekierka, I see that this has already been shared with you in much greater detail and that there is an open-source implementation in atari800-3ds. For the benefit of anybody else who happens to find this, I think asiekierka/atari800-3ds@133bb73 is the commit where the magic happened.
The text was updated successfully, but these errors were encountered: