AlisterT / openjazz

OpenJazz
GNU General Public License v2.0
275 stars 49 forks source link

240p non-interlaced mode - need help #110

Open knerlington opened 5 months ago

knerlington commented 5 months ago

I don't know if it's a bug or not, but I would really appreciate if I could get some help on this one.

First time I tried OJ for Wii I noticed that the video output doesn't run in 240p. And it doesn't seem to have any predefined support for non-interlaced 320x240 (240p, single field framebuffer, no field flipping). Neither does ogc-sdl1.2 from what I can see, but libogc does. I play on a crt and the interlacing is at least one part that makes it rather blurry. Which is why I'm here.

As a back story I don't know cmake, c/c++ (I can still follow along and figure out most things) and I certainly don't know SDL or libogc. Not to mention the OJ src. Ex Java programmer, but haven't coded since 2017 so it's a lot to take in.

But from a lot of guess work, grep use, testing and "just" reading the source code I think I've been able to piece a bit together so far. The biggest problem is that every time I want to test something I have to physically move my sd card back/fort to a real Wii w/o a debugger in place so it gets tedious.

At first I thought I would have to create a custom low level framebuffer and copy everything from the SDL_Surface screen/canvas (as it's called when using SDL2). Testing and digging deeper I realized this is not the way I want to go forth considering my dev environment.

Instead I found that libogc has a predefined struct TVNtsc240Ds: (https://github.com/devkitPro/libogc/blob/master/libogc/video.c)

GXRModeObj TVNtsc240Ds =
{
    VI_TVMODE_NTSC_DS,      // viDisplayMode
    640,             // fbWidth
    240,             // efbHeight
    240,             // xfbHeight
    (VI_MAX_WIDTH_NTSC - 640)/2,        // viXOrigin
    (VI_MAX_HEIGHT_NTSC/2 - 480/2)/2,       // viYOrigin
    640,             // viWidth
    480,             // viHeight
    VI_XFBMODE_SF,   // xFBmode
    GX_FALSE,        // field_rendering
    GX_FALSE,        // aa

ogc-sdl1.2 makes use of said GXRModeObj so I thought I could just change the mode, re-configure and flush the changes. That works! Doing so I get a 240p single field framebuffer with no interlacing in place. Instead everything drawn seem to be vertically stretched/scaled/offset and I have no idea how to fix it.

I've tried different combinations of matching the width/height/origin members from above with OJ's definitions and it's call to SDL_SetVideoMode() in Video::reset(): (OJ/video.h)

#elif defined(__wii__)
    #define DEFAULT_SCREEN_WIDTH 640
    #define DEFAULT_SCREEN_HEIGHT 480

    #define FULLSCREEN_ONLY
    #define NO_RESIZE

    #define FULLSCREEN_FLAGS (SDL_FULLSCREEN | SDL_SWSURFACE | SDL_HWPALETTE)

The fullscreen flags don't seem to be the issue so I'm thinking it's perhaps all draw calls from levels/scenes and menus? I've built the project with -DSCALE=OFF so it shouldn't be the scaling options available (not available in this case) in that regard that are causing it.

One thing that bothers me currently is the following definitions used for GXRModeObj rmode->viTVMode: (https://github.com/devkitPro/libogc/blob/master/gc/ogc/gx_struct.h)

#define VI_TVMODE(fmt, mode)   ( ((fmt) << 2) + (mode) )

#define VI_TVMODE_NTSC_INT          VI_TVMODE(VI_NTSC,        VI_INTERLACE)
#define VI_TVMODE_NTSC_DS           VI_TVMODE(VI_NTSC,        VI_NON_INTERLACE)
#define VI_TVMODE_NTSC_PROG         VI_TVMODE(VI_NTSC,        VI_PROGRESSIVE)

VI_NTSC and VI_NON_INTERLACE are both set to 0 in the same file, but rmode returned from VIDEO_GetPreferredMode(NULL) has viTVMode set to 20.

If I understand correctly bit shifting to the left by 2 above would result in (fmt * 4) + mode. That would equal 0? VI_PROGRESSIVE is set to 2 so I'm wondering if the video mode actually set is one from the TVNtsc480Prog definition. If that in turn creates a mismatch somewhere. I am using a component cable after all.

Sorry if this is confusing to read. Just trying to cover everything I've tried and I really am trying. My set240p() function currently only does this:

void Video::set240p(){
    rmode = &TVNtsc240Ds;
    VIDEO_Configure(rmode);

    //TODO - K0 to K1 cached/unchached copy?
    /* ramebuffer = (SYS_AllocateFramebuffer(rmode));
    framebuffer = (u8 *) MEM_K0_TO_K1((u8 *) SYS_AllocateFramebuffer(rmode));
    */
    VIDEO_Flush();

    //TODO - Not necessary?
    VIDEO_WaitVSync(); //VIDEO_WaitVSync();
}

I call it in Video::reset() after the SDL_SetVideoMode() function.

EDIT: Went ahead and installed homebrew channel in Dolphin (can't run the oj.dol otherwise) and interestingly when I enable the setting below the height is fixed. So it should be a simple mismatch somewhere? (Realized that if you place the game data at the root of a virtual SD card mounted in Dolphin you can run .elf or .dol files directly w/o HBC to get printf calls in the emu logs.) OJ_debug OJ_debug2

EDIT SUCCESS: Holy sh*t - I think I solved it. I don't fully understand the relationship between the efb, xfb, vi, SDL dimensions and the scaling that occurs, but looking at the libogc example (devkitpro/examples/wii/graphics/gx/neheGX/lesson01) the following function calls caught my interest:

GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1);
yScale = GX_GetYScaleFactor(rmode->efbHeight,rmode->xfbHeight);
xfbHeight = GX_SetDispCopyYScale(yScale);
GX_SetScissor(0,0,rmode->fbWidth,rmode->efbHeight);
GX_SetDispCopySrc(0,0,rmode->fbWidth,rmode->efbHeight);
GX_SetDispCopyDst(rmode->fbWidth,xfbHeight);

Adding all, but the SetScissor call to set240p():

void Video::set240p(){
    //TODO - Only have to change the GXRModeObj rmode/vmode, re-config and flush the changes?

    u32 xfbHeight;
    f32 yScale;
    rmode = &TVNtsc240Ds;
    //TODO - Y scale stuff
    GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1);
    yScale = GX_GetYScaleFactor(rmode->efbHeight,rmode->xfbHeight);
    xfbHeight = GX_SetDispCopyYScale(yScale);
    //GX_SetScissor(0,0,rmode->fbWidth,rmode->efbHeight);   //Should be correct already
    GX_SetDispCopySrc(0,0,rmode->fbWidth,rmode->efbHeight);
    GX_SetDispCopyDst(rmode->fbWidth,xfbHeight);

    VIDEO_Configure(rmode);

    //printRmode();
    VIDEO_Flush();

    //TODO - Not necessary?
    VIDEO_WaitVSync(); //VIDEO_WaitVSync();
}

240p (320x240 viewport): 240p (320x240 viewport) 240p (640x480 viewport): 240p (640x480 viewport) 480i: 480i

//Sets an appropriate y scale factor based on the actual efb and xfb height
//Needed for proper xfb presentation
yScale = GX_GetYScaleFactor(rmode->efbHeight,rmode->xfbHeight);

//Calculates the xfb height (the final output height presented to/used by the screen) in pixels 
xfbHeight = GX_SetDispCopyYScale(yScale);

//Controls the region of the efb that should be copied into the xfb. 
//In this case the entire efb
GX_SetDispCopySrc(0,0,rmode->fbWidth,rmode->efbHeight);

//Sets the width and height of the final xfb copy being presented on screen
//Now corrected for efb heights specified in the GXRModeObj presets 
GX_SetDispCopyDst(rmode->fbWidth,xfbHeight);

Meaning ogc-sdl1.2 just assumes the height of the efb and xfb is always 480 pixels?