From 44f9b921246bc69350361d34bf67760333aa0975 Mon Sep 17 00:00:00 2001 From: Pouar Date: Thu, 18 Jun 2020 19:34:57 -0500 Subject: [PATCH 01/26] Bugfix The bullets snufit shoots immediately got deleted shortly after shooting when NODRAWDISTANCE was enabled. This fixes it --- src/game/behaviors/snufit.inc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/game/behaviors/snufit.inc.c b/src/game/behaviors/snufit.inc.c index 76e78c09a..2acf442f2 100644 --- a/src/game/behaviors/snufit.inc.c +++ b/src/game/behaviors/snufit.inc.c @@ -181,10 +181,9 @@ void bhv_snufit_balls_loop(void) { // If far from Mario or in a different room, despawn. if ((o->activeFlags & ACTIVE_FLAG_IN_DIFFERENT_ROOM) #ifndef NODRAWINGDISTANCE - || (o->oTimer != 0 && o->oDistanceToMario > 1500.0f)) { -#else - || (o->oTimer != 0)) { + || (o->oTimer != 0 && o->oDistanceToMario > 1500.0f) #endif + ){ obj_mark_for_deletion(o); } From 1486bee60af0aa9fd888110205e81df0b10ebf99 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Tue, 7 Jul 2020 15:03:25 +0300 Subject: [PATCH 02/26] replace 'double' vsync option with 'auto' from sm64-port --- include/text_options_strings.h.in | 4 +- src/game/options_menu.c | 2 +- src/pc/gfx/gfx_sdl2.c | 83 +++++++++++++++++++++++++++---- 3 files changed, 76 insertions(+), 13 deletions(-) diff --git a/include/text_options_strings.h.in b/include/text_options_strings.h.in index 6d7aff9e4..a1ce481f2 100644 --- a/include/text_options_strings.h.in +++ b/include/text_options_strings.h.in @@ -52,7 +52,7 @@ #define TEXT_OPT_SFXVOLUME _("SFX VOLUME") #define TEXT_OPT_ENVVOLUME _("ENV VOLUME") #define TEXT_OPT_VSYNC _("VERTICAL SYNC") -#define TEXT_OPT_DOUBLE _("DOUBLE") +#define TEXT_OPT_AUTO _("AUTO") #define TEXT_OPT_HUD _("HUD") #define TEXT_OPT_THREEPT _("THREE POINT") #define TEXT_OPT_APPLY _("APPLY") @@ -116,7 +116,7 @@ #define TEXT_OPT_SFXVOLUME _("Sfx Volume") #define TEXT_OPT_ENVVOLUME _("Env Volume") #define TEXT_OPT_VSYNC _("Vertical Sync") -#define TEXT_OPT_DOUBLE _("Double") +#define TEXT_OPT_AUTO _("Auto") #define TEXT_OPT_HUD _("HUD") #define TEXT_OPT_THREEPT _("Three-point") #define TEXT_OPT_APPLY _("Apply") diff --git a/src/game/options_menu.c b/src/game/options_menu.c index d2b356ae1..8ce169f0f 100644 --- a/src/game/options_menu.c +++ b/src/game/options_menu.c @@ -79,7 +79,7 @@ static const u8 optsVideoStr[][32] = { { TEXT_OPT_LINEAR }, { TEXT_OPT_RESETWND }, { TEXT_OPT_VSYNC }, - { TEXT_OPT_DOUBLE }, + { TEXT_OPT_AUTO }, { TEXT_OPT_HUD }, { TEXT_OPT_THREEPT }, { TEXT_OPT_APPLY }, diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c index e46c186e0..a39b76d27 100644 --- a/src/pc/gfx/gfx_sdl2.c +++ b/src/pc/gfx/gfx_sdl2.c @@ -25,6 +25,7 @@ #endif // End of OS-Specific GL defines #include +#include #include "gfx_window_manager_api.h" #include "gfx_screen_config.h" @@ -41,8 +42,6 @@ # define FRAMERATE 30 #endif -static const Uint32 FRAME_TIME = 1000 / FRAMERATE; - static SDL_Window *wnd; static SDL_GLContext ctx = NULL; static int inverted_scancode_table[512]; @@ -51,6 +50,11 @@ static kb_callback_t kb_key_down = NULL; static kb_callback_t kb_key_up = NULL; static void (*kb_all_keys_up)(void) = NULL; +// whether to use timer for frame control +static bool use_timer = true; +static Uint64 qpc_freq = 1; +static Uint64 frame_time = 1; + const SDL_Scancode windows_scancode_table[] = { /* 0 1 2 3 4 5 6 7 */ /* 8 9 A B C D E F */ @@ -103,7 +107,57 @@ const SDL_Scancode scancode_rmapping_nonextended[][2] = { #define IS_FULLSCREEN() ((SDL_GetWindowFlags(wnd) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0) -static void gfx_sdl_set_fullscreen() { +int test_vsync(void) { + // Even if SDL_GL_SetSwapInterval succeeds, it doesn't mean that VSync actually works. + // A 60 Hz monitor should have a swap interval of 16.67 milliseconds. + // Try to detect the length of a vsync by swapping buffers some times. + // Since the graphics card may enqueue a fixed number of frames, + // first send in four dummy frames to hopefully fill the queue. + // This method will fail if the refresh rate is changed, which, in + // combination with that we can't control the queue size (i.e. lag) + // is a reason this generic SDL2 backend should only be used as last resort. + + for (int i = 0; i < 8; ++i) + SDL_GL_SwapWindow(wnd); + + Uint32 start = SDL_GetTicks(); + SDL_GL_SwapWindow(wnd); + SDL_GL_SwapWindow(wnd); + SDL_GL_SwapWindow(wnd); + SDL_GL_SwapWindow(wnd); + Uint32 end = SDL_GetTicks(); + + const float average = 4.0 * 1000.0 / (end - start); + + if (average > 27.0f && average < 33.0f) return 1; + if (average > 57.0f && average < 63.0f) return 2; + if (average > 86.0f && average < 94.0f) return 3; + if (average > 115.0f && average < 125.0f) return 4; + + return 0; +} + +static inline void gfx_sdl_set_vsync(int mode) { + if (mode > 1) { + // try to detect refresh rate + SDL_GL_SetSwapInterval(1); + const int vblanks = test_vsync(); + if (vblanks) { + printf("determined swap interval: %d\n", vblanks); + SDL_GL_SetSwapInterval(vblanks); + use_timer = false; + return; + } else { + printf("could not determine swap interval, falling back to timer sync\n"); + mode = 0; + } + } + + SDL_GL_SetSwapInterval(mode); + use_timer = (mode <= 1); +} + +static void gfx_sdl_set_fullscreen(void) { if (configWindow.reset) configWindow.fullscreen = false; if (configWindow.fullscreen == IS_FULLSCREEN()) @@ -137,7 +191,8 @@ static void gfx_sdl_reset_dimension_and_pos(void) { SDL_SetWindowSize(wnd, configWindow.w, configWindow.h); SDL_SetWindowPosition(wnd, xpos, ypos); - SDL_GL_SetSwapInterval(configWindow.vsync); // in case vsync changed + // in case vsync changed + gfx_sdl_set_vsync(configWindow.vsync); } static void gfx_sdl_init(const char *window_title) { @@ -165,10 +220,13 @@ static void gfx_sdl_init(const char *window_title) { ); ctx = SDL_GL_CreateContext(wnd); - SDL_GL_SetSwapInterval(configWindow.vsync); + gfx_sdl_set_vsync(configWindow.vsync); gfx_sdl_set_fullscreen(); + qpc_freq = SDL_GetPerformanceFrequency(); + frame_time = qpc_freq / FRAMERATE; + for (size_t i = 0; i < sizeof(windows_scancode_table) / sizeof(SDL_Scancode); i++) { inverted_scancode_table[windows_scancode_table[i]] = i; } @@ -184,15 +242,20 @@ static void gfx_sdl_init(const char *window_title) { } static void gfx_sdl_main_loop(void (*run_one_game_iter)(void)) { - Uint32 t = SDL_GetTicks(); + Uint64 t = SDL_GetPerformanceCounter(); run_one_game_iter(); - t = SDL_GetTicks() - t; - if (t < FRAME_TIME && configWindow.vsync <= 1) - SDL_Delay(FRAME_TIME - t); + t = SDL_GetPerformanceCounter() - t; + if (t < frame_time && use_timer) { + const Uint64 us = (frame_time - t) * 1000000 / qpc_freq; + usleep(us); + } } static void gfx_sdl_get_dimensions(uint32_t *width, uint32_t *height) { - SDL_GetWindowSize(wnd, width, height); + int w, h; + SDL_GetWindowSize(wnd, &w, &h); + if (width) *width = w; + if (height) *height = h; } static int translate_scancode(int scancode) { From bc3e94b11ef7b5fe64e3d46af44a8b7190dfbb7d Mon Sep 17 00:00:00 2001 From: fgsfds Date: Tue, 7 Jul 2020 15:03:45 +0300 Subject: [PATCH 03/26] bump gfx pool size --- src/game/display.h | 3 +-- src/game/game_init.h | 2 -- src/game/memory.h | 1 + 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/game/display.h b/src/game/display.h index 877c4f0b5..43eeac3b2 100644 --- a/src/game/display.h +++ b/src/game/display.h @@ -2,8 +2,7 @@ #define _DISPLAY_H #include "types.h" - -#define GFX_POOL_SIZE 64000 +#include "memory.h" extern u16 frameBufferIndex; extern u32 gGlobalTimer; diff --git a/src/game/game_init.h b/src/game/game_init.h index 59c5bddec..2dd25d1c9 100644 --- a/src/game/game_init.h +++ b/src/game/game_init.h @@ -9,8 +9,6 @@ #include "types.h" #include "memory.h" -#define GFX_POOL_SIZE 64000 - struct GfxPool { Gfx buffer[GFX_POOL_SIZE]; struct SPTask spTask; diff --git a/src/game/memory.h b/src/game/memory.h index 50339f88c..48cd703c7 100644 --- a/src/game/memory.h +++ b/src/game/memory.h @@ -8,6 +8,7 @@ #define MEMORY_POOL_LEFT 0 #define MEMORY_POOL_RIGHT 1 +#define GFX_POOL_SIZE (512 * 1024) struct AllocOnlyPool { From b3ddd3a9fc141f972a5634d9072f44c5ccb012b8 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Tue, 7 Jul 2020 20:43:46 +0300 Subject: [PATCH 04/26] remove garbage from game_loop_one_iteration --- src/game/game_init.c | 43 ++++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/src/game/game_init.c b/src/game/game_init.c index 0c27c7c30..c2df4510b 100644 --- a/src/game/game_init.c +++ b/src/game/game_init.c @@ -582,32 +582,25 @@ void thread5_game_loop(UNUSED void *arg) { } void game_loop_one_iteration(void) { - // if the reset timer is active, run the process to reset the game. - //if (gResetTimer) { -// draw_reset_bars(); (N64 target only?) -//} + profiler_log_thread5_time(THREAD5_START); - profiler_log_thread5_time(THREAD5_START); + // if any controllers are plugged in, start read the data for when + // read_controller_inputs is called later. + if (gControllerBits) { + // block_until_rumble_pak_free(); + osContStartReadData(&gSIEventMesgQueue); + } - // if any controllers are plugged in, start read the data for when - // read_controller_inputs is called later. - if (gControllerBits) { + audio_game_loop_tick(); + config_gfx_pool(); + read_controller_inputs(); + levelCommandAddr = level_script_execute(levelCommandAddr); + display_and_vsync(); - // block_until_rumble_pak_free(); - osContStartReadData(&gSIEventMesgQueue); - } - - audio_game_loop_tick(); - config_gfx_pool(); - read_controller_inputs(); - levelCommandAddr = level_script_execute(levelCommandAddr); - display_and_vsync(); - - // when debug info is enabled, print the "BUF %d" information. - if (gShowDebugText) { - // subtract the end of the gfx pool with the display list to obtain the - // amount of free space remaining. - print_text_fmt_int(180, 20, "BUF %d", gGfxPoolEnd - (u8 *) gDisplayListHead); - } -// } was here for ifdef targ 64 + // when debug info is enabled, print the "BUF %d" information. + if (gShowDebugText) { + // subtract the end of the gfx pool with the display list to obtain the + // amount of free space remaining. + print_text_fmt_int(180, 20, "BUF %d", gGfxPoolEnd - (u8 *) gDisplayListHead); + } } From 97be18f11d997574e2b12439f22072cb91dc4bf3 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Tue, 7 Jul 2020 20:52:51 +0300 Subject: [PATCH 05/26] add new 60FPS patch by Emil; nuke old patches --- enhancements/60fps_ex.patch | 1955 ++++++++++++++++++++++ enhancements/L-trigger-mapping/README.md | 12 - enhancements/README.md | 38 +- enhancements/RecordDemo.js | 184 -- enhancements/crash.patch | 486 ------ enhancements/crash_font.bin | Bin 285 -> 0 bytes enhancements/debug_box.patch | 303 ---- enhancements/ique_support.patch | 312 ---- enhancements/mem_error_screen.patch | 298 ---- enhancements/record_demo.patch | 185 -- 10 files changed, 1962 insertions(+), 1811 deletions(-) create mode 100644 enhancements/60fps_ex.patch delete mode 100644 enhancements/L-trigger-mapping/README.md delete mode 100644 enhancements/RecordDemo.js delete mode 100644 enhancements/crash.patch delete mode 100644 enhancements/crash_font.bin delete mode 100644 enhancements/debug_box.patch delete mode 100644 enhancements/ique_support.patch delete mode 100644 enhancements/mem_error_screen.patch delete mode 100644 enhancements/record_demo.patch diff --git a/enhancements/60fps_ex.patch b/enhancements/60fps_ex.patch new file mode 100644 index 000000000..01c179453 --- /dev/null +++ b/enhancements/60fps_ex.patch @@ -0,0 +1,1955 @@ +diff --git a/include/types.h b/include/types.h +index b3dc27e..c46bdf0 100644 +--- a/include/types.h ++++ b/include/types.h +@@ -118,6 +118,10 @@ struct GraphNodeObject_sub + /*0x0A 0x42*/ u16 animTimer; + /*0x0C 0x44*/ s32 animFrameAccelAssist; + /*0x10 0x48*/ s32 animAccel; ++ s16 prevAnimFrame; ++ s16 prevAnimID; ++ u32 prevAnimFrameTimestamp; ++ struct Animation *prevAnimPtr; + }; + + struct GraphNodeObject +@@ -128,11 +132,22 @@ struct GraphNodeObject + /*0x19*/ s8 unk19; + /*0x1A*/ Vec3s angle; + /*0x20*/ Vec3f pos; ++ Vec3s prevAngle; ++ Vec3f prevPos; ++ u32 prevTimestamp; ++ Vec3f prevShadowPos; ++ u32 prevShadowPosTimestamp; + /*0x2C*/ Vec3f scale; ++ Vec3f prevScale; ++ u32 prevScaleTimestamp; + /*0x38*/ struct GraphNodeObject_sub unk38; + /*0x4C*/ struct SpawnInfo *unk4C; + /*0x50*/ Mat4 *throwMatrix; // matrix ptr ++ Mat4 prevThrowMatrix; ++ u32 prevThrowMatrixTimestamp; ++ Mat4 *throwMatrixInterpolated; + /*0x54*/ Vec3f cameraToObject; ++ u32 skipInterpolationTimestamp; + }; + + struct ObjectNode +@@ -243,6 +258,10 @@ struct Surface + } normal; + /*0x28*/ f32 originOffset; + /*0x2C*/ struct Object *object; ++ Vec3s prevVertex1; ++ Vec3s prevVertex2; ++ Vec3s prevVertex3; ++ u32 modifiedTimestamp; + }; + + struct MarioBodyState +diff --git a/src/engine/graph_node.h b/src/engine/graph_node.h +index 802d97a..1b0d677 100644 +--- a/src/engine/graph_node.h ++++ b/src/engine/graph_node.h +@@ -110,6 +110,8 @@ struct GraphNodePerspective + /*0x1C*/ f32 fov; // horizontal field of view in degrees + /*0x20*/ s16 near; // near clipping plane + /*0x22*/ s16 far; // far clipping plane ++ f32 prevFov; ++ f32 prevTimestamp; + }; + + /** An entry in the master list. It is a linked list of display lists +@@ -118,7 +120,9 @@ struct GraphNodePerspective + struct DisplayListNode + { + Mtx *transform; ++ void *transformInterpolated; + void *displayList; ++ void *displayListInterpolated; + struct DisplayListNode *next; + }; + +@@ -185,7 +189,11 @@ struct GraphNodeCamera + } config; + /*0x1C*/ Vec3f pos; + /*0x28*/ Vec3f focus; ++ Vec3f prevPos; ++ Vec3f prevFocus; ++ u32 prevTimestamp; + /*0x34*/ Mat4 *matrixPtr; // pointer to look-at matrix of this camera as a Mat4 ++ Mat4 *matrixPtrInterpolated; + /*0x38*/ s16 roll; // roll in look at matrix. Doesn't account for light direction unlike rollScreen. + /*0x3A*/ s16 rollScreen; // rolls screen while keeping the light direction consistent + }; +@@ -226,7 +234,8 @@ struct GraphNodeRotation + /*0x00*/ struct GraphNode node; + /*0x14*/ void *displayList; + /*0x18*/ Vec3s rotation; +- u8 pad1E[2]; ++ Vec3s prevRotation; ++ u32 prevTimestamp; + }; + + /** GraphNode part that transforms itself and its children based on animation +@@ -323,6 +332,9 @@ struct GraphNodeBackground + /*0x00*/ struct FnGraphNode fnNode; + /*0x18*/ s32 unused; + /*0x1C*/ s32 background; // background ID, or rgba5551 color if fnNode.func is null ++ Vec3f prevCameraPos; ++ Vec3f prevCameraFocus; ++ u32 prevCameraTimestamp; + }; + + /** Renders the object that Mario is holding. +@@ -333,6 +345,8 @@ struct GraphNodeHeldObject + /*0x18*/ s32 playerIndex; + /*0x1C*/ struct Object *objNode; + /*0x20*/ Vec3s translation; ++ Vec3f prevShadowPos; ++ u32 prevShadowPosTimestamp; + }; + + /** A node that allows an object to specify a different culling radius than the +diff --git a/src/engine/surface_collision.c b/src/engine/surface_collision.c +index 5b6775f..2c11e25 100644 +--- a/src/engine/surface_collision.c ++++ b/src/engine/surface_collision.c +@@ -8,6 +8,7 @@ + #include "surface_collision.h" + #include "surface_load.h" + #include "math_util.h" ++#include "game/game_init.h" + + /************************************************** + * WALLS * +@@ -394,26 +395,44 @@ f32 find_floor_height_and_data(f32 xPos, f32 yPos, f32 zPos, struct FloorGeometr + return floorHeight; + } + ++u8 gInterpolatingSurfaces; ++ + /** + * Iterate through the list of floors and find the first floor under a given point. + */ + static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 x, s32 y, s32 z, f32 *pheight) { + register struct Surface *surf; +- register s32 x1, z1, x2, z2, x3, z3; ++ register f32 x1, z1, x2, z2, x3, z3; + f32 nx, ny, nz; + f32 oo; + f32 height; + struct Surface *floor = NULL; ++ s32 interpolate; + + // Iterate through the list of floors until there are no more floors. + while (surfaceNode != NULL) { + surf = surfaceNode->surface; + surfaceNode = surfaceNode->next; ++ interpolate = gInterpolatingSurfaces && surf->modifiedTimestamp == gGlobalTimer; + + x1 = surf->vertex1[0]; + z1 = surf->vertex1[2]; + x2 = surf->vertex2[0]; + z2 = surf->vertex2[2]; ++ if (interpolate) { ++ f32 diff = (surf->prevVertex1[0] - x1) * (surf->prevVertex1[0] - x1); ++ diff += (surf->prevVertex1[1] - surf->vertex1[1]) * (surf->prevVertex1[1] - surf->vertex1[1]); ++ diff += (surf->prevVertex1[2] - z1) * (surf->prevVertex1[2] - z1); ++ //printf("%f\n", sqrtf(diff)); ++ if (diff > 10000) { ++ interpolate = FALSE; ++ } else { ++ x1 = (surf->prevVertex1[0] + x1) / 2; ++ z1 = (surf->prevVertex1[2] + z1) / 2; ++ x2 = (surf->prevVertex2[0] + x2) / 2; ++ z2 = (surf->prevVertex2[2] + z2) / 2; ++ } ++ } + + // Check that the point is within the triangle bounds. + if ((z1 - z) * (x2 - x1) - (x1 - x) * (z2 - z1) < 0) { +@@ -423,6 +442,10 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 + // To slightly save on computation time, set this later. + x3 = surf->vertex3[0]; + z3 = surf->vertex3[2]; ++ if (interpolate) { ++ x3 = (surf->prevVertex3[0] + x3) / 2; ++ z3 = (surf->prevVertex3[2] + z3) / 2; ++ } + + if ((z2 - z) * (x3 - x2) - (x2 - x) * (z3 - z2) < 0) { + continue; +@@ -442,10 +465,30 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 + continue; + } + +- nx = surf->normal.x; +- ny = surf->normal.y; +- nz = surf->normal.z; +- oo = surf->originOffset; ++ if (interpolate) { ++ f32 y1, y2, y3; ++ f32 mag; ++ y1 = (surf->prevVertex1[1] + surf->vertex1[1]) / 2; ++ y2 = (surf->prevVertex2[1] + surf->vertex2[1]) / 2; ++ y3 = (surf->prevVertex3[1] + surf->vertex3[1]) / 2; ++ nx = (y2 - y1) * (z3 - z2) - (z2 - z1) * (y3 - y2); ++ ny = (z2 - z1) * (x3 - x2) - (x2 - x1) * (z3 - z2); ++ nz = (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2); ++ mag = sqrtf(nx * nx + ny * ny + nz * nz); ++ if (mag < 0.0001) { ++ continue; ++ } ++ mag = (f32)(1.0 / mag); ++ nx *= mag; ++ ny *= mag; ++ nz *= mag; ++ oo = -(nx * x1 + ny * y1 + nz * z1); ++ } else { ++ nx = surf->normal.x; ++ ny = surf->normal.y; ++ nz = surf->normal.z; ++ oo = surf->originOffset; ++ } + + // If a wall, ignore it. Likely a remnant, should never occur. + if (ny == 0.0f) { +@@ -460,6 +503,15 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 + } + + *pheight = height; ++ if (interpolate) { ++ static struct Surface s; ++ s.type = surf->type; ++ s.normal.x = nx; ++ s.normal.y = ny; ++ s.normal.z = nz; ++ s.originOffset = oo; ++ return &s; ++ } + floor = surf; + break; + } +diff --git a/src/engine/surface_load.c b/src/engine/surface_load.c +index ac2ee50..323b7d0 100644 +--- a/src/engine/surface_load.c ++++ b/src/engine/surface_load.c +@@ -14,6 +14,7 @@ + #include "game/mario.h" + #include "game/object_list_processor.h" + #include "surface_load.h" ++#include "game/game_init.h" + + s32 unused8038BE90; + +@@ -359,6 +360,11 @@ static struct Surface *read_surface_data(s16 *vertexData, s16 **vertexIndices) { + + surface = alloc_surface(); + ++ vec3s_copy(surface->prevVertex1, surface->vertex1); ++ vec3s_copy(surface->prevVertex2, surface->vertex2); ++ vec3s_copy(surface->prevVertex3, surface->vertex3); ++ surface->modifiedTimestamp = gGlobalTimer; ++ + surface->vertex1[0] = x1; + surface->vertex2[0] = x2; + surface->vertex3[0] = x3; +diff --git a/src/game/camera.c b/src/game/camera.c +index bde0662..9351dea 100644 +--- a/src/game/camera.c ++++ b/src/game/camera.c +@@ -484,6 +484,10 @@ CameraTransition sModeTransitions[] = { + extern u8 sDanceCutsceneIndexTable[][4]; + extern u8 sZoomOutAreaMasks[]; + ++static void skip_camera_interpolation(void) { ++ gLakituState.skipCameraInterpolationTimestamp = gGlobalTimer; ++} ++ + /** + * Starts a camera shake triggered by an interaction + */ +@@ -5552,6 +5556,7 @@ s32 set_camera_mode_fixed(struct Camera *c, s16 x, s16 y, s16 z) { + c->mode = CAMERA_MODE_FIXED; + vec3f_set(c->pos, sFixedModeBasePosition[0], sMarioCamState->pos[1], + sFixedModeBasePosition[2]); ++ skip_camera_interpolation(); + } + return basePosSet; + } +@@ -5714,6 +5719,7 @@ BAD_RETURN(s32) cam_rr_enter_building_side(struct Camera *c) { + if (c->mode != CAMERA_MODE_FIXED) { + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + c->mode = CAMERA_MODE_FIXED; ++ skip_camera_interpolation(); + } + } + +@@ -5909,6 +5915,7 @@ BAD_RETURN(s32) cam_castle_enter_lobby(struct Camera *c) { + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + set_fixed_cam_axis_sa_lobby(c->mode); + c->mode = CAMERA_MODE_FIXED; ++ skip_camera_interpolation(); + } + } + +@@ -7279,6 +7286,7 @@ BAD_RETURN(s32) cutscene_unused_loop(UNUSED struct Camera *c) { + BAD_RETURN(s32) cutscene_ending_mario_fall_start(struct Camera *c) { + vec3f_set(c->focus, -26.f, 0.f, -137.f); + vec3f_set(c->pos, 165.f, 4725.f, 324.f); ++ skip_camera_interpolation(); + } + + /** +@@ -7311,6 +7319,7 @@ BAD_RETURN(s32) cutscene_ending_mario_fall(struct Camera *c) { + BAD_RETURN(s32) cutscene_ending_mario_land_closeup(struct Camera *c) { + vec3f_set(c->focus, 85.f, 826.f, 250.f); + vec3f_set(c->pos, -51.f, 988.f, -202.f); ++ skip_camera_interpolation(); + player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); + } + +@@ -7320,6 +7329,7 @@ BAD_RETURN(s32) cutscene_ending_mario_land_closeup(struct Camera *c) { + BAD_RETURN(s32) cutscene_ending_reset_spline(UNUSED struct Camera *c) { + sCutsceneVars[9].point[0] = 0.f; + cutscene_reset_spline(); ++ skip_camera_interpolation(); + } + + /** +@@ -7355,6 +7365,7 @@ BAD_RETURN(s32) cutscene_ending_peach_appear_closeup(struct Camera *c) { + vec3f_set(c->pos, 179.f, 2463.f, -1216.f); + c->pos[1] = gCutsceneFocus->oPosY + 35.f; + vec3f_set(c->focus, gCutsceneFocus->oPosX, gCutsceneFocus->oPosY + 125.f, gCutsceneFocus->oPosZ); ++ skip_camera_interpolation(); + } + + /** +@@ -7373,6 +7384,7 @@ BAD_RETURN(s32) cutscene_ending_peach_appears(struct Camera *c) { + BAD_RETURN(s32) cutscene_ending_peach_descends_start(UNUSED struct Camera *c) { + cutscene_reset_spline(); + sCutsceneVars[2].point[1] = 150.f; ++ skip_camera_interpolation(); + } + + /** +@@ -7459,6 +7471,7 @@ BAD_RETURN(s32) cutscene_ending_peach_wakeup(struct Camera *c) { + BAD_RETURN(s32) cutscene_ending_dialog(struct Camera *c) { + vec3f_set(c->focus, 11.f, 983.f, -1273.f); + vec3f_set(c->pos, -473.f, 970.f, -1152.f); ++ skip_camera_interpolation(); + player2_rotate_cam(c, -0x800, 0x2000, -0x2000, 0x2000); + } + +@@ -7469,6 +7482,7 @@ BAD_RETURN(s32) cutscene_ending_kiss_closeup(struct Camera *c) { + set_fov_function(CAM_FOV_SET_29); + vec3f_set(c->focus, 350.f, 1034.f, -1216.f); + vec3f_set(c->pos, -149.f, 1021.f, -1216.f); ++ skip_camera_interpolation(); + } + + /** +@@ -7504,6 +7518,7 @@ BAD_RETURN(s32) cutscene_ending_kiss(struct Camera *c) { + BAD_RETURN(s32) cutscene_ending_look_at_sky(struct Camera *c) { + move_point_along_spline(c->focus, sEndingLookAtSkyFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + vec3f_set(c->pos, 699.f, 1680.f, -703.f); ++ skip_camera_interpolation(); + } + + /** +@@ -10340,6 +10355,7 @@ BAD_RETURN(s32) cutscene_door_start(struct Camera *c) { + BAD_RETURN(s32) cutscene_door_fix_cam(struct Camera *c) { + vec3f_copy(c->pos, sCutsceneVars[0].point); + vec3f_copy(c->focus, sCutsceneVars[1].point); ++ skip_camera_interpolation(); + } + + /** +@@ -10373,6 +10389,7 @@ BAD_RETURN(s32) cutscene_door_move_behind_mario(struct Camera *c) { + } + + offset_rotated(c->pos, sMarioCamState->pos, camOffset, sCutsceneVars[0].angle); ++ skip_camera_interpolation(); + } + + /** +diff --git a/src/game/camera.h b/src/game/camera.h +index 173ab8a..b1abdc4 100644 +--- a/src/game/camera.h ++++ b/src/game/camera.h +@@ -657,6 +657,8 @@ struct LakituState + /// Mario's action from the previous frame. Only used to determine if Mario just finished a dive. + /*0xB8*/ u32 lastFrameAction; + /*0xBC*/ s16 unused; ++ ++ u32 skipCameraInterpolationTimestamp; + }; + + // bss order hack to not affect BSS order. if possible, remove me, but it will be hard to match otherwise +diff --git a/src/game/envfx_bubbles.c b/src/game/envfx_bubbles.c +index 16a9272..ee1b029 100644 +--- a/src/game/envfx_bubbles.c ++++ b/src/game/envfx_bubbles.c +@@ -35,6 +35,20 @@ Vtx_t gBubbleTempVtx[3] = { + { { 0, 0, 0 }, 0, { -498, 964 }, { 0xFF, 0xFF, 0xFF, 0xFF } }, + }; + ++static Gfx sGfxSaved[60 / 5]; ++static Gfx *sBubbleInterpolatedDisplayListPos[60 / 5]; ++static Vec3s sPrevBubblePositions[60]; ++ ++void patch_interpolated_bubble_particles(void) { ++ s32 i; ++ for (i = 0; i < 60 / 5; i++) { ++ if (sBubbleInterpolatedDisplayListPos[i] != NULL) { ++ *sBubbleInterpolatedDisplayListPos[i] = sGfxSaved[i]; ++ sBubbleInterpolatedDisplayListPos[i] = NULL; ++ } ++ } ++} ++ + /** + * Check whether the particle with the given index is + * laterally within distance of point (x, z). Used to +@@ -241,6 +255,7 @@ void envfx_update_whirlpool(void) { + (gEnvFxBuffer + i)->yPos = (i + gEnvFxBuffer)->bubbleY; + (gEnvFxBuffer + i)->unusedBubbleVar = 0; + (gEnvFxBuffer + i)->isAlive = 1; ++ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; + + envfx_rotate_around_whirlpool(&(gEnvFxBuffer + i)->xPos, &(gEnvFxBuffer + i)->yPos, + &(gEnvFxBuffer + i)->zPos); +@@ -299,6 +314,7 @@ void envfx_update_jetstream(void) { + + coss((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1]; + (gEnvFxBuffer + i)->yPos = + gEnvFxBubbleConfig[ENVFX_STATE_SRC_Y] + (random_float() * 400.0f - 200.0f); ++ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; + } else { + (gEnvFxBuffer + i)->angleAndDist[1] += 10; + (gEnvFxBuffer + i)->xPos += sins((gEnvFxBuffer + i)->angleAndDist[0]) * 10.0f; +@@ -506,6 +522,12 @@ Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFro + Vec3s vertex1; + Vec3s vertex2; + Vec3s vertex3; ++ Vec3s interpolatedVertices[3]; ++ ++ static Vec3s prevVertex1; ++ static Vec3s prevVertex2; ++ static Vec3s prevVertex3; ++ static u32 prevTimestamp; + + Gfx *gfxStart; + +@@ -521,18 +543,52 @@ Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFro + envfx_bubbles_update_switch(mode, camTo, vertex1, vertex2, vertex3); + rotate_triangle_vertices(vertex1, vertex2, vertex3, pitch, yaw); + ++ if (gGlobalTimer == prevTimestamp + 1) { ++ interpolate_vectors_s16(interpolatedVertices[0], prevVertex1, vertex1); ++ interpolate_vectors_s16(interpolatedVertices[1], prevVertex2, vertex2); ++ interpolate_vectors_s16(interpolatedVertices[2], prevVertex3, vertex3); ++ } ++ vec3s_copy(prevVertex1, vertex1); ++ vec3s_copy(prevVertex2, vertex2); ++ vec3s_copy(prevVertex3, vertex3); ++ prevTimestamp = gGlobalTimer; ++ + gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006D38); + + for (i = 0; i < sBubbleParticleMaxCount; i += 5) { ++ Vtx *interpolatedVertBuf = alloc_display_list(15 * sizeof(Vtx)); ++ s32 j, k; + gDPPipeSync(sGfxCursor++); + envfx_set_bubble_texture(mode, i); +- append_bubble_vertex_buffer(sGfxCursor++, i, vertex1, vertex2, vertex3, (Vtx *) gBubbleTempVtx); ++ sBubbleInterpolatedDisplayListPos[i / 5] = sGfxCursor; ++ for (j = 0; j < 5; j++) { ++ for (k = 0; k < 3; k++) { ++ Vtx *v = &interpolatedVertBuf[j * 3 + k]; ++ v->v = gBubbleTempVtx[k]; ++ if (gGlobalTimer != gEnvFxBuffer[i + j].spawnTimestamp && mode != ENVFX_LAVA_BUBBLES) { ++ v->v.ob[0] = (sPrevBubblePositions[i + j][0] + gEnvFxBuffer[i + j].xPos) / 2.0f + interpolatedVertices[k][0]; ++ v->v.ob[1] = (sPrevBubblePositions[i + j][1] + gEnvFxBuffer[i + j].yPos) / 2.0f + interpolatedVertices[k][1]; ++ v->v.ob[2] = (sPrevBubblePositions[i + j][2] + gEnvFxBuffer[i + j].zPos) / 2.0f + interpolatedVertices[k][2]; ++ } else { ++ v->v.ob[0] = gEnvFxBuffer[i + j].xPos + interpolatedVertices[k][0]; ++ v->v.ob[1] = gEnvFxBuffer[i + j].yPos + interpolatedVertices[k][1]; ++ v->v.ob[2] = gEnvFxBuffer[i + j].zPos + interpolatedVertices[k][2]; ++ } ++ } ++ } ++ gSPVertex(sGfxCursor++, VIRTUAL_TO_PHYSICAL(interpolatedVertBuf), 15, 0); ++ append_bubble_vertex_buffer(&sGfxSaved[i / 5], i, vertex1, vertex2, vertex3, (Vtx *) gBubbleTempVtx); + gSP1Triangle(sGfxCursor++, 0, 1, 2, 0); + gSP1Triangle(sGfxCursor++, 3, 4, 5, 0); + gSP1Triangle(sGfxCursor++, 6, 7, 8, 0); + gSP1Triangle(sGfxCursor++, 9, 10, 11, 0); + gSP1Triangle(sGfxCursor++, 12, 13, 14, 0); + } ++ for (i = 0; i < sBubbleParticleMaxCount; i++) { ++ sPrevBubblePositions[i][0] = gEnvFxBuffer[i].xPos; ++ sPrevBubblePositions[i][1] = gEnvFxBuffer[i].yPos; ++ sPrevBubblePositions[i][2] = gEnvFxBuffer[i].zPos; ++ } + + gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006AB0); + gSPEndDisplayList(sGfxCursor++); +diff --git a/src/game/envfx_snow.c b/src/game/envfx_snow.c +index c3c14a5..d2212ef 100644 +--- a/src/game/envfx_snow.c ++++ b/src/game/envfx_snow.c +@@ -54,6 +54,26 @@ extern void *tiny_bubble_dl_0B006AB0; + extern void *tiny_bubble_dl_0B006A50; + extern void *tiny_bubble_dl_0B006CD8; + ++static struct { ++ Gfx *pos; ++ Vtx vertices[15]; ++} sPrevSnowVertices[140 / 5]; ++static s16 sPrevSnowParticleCount; ++static u32 sPrevSnowTimestamp; ++ ++void patch_interpolated_snow_particles(void) { ++ int i; ++ ++ if (gGlobalTimer != sPrevSnowTimestamp + 1) { ++ return; ++ } ++ ++ for (i = 0; i < sPrevSnowParticleCount; i += 5) { ++ gSPVertex(sPrevSnowVertices[i / 5].pos, ++ VIRTUAL_TO_PHYSICAL(sPrevSnowVertices[i / 5].vertices), 15, 0); ++ } ++} ++ + /** + * Initialize snow particles by allocating a buffer for storing their state + * and setting a start amount. +@@ -217,6 +237,7 @@ void envfx_update_snow_normal(s32 snowCylinderX, s32 snowCylinderY, s32 snowCyli + 400.0f * random_float() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2); + (gEnvFxBuffer + i)->yPos = 200.0f * random_float() + snowCylinderY; + (gEnvFxBuffer + i)->isAlive = 1; ++ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; + } else { + (gEnvFxBuffer + i)->xPos += random_float() * 2 - 1.0f + (s16)(deltaX / 1.2); + (gEnvFxBuffer + i)->yPos -= 2 -(s16)(deltaY * 0.8); +@@ -251,6 +272,7 @@ void envfx_update_snow_blizzard(s32 snowCylinderX, s32 snowCylinderY, s32 snowCy + 400.0f * random_float() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2); + (gEnvFxBuffer + i)->yPos = 400.0f * random_float() - 200.0f + snowCylinderY; + (gEnvFxBuffer + i)->isAlive = 1; ++ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; + } else { + (gEnvFxBuffer + i)->xPos += random_float() * 2 - 1.0f + (s16)(deltaX / 1.2) + 20.0f; + (gEnvFxBuffer + i)->yPos -= 5 -(s16)(deltaY * 0.8); +@@ -294,6 +316,7 @@ void envfx_update_snow_water(s32 snowCylinderX, s32 snowCylinderY, s32 snowCylin + (gEnvFxBuffer + i)->zPos = 400.0f * random_float() - 200.0f + snowCylinderZ; + (gEnvFxBuffer + i)->yPos = 400.0f * random_float() - 200.0f + snowCylinderY; + (gEnvFxBuffer + i)->isAlive = 1; ++ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; + } + } + } +@@ -346,6 +369,8 @@ void rotate_triangle_vertices(Vec3s vertex1, Vec3s vertex2, Vec3s vertex3, s16 p + void append_snowflake_vertex_buffer(Gfx *gfx, s32 index, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3) { + s32 i = 0; + Vtx *vertBuf = (Vtx *) alloc_display_list(15 * sizeof(Vtx)); ++ Vtx *vertBufInterpolated = (Vtx *) alloc_display_list(15 * sizeof(Vtx)); ++ Vtx *v; + #ifdef VERSION_EU + Vtx *p; + #endif +@@ -395,7 +420,23 @@ void append_snowflake_vertex_buffer(Gfx *gfx, s32 index, Vec3s vertex1, Vec3s ve + #endif + } + +- gSPVertex(gfx, VIRTUAL_TO_PHYSICAL(vertBuf), 15, 0); ++ for (i = 0; i < 15; i++) { ++ v = &sPrevSnowVertices[index / 5].vertices[i]; ++ vertBufInterpolated[i] = gSnowTempVtx[i % 3]; ++ if (index < sPrevSnowParticleCount && gGlobalTimer == sPrevSnowTimestamp + 1 && ++ gGlobalTimer != gEnvFxBuffer[index + i / 3].spawnTimestamp) { ++ vertBufInterpolated[i].v.ob[0] = (v->v.ob[0] + vertBuf[i].v.ob[0]) / 2; ++ vertBufInterpolated[i].v.ob[1] = (v->v.ob[1] + vertBuf[i].v.ob[1]) / 2; ++ vertBufInterpolated[i].v.ob[2] = (v->v.ob[2] + vertBuf[i].v.ob[2]) / 2; ++ } else { ++ vertBufInterpolated[i].v.ob[0] = vertBuf[i].v.ob[0]; ++ vertBufInterpolated[i].v.ob[1] = vertBuf[i].v.ob[1]; ++ vertBufInterpolated[i].v.ob[2] = vertBuf[i].v.ob[2]; ++ } ++ *v = vertBuf[i]; ++ } ++ sPrevSnowVertices[index / 5].pos = gfx; ++ gSPVertex(gfx, VIRTUAL_TO_PHYSICAL(vertBufInterpolated), 15, 0); + } + + /** +@@ -479,6 +520,8 @@ Gfx *envfx_update_snow(s32 snowMode, Vec3s marioPos, Vec3s camFrom, Vec3s camTo) + gSP1Triangle(gfx++, 9, 10, 11, 0); + gSP1Triangle(gfx++, 12, 13, 14, 0); + } ++ sPrevSnowParticleCount = gSnowParticleCount; ++ sPrevSnowTimestamp = gGlobalTimer; + + gSPDisplayList(gfx++, &tiny_bubble_dl_0B006AB0) gSPEndDisplayList(gfx++); + +diff --git a/src/game/envfx_snow.h b/src/game/envfx_snow.h +index 7a83b53..f4acc2d 100644 +--- a/src/game/envfx_snow.h ++++ b/src/game/envfx_snow.h +@@ -25,7 +25,8 @@ struct EnvFxParticle { + s32 angleAndDist[2]; // for whirpools, [0] = angle from center, [1] = distance from center + s32 unusedBubbleVar; // set to zero for bubbles when respawning, never used elsewhere + s32 bubbleY; // for Bubbles, yPos is always set to this +- s8 filler20[56 - 0x20]; ++ //s8 filler20[56 - 0x20]; ++ u32 spawnTimestamp; + }; + + extern s8 gEnvFxMode; +diff --git a/src/game/hud.c b/src/game/hud.c +index 1540b67..0de6e0b 100644 +--- a/src/game/hud.c ++++ b/src/game/hud.c +@@ -59,6 +59,20 @@ static struct UnusedHUDStruct sUnusedHUDValues = { 0x00, 0x0A, 0x00 }; + + static struct CameraHUD sCameraHUD = { CAM_STATUS_NONE }; + ++static u32 sPowerMeterLastRenderTimestamp; ++static s16 sPowerMeterLastY; ++static Gfx *sPowerMeterDisplayListPos; ++ ++void patch_interpolated_hud(void) { ++ if (sPowerMeterDisplayListPos != NULL) { ++ Mtx *mtx = alloc_display_list(sizeof(Mtx)); ++ guTranslate(mtx, (f32) sPowerMeterHUD.x, (f32) sPowerMeterHUD.y, 0); ++ gSPMatrix(sPowerMeterDisplayListPos, VIRTUAL_TO_PHYSICAL(mtx), ++ G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); ++ sPowerMeterDisplayListPos = NULL; ++ } ++} ++ + /** + * Renders a rgba16 16x16 glyph texture from a table list. + */ +@@ -111,6 +125,7 @@ void render_power_meter_health_segment(s16 numHealthWedges) { + */ + void render_dl_power_meter(s16 numHealthWedges) { + Mtx *mtx; ++ f32 interpolatedY; + + mtx = alloc_display_list(sizeof(Mtx)); + +@@ -118,7 +133,15 @@ void render_dl_power_meter(s16 numHealthWedges) { + return; + } + +- guTranslate(mtx, (f32) sPowerMeterHUD.x, (f32) sPowerMeterHUD.y, 0); ++ if (gGlobalTimer == sPowerMeterLastRenderTimestamp + 1) { ++ interpolatedY = (sPowerMeterLastY + sPowerMeterHUD.y) / 2.0f; ++ } else { ++ interpolatedY = sPowerMeterHUD.y; ++ } ++ guTranslate(mtx, (f32) sPowerMeterHUD.x, interpolatedY, 0); ++ sPowerMeterLastY = sPowerMeterHUD.y; ++ sPowerMeterLastRenderTimestamp = gGlobalTimer; ++ sPowerMeterDisplayListPos = gDisplayListHead; + + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx++), + G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); +diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c +index 7ae9f1e..1c23c96 100644 +--- a/src/game/ingame_menu.c ++++ b/src/game/ingame_menu.c +@@ -130,6 +130,42 @@ s32 gDialogResponse = 0; + static struct CachedChar { u8 used; u8 data[CHCACHE_BUFLEN]; } charCache[256]; + #endif // VERSION + ++static Gfx *sInterpolatedDialogOffsetPos; ++static f32 sInterpolatedDialogOffset; ++static Gfx *sInterpolatedDialogRotationPos; ++static f32 sInterpolatedDialogScale; ++static f32 sInterpolatedDialogRotation; ++static Gfx *sInterpolatedDialogZoomPos; ++ ++void patch_interpolated_dialog(void) { ++ Mtx *matrix; ++ ++ if (sInterpolatedDialogOffsetPos != NULL) { ++ matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); ++ guTranslate(matrix, 0, sInterpolatedDialogOffset, 0); ++ gSPMatrix(sInterpolatedDialogOffsetPos, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); ++ sInterpolatedDialogOffsetPos = NULL; ++ } ++ if (sInterpolatedDialogRotationPos != NULL) { ++ matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); ++ guScale(matrix, 1.0 / sInterpolatedDialogScale, 1.0 / sInterpolatedDialogScale, 1.0f); ++ gSPMatrix(sInterpolatedDialogRotationPos++, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); ++ matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); ++ guRotate(matrix, sInterpolatedDialogRotation * 4.0f, 0, 0, 1.0f); ++ gSPMatrix(sInterpolatedDialogRotationPos, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); ++ sInterpolatedDialogRotationPos = NULL; ++ } ++ if (sInterpolatedDialogZoomPos != NULL) { ++ matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); ++ guTranslate(matrix, 65.0 - (65.0 / sInterpolatedDialogScale), (40.0 / sInterpolatedDialogScale) - 40, 0); ++ gSPMatrix(sInterpolatedDialogZoomPos++, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); ++ matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); ++ guScale(matrix, 1.0 / sInterpolatedDialogScale, 1.0 / sInterpolatedDialogScale, 1.0f); ++ gSPMatrix(sInterpolatedDialogZoomPos, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); ++ sInterpolatedDialogZoomPos = NULL; ++ } ++} ++ + void create_dl_identity_matrix(void) { + Mtx *matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + +@@ -969,6 +1005,14 @@ void render_dialog_box_type(struct DialogEntry *dialog, s8 linesPerBox) { + switch (gDialogBoxType) { + case DIALOG_TYPE_ROTATE: // Renders a dialog black box with zoom and rotation + if (gDialogBoxState == DIALOG_STATE_OPENING || gDialogBoxState == DIALOG_STATE_CLOSING) { ++ sInterpolatedDialogRotationPos = gDisplayListHead; ++ if (gDialogBoxState == DIALOG_STATE_OPENING) { ++ sInterpolatedDialogScale = gDialogBoxScale - 2 / 2; ++ sInterpolatedDialogRotation = gDialogBoxOpenTimer - 7.5f / 2; ++ } else { ++ sInterpolatedDialogScale = gDialogBoxScale + 2 / 2; ++ sInterpolatedDialogRotation = gDialogBoxOpenTimer + 7.5f / 2; ++ } + create_dl_scale_matrix(MENU_MTX_NOPUSH, 1.0 / gDialogBoxScale, 1.0 / gDialogBoxScale, 1.0f); + // convert the speed into angle + create_dl_rotation_matrix(MENU_MTX_NOPUSH, gDialogBoxOpenTimer * 4.0f, 0, 0, 1.0f); +@@ -977,6 +1021,12 @@ void render_dialog_box_type(struct DialogEntry *dialog, s8 linesPerBox) { + break; + case DIALOG_TYPE_ZOOM: // Renders a dialog white box with zoom + if (gDialogBoxState == DIALOG_STATE_OPENING || gDialogBoxState == DIALOG_STATE_CLOSING) { ++ sInterpolatedDialogZoomPos = gDisplayListHead; ++ if (gDialogBoxState == DIALOG_STATE_OPENING) { ++ sInterpolatedDialogScale = gDialogBoxScale - 2 / 2; ++ } else { ++ sInterpolatedDialogScale = gDialogBoxScale + 2 / 2; ++ } + create_dl_translation_matrix(MENU_MTX_NOPUSH, 65.0 - (65.0 / gDialogBoxScale), + (40.0 / gDialogBoxScale) - 40, 0); + create_dl_scale_matrix(MENU_MTX_NOPUSH, 1.0 / gDialogBoxScale, 1.0 / gDialogBoxScale, 1.0f); +@@ -1259,6 +1309,8 @@ void handle_dialog_text_and_pages(s8 colorMode, struct DialogEntry *dialog, s8 l + #ifdef VERSION_EU + gDialogY -= gDialogScrollOffsetY; + #else ++ sInterpolatedDialogOffset = gDialogScrollOffsetY + dialog->linesPerBox; ++ sInterpolatedDialogOffsetPos = gDisplayListHead; + create_dl_translation_matrix(MENU_MTX_NOPUSH, 0, (f32) gDialogScrollOffsetY, 0); + #endif + } +diff --git a/src/game/level_geo.c b/src/game/level_geo.c +index 4c98e70..abc5121 100644 +--- a/src/game/level_geo.c ++++ b/src/game/level_geo.c +@@ -34,12 +34,16 @@ Gfx *geo_envfx_main(s32 callContext, struct GraphNode *node, Mat4 mtxf) { + vec3f_to_vec3s(marioPos, gPlayerCameraState->pos); + particleList = envfx_update_particles(snowMode, marioPos, camTo, camFrom); + if (particleList != NULL) { ++#if 0 + Mtx *mtx = alloc_display_list(sizeof(*mtx)); + + gfx = alloc_display_list(2 * sizeof(*gfx)); + mtxf_to_mtx(mtx, mtxf); + gSPMatrix(&gfx[0], VIRTUAL_TO_PHYSICAL(mtx), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); + gSPBranchList(&gfx[1], VIRTUAL_TO_PHYSICAL(particleList)); ++#else ++ gfx = particleList; ++#endif + execNode->fnNode.node.flags = (execNode->fnNode.node.flags & 0xFF) | 0x400; + } + SET_HIGH_U16_OF_32(*params, gAreaUpdateCounter); +diff --git a/src/game/object_helpers.c b/src/game/object_helpers.c +index 22b45b3..109d7f7 100644 +--- a/src/game/object_helpers.c ++++ b/src/game/object_helpers.c +@@ -1554,6 +1554,7 @@ void cur_obj_set_pos_to_home(void) { + o->oPosX = o->oHomeX; + o->oPosY = o->oHomeY; + o->oPosZ = o->oHomeZ; ++ o->header.gfx.skipInterpolationTimestamp = gGlobalTimer; + } + + void cur_obj_set_pos_to_home_and_stop(void) { +diff --git a/src/game/paintings.c b/src/game/paintings.c +index 6cae19c..a304d4a 100644 +--- a/src/game/paintings.c ++++ b/src/game/paintings.c +@@ -189,6 +189,32 @@ struct Painting **sPaintingGroups[] = { + s16 gPaintingUpdateCounter = 1; + s16 gLastPaintingUpdateCounter = 0; + ++static Vtx sLastVertices[2 * 264 * 3]; ++static u32 sLastVerticesTimestamp; ++static Vtx *sVerticesPtr[2]; ++static s32 sVerticesCount; ++ ++void patch_interpolated_paintings(void) { ++ if (sVerticesPtr[0] != NULL) { ++ s32 i; ++ if (sVerticesPtr[1] != NULL) { ++ for (i = 0; i < sVerticesCount / 2; i++) { ++ sVerticesPtr[0][i] = sLastVertices[i]; ++ } ++ for (; i < sVerticesCount; i++) { ++ sVerticesPtr[1][i - sVerticesCount / 2] = sLastVertices[i]; ++ } ++ } else { ++ for (i = 0; i < sVerticesCount; i++) { ++ sVerticesPtr[0][i] = sLastVertices[i]; ++ } ++ } ++ sVerticesPtr[0] = NULL; ++ sVerticesPtr[1] = NULL; ++ sVerticesCount = 0; ++ } ++} ++ + /** + * Stop paintings in paintingGroup from rippling if their id is different from *idptr. + */ +@@ -890,6 +916,23 @@ Gfx *render_painting(u8 *img, s16 tWidth, s16 tHeight, s16 *textureMap, s16 mapV + gSP1Triangle(gfx++, group * 3, group * 3 + 1, group * 3 + 2, 0); + } + ++ if (sVerticesCount >= numVtx * 2) { ++ sVerticesCount = 0; ++ } ++ for (map = 0; map < numVtx; map++) { ++ Vtx v = verts[map]; ++ if (gGlobalTimer == sLastVerticesTimestamp + 1) { ++ s32 i; ++ for (i = 0; i < 3; i++) { ++ verts[map].n.ob[i] = (v.n.ob[i] + sLastVertices[sVerticesCount + map].n.ob[i]) / 2; ++ verts[map].n.n[i] = (v.n.n[i] + sLastVertices[sVerticesCount + map].n.n[i]) / 2; ++ } ++ } ++ sLastVertices[sVerticesCount + map] = v; ++ } ++ sVerticesPtr[sVerticesCount / numVtx] = verts; ++ sVerticesCount += numVtx; ++ + gSPEndDisplayList(gfx); + return dlist; + } +@@ -954,6 +997,7 @@ Gfx *painting_ripple_image(struct Painting *painting) { + meshTris = textureMap[meshVerts * 3 + 1]; + gSPDisplayList(gfx++, render_painting(textures[i], tWidth, tHeight, textureMap, meshVerts, meshTris, painting->alpha)); + } ++ sLastVerticesTimestamp = gGlobalTimer; + + // Update the ripple, may automatically reset the painting's state. + painting_update_ripple_state(painting); +@@ -991,6 +1035,7 @@ Gfx *painting_ripple_env_mapped(struct Painting *painting) { + meshVerts = textureMap[0]; + meshTris = textureMap[meshVerts * 3 + 1]; + gSPDisplayList(gfx++, render_painting(tArray[0], tWidth, tHeight, textureMap, meshVerts, meshTris, painting->alpha)); ++ sLastVerticesTimestamp = gGlobalTimer; + + // Update the ripple, may automatically reset the painting's state. + painting_update_ripple_state(painting); +diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c +index d5bf577..71656b4 100644 +--- a/src/game/rendering_graph_node.c ++++ b/src/game/rendering_graph_node.c +@@ -39,6 +39,8 @@ + s16 gMatStackIndex; + Mat4 gMatStack[32]; + Mtx *gMatStackFixed[32]; ++Mat4 gMatStackInterpolated[32]; ++Mtx *gMatStackInterpolatedFixed[32]; + + /** + * Animation nodes have state in global variables, so this struct captures +@@ -52,6 +54,7 @@ struct GeoAnimState { + /*0x04*/ f32 translationMultiplier; + /*0x08*/ u16 *attribute; + /*0x0C*/ s16 *data; ++ s16 prevFrame; + }; + + // For some reason, this is a GeoAnimState struct, but the current state consists +@@ -61,6 +64,7 @@ struct GeoAnimState gGeoTempState; + u8 gCurAnimType; + u8 gCurAnimEnabled; + s16 gCurrAnimFrame; ++s16 gPrevAnimFrame; + f32 gCurAnimTranslationMultiplier; + u16 *gCurrAnimAttribute; + s16 *gCurAnimData; +@@ -129,6 +133,46 @@ u16 gAreaUpdateCounter = 0; + LookAt lookAt; + #endif + ++static Gfx *sPerspectivePos; ++static Mtx *sPerspectiveMtx; ++ ++struct { ++ Gfx *pos; ++ void *mtx; ++ void *displayList; ++} gMtxTbl[6400]; ++s32 gMtxTblSize; ++ ++static Gfx *sViewportPos; ++static Vp sPrevViewport; ++ ++void mtx_patch_interpolated(void) { ++ s32 i; ++ ++ if (sPerspectivePos != NULL) { ++ gSPMatrix(sPerspectivePos, VIRTUAL_TO_PHYSICAL(sPerspectiveMtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); ++ } ++ ++ for (i = 0; i < gMtxTblSize; i++) { ++ Gfx *pos = gMtxTbl[i].pos; ++ gSPMatrix(pos++, VIRTUAL_TO_PHYSICAL(gMtxTbl[i].mtx), ++ G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); ++ gSPDisplayList(pos++, gMtxTbl[i].displayList); ++ } ++ ++ if (sViewportPos != NULL) { ++ Gfx *saved = gDisplayListHead; ++ gDisplayListHead = sViewportPos; ++ make_viewport_clip_rect(&sPrevViewport); ++ gSPViewport(gDisplayListHead, VIRTUAL_TO_PHYSICAL(&sPrevViewport)); ++ gDisplayListHead = saved; ++ } ++ ++ gMtxTblSize = 0; ++ sPerspectivePos = NULL; ++ sViewportPos = NULL; ++} ++ + /** + * Process a master list node. + */ +@@ -156,9 +200,14 @@ static void geo_process_master_list_sub(struct GraphNodeMasterList *node) { + if ((currList = node->listHeads[i]) != NULL) { + gDPSetRenderMode(gDisplayListHead++, modeList->modes[i], mode2List->modes[i]); + while (currList != NULL) { +- gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(currList->transform), ++ if ((u32) gMtxTblSize < sizeof(gMtxTbl) / sizeof(gMtxTbl[0])) { ++ gMtxTbl[gMtxTblSize].pos = gDisplayListHead; ++ gMtxTbl[gMtxTblSize].mtx = currList->transform; ++ gMtxTbl[gMtxTblSize++].displayList = currList->displayList; ++ } ++ gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(currList->transformInterpolated), + G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); +- gSPDisplayList(gDisplayListHead++, currList->displayList); ++ gSPDisplayList(gDisplayListHead++, currList->displayListInterpolated); + currList = currList->next; + } + } +@@ -174,7 +223,7 @@ static void geo_process_master_list_sub(struct GraphNodeMasterList *node) { + * parameter. Look at the RenderModeContainer struct to see the corresponding + * render modes of layers. + */ +-static void geo_append_display_list(void *displayList, s16 layer) { ++static void geo_append_display_list2(void *displayList, void *displayListInterpolated, s16 layer) { + + #ifdef F3DEX_GBI_2 + gSPLookAt(gDisplayListHead++, &lookAt); +@@ -184,7 +233,9 @@ static void geo_append_display_list(void *displayList, s16 layer) { + alloc_only_pool_alloc(gDisplayListHeap, sizeof(struct DisplayListNode)); + + listNode->transform = gMatStackFixed[gMatStackIndex]; ++ listNode->transformInterpolated = gMatStackInterpolatedFixed[gMatStackIndex]; + listNode->displayList = displayList; ++ listNode->displayListInterpolated = displayListInterpolated; + listNode->next = 0; + if (gCurGraphNodeMasterList->listHeads[layer] == 0) { + gCurGraphNodeMasterList->listHeads[layer] = listNode; +@@ -195,6 +246,10 @@ static void geo_append_display_list(void *displayList, s16 layer) { + } + } + ++static void geo_append_display_list(void *displayList, s16 layer) { ++ geo_append_display_list2(displayList, displayList, layer); ++} ++ + /** + * Process the master list node. + */ +@@ -241,7 +296,9 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { + } + if (node->fnNode.node.children != NULL) { + u16 perspNorm; ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ f32 fovInterpolated; + + #ifdef VERSION_EU + f32 aspect = ((f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height) * 1.1f; +@@ -250,9 +307,23 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { + #endif + + guPerspective(mtx, &perspNorm, node->fov, aspect, node->near, node->far, 1.0f); +- gSPPerspNormalize(gDisplayListHead++, perspNorm); + +- gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); ++ if (gGlobalTimer == node->prevTimestamp + 1 && gGlobalTimer != gLakituState.skipCameraInterpolationTimestamp) { ++ ++ fovInterpolated = (node->prevFov + node->fov) / 2.0f; ++ guPerspective(mtxInterpolated, &perspNorm, fovInterpolated, aspect, node->near, node->far, 1.0f); ++ gSPPerspNormalize(gDisplayListHead++, perspNorm); ++ ++ sPerspectivePos = gDisplayListHead; ++ sPerspectiveMtx = mtx; ++ gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtxInterpolated), ++ G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); ++ } else { ++ gSPPerspNormalize(gDisplayListHead++, perspNorm); ++ gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); ++ } ++ node->prevFov = node->fov; ++ node->prevTimestamp = gGlobalTimer; + + gCurGraphNodeCamFrustum = node; + geo_process_node_and_siblings(node->fnNode.node.children); +@@ -297,6 +368,39 @@ static void geo_process_switch(struct GraphNodeSwitchCase *node) { + } + } + ++void interpolate_vectors(Vec3f res, Vec3f a, Vec3f b) { ++ res[0] = (a[0] + b[0]) / 2.0f; ++ res[1] = (a[1] + b[1]) / 2.0f; ++ res[2] = (a[2] + b[2]) / 2.0f; ++} ++ ++void interpolate_vectors_s16(Vec3s res, Vec3s a, Vec3s b) { ++ res[0] = (a[0] + b[0]) / 2; ++ res[1] = (a[1] + b[1]) / 2; ++ res[2] = (a[2] + b[2]) / 2; ++} ++ ++static s16 interpolate_angle(s16 a, s16 b) { ++ s32 absDiff = b - a; ++ if (absDiff < 0) { ++ absDiff = -absDiff; ++ } ++ if (absDiff >= 0x4000 && absDiff <= 0xC000) { ++ return b; ++ } ++ if (absDiff <= 0x8000) { ++ return (a + b) / 2; ++ } else { ++ return (a + b) / 2 + 0x8000; ++ } ++} ++ ++static void interpolate_angles(Vec3s res, Vec3s a, Vec3s b) { ++ res[0] = interpolate_angle(a[0], b[0]); ++ res[1] = interpolate_angle(a[1], b[1]); ++ res[2] = interpolate_angle(a[2], b[2]); ++} ++ + /** + * Process a camera node. + */ +@@ -304,6 +408,9 @@ static void geo_process_camera(struct GraphNodeCamera *node) { + Mat4 cameraTransform; + Mtx *rollMtx = alloc_display_list(sizeof(*rollMtx)); + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); ++ Vec3f posInterpolated; ++ Vec3f focusInterpolated; + + if (node->fnNode.func != NULL) { + node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); +@@ -314,12 +421,40 @@ static void geo_process_camera(struct GraphNodeCamera *node) { + + mtxf_lookat(cameraTransform, node->pos, node->focus, node->roll); + mtxf_mul(gMatStack[gMatStackIndex + 1], cameraTransform, gMatStack[gMatStackIndex]); ++ ++ if (gGlobalTimer == node->prevTimestamp + 1 && gGlobalTimer != gLakituState.skipCameraInterpolationTimestamp) { ++ interpolate_vectors(posInterpolated, node->prevPos, node->pos); ++ interpolate_vectors(focusInterpolated, node->prevFocus, node->focus); ++ float magnitude = 0; ++ for (int i = 0; i < 3; i++) { ++ float diff = node->pos[i] - node->prevPos[i]; ++ magnitude += diff * diff; ++ } ++ if (magnitude > 500000) { ++ // Observed ~479000 in BBH when toggling R camera ++ // Can get over 3 million in VCUTM though... ++ vec3f_copy(posInterpolated, node->pos); ++ vec3f_copy(focusInterpolated, node->focus); ++ } ++ } else { ++ vec3f_copy(posInterpolated, node->pos); ++ vec3f_copy(focusInterpolated, node->focus); ++ } ++ vec3f_copy(node->prevPos, node->pos); ++ vec3f_copy(node->prevFocus, node->focus); ++ node->prevTimestamp = gGlobalTimer; ++ mtxf_lookat(cameraTransform, posInterpolated, focusInterpolated, node->roll); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], cameraTransform, gMatStackInterpolated[gMatStackIndex]); ++ + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->fnNode.node.children != 0) { + gCurGraphNodeCamera = node; + node->matrixPtr = &gMatStack[gMatStackIndex]; ++ node->matrixPtrInterpolated = &gMatStackInterpolated[gMatStackIndex]; + geo_process_node_and_siblings(node->fnNode.node.children); + gCurGraphNodeCamera = NULL; + } +@@ -336,13 +471,17 @@ static void geo_process_translation_rotation(struct GraphNodeTranslationRotation + Mat4 mtxf; + Vec3f translation; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + + vec3s_to_vec3f(translation, node->translation); + mtxf_rotate_zxy_and_translate(mtxf, translation, node->rotation); + mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } +@@ -361,13 +500,17 @@ static void geo_process_translation(struct GraphNodeTranslation *node) { + Mat4 mtxf; + Vec3f translation; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + + vec3s_to_vec3f(translation, node->translation); + mtxf_rotate_zxy_and_translate(mtxf, translation, gVec3sZero); + mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } +@@ -385,12 +528,23 @@ static void geo_process_translation(struct GraphNodeTranslation *node) { + static void geo_process_rotation(struct GraphNodeRotation *node) { + Mat4 mtxf; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); ++ Vec3s rotationInterpolated; + + mtxf_rotate_zxy_and_translate(mtxf, gVec3fZero, node->rotation); + mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); ++ if (gGlobalTimer == node->prevTimestamp + 1) { ++ interpolate_angles(rotationInterpolated, node->prevRotation, node->rotation); ++ mtxf_rotate_zxy_and_translate(mtxf, gVec3fZero, rotationInterpolated); ++ } ++ vec3s_copy(node->prevRotation, node->rotation); ++ node->prevTimestamp = gGlobalTimer; ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } +@@ -409,12 +563,16 @@ static void geo_process_scale(struct GraphNodeScale *node) { + UNUSED Mat4 transform; + Vec3f scaleVec; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + + vec3f_set(scaleVec, node->scale, node->scale, node->scale); + mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], scaleVec); ++ mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex], scaleVec); + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } +@@ -433,21 +591,30 @@ static void geo_process_scale(struct GraphNodeScale *node) { + static void geo_process_billboard(struct GraphNodeBillboard *node) { + Vec3f translation; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + + gMatStackIndex++; + vec3s_to_vec3f(translation, node->translation); + mtxf_billboard(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex - 1], translation, + gCurGraphNodeCamera->roll); ++ mtxf_billboard(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex - 1], translation, ++ gCurGraphNodeCamera->roll); + if (gCurGraphNodeHeldObject != NULL) { + mtxf_scale_vec3f(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex], + gCurGraphNodeHeldObject->objNode->header.gfx.scale); ++ mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex], ++ gCurGraphNodeHeldObject->objNode->header.gfx.scale); + } else if (gCurGraphNodeObject != NULL) { + mtxf_scale_vec3f(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex], + gCurGraphNodeObject->scale); ++ mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex], ++ gCurGraphNodeObject->scale); + } + + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } +@@ -496,13 +663,39 @@ static void geo_process_generated_list(struct GraphNodeGenerated *node) { + */ + static void geo_process_background(struct GraphNodeBackground *node) { + Gfx *list = NULL; ++ Gfx *listInterpolated = NULL; + + if (node->fnNode.func != NULL) { ++ Vec3f posCopy; ++ Vec3f focusCopy; ++ Vec3f posInterpolated; ++ Vec3f focusInterpolated; ++ ++ if (gGlobalTimer == node->prevCameraTimestamp + 1 && ++ gGlobalTimer != gLakituState.skipCameraInterpolationTimestamp) { ++ interpolate_vectors(posInterpolated, node->prevCameraPos, gLakituState.pos); ++ interpolate_vectors(focusInterpolated, node->prevCameraFocus, gLakituState.focus); ++ } else { ++ vec3f_copy(posInterpolated, gLakituState.pos); ++ vec3f_copy(focusInterpolated, gLakituState.focus); ++ } ++ vec3f_copy(node->prevCameraPos, gLakituState.pos); ++ vec3f_copy(node->prevCameraFocus, gLakituState.focus); ++ node->prevCameraTimestamp = gGlobalTimer; ++ + list = node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, + (struct AllocOnlyPool *) gMatStack[gMatStackIndex]); ++ vec3f_copy(posCopy, gLakituState.pos); ++ vec3f_copy(focusCopy, gLakituState.focus); ++ vec3f_copy(gLakituState.pos, posInterpolated); ++ vec3f_copy(gLakituState.focus, focusInterpolated); ++ listInterpolated = node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, NULL); ++ vec3f_copy(gLakituState.pos, posCopy); ++ vec3f_copy(gLakituState.focus, focusCopy); + } + if (list != 0) { +- geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(list), node->fnNode.node.flags >> 8); ++ geo_append_display_list2((void *) VIRTUAL_TO_PHYSICAL(list), ++ (void *) VIRTUAL_TO_PHYSICAL(listInterpolated), node->fnNode.node.flags >> 8); + } else if (gCurGraphNodeMasterList != NULL) { + #ifndef F3DEX_GBI_2E + Gfx *gfxStart = alloc_display_list(sizeof(Gfx) * 7); +@@ -527,61 +720,81 @@ static void geo_process_background(struct GraphNodeBackground *node) { + } + } + +-/** +- * Render an animated part. The current animation state is not part of the node +- * but set in global variables. If an animated part is skipped, everything afterwards desyncs. +- */ +-static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) { +- Mat4 matrix; +- Vec3s rotation; +- Vec3f translation; +- Mtx *matrixPtr = alloc_display_list(sizeof(*matrixPtr)); +- +- vec3s_copy(rotation, gVec3sZero); +- vec3f_set(translation, node->translation[0], node->translation[1], node->translation[2]); +- if (gCurAnimType == ANIM_TYPE_TRANSLATION) { +- translation[0] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] ++static void anim_process(Vec3f translation, Vec3s rotation, u8 *animType, s16 animFrame, u16 **animAttribute) { ++ if (*animType == ANIM_TYPE_TRANSLATION) { ++ translation[0] += gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurAnimTranslationMultiplier; +- translation[1] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] ++ translation[1] += gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurAnimTranslationMultiplier; +- translation[2] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] ++ translation[2] += gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurAnimTranslationMultiplier; +- gCurAnimType = ANIM_TYPE_ROTATION; ++ *animType = ANIM_TYPE_ROTATION; + } else { +- if (gCurAnimType == ANIM_TYPE_LATERAL_TRANSLATION) { ++ if (*animType == ANIM_TYPE_LATERAL_TRANSLATION) { + translation[0] += +- gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] ++ gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurAnimTranslationMultiplier; +- gCurrAnimAttribute += 2; ++ *animAttribute += 2; + translation[2] += +- gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] ++ gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurAnimTranslationMultiplier; +- gCurAnimType = ANIM_TYPE_ROTATION; ++ *animType = ANIM_TYPE_ROTATION; + } else { +- if (gCurAnimType == ANIM_TYPE_VERTICAL_TRANSLATION) { +- gCurrAnimAttribute += 2; ++ if (*animType == ANIM_TYPE_VERTICAL_TRANSLATION) { ++ *animAttribute += 2; + translation[1] += +- gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] ++ gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurAnimTranslationMultiplier; +- gCurrAnimAttribute += 2; +- gCurAnimType = ANIM_TYPE_ROTATION; +- } else if (gCurAnimType == ANIM_TYPE_NO_TRANSLATION) { +- gCurrAnimAttribute += 6; +- gCurAnimType = ANIM_TYPE_ROTATION; ++ *animAttribute += 2; ++ *animType = ANIM_TYPE_ROTATION; ++ } else if (*animType == ANIM_TYPE_NO_TRANSLATION) { ++ *animAttribute += 6; ++ *animType = ANIM_TYPE_ROTATION; + } + } + } + +- if (gCurAnimType == ANIM_TYPE_ROTATION) { +- rotation[0] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; +- rotation[1] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; +- rotation[2] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; ++ if (*animType == ANIM_TYPE_ROTATION) { ++ rotation[0] = gCurAnimData[retrieve_animation_index(animFrame, animAttribute)]; ++ rotation[1] = gCurAnimData[retrieve_animation_index(animFrame, animAttribute)]; ++ rotation[2] = gCurAnimData[retrieve_animation_index(animFrame, animAttribute)]; + } ++} ++ ++/** ++ * Render an animated part. The current animation state is not part of the node ++ * but set in global variables. If an animated part is skipped, everything afterwards desyncs. ++ */ ++static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) { ++ Mat4 matrix; ++ Vec3s rotation; ++ Vec3f translation; ++ Vec3s rotationInterpolated; ++ Vec3f translationInterpolated; ++ Mtx *matrixPtr = alloc_display_list(sizeof(*matrixPtr)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); ++ u16 *animAttribute = gCurrAnimAttribute; ++ u8 animType = gCurAnimType; ++ ++ vec3s_copy(rotation, gVec3sZero); ++ vec3f_set(translation, node->translation[0], node->translation[1], node->translation[2]); ++ vec3s_copy(rotationInterpolated, rotation); ++ vec3f_copy(translationInterpolated, translation); ++ ++ anim_process(translationInterpolated, rotationInterpolated, &animType, gPrevAnimFrame, &animAttribute); ++ anim_process(translation, rotation, &gCurAnimType, gCurrAnimFrame, &gCurrAnimAttribute); ++ interpolate_vectors(translationInterpolated, translationInterpolated, translation); ++ interpolate_angles(rotationInterpolated, rotationInterpolated, rotation); ++ + mtxf_rotate_xyz_and_translate(matrix, translation, rotation); + mtxf_mul(gMatStack[gMatStackIndex + 1], matrix, gMatStack[gMatStackIndex]); ++ mtxf_rotate_xyz_and_translate(matrix, translationInterpolated, rotationInterpolated); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], matrix, gMatStackInterpolated[gMatStackIndex]); + gMatStackIndex++; + mtxf_to_mtx(matrixPtr, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = matrixPtr; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } +@@ -613,6 +826,17 @@ void geo_set_animation_globals(struct GraphNodeObject_sub *node, s32 hasAnimatio + } + + gCurrAnimFrame = node->animFrame; ++ if (node->prevAnimPtr == anim && node->prevAnimID == node->animID && ++ gGlobalTimer == node->prevAnimFrameTimestamp + 1) { ++ gPrevAnimFrame = node->prevAnimFrame; ++ } else { ++ gPrevAnimFrame = node->animFrame; ++ } ++ node->prevAnimPtr = anim; ++ node->prevAnimID = node->animID; ++ node->prevAnimFrame = node->animFrame; ++ node->prevAnimFrameTimestamp = gGlobalTimer; ++ + gCurAnimEnabled = (anim->flags & ANIM_FLAG_5) == 0; + gCurrAnimAttribute = segmented_to_virtual((void *) anim->index); + gCurAnimData = segmented_to_virtual((void *) anim->values); +@@ -631,8 +855,10 @@ void geo_set_animation_globals(struct GraphNodeObject_sub *node, s32 hasAnimatio + */ + static void geo_process_shadow(struct GraphNodeShadow *node) { + Gfx *shadowList; ++ Gfx *shadowListInterpolated; + Mat4 mtxf; + Vec3f shadowPos; ++ Vec3f shadowPosInterpolated; + Vec3f animOffset; + f32 objScale; + f32 shadowScale; +@@ -640,6 +866,7 @@ static void geo_process_shadow(struct GraphNodeShadow *node) { + f32 cosAng; + struct GraphNode *geo; + Mtx *mtx; ++ Mtx *mtxInterpolated; + + if (gCurGraphNodeCamera != NULL && gCurGraphNodeObject != NULL) { + if (gCurGraphNodeHeldObject != NULL) { +@@ -678,21 +905,57 @@ static void geo_process_shadow(struct GraphNodeShadow *node) { + } + } + ++ if (gCurGraphNodeHeldObject != NULL) { ++ if (gGlobalTimer == gCurGraphNodeHeldObject->prevShadowPosTimestamp + 1) { ++ interpolate_vectors(shadowPosInterpolated, gCurGraphNodeHeldObject->prevShadowPos, shadowPos); ++ } else { ++ vec3f_copy(shadowPosInterpolated, shadowPos); ++ } ++ vec3f_copy(gCurGraphNodeHeldObject->prevShadowPos, shadowPos); ++ gCurGraphNodeHeldObject->prevShadowPosTimestamp = gGlobalTimer; ++ } else { ++ if (gGlobalTimer == gCurGraphNodeObject->prevShadowPosTimestamp + 1 && ++ gGlobalTimer != gCurGraphNodeObject->skipInterpolationTimestamp) { ++ interpolate_vectors(shadowPosInterpolated, gCurGraphNodeObject->prevShadowPos, shadowPos); ++ } else { ++ vec3f_copy(shadowPosInterpolated, shadowPos); ++ } ++ vec3f_copy(gCurGraphNodeObject->prevShadowPos, shadowPos); ++ gCurGraphNodeObject->prevShadowPosTimestamp = gGlobalTimer; ++ } ++ ++ extern u8 gInterpolatingSurfaces; ++ gInterpolatingSurfaces = TRUE; ++ shadowListInterpolated = create_shadow_below_xyz(shadowPosInterpolated[0], shadowPosInterpolated[1], ++ shadowPosInterpolated[2], shadowScale, ++ node->shadowSolidity, node->shadowType); ++ gInterpolatingSurfaces = FALSE; + shadowList = create_shadow_below_xyz(shadowPos[0], shadowPos[1], shadowPos[2], shadowScale, + node->shadowSolidity, node->shadowType); +- if (shadowList != NULL) { ++ if (shadowListInterpolated != NULL && shadowList != NULL) { + mtx = alloc_display_list(sizeof(*mtx)); ++ mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + gMatStackIndex++; ++ + mtxf_translate(mtxf, shadowPos); + mtxf_mul(gMatStack[gMatStackIndex], mtxf, *gCurGraphNodeCamera->matrixPtr); + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ ++ mtxf_translate(mtxf, shadowPosInterpolated); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex], mtxf, *gCurGraphNodeCamera->matrixPtrInterpolated); ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; ++ + if (gShadowAboveWaterOrLava == 1) { +- geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 4); ++ geo_append_display_list2((void *) VIRTUAL_TO_PHYSICAL(shadowList), ++ (void *) VIRTUAL_TO_PHYSICAL(shadowListInterpolated), 4); + } else if (gMarioOnIceOrCarpet == 1) { +- geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 5); ++ geo_append_display_list2((void *) VIRTUAL_TO_PHYSICAL(shadowList), ++ (void *) VIRTUAL_TO_PHYSICAL(shadowListInterpolated), 5); + } else { +- geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 6); ++ geo_append_display_list2((void *) VIRTUAL_TO_PHYSICAL(shadowList), ++ (void *) VIRTUAL_TO_PHYSICAL(shadowListInterpolated), 6); + } + gMatStackIndex--; + } +@@ -789,31 +1052,101 @@ static int obj_is_in_view(struct GraphNodeObject *node, Mat4 matrix) { + return TRUE; + } + ++static void interpolate_matrix(Mat4 result, Mat4 a, Mat4 b) { ++ s32 i, j; ++ for (i = 0; i < 4; i++) { ++ for (j = 0; j < 4; j++) { ++ result[i][j] = (a[i][j] + b[i][j]) / 2.0f; ++ } ++ } ++} ++ + /** + * Process an object node. + */ + static void geo_process_object(struct Object *node) { + Mat4 mtxf; + s32 hasAnimation = (node->header.gfx.node.flags & GRAPH_RENDER_HAS_ANIMATION) != 0; ++ Vec3f scaleInterpolated; + + if (node->header.gfx.unk18 == gCurGraphNodeRoot->areaIndex) { + if (node->header.gfx.throwMatrix != NULL) { + mtxf_mul(gMatStack[gMatStackIndex + 1], *node->header.gfx.throwMatrix, + gMatStack[gMatStackIndex]); ++ if (gGlobalTimer == node->header.gfx.prevThrowMatrixTimestamp + 1 && ++ gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { ++ interpolate_matrix(mtxf, *node->header.gfx.throwMatrix, node->header.gfx.prevThrowMatrix); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, ++ gMatStackInterpolated[gMatStackIndex]); ++ } else { ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], (void *) node->header.gfx.throwMatrix, ++ gMatStackInterpolated[gMatStackIndex]); ++ } ++ mtxf_copy(node->header.gfx.prevThrowMatrix, *node->header.gfx.throwMatrix); ++ node->header.gfx.prevThrowMatrixTimestamp = gGlobalTimer; + } else if (node->header.gfx.node.flags & GRAPH_RENDER_CYLBOARD) { ++ Vec3f posInterpolated; ++ if (gGlobalTimer == node->header.gfx.prevTimestamp + 1 && ++ gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { ++ interpolate_vectors(posInterpolated, node->header.gfx.prevPos, node->header.gfx.pos); ++ } else { ++ vec3f_copy(posInterpolated, node->header.gfx.pos); ++ } ++ vec3f_copy(node->header.gfx.prevPos, node->header.gfx.pos); ++ node->header.gfx.prevTimestamp = gGlobalTimer; + mtxf_cylboard(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], + node->header.gfx.pos, gCurGraphNodeCamera->roll); ++ mtxf_cylboard(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex], ++ posInterpolated, gCurGraphNodeCamera->roll); + } else if (node->header.gfx.node.flags & GRAPH_RENDER_BILLBOARD) { ++ Vec3f posInterpolated; ++ if (gGlobalTimer == node->header.gfx.prevTimestamp + 1 && ++ gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { ++ interpolate_vectors(posInterpolated, node->header.gfx.prevPos, node->header.gfx.pos); ++ } else { ++ vec3f_copy(posInterpolated, node->header.gfx.pos); ++ } ++ vec3f_copy(node->header.gfx.prevPos, node->header.gfx.pos); ++ node->header.gfx.prevTimestamp = gGlobalTimer; + mtxf_billboard(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], + node->header.gfx.pos, gCurGraphNodeCamera->roll); ++ mtxf_billboard(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex], ++ posInterpolated, gCurGraphNodeCamera->roll); + } else { ++ Vec3f posInterpolated; ++ Vec3s angleInterpolated; ++ if (gGlobalTimer == node->header.gfx.prevTimestamp + 1 && ++ gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { ++ interpolate_vectors(posInterpolated, node->header.gfx.prevPos, node->header.gfx.pos); ++ interpolate_angles(angleInterpolated, node->header.gfx.prevAngle, node->header.gfx.angle); ++ } else { ++ vec3f_copy(posInterpolated, node->header.gfx.pos); ++ vec3s_copy(angleInterpolated, node->header.gfx.angle); ++ } ++ vec3f_copy(node->header.gfx.prevPos, node->header.gfx.pos); ++ vec3s_copy(node->header.gfx.prevAngle, node->header.gfx.angle); ++ node->header.gfx.prevTimestamp = gGlobalTimer; + mtxf_rotate_zxy_and_translate(mtxf, node->header.gfx.pos, node->header.gfx.angle); + mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); ++ mtxf_rotate_zxy_and_translate(mtxf, posInterpolated, angleInterpolated); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); ++ } ++ ++ if (gGlobalTimer == node->header.gfx.prevScaleTimestamp + 1 && ++ gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { ++ interpolate_vectors(scaleInterpolated, node->header.gfx.prevScale, node->header.gfx.scale); ++ } else { ++ vec3f_copy(scaleInterpolated, node->header.gfx.scale); + } ++ vec3f_copy(node->header.gfx.prevScale, node->header.gfx.scale); ++ node->header.gfx.prevScaleTimestamp = gGlobalTimer; + + mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], + node->header.gfx.scale); ++ mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex + 1], ++ scaleInterpolated); + node->header.gfx.throwMatrix = &gMatStack[++gMatStackIndex]; ++ node->header.gfx.throwMatrixInterpolated = &gMatStackInterpolated[gMatStackIndex]; + node->header.gfx.cameraToObject[0] = gMatStack[gMatStackIndex][3][0]; + node->header.gfx.cameraToObject[1] = gMatStack[gMatStackIndex][3][1]; + node->header.gfx.cameraToObject[2] = gMatStack[gMatStackIndex][3][2]; +@@ -824,9 +1157,12 @@ static void geo_process_object(struct Object *node) { + } + if (obj_is_in_view(&node->header.gfx, gMatStack[gMatStackIndex])) { + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->header.gfx.sharedChild != NULL) { + gCurGraphNodeObject = (struct GraphNodeObject *) node; + node->header.gfx.sharedChild->parent = &node->header.gfx.node; +@@ -837,11 +1173,16 @@ static void geo_process_object(struct Object *node) { + if (node->header.gfx.node.children != NULL) { + geo_process_node_and_siblings(node->header.gfx.node.children); + } ++ } else { ++ node->header.gfx.prevThrowMatrixTimestamp = 0; ++ node->header.gfx.prevTimestamp = 0; ++ node->header.gfx.prevScaleTimestamp = 0; + } + + gMatStackIndex--; + gCurAnimType = ANIM_TYPE_NONE; + node->header.gfx.throwMatrix = NULL; ++ node->header.gfx.throwMatrixInterpolated = NULL; + } + } + +@@ -868,6 +1209,8 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { + Mat4 mat; + Vec3f translation; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); ++ Vec3f scaleInterpolated; + + #ifdef F3DEX_GBI_2 + gSPLookAt(gDisplayListHead++, &lookAt); +@@ -883,6 +1226,14 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { + translation[1] = node->translation[1] / 4.0f; + translation[2] = node->translation[2] / 4.0f; + ++ if (gGlobalTimer == node->objNode->header.gfx.prevScaleTimestamp + 1) { ++ interpolate_vectors(scaleInterpolated, node->objNode->header.gfx.prevScale, node->objNode->header.gfx.scale); ++ } else { ++ vec3f_copy(scaleInterpolated, node->objNode->header.gfx.scale); ++ } ++ vec3f_copy(node->objNode->header.gfx.prevScale, node->objNode->header.gfx.scale); ++ node->objNode->header.gfx.prevScaleTimestamp = gGlobalTimer; ++ + mtxf_translate(mat, translation); + mtxf_copy(gMatStack[gMatStackIndex + 1], *gCurGraphNodeObject->throwMatrix); + gMatStack[gMatStackIndex + 1][3][0] = gMatStack[gMatStackIndex][3][0]; +@@ -891,6 +1242,13 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { + mtxf_mul(gMatStack[gMatStackIndex + 1], mat, gMatStack[gMatStackIndex + 1]); + mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], + node->objNode->header.gfx.scale); ++ mtxf_copy(gMatStackInterpolated[gMatStackIndex + 1], (void *) gCurGraphNodeObject->throwMatrixInterpolated); ++ gMatStackInterpolated[gMatStackIndex + 1][3][0] = gMatStackInterpolated[gMatStackIndex][3][0]; ++ gMatStackInterpolated[gMatStackIndex + 1][3][1] = gMatStackInterpolated[gMatStackIndex][3][1]; ++ gMatStackInterpolated[gMatStackIndex + 1][3][2] = gMatStackInterpolated[gMatStackIndex][3][2]; ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mat, gMatStackInterpolated[gMatStackIndex + 1]); ++ mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex + 1], ++ scaleInterpolated); + if (node->fnNode.func != NULL) { + node->fnNode.func(GEO_CONTEXT_HELD_OBJ, &node->fnNode.node, + (struct AllocOnlyPool *) gMatStack[gMatStackIndex + 1]); +@@ -898,12 +1256,15 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + gGeoTempState.type = gCurAnimType; + gGeoTempState.enabled = gCurAnimEnabled; + gGeoTempState.frame = gCurrAnimFrame; + gGeoTempState.translationMultiplier = gCurAnimTranslationMultiplier; + gGeoTempState.attribute = gCurrAnimAttribute; + gGeoTempState.data = gCurAnimData; ++ gGeoTempState.prevFrame = gPrevAnimFrame; + gCurAnimType = 0; + gCurGraphNodeHeldObject = (void *) node; + if (node->objNode->header.gfx.unk38.curAnim != NULL) { +@@ -918,6 +1279,7 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { + gCurAnimTranslationMultiplier = gGeoTempState.translationMultiplier; + gCurrAnimAttribute = gGeoTempState.attribute; + gCurAnimData = gGeoTempState.data; ++ gPrevAnimFrame = gGeoTempState.prevFrame; + gMatStackIndex--; + } + +@@ -1039,6 +1401,7 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) + if (node->node.flags & GRAPH_RENDER_ACTIVE) { + Mtx *initialMatrix; + Vp *viewport = alloc_display_list(sizeof(*viewport)); ++ Vp *viewportInterpolated = viewport; + + gDisplayListHeap = alloc_only_pool_init(main_pool_available() - sizeof(struct AllocOnlyPool), + MEMORY_POOL_LEFT); +@@ -1049,7 +1412,12 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) + vec3s_set(viewport->vp.vscale, node->width * 4, node->height * 4, 511); + if (b != NULL) { + clear_frame_buffer(clearColor); +- make_viewport_clip_rect(b); ++ viewportInterpolated = alloc_display_list(sizeof(*viewportInterpolated)); ++ interpolate_vectors_s16(viewportInterpolated->vp.vtrans, sPrevViewport.vp.vtrans, b->vp.vtrans); ++ interpolate_vectors_s16(viewportInterpolated->vp.vscale, sPrevViewport.vp.vscale, b->vp.vscale); ++ ++ sViewportPos = gDisplayListHead; ++ make_viewport_clip_rect(viewportInterpolated); + *viewport = *b; + } + +@@ -1057,11 +1425,16 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) + clear_frame_buffer(clearColor); + make_viewport_clip_rect(c); + } ++ sPrevViewport = *viewport; + + mtxf_identity(gMatStack[gMatStackIndex]); + mtxf_to_mtx(initialMatrix, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = initialMatrix; +- gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(viewport)); ++ ++ mtxf_identity(gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = initialMatrix; ++ ++ gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(viewportInterpolated)); + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(gMatStackFixed[gMatStackIndex]), + G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); + gCurGraphNodeRoot = node; +diff --git a/src/game/screen_transition.c b/src/game/screen_transition.c +index b49ddaf..d6656af 100644 +--- a/src/game/screen_transition.c ++++ b/src/game/screen_transition.c +@@ -16,6 +16,19 @@ + u8 sTransitionColorFadeCount[4] = { 0 }; + u16 sTransitionTextureFadeCount[2] = { 0 }; + ++static Gfx *sScreenTransitionVerticesPos[2]; ++static Vtx *sScreenTransitionVertices; ++ ++void patch_screen_transition_interpolated(void) { ++ if (sScreenTransitionVerticesPos[0] != NULL) { ++ gSPVertex(sScreenTransitionVerticesPos[0], VIRTUAL_TO_PHYSICAL(sScreenTransitionVertices), 8, 0); ++ gSPVertex(sScreenTransitionVerticesPos[1], VIRTUAL_TO_PHYSICAL(sScreenTransitionVertices), 4, 0); ++ sScreenTransitionVerticesPos[0] = NULL; ++ sScreenTransitionVerticesPos[1] = NULL; ++ sScreenTransitionVertices = NULL; ++ } ++} ++ + s32 set_and_reset_transition_fade_timer(s8 fadeTimer, u8 transTime) { + s32 reset = FALSE; + +@@ -85,14 +98,29 @@ s32 render_fade_transition_into_color(s8 fadeTimer, u8 transTime, struct WarpTra + return dl_transition_color(fadeTimer, transTime, transData, alpha); + } + ++#if 0 ++ + s16 calc_tex_transition_radius(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData) { + f32 texRadius = transData->endTexRadius - transData->startTexRadius; + f32 radiusTime = sTransitionColorFadeCount[fadeTimer] * texRadius / (f32)(transTime - 1); + f32 result = transData->startTexRadius + radiusTime; + +- return (s16)(result + 0.5);; ++ return (s16)(result + 0.5); + } + ++#else ++ ++s16 calc_tex_transition_radius(s8 fadeTimer, f32 interpolationFraction, s8 transTime, struct WarpTransitionData *transData) { ++ f32 texRadius = transData->endTexRadius - transData->startTexRadius; ++ f32 radiusTime = (sTransitionColorFadeCount[fadeTimer] == 0 ? 0 : ++ sTransitionColorFadeCount[fadeTimer] - 1 + interpolationFraction) * texRadius / (f32)(transTime - 1); ++ f32 result = transData->startTexRadius + radiusTime; ++ ++ return (s16)(result + 0.5); ++} ++ ++#endif ++ + f32 calc_tex_transition_time(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData) { + f32 startX = transData->startTexX; + f32 startY = transData->startTexY; +@@ -166,6 +194,8 @@ void *sTextureTransitionID[] = { + texture_transition_bowser_half, + }; + ++#if 0 ++ + s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData, s8 texID, s8 transTexType) { + f32 texTransTime = calc_tex_transition_time(fadeTimer, transTime, transData); + u16 texTransPos = convert_tex_transition_angle_to_pos(transData); +@@ -206,6 +236,56 @@ s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransition + return set_and_reset_transition_fade_timer(fadeTimer, transTime); + } + ++#else ++ ++s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData, s8 texID, s8 transTexType) { ++ f32 texTransTime = calc_tex_transition_time(fadeTimer, transTime, transData); ++ u16 texTransPos = convert_tex_transition_angle_to_pos(transData); ++ s16 centerTransX = center_tex_transition_x(transData, texTransTime, texTransPos); ++ s16 centerTransY = center_tex_transition_y(transData, texTransTime, texTransPos); ++ s16 texTransRadius = calc_tex_transition_radius(fadeTimer, 1.0f, transTime, transData); ++ s16 texTransRadiusInterpolated = calc_tex_transition_radius(fadeTimer, 0.5f, transTime, transData); ++ Vtx *verts = alloc_display_list(8 * sizeof(*verts)); ++ Vtx *vertsInterpolated = alloc_display_list(8 * sizeof(*vertsInterpolated)); ++ ++ if (verts != NULL && vertsInterpolated != NULL) { ++ load_tex_transition_vertex(verts, fadeTimer, transData, centerTransX, centerTransY, texTransRadius, transTexType); ++ load_tex_transition_vertex(vertsInterpolated, fadeTimer, transData, centerTransX, centerTransY, texTransRadiusInterpolated, transTexType); ++ sScreenTransitionVertices = verts; ++ gSPDisplayList(gDisplayListHead++, dl_proj_mtx_fullscreen) ++ gDPSetCombineMode(gDisplayListHead++, G_CC_SHADE, G_CC_SHADE); ++ gDPSetRenderMode(gDisplayListHead++, G_RM_AA_OPA_SURF, G_RM_AA_OPA_SURF2); ++ sScreenTransitionVerticesPos[0] = gDisplayListHead; ++ gSPVertex(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(vertsInterpolated), 8, 0); ++ gSPDisplayList(gDisplayListHead++, dl_transition_draw_filled_region); ++ gDPPipeSync(gDisplayListHead++); ++ gDPSetCombineMode(gDisplayListHead++, G_CC_MODULATEIDECALA, G_CC_MODULATEIDECALA); ++ gDPSetRenderMode(gDisplayListHead++, G_RM_AA_XLU_SURF, G_RM_AA_XLU_SURF2); ++ gDPSetTextureFilter(gDisplayListHead++, G_TF_BILERP); ++ switch (transTexType) { ++ case TRANS_TYPE_MIRROR: ++ gDPLoadTextureBlock(gDisplayListHead++, sTextureTransitionID[texID], G_IM_FMT_IA, G_IM_SIZ_8b, 32, 64, 0, ++ G_TX_WRAP | G_TX_MIRROR, G_TX_WRAP | G_TX_MIRROR, 5, 6, G_TX_NOLOD, G_TX_NOLOD); ++ break; ++ case TRANS_TYPE_CLAMP: ++ gDPLoadTextureBlock(gDisplayListHead++, sTextureTransitionID[texID], G_IM_FMT_IA, G_IM_SIZ_8b, 64, 64, 0, ++ G_TX_CLAMP, G_TX_CLAMP, 6, 6, G_TX_NOLOD, G_TX_NOLOD); ++ break; ++ } ++ gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON); ++ sScreenTransitionVerticesPos[1] = gDisplayListHead; ++ gSPVertex(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(vertsInterpolated), 4, 0); ++ gSPDisplayList(gDisplayListHead++, dl_draw_quad_verts_0123); ++ gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_OFF); ++ gSPDisplayList(gDisplayListHead++, dl_screen_transition_end); ++ sTransitionTextureFadeCount[fadeTimer] += transData->texTimer; ++ } else { ++ } ++ return set_and_reset_transition_fade_timer(fadeTimer, transTime); ++} ++ ++#endif ++ + int render_screen_transition(s8 fadeTimer, s8 transType, u8 transTime, struct WarpTransitionData *transData) { + switch (transType) { + case WARP_TRANSITION_FADE_FROM_COLOR: +diff --git a/src/menu/intro_geo.c b/src/menu/intro_geo.c +index 37c6752..d823d40 100644 +--- a/src/menu/intro_geo.c ++++ b/src/menu/intro_geo.c +@@ -1,5 +1,6 @@ + #include + ++#include "engine/math_util.h" + #include "game/memory.h" + #include "game/segment2.h" + #include "game/segment7.h" +@@ -70,6 +71,18 @@ s8 gameOverBackgroundTable[] = { + s8 gameOverBackgroundFlipOrder[] = { 0x00, 0x01, 0x02, 0x03, 0x07, 0x0B, + 0x0a, 0x09, 0x08, 0x04, 0x05, 0x06 }; + ++static Gfx *sIntroScalePos; ++static Vec3f sIntroScale; ++ ++void patch_title_screen_scales(void) { ++ if (sIntroScalePos != NULL) { ++ Mtx *scaleMat = alloc_display_list(sizeof(*scaleMat)); ++ guScale(scaleMat, sIntroScale[0], sIntroScale[1], sIntroScale[2]); ++ gSPMatrix(sIntroScalePos, scaleMat, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); ++ sIntroScalePos = NULL; ++ } ++} ++ + Gfx *geo_title_screen(s32 sp50, struct GraphNode *sp54, UNUSED void *context) { + struct GraphNode *graphNode; // sp4c + Gfx *displayList; // sp48 +@@ -80,6 +93,8 @@ Gfx *geo_title_screen(s32 sp50, struct GraphNode *sp54, UNUSED void *context) { + f32 scaleX; // sp34 + f32 scaleY; // sp30 + f32 scaleZ; // sp2c ++ Vec3f scale; ++ Vec3f scaleInterpolated; + graphNode = sp54; + displayList = NULL; + displayListIter = NULL; +@@ -110,7 +125,11 @@ Gfx *geo_title_screen(s32 sp50, struct GraphNode *sp54, UNUSED void *context) { + scaleY = 0.0f; + scaleZ = 0.0f; + } +- guScale(scaleMat, scaleX, scaleY, scaleZ); ++ vec3f_set(scale, scaleX, scaleY, scaleZ); ++ interpolate_vectors(scaleInterpolated, sIntroScale, scale); ++ vec3f_set(sIntroScale, scaleX, scaleY, scaleZ); ++ guScale(scaleMat, scaleInterpolated[0], scaleInterpolated[1], scaleInterpolated[2]); ++ sIntroScalePos = displayListIter; + gSPMatrix(displayListIter++, scaleMat, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); + gSPDisplayList(displayListIter++, &intro_seg7_dl_0700B3A0); + gSPPopMatrix(displayListIter++, G_MTX_MODELVIEW); +diff --git a/src/pc/gfx/gfx_dxgi.cpp b/src/pc/gfx/gfx_dxgi.cpp +index 0467495..fa4eb33 100644 +--- a/src/pc/gfx/gfx_dxgi.cpp ++++ b/src/pc/gfx/gfx_dxgi.cpp +@@ -36,10 +36,10 @@ + + #ifdef VERSION_EU + #define FRAME_INTERVAL_US_NUMERATOR 40000 +-#define FRAME_INTERVAL_US_DENOMINATOR 1 ++#define FRAME_INTERVAL_US_DENOMINATOR 2 + #else + #define FRAME_INTERVAL_US_NUMERATOR 100000 +-#define FRAME_INTERVAL_US_DENOMINATOR 3 ++#define FRAME_INTERVAL_US_DENOMINATOR 6 + #endif + + using namespace Microsoft::WRL; // For ComPtr +diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c +index a39b76d..3fb1bb1 100644 +--- a/src/pc/gfx/gfx_sdl2.c ++++ b/src/pc/gfx/gfx_sdl2.c +@@ -141,7 +141,7 @@ static inline void gfx_sdl_set_vsync(int mode) { + if (mode > 1) { + // try to detect refresh rate + SDL_GL_SetSwapInterval(1); +- const int vblanks = test_vsync(); ++ const int vblanks = test_vsync() / 2; + if (vblanks) { + printf("determined swap interval: %d\n", vblanks); + SDL_GL_SetSwapInterval(vblanks); +@@ -225,7 +225,7 @@ static void gfx_sdl_init(const char *window_title) { + gfx_sdl_set_fullscreen(); + + qpc_freq = SDL_GetPerformanceFrequency(); +- frame_time = qpc_freq / FRAMERATE; ++ frame_time = qpc_freq / (2 * FRAMERATE); + + for (size_t i = 0; i < sizeof(windows_scancode_table) / sizeof(SDL_Scancode); i++) { + inverted_scancode_table[windows_scancode_table[i]] = i; +diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c +index ed6ee74..63679ad 100644 +--- a/src/pc/pc_main.c ++++ b/src/pc/pc_main.c +@@ -83,6 +83,25 @@ void send_display_list(struct SPTask *spTask) { + #define SAMPLES_LOW 528 + #endif + ++static inline void patch_interpolations(void) { ++ extern void mtx_patch_interpolated(void); ++ extern void patch_screen_transition_interpolated(void); ++ extern void patch_title_screen_scales(void); ++ extern void patch_interpolated_dialog(void); ++ extern void patch_interpolated_hud(void); ++ extern void patch_interpolated_paintings(void); ++ extern void patch_interpolated_bubble_particles(void); ++ extern void patch_interpolated_snow_particles(void); ++ mtx_patch_interpolated(); ++ patch_screen_transition_interpolated(); ++ patch_title_screen_scales(); ++ patch_interpolated_dialog(); ++ patch_interpolated_hud(); ++ patch_interpolated_paintings(); ++ patch_interpolated_bubble_particles(); ++ patch_interpolated_snow_particles(); ++} ++ + void produce_one_frame(void) { + gfx_start_frame(); + +@@ -110,6 +129,11 @@ void produce_one_frame(void) { + audio_api->play((u8 *)audio_buffer, 2 * num_audio_samples * 4); + + gfx_end_frame(); ++ ++ gfx_start_frame(); ++ patch_interpolations(); ++ send_display_list(gGfxSPTask); ++ gfx_end_frame(); + } + + void audio_shutdown(void) { diff --git a/enhancements/L-trigger-mapping/README.md b/enhancements/L-trigger-mapping/README.md deleted file mode 100644 index 0538f0943..000000000 --- a/enhancements/L-trigger-mapping/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# L-trigger mapping - -Some parts of the code might require the pressing of the L-trigger for testing reasons. - -If you need that, alter `controller_sdl.c`. -In the following line: -``` - if (SDL_GameControllerGetButton(sdl_cntrl, SDL_CONTROLLER_BUTTON_LEFTSHOULDER)) pad->button |= Z_TRIG; -``` -Replace `Z_TRIG` with `L_TRIG`, save and rebuild. - -On a DS4, this now means that Z-trigger will be mapped to L2 and the L-trigger to L1. diff --git a/enhancements/README.md b/enhancements/README.md index 1bc26dff0..4d1e13cee 100644 --- a/enhancements/README.md +++ b/enhancements/README.md @@ -10,40 +10,16 @@ to the source code. Likewise, to undo the changes from a patch you applied, run `tools/revert_patch.sh` with the name of the .patch file you wish to undo. -To create your own enhancement patch, switch to the `master` Git -branch, make your changes to the code (but do not commit), then run `tools/create_patch.sh`. Your changes will be stored in the .patch file you specify. +To create your own enhancement patch, switch to the `nightly` Git +branch, make your changes to the code (but do not commit), then run `tools/create_patch.sh`. +Your changes will be stored in the .patch file you specify. The following enhancements are included in this directory: -## Crash Screen - `crash.patch` +## 60 FPS - `60fps_ex.patch` -This enhancement provides a crash screen that is displayed when the code throws a hardware exception. This may be useful for diagnosing crashes in game code. +This allows the game to be rendered at 60 FPS instead of 30 FPS by interpolation (game logic still runs at 30 FPS). -## Debug Box - `debug_box.patch` +The Mario head intro is the only exception which is still rendered at 30 FPS. -This allows you to draw 3D boxes for debugging purposes. - -Call the `debug_box` function whenever you want to draw one. `debug_box` by default takes two arguments: a center and bounds vec3f. This will draw a box starting from the point (center - bounds) to (center + bounds). -Use `debug_box_rot` to draw a box rotated in the xz-plane. If you want to draw a box by specifying min and max points, use `debug_box_pos` instead. - -## iQue Player Support - `ique_support.patch` - -This enhancement allows the same ROM to work on both the Nintendo 64 and the iQue Player. - -## Memory Expansion Pak Error Screen - `mem_error_screen.patch` - -Use this patch if your game requires over 4 MB of memory and requires the -Expansion Pak. If the Expansion Pak is not present, an error message will be -shown on startup. - -## Demo Input Recorder - `record_demo.patch` - -This patch allows you to record gameplay demos for the attract screen. It requires the latest nightly versions of Project64, and uses the Project64 JavaScript API to dump the demo input data from RAM and write it to a file. - -Place the `enhancements/RecordDemo.js` file in the `/Scripts/` folder in the Project64 directory. - -In the Scripts window, double click on "RecordDemo" on the list on the left side. - -When this is done, it should turn green which lets you know that it has started. - -When your demo has been recorded, it will be dumped to the newly created `/SM64_DEMOS/` folder within the Project64 directory. +This is the 60fps patch from [sm64-port](https://github.com/sm64-port/sm64-port/tree/master/enhancements) adapted for sm64ex. diff --git a/enhancements/RecordDemo.js b/enhancements/RecordDemo.js deleted file mode 100644 index 9442b804b..000000000 --- a/enhancements/RecordDemo.js +++ /dev/null @@ -1,184 +0,0 @@ -/* - * This is a companion file for the record_demo.inc.c enhancement. - * - * You will need the PJ64 javascript API to get this to work, so - * you should download a nightly build from here (Windows only atm): - * https://www.pj64-emu.com/nightly-builds - * - * Place this .js file into the /Scripts/ folder in the PJ64 directory. - * - * In the Scripts window, double click on "RecordDemo" on the list on the left side. - * When this is done, it should turn green which lets you know that it has started. - * - * When your demo has been recorded, it will be dumped to the newly created - * /SM64_DEMOS/ folder within the PJ64 directory. - */ - -var RAM_SIZE = 4 * 1048576 // 4 MB - -// Get a copy of the first 4MB of memory. -var RAM = mem.getblock(0x80000000, RAM_SIZE) - -// Create SM64_DEMOS Directory if it already doesn't exist. -fs.mkdir("SM64_DEMOS/"); - -// string "DEMORECVARS" -var pattern = [0x44, 0x45, 0x4D, 0x4F, 0x52, 0x45, 0x43, 0x56, 0x41, 0x52, 0x53, 0x00] - -var matches = find_matches_fast(pattern) - -if(matches.length > 1) { - console.log('Error: More than 1 instance of "DEMORECVARS" was found. Abort!') -} else if(matches.length < 1) { - console.log('Error: No instance of "DEMORECVARS" was found. Abort!') -} else { - console.clear() - var demoRecVarsLocation = 0x80000000 + matches[0] + 12 - - // Control variables addresses - var gRecordingStatus_vaddr = demoRecVarsLocation + 0 - var gDoneDelay_vaddr = demoRecVarsLocation + 4 - var gNumOfRecordedInputs_vaddr = demoRecVarsLocation + 8 - var gRecordedInputsPtr_vaddr = demoRecVarsLocation + 12 - - console.log('Recording variables were found at address 0x' + demoRecVarsLocation.toString(16)) - console.log('Initialization successful! Press L in-game to ready the demo recording before entering in a level.') - - // This runs every frame that is drawn. - events.ondraw(function() { - var gRecordingStatus = mem.u32[gRecordingStatus_vaddr] - - if(gRecordingStatus == 3) { // gRecordingStatus == DEMOREC_STATUS_STOPPING - var gNumOfRecordedInputs = mem.u32[gNumOfRecordedInputs_vaddr] - - if(gNumOfRecordedInputs < 1) { - console.log('Error: No inputs could be recorded!') - } else { - var gRecordedInputsPtr = mem.u32[gRecordedInputsPtr_vaddr] - - console.log('Recorded ' + gNumOfRecordedInputs + ' demo inputs.') - - // Grab demo data from RAM. - var demo_data = mem.getblock(gRecordedInputsPtr, (gNumOfRecordedInputs + 1) * 4) - - // Create filename with random id added onto it. - var filename = 'SM64_DEMOS/demo_' + get_random_int(0, 0xFFFFFFFF).toString(16) + '.bin' - - // Dump demo data to file. - var file = fs.open(filename, 'wb'); - fs.write(file, demo_data); - fs.close(file); - - console.log('Dumped data to file ' + filename) - } - - // Set status to DEMOREC_STATUS_DONE - mem.u32[gRecordingStatus_vaddr] = 4; - - // Decomp memes - console.log('OK'); - } - }) -} - -function get_random_int(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1)) + min; -} - -/* - * Finds a byte pattern that is 4-byte aligned. - * - * The javascript api is pretty slow when reading memory directly, - * so I made this to search a copy of RAM to make things a little faster. - */ -function find_matches_fast(pattern) { - var targetLength = pattern.length - var targetLengthMinusOne = targetLength - 1 - var matches = [] - var matching = 0 - - // Increments by 8 to speed things up. - for(var i = 0; i < RAM_SIZE; i += 8) { - if(RAM[i] == pattern[matching]) - matching++ - else - matching = 0 - if(matching == targetLength) { - matches.push(i - targetLengthMinusOne) - matching = 0 - } - if(matching > 0) { - if(RAM[i + 1] == pattern[matching]) - matching++ - else - matching = 0 - if(matching == targetLength) { - matches.push(i + 1 - targetLengthMinusOne) - matching = 0 - } - if(matching > 1) { - if(RAM[i + 2] == pattern[matching]) - matching++ - else - matching = 0 - if(matching == targetLength) { - matches.push(i + 2 - targetLengthMinusOne) - matching = 0 - } - if(matching > 2) { - if(RAM[i + 3] == pattern[matching]) - matching++ - else - matching = 0 - if(matching == targetLength) { - matches.push(i + 3 - targetLengthMinusOne) - matching = 0 - } - } - } - } - - if(RAM[i + 4] == pattern[matching]) - matching++ - else - matching = 0 - if(matching == targetLength) { - matches.push(i + 4 - targetLengthMinusOne) - matching = 0 - } - if(matching > 0) { - if(RAM[i + 5] == pattern[matching]) - matching++ - else - matching = 0 - if(matching == targetLength) { - matches.push(i + 5 - targetLengthMinusOne) - matching = 0 - } - if(matching > 1) { - if(RAM[i + 6] == pattern[matching]) - matching++ - else - matching = 0 - if(matching == targetLength) { - matches.push(i + 6 - targetLengthMinusOne) - matching = 0 - } - if(matching > 2) { - if(RAM[i + 7] == pattern[matching]) - matching++ - else - matching = 0 - if(matching == targetLength) { - matches.push(i + 7 - targetLengthMinusOne) - matching = 0 - } - } - } - } - } - - return matches -} diff --git a/enhancements/crash.patch b/enhancements/crash.patch deleted file mode 100644 index f0eb61a9a..000000000 --- a/enhancements/crash.patch +++ /dev/null @@ -1,486 +0,0 @@ -diff --git a/asm/crash.s b/asm/crash.s -new file mode 100644 -index 00000000..7c272050 ---- /dev/null -+++ b/asm/crash.s -@@ -0,0 +1,153 @@ -+# SM64 Crash Handler -+# See Readme below. -+ -+.include "macros.inc" -+ -+/* --------------------------------------------------------------- -+ * IMPORTANT README: -+ * --------------------------------------------------------------- -+ * Frame buffer emulation is required. To enable it in GlideN64, -+ * check "Emulate frame buffer" and "Render frame buffer to output" -+ * in the "Frame buffer" tab. -+ * -+ * Your emulator's CPU core style should be set to interpreter for best results. -+ * -+ * See the DEBUG_ASSERT macro on how to call the crash screen for -+ * detected exceptions. -+ * -+ */ -+ -+.set noat -+.set noreorder -+.set gp=64 -+ -+.set COP0_CAUSE, $13 -+.set COP0_EPC, $14 -+.set COP0_BADVADDR, $8 -+ -+glabel crashFont -+ .incbin "enhancements/crash_font.bin" -+ .align 4 -+ -+glabel exceptionRegContext -+ .fill 0x108 -+ -+glabel pAssertFile -+ .dword 0 -+glabel nAssertLine -+ .dword 0 -+glabel pAssertExpression -+ .dword 0 -+glabel nAssertStopProgram -+ .dword 0 -+ -+glabel _n64_assert -+ lui $at, %hi(pAssertFile) -+ sw $a0, %lo(pAssertFile)($at) -+ lui $at, %hi(nAssertLine) -+ sw $a1, %lo(nAssertLine)($at) -+ lui $at, %hi(pAssertExpression) -+ sw $a2, %lo(pAssertExpression)($at) -+ lui $at, %hi(nAssertStopProgram) -+ sw $a3, %lo(nAssertStopProgram)($at) -+ beqz $a3, .end_2 -+ nop -+ syscall # trigger crash screen -+.end_2: -+ jr $ra -+ nop -+ -+glabel cop0_get_cause -+ jr $ra -+ mfc0 $v0, COP0_CAUSE -+ -+glabel cop0_get_epc -+ jr $ra -+ mfc0 $v0, COP0_EPC -+ -+glabel cop0_get_badvaddr -+ jr $ra -+ mfc0 $v0, COP0_BADVADDR -+ -+# If the error code field of cop0's cause register is non-zero, -+# draw crash details to the screen and hang -+# -+# If there wasn't an error, continue to the original handler -+ -+glabel __crash_handler_entry -+ mfc0 $k1, COP0_CAUSE -+ andi $k1, $k1, (0x1F << 2) -+ beqzl $k1, .end2 # exit if ExCode is 0 -+ lui $k0, %hi(__osException) -+ la $k0, exceptionRegContext -+ sd $zero, 0x018 ($k0) -+ sd $at, 0x020 ($k0) -+ sd $v0, 0x028 ($k0) -+ sd $v1, 0x030 ($k0) -+ sd $a0, 0x038 ($k0) -+ sd $a1, 0x040 ($k0) -+ sd $a2, 0x048 ($k0) -+ sd $a3, 0x050 ($k0) -+ sd $t0, 0x058 ($k0) -+ sd $t1, 0x060 ($k0) -+ sd $t2, 0x068 ($k0) -+ sd $t3, 0x070 ($k0) -+ sd $t4, 0x078 ($k0) -+ sd $t5, 0x080 ($k0) -+ sd $t6, 0x088 ($k0) -+ sd $t7, 0x090 ($k0) -+ sd $s0, 0x098 ($k0) -+ sd $s1, 0x0A0 ($k0) -+ sd $s2, 0x0A8 ($k0) -+ sd $s3, 0x0B0 ($k0) -+ sd $s4, 0x0B8 ($k0) -+ sd $s5, 0x0C0 ($k0) -+ sd $s6, 0x0C8 ($k0) -+ sd $s7, 0x0D0 ($k0) -+ sd $t8, 0x0D8 ($k0) -+ sd $t9, 0x0E0 ($k0) -+ sd $gp, 0x0E8 ($k0) -+ sd $sp, 0x0F0 ($k0) -+ sd $fp, 0x0F8 ($k0) -+ sd $ra, 0x100 ($k0) -+ # cop unusable exception fired twice on startup so we'll ignore it for now -+ li $t0, (0x0B << 2) -+ beq $k1, $t0, .end -+ nop -+ jal show_crash_screen_and_hang -+ nop -+ .end: -+ ld $at, 0x020 ($k0) -+ ld $v0, 0x028 ($k0) -+ ld $v1, 0x030 ($k0) -+ ld $a0, 0x038 ($k0) -+ ld $a1, 0x040 ($k0) -+ ld $a2, 0x048 ($k0) -+ ld $a3, 0x050 ($k0) -+ ld $t0, 0x058 ($k0) -+ ld $t1, 0x060 ($k0) -+ ld $t2, 0x068 ($k0) -+ ld $t3, 0x070 ($k0) -+ ld $t4, 0x078 ($k0) -+ ld $t5, 0x080 ($k0) -+ ld $t6, 0x088 ($k0) -+ ld $t7, 0x090 ($k0) -+ ld $s0, 0x098 ($k0) -+ ld $s1, 0x0A0 ($k0) -+ ld $s2, 0x0A8 ($k0) -+ ld $s3, 0x0B0 ($k0) -+ ld $s4, 0x0B8 ($k0) -+ ld $s5, 0x0C0 ($k0) -+ ld $s6, 0x0C8 ($k0) -+ ld $s7, 0x0D0 ($k0) -+ ld $t8, 0x0D8 ($k0) -+ ld $t9, 0x0E0 ($k0) -+ ld $gp, 0x0E8 ($k0) -+ ld $sp, 0x0F0 ($k0) -+ ld $fp, 0x0F8 ($k0) -+ ld $ra, 0x100 ($k0) -+ lui $k0, %hi(__osException) -+ .end2: -+ addiu $k0, $k0, %lo(__osException) -+ jr $k0 # run the original handler -+ nop -diff --git a/lib/asm/__osExceptionPreamble.s b/lib/asm/__osExceptionPreamble.s -index 865273d9..f9ce7596 100644 ---- a/lib/asm/__osExceptionPreamble.s -+++ b/lib/asm/__osExceptionPreamble.s -@@ -15,8 +15,8 @@ - .endif - - glabel __osExceptionPreamble -- lui $k0, %hi(__osException) -- addiu $k0, %lo(__osException) -+ lui $k0, %hi(__crash_handler_entry) -+ addiu $k0, %lo(__crash_handler_entry) - jr $k0 - nop - -diff --git a/sm64.ld b/sm64.ld -index e6f5c942..c0feb343 ---- a/sm64.ld -+++ b/sm64.ld -@@ -116,6 +116,7 @@ SECTIONS - BUILD_DIR/src/game/rendering_graph_node.o(.text); - BUILD_DIR/src/game/profiler.o(.text); - BUILD_DIR/asm/decompress.o(.text); -+ BUILD_DIR/asm/crash.o(.text); - BUILD_DIR/src/game/camera.o(.text); - BUILD_DIR/src/game/debug_course.o(.text); - BUILD_DIR/src/game/object_list_processor.o(.text); -diff --git a/src/game/crash.c b/src/game/crash.c -new file mode 100644 -index 00000000..716adfbd ---- /dev/null -+++ b/src/game/crash.c -@@ -0,0 +1,260 @@ -+/* SM64 Crash Handler */ -+ -+#include -+ -+#include "crash.h" -+ -+extern u32 exceptionRegContext[]; -+ -+extern char *pAssertFile; -+extern int nAssertLine; -+extern char *pAssertExpression; -+extern int nAssertStopProgram; -+ -+u16 fbFillColor = 0xFFFF; -+u16 fbShadeColor = 0x0000; -+u16 *fbAddress = NULL; -+ -+extern u8 crashFont[]; -+ -+const char *szErrCodes[] = { -+ "INTERRUPT", -+ "TLB MOD", -+ "UNMAPPED LOAD ADDR", -+ "UNMAPPED STORE ADDR", -+ "BAD LOAD ADDR", -+ "BAD STORE ADDR", -+ "BUS ERR ON INSTR FETCH", -+ "BUS ERR ON LOADSTORE", -+ "SYSCALL", -+ "BREAKPOINT", -+ "UNKNOWN INSTR", -+ "COP UNUSABLE", -+ "ARITHMETIC OVERFLOW", -+ "TRAP EXC", -+ "VIRTUAL COHERENCY INSTR", -+ "FLOAT EXC", -+}; -+ -+const char *szGPRegisters1[] = { "R0", "AT", "V0", "V1", "A0", "A1", "A2", "A3", -+ "T0", "T1", "T2", "T3", "T4", "T5", "T6", NULL }; -+ -+const char *szGPRegisters2[] = { "T7", "S0", "S1", "S2", "S3", "S4", -+ "S5", "S6", "S7", "T8", "T9", /*"K0", "K1",*/ -+ "GP", "SP", "FP", "RA", NULL }; -+ -+int crash_strlen(char *str) { -+ int len = 0; -+ while (*str++) { -+ len++; -+ } -+ return len; -+} -+ -+void show_crash_screen_and_hang(void) { -+ u32 cause; -+ u32 epc; -+ u8 errno; -+ -+ fb_set_address((void *) (*(u32 *) 0xA4400004 | 0x80000000)); // replace me -+ -+ cause = cop0_get_cause(); -+ epc = cop0_get_epc(); -+ -+ errno = (cause >> 2) & 0x1F; -+ -+ if (nAssertStopProgram == 0) { -+ fbFillColor = 0x6253; -+ fb_fill(10, 10, 300, 220); -+ -+ fb_print_str(80, 20, "AN ERROR HAS OCCURRED!"); -+ fb_print_int_hex(80, 30, errno, 8); -+ fb_print_str(95, 30, szErrCodes[errno]); -+ -+ if (errno >= 2 && errno <= 5) { -+ /* -+ 2 UNMAPPED LOAD ADDR -+ 3 UNMAPPED STORE ADDR -+ 4 BAD LOAD ADDR -+ 5 BAD STORE ADDR -+ */ -+ u32 badvaddr = cop0_get_badvaddr(); -+ -+ fb_print_str(145, 50, "VA"); -+ fb_print_int_hex(160, 50, badvaddr, 32); -+ } -+ } else { -+ int afterFileX; -+ int exprBoxWidth; -+ fbFillColor = 0x5263; -+ fb_fill(10, 10, 300, 220); -+ -+ fb_print_str(80, 20, "ASSERTION FAILED!"); -+ -+ afterFileX = fb_print_str(80, 30, pAssertFile); -+ fb_print_str(afterFileX, 30, ":"); -+ fb_print_uint(afterFileX + 5, 30, nAssertLine); -+ -+ exprBoxWidth = (crash_strlen(pAssertExpression) * 5) + 2; -+ fbFillColor = 0x0001; -+ fb_fill(80 - 1, 40 - 1, exprBoxWidth, 10); -+ fb_print_str(80, 40, pAssertExpression); -+ } -+ -+ fb_print_str(80, 50, "PC"); -+ fb_print_int_hex(95, 50, epc, 32); -+ -+ fb_print_gpr_states(80, 70, szGPRegisters1, &exceptionRegContext[6 + 0]); -+ fb_print_gpr_states(145, 70, szGPRegisters2, &exceptionRegContext[6 + 15 * 2]); -+ -+ fb_swap(); -+ osWritebackDCacheAll(); -+ -+ while (1) // hang forever -+ { -+ UNUSED volatile int t = 0; // keep pj64 happy -+ } -+} -+ -+u8 ascii_to_idx(char c) { -+ return c - 0x20; -+} -+ -+void fb_set_address(void *address) { -+ fbAddress = (u16 *) address; -+} -+ -+void fb_swap() { -+ // update VI frame buffer register -+ // todo other registers -+ *(u32 *) (0xA4400004) = (u32) fbAddress & 0x00FFFFFF; -+} -+ -+void fb_fill(int baseX, int baseY, int width, int height) { -+ int y, x; -+ -+ for (y = baseY; y < baseY + height; y++) { -+ for (x = baseX; x < baseX + width; x++) { -+ fbAddress[y * 320 + x] = fbFillColor; -+ } -+ } -+} -+ -+void fb_draw_char(int x, int y, u8 idx) { -+ u16 *out = &fbAddress[y * 320 + x]; -+ const u8 *in = &crashFont[idx * 3]; -+ int nbyte; -+ int nrow; -+ int ncol; -+ -+ for (nbyte = 0; nbyte < 3; nbyte++) { -+ u8 curbyte = in[nbyte]; -+ for (nrow = 0; nrow < 2; nrow++) { -+ for (ncol = 0; ncol < 4; ncol++) { -+ u8 px = curbyte & (1 << (7 - (nrow * 4 + ncol))); -+ if (px != 0) { -+ out[ncol] = fbFillColor; -+ } -+ } -+ out += 320; -+ } -+ } -+} -+ -+void fb_draw_char_shaded(int x, int y, u8 idx) { -+ fbFillColor = 0x0001; -+ fb_draw_char(x - 1, y + 1, idx); -+ -+ fbFillColor = 0xFFFF; -+ fb_draw_char(x, y, idx); -+} -+ -+int fb_print_str(int x, int y, const char *str) { -+ while (1) { -+ int yoffs = 0; -+ u8 idx; -+ char c = *str++; -+ -+ if (c == '\0') { -+ break; -+ } -+ -+ if (c == ' ') { -+ x += 5; -+ continue; -+ } -+ -+ switch (c) { -+ case 'j': -+ case 'g': -+ case 'p': -+ case 'q': -+ case 'y': -+ case 'Q': -+ yoffs = 1; -+ break; -+ case ',': -+ yoffs = 2; -+ break; -+ } -+ -+ idx = ascii_to_idx(c); -+ fb_draw_char_shaded(x, y + yoffs, idx); -+ x += 5; -+ } -+ -+ return x; -+} -+ -+void fb_print_int_hex(int x, int y, u32 value, int nbits) { -+ nbits -= 4; -+ -+ while (nbits >= 0) { -+ int nib = ((value >> nbits) & 0xF); -+ u8 idx; -+ -+ if (nib > 9) { -+ idx = ('A' - 0x20) + (nib - 0xa); -+ } else { -+ idx = ('0' - 0x20) + nib; -+ } -+ -+ fb_draw_char_shaded(x, y, idx); -+ x += 5; -+ -+ nbits -= 4; -+ } -+} -+ -+int fb_print_uint(int x, int y, u32 value) { -+ int nchars = 0; -+ -+ int v = value; -+ int i; -+ while (v /= 10) { -+ nchars++; -+ } -+ -+ x += nchars * 5; -+ -+ for (i = nchars; i >= 0; i--) { -+ fb_draw_char_shaded(x, y, ('0' - 0x20) + (value % 10)); -+ value /= 10; -+ x -= 5; -+ } -+ -+ return (x + nchars * 5); -+} -+ -+void fb_print_gpr_states(int x, int y, const char *regNames[], u32 *regContext) { -+ int i; -+ for (i = 0;; i++) { -+ if (regNames[i] == NULL) { -+ break; -+ } -+ -+ fb_print_str(x, y, regNames[i]); -+ fb_print_int_hex(x + 15, y, regContext[i * 2 + 1], 32); -+ y += 10; -+ } -+} -diff --git a/src/game/crash.h b/src/game/crash.h -new file mode 100644 -index 00000000..1386930d ---- /dev/null -+++ b/src/game/crash.h -@@ -0,0 +1,28 @@ -+#ifndef _CRASH_H_ -+#define _CRASH_H_ -+ -+#include -+ -+#define CRASH_SCREEN_INCLUDED 1 -+ -+extern u32 cop0_get_cause(void); -+extern u32 cop0_get_epc(void); -+extern u32 cop0_get_badvaddr(void); -+ -+extern void _n64_assert(const char* pFile, int nLine, const char *pExpression, int nStopProgram); -+ -+extern u8 __crash_handler_entry[]; -+ -+void show_crash_screen_and_hang(void); -+u8 ascii_to_idx(char c); -+void fb_set_address(void *address); -+void fb_swap(void); -+void fb_fill(int baseX, int baseY, int width, int height); -+void fb_draw_char(int x, int y, u8 idx); -+void fb_draw_char_shaded(int x, int y, u8 idx); -+int fb_print_str(int x, int y, const char *str); -+int fb_print_uint(int x, int y, u32 value); -+void fb_print_int_hex(int x, int y, u32 value, int nbits); -+void fb_print_gpr_states(int x, int y, const char* regStrs[], u32 *regContext); -+ -+#endif /* _CRASH_H_ */ diff --git a/enhancements/crash_font.bin b/enhancements/crash_font.bin deleted file mode 100644 index 1577738864e1d21376a4142fee923f9bf5944aec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 285 zcmWlTF-yZh7=}MeY{}9Xhq{I4<7`G3-6cJ0Hbro8aEPUNxOFS;=ENb}ou5Lo1WKWs zqpLUw{(|BlIP|twETn^AglBuTv{K=#{oE}-)umBO#)VKqw=6iYAgVunk04, D_8032CE74, D_8032CE78, gFBSetColor); - -+ render_debug_boxes(); -+ - gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(&D_8032CF00)); - - gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, BORDER_HEIGHT, SCREEN_WIDTH, -diff --git a/src/game/debug_box.c b/src/game/debug_box.c -new file mode 100644 -index 00000000..0ee87ec7 ---- /dev/null -+++ b/src/game/debug_box.c -@@ -0,0 +1,244 @@ -+#include -+ -+#include "sm64.h" -+#include "game/game_init.h" -+#include "game/geo_misc.h" -+#include "engine/math_util.h" -+ -+#include "debug_box.h" -+ -+/** -+ * @file debug_box.c -+ * Draws 3D boxes for debugging purposes. -+ * -+ * How to use: -+ * -+ * In render_game() in area.c, add a call to render_debug_boxes(): -+ * -+ * void render_game(void) { -+ * if (gCurrentArea != NULL && !gWarpTransition.pauseRendering) { -+ * geo_process_root(...); -+ * -+ * render_debug_boxes(); // add here -+ * -+ * gSPViewport(...); -+ * gDPSetScissor(...); -+ * //... -+ * -+ * Now just call debug_box() whenever you want to draw one! -+ * -+ * debug_box by default takes two arguments: a center and bounds vec3f. -+ * This will draw a box starting from the point (center - bounds) to (center + bounds). -+ * -+ * Use debug_box_rot to draw a box rotated in the xz-plane. -+ * -+ * If you want to draw a box by specifying min and max points, use debug_box_pos() instead. -+ */ -+ -+/** -+ * Internal struct containing box info -+ */ -+struct DebugBox { -+ Vec3s center; -+ Vec3s bounds; -+ s16 yaw; -+}; -+ -+struct DebugBox *sBoxes[MAX_DEBUG_BOXES]; -+s16 sNumBoxes = 0; -+ -+extern Mat4 gMatStack[32]; //XXX: Hack -+ -+/** -+ * The debug boxes' transparency -+ */ -+#define DBG_BOX_ALPHA 0x7F -+/** -+ * The debug boxes' color -+ */ -+#define DBG_BOX_COL 0xFF, 0x00, 0x00, DBG_BOX_ALPHA -+ -+/** -+ * Sets up the RCP for drawing the boxes -+ */ -+static const Gfx dl_debug_box_begin[] = { -+ gsDPPipeSync(), -+#if DBG_BOX_ALPHA < 0xFF -+ gsDPSetRenderMode(G_RM_ZB_XLU_SURF, G_RM_NOOP2), -+#else -+ gsDPSetRenderMode(G_RM_ZB_OPA_SURF, G_RM_NOOP2), -+#endif -+ gsSPClearGeometryMode(G_LIGHTING | G_CULL_BACK), -+ gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_SHADING_SMOOTH), -+ gsSPTexture(0, 0, 0, 0, G_OFF), -+ gsDPSetCombineMode(G_CC_SHADE, G_CC_SHADE), -+ gsSPEndDisplayList(), -+}; -+ -+/** -+ * Actually draws the box -+ */ -+static const Gfx dl_debug_draw_box[] = { -+ gsSP2Triangles( 0, 1, 2, 0x0, 2, 1, 3, 0x0), -+ gsSP2Triangles( 2, 3, 6, 0x0, 6, 3, 7, 0x0), -+ -+ gsSP2Triangles( 4, 0, 2, 0x0, 2, 6, 4, 0x0), -+ gsSP2Triangles( 1, 5, 3, 0x0, 3, 5, 7, 0x0), -+ -+ gsSP2Triangles( 1, 0, 4, 0x0, 1, 4, 5, 0x0), -+ gsSP2Triangles( 5, 4, 6, 0x0, 5, 6, 7, 0x0), -+ -+ gsSPEndDisplayList(), -+}; -+ -+/** -+ * Adds a box to the list to be rendered this frame. -+ * -+ * If there are already MAX_DEBUG_BOXES boxes, does nothing. -+ */ -+static void append_debug_box(Vec3f center, Vec3f bounds, s16 yaw) -+{ -+ if (sNumBoxes >= MAX_DEBUG_BOXES || -+ (sBoxes[sNumBoxes] = mem_pool_alloc(gEffectsMemoryPool, sizeof(struct DebugBox))) == NULL) { -+ return; -+ } -+ -+ vec3f_to_vec3s(sBoxes[sNumBoxes]->center, center); -+ vec3f_to_vec3s(sBoxes[sNumBoxes]->bounds, bounds); -+ -+ sBoxes[sNumBoxes]->yaw = yaw; -+ -+ ++sNumBoxes; -+} -+ -+/** -+ * Draws a debug box from (center - bounds) to (center + bounds) -+ * To draw a rotated box, use debug_box_rot() -+ * -+ * @see debug_box_rot() -+ */ -+void debug_box(Vec3f center, Vec3f bounds) -+{ -+ append_debug_box(center, bounds, 0); -+} -+ -+/** -+ * Draws a debug box from (center - bounds) to (center + bounds), rotating it by `yaw` -+ */ -+void debug_box_rot(Vec3f center, Vec3f bounds, s16 yaw) -+{ -+ append_debug_box(center, bounds, yaw); -+} -+ -+/** -+ * Draws a debug box from pMin to pMax -+ * To draw a rotated box this way, use debug_box_pos_rot() -+ * -+ * @see debug_box_pos_rot() -+ */ -+void debug_box_pos(Vec3f pMin, Vec3f pMax) -+{ -+ debug_box_pos_rot(pMin, pMax, 0); -+} -+ -+/** -+ * Draws a debug box from pMin to pMax, rotating it in the xz-plane by `yaw` -+ */ -+void debug_box_pos_rot(Vec3f pMin, Vec3f pMax, s16 yaw) -+{ -+ Vec3f center, bounds; -+ -+ bounds[0] = pMax[0] - pMin[0] / 2.0f; -+ bounds[1] = pMax[1] - pMin[1] / 2.0f; -+ bounds[2] = pMax[2] - pMin[2] / 2.0f; -+ -+ center[0] = pMin[0] + bounds[0]; -+ center[1] = pMin[1] + bounds[1]; -+ center[2] = pMin[2] + bounds[2]; -+ -+ append_debug_box(center, bounds, yaw); -+} -+ -+static void render_box(struct DebugBox *box) -+{ -+ Vtx *verts = alloc_display_list(8 * sizeof(Vtx)); -+ Mtx *translate; -+ Mtx *rotate; -+ Mtx *translateback; -+ s32 x0 = box->center[0], -+ y0 = box->center[1], -+ z0 = box->center[2]; -+ -+ s32 xb = box->bounds[0], -+ yb = box->bounds[1], -+ zb = box->bounds[2]; -+ -+ if (verts != NULL) { -+ if (box->yaw != 0) { -+ // Translate to the origin, rotate, then translate back, effectively rotating the box about -+ // its center -+ translate = alloc_display_list(sizeof(Mtx)); -+ rotate = alloc_display_list(sizeof(Mtx)); -+ translateback = alloc_display_list(sizeof(Mtx)); -+ -+ guTranslate(translate, box->center[0], box->center[1], box->center[2]); -+ guRotate(rotate, box->yaw / (float)0x10000 * 360.0f, 0, 1.0f, 0); -+ guTranslate(translateback, -box->center[0], -box->center[1], -box->center[2]); -+ -+ gSPMatrix(gDisplayListHead++, translate, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); -+ gSPMatrix(gDisplayListHead++, rotate, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); -+ gSPMatrix(gDisplayListHead++, translateback, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); -+ } -+ -+#define DBG_BOX_VTX(i, x, y, z) make_vertex(verts, i, x, y, z, 0, 0, DBG_BOX_COL) -+ DBG_BOX_VTX(0, x0 - xb, y0 + yb, z0 - zb); -+ DBG_BOX_VTX(1, x0 + xb, y0 + yb, z0 - zb); -+ DBG_BOX_VTX(2, x0 - xb, y0 - yb, z0 - zb); -+ DBG_BOX_VTX(3, x0 + xb, y0 - yb, z0 - zb); -+ DBG_BOX_VTX(4, x0 - xb, y0 + yb, z0 + zb); -+ DBG_BOX_VTX(5, x0 + xb, y0 + yb, z0 + zb); -+ DBG_BOX_VTX(6, x0 - xb, y0 - yb, z0 + zb); -+ DBG_BOX_VTX(7, x0 + xb, y0 - yb, z0 + zb); -+#undef DBG_BOX_VTX -+ -+ gSPVertex(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(verts), 8, 0); -+ -+ gSPDisplayList(gDisplayListHead++, dl_debug_draw_box); -+ -+ if (box->yaw != 0) { -+ gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); -+ } -+ } -+} -+ -+void render_debug_boxes(void) -+{ -+ s32 i; -+ Mtx *mtx; -+ -+ if (sNumBoxes == 0) { -+ return; -+ } -+ -+ mtx = alloc_display_list(sizeof(Mtx)); -+ if (mtx == NULL) { -+ for (i = 0; i < sNumBoxes; ++i) { -+ mem_pool_free(gEffectsMemoryPool, sBoxes[i]); -+ } -+ sNumBoxes = 0; -+ return; -+ } -+ -+ //XXX: This is hacky. Ths camera's look-at matrix is stored in gMatStack[1], so this is a simple way -+ // of using it without reconstructing the matrix. -+ mtxf_to_mtx(mtx, gMatStack[1]); -+ gSPMatrix(gDisplayListHead++, mtx, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); -+ gSPDisplayList(gDisplayListHead++, dl_debug_box_begin); -+ -+ for (i = 0; i < sNumBoxes; ++i) { -+ render_box(sBoxes[i]); -+ mem_pool_free(gEffectsMemoryPool, sBoxes[i]); -+ } -+ -+ sNumBoxes = 0; -+} -diff --git a/src/game/debug_box.h b/src/game/debug_box.h -new file mode 100644 -index 00000000..cdb3dc9d ---- /dev/null -+++ b/src/game/debug_box.h -@@ -0,0 +1,25 @@ -+#ifndef _DEBUG_DRAW_CUBE_H -+#define _DEBUG_DRAW_CUBE_H -+ -+/** -+ * @file debug_box.h -+ * Draws debug boxes, see debug_box.inc.c for details -+ */ -+ -+#include "types.h" -+ -+/** -+ * The max amount of debug boxes before debug_box() just returns. -+ * You can set this to something higher like 1000, but things like text will stop rendering. -+ */ -+#define MAX_DEBUG_BOXES 100 -+ -+void debug_box(Vec3f center, Vec3f bounds); -+void debug_box_rot(Vec3f center, Vec3f bounds, s16 yaw); -+ -+void debug_box_pos(Vec3f pMin, Vec3f pMax); -+void debug_box_pos_rot(Vec3f pMin, Vec3f pMax, s16 yaw); -+ -+void render_debug_boxes(void); -+ -+#endif /* _DEBUG_DRAW_CUBE_H */ diff --git a/enhancements/ique_support.patch b/enhancements/ique_support.patch deleted file mode 100644 index f52b9b16a..000000000 --- a/enhancements/ique_support.patch +++ /dev/null @@ -1,312 +0,0 @@ -diff --git a/include/PR/console_type.h b/include/PR/console_type.h -new file mode 100644 -index 00000000..e60550ab ---- /dev/null -+++ b/include/PR/console_type.h -@@ -0,0 +1,7 @@ -+enum ConsoleType { -+ CONSOLE_N64, -+ CONSOLE_IQUE -+}; -+ -+extern enum ConsoleType gConsoleType; -+extern enum ConsoleType get_console_type(void); -diff --git a/lib/asm/skGetId.s b/lib/asm/skGetId.s -new file mode 100644 -index 00000000..8fb4c449 ---- /dev/null -+++ b/lib/asm/skGetId.s -@@ -0,0 +1,18 @@ -+# Code by stuckpixel -+ -+.set noreorder -+.set gp=64 -+ -+.include "macros.inc" -+ -+glabel skGetId -+ li $v0, 0 -+ li $t0, 0xA4300014 -+ lw $t1, 0x00($t0) -+ nop -+ jr $ra -+ nop -+ nop -+ nop -+ nop -+ nop -diff --git a/lib/src/__osViSwapContext.c b/lib/src/__osViSwapContext.c -index d7741994..9aced7cf 100644 ---- a/lib/src/__osViSwapContext.c -+++ b/lib/src/__osViSwapContext.c -@@ -52,7 +52,9 @@ void __osViSwapContext() { - HW_REG(VI_INTR_REG, u32) = s0->fldRegs[field].vIntr; - HW_REG(VI_X_SCALE_REG, u32) = s1->unk20; - HW_REG(VI_Y_SCALE_REG, u32) = s1->unk2c; -- HW_REG(VI_CONTROL_REG, u32) = s1->features; -+ /* Make sure bit 13 is cleared. Otherwise, graphics will be corrupted on -+ * iQue Player. This has no effect on N64. */ -+ HW_REG(VI_CONTROL_REG, u32) = s1->features & ~(1 << 13); - D_80334914 = D_80334910; - D_80334910 = s1; - *D_80334914 = *D_80334910; -diff --git a/lib/src/consoleType.c b/lib/src/consoleType.c -new file mode 100644 -index 00000000..ef08d1ef ---- /dev/null -+++ b/lib/src/consoleType.c -@@ -0,0 +1,12 @@ -+#include "libultra_internal.h" -+#include -+ -+enum ConsoleType gConsoleType; -+ -+void skGetId(u32 *out); -+ -+enum ConsoleType get_console_type(void) { -+ u32 id = 0; -+ skGetId(&id); -+ return (id == 0) ? CONSOLE_N64 : CONSOLE_IQUE; -+} -diff --git a/lib/src/osEepromProbe.c b/lib/src/osEepromProbe.c -index d550b846..bbaf2bcc 100644 ---- a/lib/src/osEepromProbe.c -+++ b/lib/src/osEepromProbe.c -@@ -1,4 +1,5 @@ - #include "libultra_internal.h" -+#include - - // TODO: merge with osEepromWrite - typedef struct { -@@ -13,11 +14,23 @@ s32 osEepromProbe(OSMesgQueue *mq) { - unkStruct sp18; - - __osSiGetAccess(); -- status = __osEepStatus(mq, &sp18); -- if (status == 0 && (sp18.unk00 & 0x8000) != 0) { -- status = 1; -- } else { -- status = 0; -+ if (gConsoleType == CONSOLE_N64) { -+ status = __osEepStatus(mq, &sp18); -+ if (status == 0 && (sp18.unk00 & 0x8000) != 0) { -+ status = 1; -+ } else { -+ status = 0; -+ } -+ } else if (gConsoleType == CONSOLE_IQUE) { -+ s32 __osBbEepromSize = * (s32*) 0x80000360; -+ -+ if (__osBbEepromSize == 0x200) { -+ status = 1; -+ } -+ -+ if (__osBbEepromSize == 0x800) { -+ status = 2; -+ } - } - __osSiRelAccess(); - return status; -diff --git a/lib/src/osEepromRead.c b/lib/src/osEepromRead.c -index ea784b2c..116dae2d 100644 ---- a/lib/src/osEepromRead.c -+++ b/lib/src/osEepromRead.c -@@ -1,4 +1,5 @@ - #include "libultra_internal.h" -+#include - - extern u8 _osLastSentSiCmd; - -@@ -42,33 +43,44 @@ s32 osEepromRead(OSMesgQueue *mq, u8 address, u8 *buffer) { - return -1; - } - __osSiGetAccess(); -- sp34 = __osEepStatus(mq, &sp28); -- if (sp34 != 0 || sp28.unk00 != 0x8000) { -+ if (gConsoleType == CONSOLE_N64) { -+ sp34 = __osEepStatus(mq, &sp28); -+ if (sp34 != 0 || sp28.unk00 != 0x8000) { - -- return 8; -- } -- while (sp28.unk02 & 0x80) { -- __osEepStatus(mq, &sp28); -- } -- __osPackEepReadData(address); -- sp34 = __osSiRawStartDma(OS_WRITE, &D_80365E00); -- osRecvMesg(mq, NULL, OS_MESG_BLOCK); -- for (sp30 = 0; sp30 < 0x10; sp30++) { -- (D_80365E00)[sp30] = 255; -- } -- D_80365E3C = 0; -- sp34 = __osSiRawStartDma(OS_READ, D_80365E00); -- _osLastSentSiCmd = 4; -- osRecvMesg(mq, NULL, OS_MESG_BLOCK); -- for (sp30 = 0; sp30 < 4; sp30++) { -- sp2c++; -- } -- sp20 = *(unkStruct2 *) sp2c; -- sp34 = (sp20.unk01 & 0xc0) >> 4; -- if (sp34 == 0) { -- for (sp30 = 0; sp30 < 8; sp30++) { -- *buffer++ = ((u8 *) &sp20.unk04)[sp30]; -+ return 8; -+ } -+ while (sp28.unk02 & 0x80) { -+ __osEepStatus(mq, &sp28); -+ } -+ __osPackEepReadData(address); -+ sp34 = __osSiRawStartDma(OS_WRITE, &D_80365E00); -+ osRecvMesg(mq, NULL, OS_MESG_BLOCK); -+ for (sp30 = 0; sp30 < 0x10; sp30++) { -+ (D_80365E00)[sp30] = 255; - } -+ D_80365E3C = 0; -+ sp34 = __osSiRawStartDma(OS_READ, D_80365E00); -+ _osLastSentSiCmd = 4; -+ osRecvMesg(mq, NULL, OS_MESG_BLOCK); -+ for (sp30 = 0; sp30 < 4; sp30++) { -+ sp2c++; -+ } -+ sp20 = *(unkStruct2 *) sp2c; -+ sp34 = (sp20.unk01 & 0xc0) >> 4; -+ if (sp34 == 0) { -+ for (sp30 = 0; sp30 < 8; sp30++) { -+ *buffer++ = ((u8 *) &sp20.unk04)[sp30]; -+ } -+ } -+ } else if (gConsoleType == CONSOLE_IQUE) { -+ u8 *__osBbEepromAddress = * (u8**) 0x8000035C; -+ s32 i; -+ -+ for (i = 0; i < 8; i++) { -+ buffer[i] = __osBbEepromAddress[(address << 3) + i]; -+ } -+ -+ sp34 = 0; - } - __osSiRelAccess(); - return sp34; -diff --git a/lib/src/osEepromWrite.c b/lib/src/osEepromWrite.c -index 1a86477b..52a23e5e 100644 ---- a/lib/src/osEepromWrite.c -+++ b/lib/src/osEepromWrite.c -@@ -1,5 +1,6 @@ - #include "libultra_internal.h" - #include "osContInternal.h" -+#include - - #ifndef AVOID_UB - ALIGNED8 u32 D_80365E00[15]; -@@ -52,36 +53,47 @@ s32 osEepromWrite(OSMesgQueue *mq, u8 address, u8 *buffer) { - } - - __osSiGetAccess(); -- sp34 = __osEepStatus(mq, &sp1c); -+ if (gConsoleType == CONSOLE_N64) { -+ sp34 = __osEepStatus(mq, &sp1c); - -- if (sp34 != 0 || sp1c.unk00 != 0x8000) { -- return 8; -- } -+ if (sp34 != 0 || sp1c.unk00 != 0x8000) { -+ return 8; -+ } - -- while (sp1c.unk02 & 0x80) { -- __osEepStatus(mq, &sp1c); -- } -+ while (sp1c.unk02 & 0x80) { -+ __osEepStatus(mq, &sp1c); -+ } - -- __osPackEepWriteData(address, buffer); -+ __osPackEepWriteData(address, buffer); - -- sp34 = __osSiRawStartDma(OS_WRITE, &D_80365E00); -- osRecvMesg(mq, NULL, OS_MESG_BLOCK); -+ sp34 = __osSiRawStartDma(OS_WRITE, &D_80365E00); -+ osRecvMesg(mq, NULL, OS_MESG_BLOCK); - -- for (sp30 = 0; sp30 < 0x10; sp30++) { -- (D_80365E00)[sp30] = 255; -- } -+ for (sp30 = 0; sp30 < 0x10; sp30++) { -+ (D_80365E00)[sp30] = 255; -+ } - -- D_80365E3C = 0; -- sp34 = __osSiRawStartDma(OS_READ, D_80365E00); -- _osLastSentSiCmd = 5; -- osRecvMesg(mq, NULL, OS_MESG_BLOCK); -+ D_80365E3C = 0; -+ sp34 = __osSiRawStartDma(OS_READ, D_80365E00); -+ _osLastSentSiCmd = 5; -+ osRecvMesg(mq, NULL, OS_MESG_BLOCK); - -- for (sp30 = 0; sp30 < 4; sp30++) { -- sp2c++; -- } -+ for (sp30 = 0; sp30 < 4; sp30++) { -+ sp2c++; -+ } -+ -+ sp20 = *(unkStruct2 *) sp2c; -+ sp34 = (sp20.unk01 & 0xc0) >> 4; -+ } else if (gConsoleType == CONSOLE_N64) { -+ u8 *__osBbEepromAddress = * (u8**) 0x8000035C; -+ s32 i; - -- sp20 = *(unkStruct2 *) sp2c; -- sp34 = (sp20.unk01 & 0xc0) >> 4; -+ for (i = 0; i < 8; i++) { -+ __osBbEepromAddress[(address << 3) + i] = buffer[i]; -+ } -+ -+ sp34 = 0; -+ } - __osSiRelAccess(); - return sp34; - } -diff --git a/lib/src/osInitialize.c b/lib/src/osInitialize.c -index 3cbca8ea..20723b2f 100644 ---- a/lib/src/osInitialize.c -+++ b/lib/src/osInitialize.c -@@ -1,6 +1,7 @@ - #include "libultra_internal.h" - #include "hardware.h" - #include -+#include - - #define PIF_ADDR_START (void *) 0x1FC007FC - -@@ -46,6 +47,7 @@ void osInitialize(void) { - UNUSED u32 eu_sp30; - #endif - UNUSED u32 sp2c; -+ gConsoleType = get_console_type(); - D_80365CD0 = TRUE; - __osSetSR(__osGetSR() | 0x20000000); - __osSetFpcCsr(0x01000800); -diff --git a/sm64.ld b/sm64.ld -index 3275819f..6f97698f 100755 ---- a/sm64.ld -+++ b/sm64.ld -@@ -261,6 +261,8 @@ SECTIONS - BUILD_DIR/libultra.a:func_802F7140.o(.text) - BUILD_DIR/libultra.a:func_802F71A0.o(.text) - BUILD_DIR/libultra.a:func_802F71F0.o(.text) -+ BUILD_DIR/libultra.a:consoleType.o(.text) -+ BUILD_DIR/libultra.a:skGetId.o(.text) - BUILD_DIR/lib/rsp.o(.text); - #else - BUILD_DIR/src/game*.o(.text); -@@ -371,6 +373,8 @@ SECTIONS - BUILD_DIR/libultra.a:__osGetCause.o(.text); - BUILD_DIR/libultra.a:__osAtomicDec.o(.text); - BUILD_DIR/libultra.a:guLookAtRef.o(.text); /* Fast3DEX2 only */ -+ BUILD_DIR/libultra.a:consoleType.o(.text); -+ BUILD_DIR/libultra.a:skGetId.o(.text); - BUILD_DIR/lib/rsp.o(.text); - #endif - diff --git a/enhancements/mem_error_screen.patch b/enhancements/mem_error_screen.patch deleted file mode 100644 index 87d995d68..000000000 --- a/enhancements/mem_error_screen.patch +++ /dev/null @@ -1,298 +0,0 @@ -diff --git a/Makefile b/Makefile -index 88c8dbbe..f60df04f 100644 ---- a/Makefile -+++ b/Makefile -@@ -382,6 +382,7 @@ $(BUILD_DIR)/include/text_strings.h: $(BUILD_DIR)/include/text_menu_strings.h - $(BUILD_DIR)/src/menu/file_select.o: $(BUILD_DIR)/include/text_strings.h - $(BUILD_DIR)/src/menu/star_select.o: $(BUILD_DIR)/include/text_strings.h - $(BUILD_DIR)/src/game/ingame_menu.o: $(BUILD_DIR)/include/text_strings.h -+$(BUILD_DIR)/src/game/mem_error_screen.o: $(BUILD_DIR)/include/text_strings.h - - ################################################################ - # TEXTURE GENERATION # -diff --git a/include/segments.h b/include/segments.h -index c98040a8..eeecb4f6 100644 ---- a/include/segments.h -+++ b/include/segments.h -@@ -1,6 +1,9 @@ - #ifndef _SEGMENTS_H - #define _SEGMENTS_H - -+/* Use expansion pack RAM */ -+#define USE_EXT_RAM 1 -+ - /* - * Memory addresses for segments. Ideally, this header file would not be - * needed, and the addresses would be defined in sm64.ld and linker-inserted -diff --git a/include/text_strings.h.in b/include/text_strings.h.in -index 4e36eb96..7aadf0cb 100644 ---- a/include/text_strings.h.in -+++ b/include/text_strings.h.in -@@ -25,6 +25,11 @@ - #define TEXT_PAUSE _("PAUSE") // Pause text, Castle Courses - #define TEXT_HUD_CONGRATULATIONS _("CONGRATULATIONS") // Course Complete Text, Bowser Courses - -+// Memory Expansion Error Screen -+#define TEXT_CONSOLE_8MB _("If you're using an N64 console, then you will need to buy an\nExpansion Pak to play this ROM hack.") -+#define TEXT_PJ64 _("If you are using PJ64 1.6, go to:\nOptions > Settings > Rom Settings Tab > Memory Size\nthen select 8 MB from the drop-down box.") -+#define TEXT_PJ64_2 _("If you are using PJ64 2.X, go to:\nOptions > Settings > Config: > Memory Size, select 8 MB") -+ - #if defined(VERSION_JP) || defined(VERSION_SH) - - /** -diff --git a/levels/entry.c b/levels/entry.c -index 015eeb6b..cc010ca1 100644 ---- a/levels/entry.c -+++ b/levels/entry.c -@@ -15,3 +15,12 @@ const LevelScript level_script_entry[] = { - EXECUTE(/*seg*/ 0x14, /*script*/ _introSegmentRomStart, /*scriptEnd*/ _introSegmentRomEnd, /*entry*/ level_intro_entry_1), - JUMP(/*target*/ level_script_entry), - }; -+ -+const LevelScript level_script_entry_error_screen[] = { -+ INIT_LEVEL(), -+ SLEEP(/*frames*/ 2), -+ BLACKOUT(/*active*/ FALSE), -+ SET_REG(/*value*/ 0), -+ EXECUTE(/*seg*/ 0x14, /*script*/ _introSegmentRomStart, /*scriptEnd*/ _introSegmentRomEnd, /*entry*/ level_intro_entry_error_screen), -+ JUMP(/*target*/ level_script_entry_error_screen), -+}; -diff --git a/levels/intro/geo.c b/levels/intro/geo.c -index 8ac70024..72766f3f 100644 ---- a/levels/intro/geo.c -+++ b/levels/intro/geo.c -@@ -15,6 +15,24 @@ - - #include "levels/intro/header.h" - -+const GeoLayout intro_geo_error_screen[] = { -+ GEO_NODE_SCREEN_AREA(0, SCREEN_WIDTH/2, SCREEN_HEIGHT/2, SCREEN_WIDTH/2, SCREEN_HEIGHT/2), -+ GEO_OPEN_NODE(), -+ GEO_ZBUFFER(0), -+ GEO_OPEN_NODE(), -+ GEO_NODE_ORTHO(100), -+ GEO_OPEN_NODE(), -+ GEO_BACKGROUND_COLOR(0x0001), -+ GEO_CLOSE_NODE(), -+ GEO_CLOSE_NODE(), -+ GEO_ZBUFFER(0), -+ GEO_OPEN_NODE(), -+ GEO_ASM(0, geo18_display_error_message), -+ GEO_CLOSE_NODE(), -+ GEO_CLOSE_NODE(), -+ GEO_END(), -+}; -+ - // 0x0E0002D0 - const GeoLayout intro_geo_0002D0[] = { - GEO_NODE_SCREEN_AREA(0, SCREEN_WIDTH/2, SCREEN_HEIGHT/2, SCREEN_WIDTH/2, SCREEN_HEIGHT/2), -diff --git a/levels/intro/header.h b/levels/intro/header.h -index e0f6292d..8f77fb26 100644 ---- a/levels/intro/header.h -+++ b/levels/intro/header.h -@@ -26,4 +26,8 @@ extern const LevelScript script_intro_L3[]; - extern const LevelScript script_intro_L4[]; - extern const LevelScript script_intro_L5[]; - -+extern const GeoLayout intro_geo_error_screen[]; -+extern const LevelScript level_intro_entry_error_screen[]; -+extern Gfx *geo18_display_error_message(u32 run, UNUSED struct GraphNode *sp44, UNUSED u32 sp48); -+ - #endif -diff --git a/levels/intro/script.c b/levels/intro/script.c -index 4975dbb2..5ee6c688 100644 ---- a/levels/intro/script.c -+++ b/levels/intro/script.c -@@ -18,6 +18,21 @@ - #include "make_const_nonconst.h" - #include "levels/intro/header.h" - -+const LevelScript level_intro_entry_error_screen[] = { -+ INIT_LEVEL(), -+ FIXED_LOAD(/*loadAddr*/ _goddardSegmentStart, /*romStart*/ _goddardSegmentRomStart, /*romEnd*/ _goddardSegmentRomEnd), -+ LOAD_MIO0(/*seg*/ 0x07, _intro_segment_7SegmentRomStart, _intro_segment_7SegmentRomEnd), -+ ALLOC_LEVEL_POOL(), -+ -+ AREA(/*index*/ 1, intro_geo_error_screen), -+ END_AREA(), -+ -+ FREE_LEVEL_POOL(), -+ LOAD_AREA(/*area*/ 1), -+ SLEEP(/*frames*/ 32767), -+ EXIT_AND_EXECUTE(/*seg*/ 0x14, _introSegmentRomStart, _introSegmentRomEnd, level_intro_entry_error_screen), -+}; -+ - const LevelScript level_intro_entry_1[] = { - INIT_LEVEL(), - FIXED_LOAD(/*loadAddr*/ _goddardSegmentStart, /*romStart*/ _goddardSegmentRomStart, /*romEnd*/ _goddardSegmentRomEnd), -diff --git a/src/engine/level_script.h b/src/engine/level_script.h -index 89bfb4ed..0ea607c5 100644 ---- a/src/engine/level_script.h -+++ b/src/engine/level_script.h -@@ -4,5 +4,6 @@ - struct LevelCommand *level_script_execute(struct LevelCommand *cmd); - - extern u8 level_script_entry[]; -+extern u8 level_script_entry_error_screen[]; - - #endif /* _LEVEL_SCRIPT_H */ -diff --git a/src/game/main.c b/src/game/main.c -index a3afffee..8b05fcf1 100644 ---- a/src/game/main.c -+++ b/src/game/main.c -@@ -12,6 +12,7 @@ - #include "segments.h" - #include "main.h" - #include "thread6.h" -+#include "mem_error_screen.h" - - // Message IDs - #define MESG_SP_COMPLETE 100 -@@ -125,6 +126,10 @@ void alloc_pool(void) { - void *start = (void *) SEG_POOL_START; - void *end = (void *) SEG_POOL_END; - -+ // Detect memory size -+ if (does_pool_end_lie_out_of_bounds(end)) -+ end = (void *)SEG_POOL_END_4MB; -+ - main_pool_init(start, end); - gEffectsMemoryPool = mem_pool_init(0x4000, MEMORY_POOL_LEFT); - } -@@ -330,7 +335,10 @@ void thread3_main(UNUSED void *arg) { - create_thread(&gSoundThread, 4, thread4_sound, NULL, gThread4Stack + 0x2000, 20); - osStartThread(&gSoundThread); - -- create_thread(&gGameLoopThread, 5, thread5_game_loop, NULL, gThread5Stack + 0x2000, 10); -+ if (!gNotEnoughMemory) -+ create_thread(&gGameLoopThread, 5, thread5_game_loop, NULL, gThread5Stack + 0x2000, 10); -+ else -+ create_thread(&gGameLoopThread, 5, thread5_mem_error_message_loop, NULL, gThread5Stack + 0x2000, 10); - osStartThread(&gGameLoopThread); - - while (1) { -diff --git a/src/game/mem_error_screen.c b/src/game/mem_error_screen.c -new file mode 100644 -index 00000000..81efaf91 ---- /dev/null -+++ b/src/game/mem_error_screen.c -@@ -0,0 +1,104 @@ -+/* clang-format off */ -+/* -+ * mem_error_screen.inc.c -+ * -+ * This enhancement should be used for ROM hacks that require the expansion pak. -+ * -+ */ -+/* clang-format on */ -+ -+#include -+#include "segments.h" -+#include "text_strings.h" -+#include "game_init.h" -+#include "main.h" -+#include "print.h" -+#include "ingame_menu.h" -+#include "segment2.h" -+#include "../engine/level_script.h" -+ -+// Ensure that USE_EXT_RAM is defined. -+#ifndef USE_EXT_RAM -+#error You have to define USE_EXT_RAM in 'include/segments.h' -+#endif -+ -+// Require 8 MB of RAM, even if the pool doesn't go into extended memory. -+// Change the '8' to whatever MB limit you want. -+// Note: only special emulators allow for RAM sizes above 8 MB. -+#define REQUIRED_MIN_MEM_SIZE 1048576 * 8 -+ -+u8 gNotEnoughMemory = FALSE; -+u8 gDelayForErrorMessage = 0; -+ -+u8 does_pool_end_lie_out_of_bounds(void *end) { -+ u32 endPhy = ((u32) end) & 0x1FFFFFFF; -+ u32 memSize = *((u32 *) 0x80000318); -+ -+ if (endPhy > memSize) { -+ gNotEnoughMemory = TRUE; -+ return TRUE; -+ } else { -+ if (memSize < REQUIRED_MIN_MEM_SIZE) { -+ gNotEnoughMemory = TRUE; -+ } -+ return FALSE; -+ } -+} -+ -+// If you're using an N64 console, then you will need to buy an\nexpansion pak to play this ROM hack. -+u8 text_console_8mb[] = { TEXT_CONSOLE_8MB }; -+ -+// If you are using PJ64 1.6, go to: Options ► Settings ► Rom Settings Tab ► Memory Size then select 8 -+// MB from the drop-down box. -+u8 text_pj64[] = { TEXT_PJ64 }; -+ -+// If you are using PJ64 2.X, go to: Options ► Settings ► Config: ► Memory Size, select 8 MB -+u8 text_pj64_2[] = { TEXT_PJ64_2 }; -+ -+Gfx *geo18_display_error_message(u32 run, UNUSED struct GraphNode *sp44, UNUSED u32 sp48) { -+ if (run) { -+ if (gDelayForErrorMessage > 0) { -+ // Draw color text title. -+ print_text(10, 210, "ERROR Need more memory"); -+ -+ // Init generic text rendering -+ create_dl_ortho_matrix(); -+ gSPDisplayList(gDisplayListHead++, -+ dl_ia_text_begin); // Init rendering stuff for generic text -+ -+ // Set text color to white -+ gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); -+ -+ print_generic_string(8, 170, text_console_8mb); -+ print_generic_string(8, 120, text_pj64); -+ print_generic_string(8, 54, text_pj64_2); -+ -+ // Cleanup -+ gSPDisplayList(gDisplayListHead++, -+ dl_ia_text_end); // Reset back to default render settings. -+ gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); -+ } else { -+ gDelayForErrorMessage += 1; -+ } -+ } -+ -+ return 0; -+} -+ -+// Basic main loop for the error screen. Note that controllers are not enabled here. -+void thread5_mem_error_message_loop(UNUSED void *arg) { -+ struct LevelCommand *addr; -+ -+ setup_game_memory(); -+ set_vblank_handler(2, &gGameVblankHandler, &gGameVblankQueue, (OSMesg) 1); -+ -+ addr = segmented_to_virtual(level_script_entry_error_screen); -+ -+ rendering_init(); -+ -+ while (1) { -+ config_gfx_pool(); -+ addr = level_script_execute(addr); -+ display_and_vsync(); -+ } -+} -\ No newline at end of file -diff --git a/src/game/mem_error_screen.h b/src/game/mem_error_screen.h -new file mode 100644 -index 00000000..9fbff34c ---- /dev/null -+++ b/src/game/mem_error_screen.h -@@ -0,0 +1,8 @@ -+#ifndef MEM_ERROR_SCREEN_H -+#define MEM_ERROR_SCREEN_H -+ -+extern u8 gNotEnoughMemory; -+void thread5_mem_error_message_loop(UNUSED void *arg); -+u8 does_pool_end_lie_out_of_bounds(void *end); -+ -+#endif diff --git a/enhancements/record_demo.patch b/enhancements/record_demo.patch deleted file mode 100644 index 5d2c25dea..000000000 --- a/enhancements/record_demo.patch +++ /dev/null @@ -1,185 +0,0 @@ -diff --git a/src/game/game_init.c b/src/game/game_init.c -index a4302124..5ffbf3ed 100644 ---- a/src/game/game_init.c -+++ b/src/game/game_init.c -@@ -11,6 +11,7 @@ - #include "game_init.h" - #include "main.h" - #include "memory.h" -+#include "object_list_processor.h" - #include "profiler.h" - #include "save_file.h" - #include "seq_ids.h" -@@ -335,6 +336,45 @@ void display_and_vsync(void) { - gGlobalTimer++; - } - -+/* -+ * This enhancement allows you to record gameplay demos for the mario head screen. -+ * -+ * Note: -+ * This enhancement does require the lastest versions of PJ64 from the nightly builds, -+ * because it uses the javascript API to automatically dump the demo files from RAM -+ * once the demo is completed. See enhancements/RecordDemo.js for more info -+ * -+*/ -+ -+#include "../src/game/mario.h" -+ -+#define DEMOREC_STATUS_NOT_RECORDING 0 -+#define DEMOREC_STATUS_PREPARING 1 -+#define DEMOREC_STATUS_RECORDING 2 -+#define DEMOREC_STATUS_STOPPING 3 -+#define DEMOREC_STATUS_DONE 4 -+ -+#define DEMOREC_PRINT_X 10 -+#define DEMOREC_PRINT_Y 10 -+ -+#define DEMOREC_DONE_DELAY 60 // Show "DONE" message for 2 seconds. -+ -+#define DEMOREC_MAX_INPUTS 1025 // Max number of recorded inputs. -+ -+/* -+ DO NOT REMOVE, MODIFY, OR MAKE A COPY OF THIS EXACT STRING! -+ This is here so that the js dump script can find the control variables easily. -+*/ -+char gDemoRecTag[] = "DEMORECVARS"; -+ -+// Control variables. It is easier if they are each 4 byte aligned, which is why they are u32. -+u32 gRecordingStatus = DEMOREC_STATUS_NOT_RECORDING; -+u32 gDoneDelay = 0; -+u32 gNumOfRecordedInputs = 0; -+struct DemoInput gRecordedInputs[DEMOREC_MAX_INPUTS]; -+struct DemoInput* gRecordedInputsPtr = (struct DemoInput*)gRecordedInputs; -+struct DemoInput gRecordedDemoInputCopy; -+ - // this function records distinct inputs over a 255-frame interval to RAM locations and was likely - // used to record the demo sequences seen in the final game. This function is unused. - static void record_demo(void) { -@@ -368,6 +408,118 @@ static void record_demo(void) { - gRecordedDemoInput.timer++; - } - -+void record_new_demo_input(void) { -+ if(gRecordedDemoInput.timer == 1 && gRecordedDemoInputCopy.timer > 0) { -+ gRecordedInputs[gNumOfRecordedInputs].timer = gRecordedDemoInputCopy.timer; -+ gRecordedInputs[gNumOfRecordedInputs + 1].timer = 0; -+ gRecordedInputs[gNumOfRecordedInputs].rawStickX = gRecordedDemoInputCopy.rawStickX; -+ gRecordedInputs[gNumOfRecordedInputs + 1].rawStickX = gRecordedDemoInputCopy.rawStickX; -+ gRecordedInputs[gNumOfRecordedInputs].rawStickY = gRecordedDemoInputCopy.rawStickY; -+ gRecordedInputs[gNumOfRecordedInputs + 1].rawStickY = gRecordedDemoInputCopy.rawStickY; -+ gRecordedInputs[gNumOfRecordedInputs].buttonMask = gRecordedDemoInputCopy.buttonMask; -+ gRecordedInputs[gNumOfRecordedInputs + 1].buttonMask = gRecordedDemoInputCopy.buttonMask; -+ gNumOfRecordedInputs++; -+ } -+} -+ -+// Self explanitory -+void copy_gRecordedDemoInput(void) { -+ gRecordedDemoInputCopy.timer = gRecordedDemoInput.timer; -+ gRecordedDemoInputCopy.rawStickX = gRecordedDemoInput.rawStickX; -+ gRecordedDemoInputCopy.rawStickY = gRecordedDemoInput.rawStickY; -+ gRecordedDemoInputCopy.buttonMask = gRecordedDemoInput.buttonMask; -+} -+ -+// Runs when the demo is recording. -+void recording(void) { -+ -+ // Force-stop when someone makes too many inputs. -+ if(gNumOfRecordedInputs + 1 > DEMOREC_MAX_INPUTS) { -+ gRecordingStatus = DEMOREC_STATUS_STOPPING; -+ return; -+ } -+ -+ copy_gRecordedDemoInput(); -+ record_demo(); // Defined in game.c -+ record_new_demo_input(); -+} -+ -+// Makes sure the last demo input is zeroed out, to make it look more clean. -+void record_cleanup(void) { -+ gRecordedInputs[gNumOfRecordedInputs].timer = 0; -+ gRecordedInputs[gNumOfRecordedInputs].rawStickX = 0; -+ gRecordedInputs[gNumOfRecordedInputs].rawStickY = 0; -+ gRecordedInputs[gNumOfRecordedInputs].buttonMask = 0; -+ -+ // Make sure the done delay is reset before moving to DONE status. -+ gDoneDelay = 0; -+} -+ -+void record_run(void) { -+ switch(gRecordingStatus) { -+ case DEMOREC_STATUS_NOT_RECORDING: -+ break; -+ case DEMOREC_STATUS_PREPARING: -+ if(gMarioObject != NULL && gCurrLevelNum >= 5) { // If the game is in an active level -+ gRecordingStatus = DEMOREC_STATUS_RECORDING; -+ -+ // A bit of a hack, but it works. -+ gNumOfRecordedInputs = 1; -+ gRecordedInputs[0].timer = gCurrLevelNum; -+ gRecordedInputs[0].rawStickX = 0; -+ gRecordedInputs[0].rawStickY = 0; -+ gRecordedInputs[0].buttonMask = 0; -+ } -+ break; -+ case DEMOREC_STATUS_RECORDING: -+ recording(); -+ break; -+ case DEMOREC_STATUS_DONE: -+ if(gDoneDelay > DEMOREC_DONE_DELAY) -+ gRecordingStatus = DEMOREC_STATUS_NOT_RECORDING; -+ else -+ gDoneDelay++; -+ break; -+ } -+} -+ -+// Prints the status on the bottom-left side of the screen in colorful text. -+void print_status(void) { -+ switch(gRecordingStatus) { -+ case DEMOREC_STATUS_PREPARING: -+ print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "READY"); -+ break; -+ case DEMOREC_STATUS_RECORDING: -+ print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "REC"); -+ break; -+ case DEMOREC_STATUS_STOPPING: -+ print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "WAIT"); -+ break; -+ case DEMOREC_STATUS_DONE: -+ print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "DONE"); -+ break; -+ } -+} -+ -+// Main function that should be called from thread5_game_loop() -+void recordingDemo(void) { -+ // Mario needs to enter directly into a level and not from a warp, -+ // so the debug level select is used for that. -+ gDebugLevelSelect = TRUE; -+ -+ if(gPlayer1Controller->buttonPressed & L_TRIG) { -+ if(gRecordingStatus == DEMOREC_STATUS_NOT_RECORDING) { -+ gRecordingStatus = DEMOREC_STATUS_PREPARING; -+ } else if (gRecordingStatus == DEMOREC_STATUS_RECORDING) { -+ gRecordingStatus = DEMOREC_STATUS_STOPPING; -+ record_cleanup(); -+ } -+ } -+ -+ record_run(); -+ print_status(); -+} -+ - // take the updated controller struct and calculate - // the new x, y, and distance floats. - void adjust_analog_stick(struct Controller *controller) { -@@ -623,6 +775,7 @@ void thread5_game_loop(UNUSED void *arg) { - audio_game_loop_tick(); - config_gfx_pool(); - read_controller_inputs(); -+ recordingDemo(); - addr = level_script_execute(addr); - display_and_vsync(); - From fde15809b71ea915e52541cb710efea454922bca Mon Sep 17 00:00:00 2001 From: fgsfds Date: Tue, 7 Jul 2020 20:57:18 +0300 Subject: [PATCH 06/26] add sanity checks in save_file.c though it would be a better idea to solve the problem that leads to -1 being passed to these instead --- src/game/save_file.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/game/save_file.c b/src/game/save_file.c index fea9b5143..45ac71f4a 100644 --- a/src/game/save_file.c +++ b/src/game/save_file.c @@ -342,6 +342,9 @@ static void save_file_bswap(struct SaveBuffer *buf) { } void save_file_do_save(s32 fileIndex) { + if (fileIndex < 0 || fileIndex >= NUM_SAVE_FILES) + return; + if (gSaveFileModified) #ifdef TEXTSAVES { @@ -370,6 +373,9 @@ void save_file_do_save(s32 fileIndex) { } void save_file_erase(s32 fileIndex) { + if (fileIndex < 0 || fileIndex >= NUM_SAVE_FILES) + return; + touch_high_score_ages(fileIndex); bzero(&gSaveBuffer.files[fileIndex][0], sizeof(gSaveBuffer.files[fileIndex][0])); @@ -379,7 +385,8 @@ void save_file_erase(s32 fileIndex) { //! Needs to be s32 to match on -O2, despite no return value. BAD_RETURN(s32) save_file_copy(s32 srcFileIndex, s32 destFileIndex) { - UNUSED s32 pad; + if (srcFileIndex < 0 || srcFileIndex >= NUM_SAVE_FILES || destFileIndex < 0 || destFileIndex >= NUM_SAVE_FILES) + return; touch_high_score_ages(destFileIndex); bcopy(&gSaveBuffer.files[srcFileIndex][0], &gSaveBuffer.files[destFileIndex][0], From 92e869d2630ea61b60a7487fb4d35f456034661f Mon Sep 17 00:00:00 2001 From: fgsfds Date: Wed, 8 Jul 2020 12:51:15 +0300 Subject: [PATCH 07/26] roll back to using SDL_Delay/SDL_GetTicks --- src/pc/gfx/gfx_sdl2.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c index a39b76d27..a2e433670 100644 --- a/src/pc/gfx/gfx_sdl2.c +++ b/src/pc/gfx/gfx_sdl2.c @@ -52,8 +52,8 @@ static void (*kb_all_keys_up)(void) = NULL; // whether to use timer for frame control static bool use_timer = true; -static Uint64 qpc_freq = 1; -static Uint64 frame_time = 1; +// time between consequtive game frames +static const Uint32 frame_time = 1000 / FRAMERATE; const SDL_Scancode windows_scancode_table[] = { /* 0 1 2 3 4 5 6 7 */ @@ -148,8 +148,8 @@ static inline void gfx_sdl_set_vsync(int mode) { use_timer = false; return; } else { - printf("could not determine swap interval, falling back to timer sync\n"); - mode = 0; + printf("could not determine swap interval, falling back to one vblank + timer sync\n"); + mode = 1; } } @@ -224,9 +224,6 @@ static void gfx_sdl_init(const char *window_title) { gfx_sdl_set_fullscreen(); - qpc_freq = SDL_GetPerformanceFrequency(); - frame_time = qpc_freq / FRAMERATE; - for (size_t i = 0; i < sizeof(windows_scancode_table) / sizeof(SDL_Scancode); i++) { inverted_scancode_table[windows_scancode_table[i]] = i; } @@ -242,13 +239,11 @@ static void gfx_sdl_init(const char *window_title) { } static void gfx_sdl_main_loop(void (*run_one_game_iter)(void)) { - Uint64 t = SDL_GetPerformanceCounter(); + Uint32 t = SDL_GetTicks(); run_one_game_iter(); - t = SDL_GetPerformanceCounter() - t; - if (t < frame_time && use_timer) { - const Uint64 us = (frame_time - t) * 1000000 / qpc_freq; - usleep(us); - } + t = SDL_GetTicks() - t; + if (t < frame_time && use_timer) + SDL_Delay(frame_time - t); } static void gfx_sdl_get_dimensions(uint32_t *width, uint32_t *height) { From 2982fcdb8e3d74534707aebae4cf17c7a15ddc5e Mon Sep 17 00:00:00 2001 From: fgsfds Date: Wed, 8 Jul 2020 13:07:05 +0300 Subject: [PATCH 08/26] time video frames instead of game frames --- src/pc/gfx/gfx_sdl2.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c index a2e433670..aef297809 100644 --- a/src/pc/gfx/gfx_sdl2.c +++ b/src/pc/gfx/gfx_sdl2.c @@ -54,6 +54,7 @@ static void (*kb_all_keys_up)(void) = NULL; static bool use_timer = true; // time between consequtive game frames static const Uint32 frame_time = 1000 / FRAMERATE; +static Uint32 frame_start = 0; const SDL_Scancode windows_scancode_table[] = { /* 0 1 2 3 4 5 6 7 */ @@ -239,11 +240,7 @@ static void gfx_sdl_init(const char *window_title) { } static void gfx_sdl_main_loop(void (*run_one_game_iter)(void)) { - Uint32 t = SDL_GetTicks(); run_one_game_iter(); - t = SDL_GetTicks() - t; - if (t < frame_time && use_timer) - SDL_Delay(frame_time - t); } static void gfx_sdl_get_dimensions(uint32_t *width, uint32_t *height) { @@ -327,10 +324,18 @@ static void gfx_sdl_set_keyboard_callbacks(kb_callback_t on_key_down, kb_callbac } static bool gfx_sdl_start_frame(void) { + frame_start = SDL_GetTicks(); return true; } +static inline void sync_framerate_with_timer(void) { + const Uint32 elapsed = SDL_GetTicks() - frame_start; + if (elapsed < frame_time) + SDL_Delay(frame_time - elapsed); +} + static void gfx_sdl_swap_buffers_begin(void) { + if (use_timer) sync_framerate_with_timer(); SDL_GL_SwapWindow(wnd); } From 685efc81c6dd1f682012360015d2d9105eeb56c7 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Wed, 8 Jul 2020 13:31:34 +0300 Subject: [PATCH 09/26] update 60fps patch --- enhancements/60fps_ex.patch | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/enhancements/60fps_ex.patch b/enhancements/60fps_ex.patch index 01c179453..fe7c6e244 100644 --- a/enhancements/60fps_ex.patch +++ b/enhancements/60fps_ex.patch @@ -1890,10 +1890,19 @@ index 0467495..fa4eb33 100644 using namespace Microsoft::WRL; // For ComPtr diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c -index a39b76d..3fb1bb1 100644 +index aef2978..ff53b27 100644 --- a/src/pc/gfx/gfx_sdl2.c +++ b/src/pc/gfx/gfx_sdl2.c -@@ -141,7 +141,7 @@ static inline void gfx_sdl_set_vsync(int mode) { +@@ -53,7 +53,7 @@ static void (*kb_all_keys_up)(void) = NULL; + // whether to use timer for frame control + static bool use_timer = true; + // time between consequtive game frames +-static const Uint32 frame_time = 1000 / FRAMERATE; ++static const Uint32 frame_time = 1000 / (2 * FRAMERATE); + static Uint32 frame_start = 0; + + const SDL_Scancode windows_scancode_table[] = { +@@ -142,7 +142,7 @@ static inline void gfx_sdl_set_vsync(int mode) { if (mode > 1) { // try to detect refresh rate SDL_GL_SetSwapInterval(1); @@ -1902,15 +1911,6 @@ index a39b76d..3fb1bb1 100644 if (vblanks) { printf("determined swap interval: %d\n", vblanks); SDL_GL_SetSwapInterval(vblanks); -@@ -225,7 +225,7 @@ static void gfx_sdl_init(const char *window_title) { - gfx_sdl_set_fullscreen(); - - qpc_freq = SDL_GetPerformanceFrequency(); -- frame_time = qpc_freq / FRAMERATE; -+ frame_time = qpc_freq / (2 * FRAMERATE); - - for (size_t i = 0; i < sizeof(windows_scancode_table) / sizeof(SDL_Scancode); i++) { - inverted_scancode_table[windows_scancode_table[i]] = i; diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index ed6ee74..63679ad 100644 --- a/src/pc/pc_main.c From 572a4b698b524e58fb2e8d7dd44a49e861135cfa Mon Sep 17 00:00:00 2001 From: fgsfds Date: Thu, 9 Jul 2020 17:02:43 +0300 Subject: [PATCH 10/26] do vsync exactly like sm64-port does it maybe this will finally work better --- src/game/options_menu.c | 2 +- src/pc/configfile.c | 4 ++-- src/pc/configfile.h | 2 +- src/pc/gfx/gfx_sdl2.c | 19 +++++++++---------- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/game/options_menu.c b/src/game/options_menu.c index 8ce169f0f..56dba619e 100644 --- a/src/game/options_menu.c +++ b/src/game/options_menu.c @@ -255,7 +255,7 @@ static struct Option optsControls[] = { static struct Option optsVideo[] = { DEF_OPT_TOGGLE( optsVideoStr[0], &configWindow.fullscreen ), - DEF_OPT_CHOICE( optsVideoStr[5], &configWindow.vsync, vsyncChoices ), + DEF_OPT_TOGGLE( optsVideoStr[5], &configWindow.vsync ), DEF_OPT_CHOICE( optsVideoStr[1], &configFiltering, filterChoices ), DEF_OPT_TOGGLE( optsVideoStr[7], &configHUD ), DEF_OPT_BUTTON( optsVideoStr[4], optvideo_reset_window ), diff --git a/src/pc/configfile.c b/src/pc/configfile.c index f55334f43..7411d4f84 100644 --- a/src/pc/configfile.c +++ b/src/pc/configfile.c @@ -100,7 +100,7 @@ static const struct ConfigOption options[] = { {.name = "window_y", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.y}, {.name = "window_w", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.w}, {.name = "window_h", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.h}, - {.name = "vsync", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.vsync}, + {.name = "vsync", .type = CONFIG_TYPE_BOOL, .boolValue = &configWindow.vsync}, {.name = "texture_filtering", .type = CONFIG_TYPE_UINT, .uintValue = &configFiltering}, {.name = "master_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configMasterVolume}, {.name = "music_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configMusicVolume}, @@ -268,7 +268,7 @@ void configfile_load(const char *filename) { case CONFIG_TYPE_BOOL: if (strcmp(tokens[1], "true") == 0) *option->boolValue = true; - else if (strcmp(tokens[1], "false") == 0) + else *option->boolValue = false; break; case CONFIG_TYPE_UINT: diff --git a/src/pc/configfile.h b/src/pc/configfile.h index 848a34943..b92ae7bea 100644 --- a/src/pc/configfile.h +++ b/src/pc/configfile.h @@ -10,7 +10,7 @@ typedef struct { unsigned int x, y, w, h; - unsigned int vsync; + bool vsync; bool reset; bool fullscreen; bool exiting_fullscreen; diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c index aef297809..8729e07d8 100644 --- a/src/pc/gfx/gfx_sdl2.c +++ b/src/pc/gfx/gfx_sdl2.c @@ -53,8 +53,7 @@ static void (*kb_all_keys_up)(void) = NULL; // whether to use timer for frame control static bool use_timer = true; // time between consequtive game frames -static const Uint32 frame_time = 1000 / FRAMERATE; -static Uint32 frame_start = 0; +static const int frame_time = 1000 / FRAMERATE; const SDL_Scancode windows_scancode_table[] = { /* 0 1 2 3 4 5 6 7 */ @@ -138,8 +137,8 @@ int test_vsync(void) { return 0; } -static inline void gfx_sdl_set_vsync(int mode) { - if (mode > 1) { +static inline void gfx_sdl_set_vsync(const bool enabled) { + if (enabled) { // try to detect refresh rate SDL_GL_SetSwapInterval(1); const int vblanks = test_vsync(); @@ -149,13 +148,12 @@ static inline void gfx_sdl_set_vsync(int mode) { use_timer = false; return; } else { - printf("could not determine swap interval, falling back to one vblank + timer sync\n"); - mode = 1; + printf("could not determine swap interval, falling back to timer sync\n"); } } - SDL_GL_SetSwapInterval(mode); - use_timer = (mode <= 1); + use_timer = true; + SDL_GL_SetSwapInterval(0); } static void gfx_sdl_set_fullscreen(void) { @@ -324,14 +322,15 @@ static void gfx_sdl_set_keyboard_callbacks(kb_callback_t on_key_down, kb_callbac } static bool gfx_sdl_start_frame(void) { - frame_start = SDL_GetTicks(); return true; } static inline void sync_framerate_with_timer(void) { - const Uint32 elapsed = SDL_GetTicks() - frame_start; + static Uint32 last_time = 0; + const int elapsed = SDL_GetTicks() - last_time; if (elapsed < frame_time) SDL_Delay(frame_time - elapsed); + last_time += frame_time; } static void gfx_sdl_swap_buffers_begin(void) { From 4b15a8f7b680e89eb783bbc963e06f80b50b4dda Mon Sep 17 00:00:00 2001 From: fgsfds Date: Thu, 9 Jul 2020 17:24:15 +0300 Subject: [PATCH 11/26] add a catch for 240hz refresh rates hopefully 150, 180 and 210hz monitors don't exist --- src/pc/gfx/gfx_sdl2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c index 8729e07d8..25c9f0699 100644 --- a/src/pc/gfx/gfx_sdl2.c +++ b/src/pc/gfx/gfx_sdl2.c @@ -133,6 +133,7 @@ int test_vsync(void) { if (average > 57.0f && average < 63.0f) return 2; if (average > 86.0f && average < 94.0f) return 3; if (average > 115.0f && average < 125.0f) return 4; + if (average > 234.0f && average < 246.0f) return 8; return 0; } From 54ca76162a0f98b799af4bfd8a132f2a22ab207a Mon Sep 17 00:00:00 2001 From: fgsfds Date: Thu, 9 Jul 2020 17:33:04 +0300 Subject: [PATCH 12/26] update 60fps patch --- enhancements/60fps_ex.patch | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/enhancements/60fps_ex.patch b/enhancements/60fps_ex.patch index fe7c6e244..9f4593594 100644 --- a/enhancements/60fps_ex.patch +++ b/enhancements/60fps_ex.patch @@ -1890,24 +1890,28 @@ index 0467495..fa4eb33 100644 using namespace Microsoft::WRL; // For ComPtr diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c -index aef2978..ff53b27 100644 +index 25c9f06..a6461ae 100644 --- a/src/pc/gfx/gfx_sdl2.c +++ b/src/pc/gfx/gfx_sdl2.c @@ -53,7 +53,7 @@ static void (*kb_all_keys_up)(void) = NULL; // whether to use timer for frame control static bool use_timer = true; // time between consequtive game frames --static const Uint32 frame_time = 1000 / FRAMERATE; -+static const Uint32 frame_time = 1000 / (2 * FRAMERATE); - static Uint32 frame_start = 0; +-static const int frame_time = 1000 / FRAMERATE; ++static const int frame_time = 1000 / (2 * FRAMERATE); const SDL_Scancode windows_scancode_table[] = { -@@ -142,7 +142,7 @@ static inline void gfx_sdl_set_vsync(int mode) { - if (mode > 1) { + /* 0 1 2 3 4 5 6 7 */ +@@ -142,7 +142,11 @@ static inline void gfx_sdl_set_vsync(const bool enabled) { + if (enabled) { // try to detect refresh rate SDL_GL_SetSwapInterval(1); - const int vblanks = test_vsync(); -+ const int vblanks = test_vsync() / 2; ++ int vblanks = test_vsync(); ++ if (vblanks & 1) ++ vblanks = 0; // not divisible by 60, fuck that ++ else ++ vblanks /= 2; if (vblanks) { printf("determined swap interval: %d\n", vblanks); SDL_GL_SetSwapInterval(vblanks); From 597546125ec0847f459401055e3a76faa7b85832 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Fri, 10 Jul 2020 15:44:58 +0300 Subject: [PATCH 13/26] don't assume first frame happens at 0 ticks --- src/pc/gfx/gfx_sdl2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c index 25c9f0699..4d907893a 100644 --- a/src/pc/gfx/gfx_sdl2.c +++ b/src/pc/gfx/gfx_sdl2.c @@ -328,6 +328,8 @@ static bool gfx_sdl_start_frame(void) { static inline void sync_framerate_with_timer(void) { static Uint32 last_time = 0; + // get base timestamp on the first frame (might be different from 0) + if (last_time == 0) last_time = SDL_GetTicks(); const int elapsed = SDL_GetTicks() - last_time; if (elapsed < frame_time) SDL_Delay(frame_time - elapsed); From 77eede660be099892fac5aad2811c2ae70f1eabd Mon Sep 17 00:00:00 2001 From: CrispyBuns <57575774+CrispyBuns@users.noreply.github.com> Date: Tue, 14 Jul 2020 10:54:07 -0600 Subject: [PATCH 14/26] Add files via upload --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b78192ca3..bc82c2cb4 100644 --- a/Makefile +++ b/Makefile @@ -438,8 +438,8 @@ AS := i686-w64-mingw32-as endif ifneq ($(TARGET_WEB),1) # As in, not-web PC port - CC := $(CROSS)gcc - CXX := $(CROSS)g++ + CC ?= $(CROSS)gcc + CXX ?= $(CROSS)g++ else CC := emcc CXX := emcc From d8ddf20dbb6cd18b0ad563ec65c228475542a95d Mon Sep 17 00:00:00 2001 From: fgsfds Date: Mon, 27 Jul 2020 17:28:32 +0300 Subject: [PATCH 15/26] fix GL_LEGACY --- src/pc/gfx/gfx_opengl_legacy.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pc/gfx/gfx_opengl_legacy.c b/src/pc/gfx/gfx_opengl_legacy.c index ebee0d178..5f7c7daa8 100644 --- a/src/pc/gfx/gfx_opengl_legacy.c +++ b/src/pc/gfx/gfx_opengl_legacy.c @@ -589,9 +589,6 @@ static void gfx_opengl_finish_render(void) { static void gfx_opengl_shutdown(void) { } -static void gfx_opengl_shutdown(void) { -} - struct GfxRenderingAPI gfx_opengl_api = { gfx_opengl_z_is_from_0_to_1, gfx_opengl_unload_shader, From b35eb4f5c55bbca4b31a9774841900d7ca2c4495 Mon Sep 17 00:00:00 2001 From: Martin Pham Date: Wed, 5 Aug 2020 23:12:02 +0200 Subject: [PATCH 16/26] Update exoquant.c fix mac build --- tools/n64graphics_ci_dir/exoquant/exoquant.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/n64graphics_ci_dir/exoquant/exoquant.c b/tools/n64graphics_ci_dir/exoquant/exoquant.c index a70bc0384..fdccf4b96 100644 --- a/tools/n64graphics_ci_dir/exoquant/exoquant.c +++ b/tools/n64graphics_ci_dir/exoquant/exoquant.c @@ -24,7 +24,9 @@ SOFTWARE. #include "exoquant.h" -#ifndef OSX_BUILD // OSX build cannot have malloc defined +#ifdef __APPLE__ +// No malloc on mac +#else #include #endif From 9ed98273d7b44056ab20202e1b25ff5d59ddfbe7 Mon Sep 17 00:00:00 2001 From: ineedhelpbad <69617455+ineedhelpbad@users.noreply.github.com> Date: Thu, 13 Aug 2020 00:26:23 -0500 Subject: [PATCH 17/26] Update mario_actions_airborne.c --- src/game/mario_actions_airborne.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/mario_actions_airborne.c b/src/game/mario_actions_airborne.c index e8c1e2140..17e45ae43 100644 --- a/src/game/mario_actions_airborne.c +++ b/src/game/mario_actions_airborne.c @@ -1754,7 +1754,7 @@ s32 act_flying(struct MarioState *m) { set_camera_mode(m->area->camera, CAMERA_MODE_BEHIND_MARIO, 1); #else if (newcam_active == 0) - set_camera_mode(m->area->camera, m->area->camera->defMode, 1); + set_camera_mode(m->area->camera, CAMERA_MODE_BEHIND_MARIO, 1); else { m->area->camera->mode = CAMERA_MODE_NEWCAM; From 38b3ead13e32adf439cbd4eb602641c36d15837b Mon Sep 17 00:00:00 2001 From: ineedhelpbad <69617455+ineedhelpbad@users.noreply.github.com> Date: Thu, 13 Aug 2020 13:49:02 -0500 Subject: [PATCH 18/26] BUGFIX: Camera invert #373 Fix: Free Camera is forever inverted on the Y-Axis, changing the setting does nothing. #373 --- src/game/bettercamera.inc.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/game/bettercamera.inc.h b/src/game/bettercamera.inc.h index 9f4b80896..6e224d80c 100644 --- a/src/game/bettercamera.inc.h +++ b/src/game/bettercamera.inc.h @@ -269,18 +269,18 @@ static void newcam_rotate_button(void) { play_sound(SOUND_MENU_CAMERA_ZOOM_IN, gDefaultSoundArgs); #endif if (newcam_modeflags & NC_FLAG_8D) - newcam_yaw_target = newcam_yaw_target+(ivrt(newcam_invertX)*0x2000); + newcam_yaw_target = newcam_yaw_target+(ivrt(0)*0x2000); else - newcam_yaw_target = newcam_yaw_target+(ivrt(newcam_invertX)*0x4000); + newcam_yaw_target = newcam_yaw_target+(ivrt(0)*0x4000); newcam_centering = 1; } else if ((gPlayer1Controller->buttonPressed & R_CBUTTONS) && newcam_analogue == 0) { #ifndef nosound play_sound(SOUND_MENU_CAMERA_ZOOM_IN, gDefaultSoundArgs); #endif if (newcam_modeflags & NC_FLAG_8D) - newcam_yaw_target = newcam_yaw_target-(ivrt(newcam_invertX)*0x2000); + newcam_yaw_target = newcam_yaw_target-(ivrt(0)*0x2000); else - newcam_yaw_target = newcam_yaw_target-(ivrt(newcam_invertX)*0x4000); + newcam_yaw_target = newcam_yaw_target-(ivrt(0)*0x4000); newcam_centering = 1; } } else if (newcam_modeflags & NC_FLAG_XTURN) { @@ -314,7 +314,7 @@ static void newcam_rotate_button(void) { newcam_framessincec[1] ++; if ((gPlayer1Controller->buttonPressed & L_CBUTTONS) && newcam_modeflags & NC_FLAG_XTURN && !(newcam_modeflags & NC_FLAG_8D) && newcam_analogue == 0) { if (newcam_framessincec[0] < 6) { - newcam_yaw_target = newcam_yaw+(ivrt(newcam_invertX)*0x3000); + newcam_yaw_target = newcam_yaw+(ivrt(0)*0x3000); newcam_centering = 1; #ifndef nosound play_sound(SOUND_MENU_CAMERA_ZOOM_IN, gDefaultSoundArgs); @@ -324,7 +324,7 @@ static void newcam_rotate_button(void) { } if ((gPlayer1Controller->buttonPressed & R_CBUTTONS) && newcam_modeflags & NC_FLAG_XTURN && !(newcam_modeflags & NC_FLAG_8D) && newcam_analogue == 0) { if (newcam_framessincec[1] < 6) { - newcam_yaw_target = newcam_yaw-(ivrt(newcam_invertX)*0x3000); + newcam_yaw_target = newcam_yaw-(ivrt(0)*0x3000); newcam_centering = 1; #ifndef nosound play_sound(SOUND_MENU_CAMERA_ZOOM_IN, gDefaultSoundArgs); @@ -350,14 +350,14 @@ static void newcam_rotate_button(void) { #endif if (newcam_stick2[0] > 20) { if (newcam_modeflags & NC_FLAG_8D) - newcam_yaw_target = newcam_yaw_target+(ivrt(newcam_invertX)*0x2000); + newcam_yaw_target = newcam_yaw_target+(ivrt(0)*0x2000); else - newcam_yaw_target = newcam_yaw_target+(ivrt(newcam_invertX)*0x4000); + newcam_yaw_target = newcam_yaw_target+(ivrt(0)*0x4000); } else { if (newcam_modeflags & NC_FLAG_8D) - newcam_yaw_target = newcam_yaw_target-(ivrt(newcam_invertX)*0x2000); + newcam_yaw_target = newcam_yaw_target-(ivrt(0)*0x2000); else - newcam_yaw_target = newcam_yaw_target-(ivrt(newcam_invertX)*0x4000); + newcam_yaw_target = newcam_yaw_target-(ivrt(0)*0x4000); } } } else { @@ -425,9 +425,9 @@ static void newcam_update_values(void) { u8 waterflag = 0; if (newcam_modeflags & NC_FLAG_XTURN) - newcam_yaw -= ((newcam_yaw_acc*(newcam_sensitivityX/10))*ivrt(newcam_invertX)); + newcam_yaw -= ((newcam_yaw_acc*(newcam_sensitivityX/10))*ivrt(0)); if (((newcam_tilt <= 12000) && (newcam_tilt >= -12000)) && newcam_modeflags & NC_FLAG_YTURN) - newcam_tilt += ((newcam_tilt_acc*ivrt(newcam_invertY))*(newcam_sensitivityY/10)); + newcam_tilt += ((newcam_tilt_acc*ivrt(1))*(newcam_sensitivityY/10)); if (newcam_tilt > 12000) newcam_tilt = 12000; From 3e9e2595aa0ce3992430d1af3850b7e15a7cd118 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sat, 15 Aug 2020 07:15:28 +0300 Subject: [PATCH 19/26] add barebones SDL1.2 backends for that sweet Win9x support --- Makefile | 36 ++- src/pc/audio/audio_sdl1.c | 190 ++++++++++++ src/pc/audio/{audio_sdl.c => audio_sdl2.c} | 3 +- src/pc/controller/controller_entry_point.c | 2 + .../{controller_sdl.c => controller_sdl2.c} | 2 +- src/pc/gfx/gfx_opengl.c | 19 +- src/pc/gfx/gfx_opengl_legacy.c | 13 +- src/pc/gfx/gfx_sdl1.c | 280 ++++++++++++++++++ src/pc/pc_main.c | 2 + 9 files changed, 525 insertions(+), 22 deletions(-) create mode 100644 src/pc/audio/audio_sdl1.c rename src/pc/audio/{audio_sdl.c => audio_sdl2.c} (97%) rename src/pc/controller/{controller_sdl.c => controller_sdl2.c} (99%) create mode 100644 src/pc/gfx/gfx_sdl1.c diff --git a/Makefile b/Makefile index bc82c2cb4..fc8f8e61d 100644 --- a/Makefile +++ b/Makefile @@ -58,9 +58,9 @@ NO_LDIV ?= 0 # Renderers: GL, GL_LEGACY, D3D11, D3D12 RENDER_API ?= GL -# Window managers: SDL2, DXGI (forced if D3D11 or D3D12 in RENDER_API) +# Window managers: SDL1, SDL2, DXGI (forced if D3D11 or D3D12 in RENDER_API) WINDOW_API ?= SDL2 -# Audio backends: SDL2 +# Audio backends: SDL1, SDL2 AUDIO_API ?= SDL2 # Controller backends (can have multiple, space separated): SDL2 CONTROLLER_API ?= SDL2 @@ -481,7 +481,9 @@ SDLCONFIG := $(CROSS)sdl2-config BACKEND_CFLAGS := -DRAPI_$(RENDER_API)=1 -DWAPI_$(WINDOW_API)=1 -DAAPI_$(AUDIO_API)=1 # can have multiple controller APIs BACKEND_CFLAGS += $(foreach capi,$(CONTROLLER_API),-DCAPI_$(capi)=1) -BACKEND_LDFLAGS := +BACKEND_LDFLAG0S := + +SDL1_USED := 0 SDL2_USED := 0 # for now, it's either SDL+GL or DXGI+DirectX, so choose based on WAPI @@ -492,7 +494,7 @@ ifeq ($(WINDOW_API),DXGI) endif BACKEND_LDFLAGS += -ld3dcompiler -ldxgi -ldxguid BACKEND_LDFLAGS += -lsetupapi -ldinput8 -luser32 -lgdi32 -limm32 -lole32 -loleaut32 -lshell32 -lwinmm -lversion -luuid -static -else ifeq ($(WINDOW_API),SDL2) +else ifeq ($(findstring SDL,$(WINDOW_API)),SDL) ifeq ($(WINDOWS_BUILD),1) BACKEND_LDFLAGS += -lglew32 -lglu32 -lopengl32 else ifeq ($(TARGET_RPI),1) @@ -502,20 +504,32 @@ else ifeq ($(WINDOW_API),SDL2) else BACKEND_LDFLAGS += -lGL endif - SDL_USED := 2 endif -ifeq ($(AUDIO_API),SDL2) - SDL_USED := 2 +ifneq (,$(findstring SDL2,$(AUDIO_API)$(WINDOW_API)$(CONTROLLER_API))) + SDL2_USED := 1 endif -ifneq (,$(findstring SDL,$(CONTROLLER_API))) - SDL_USED := 2 +ifneq (,$(findstring SDL1,$(AUDIO_API)$(WINDOW_API)$(CONTROLLER_API))) + SDL1_USED := 1 +endif + +ifeq ($(SDL1_USED)$(SDL2_USED),11) + $(error Cannot link both SDL1 and SDL2 at the same time) endif # SDL can be used by different systems, so we consolidate all of that shit into this -ifeq ($(SDL_USED),2) - BACKEND_CFLAGS += -DHAVE_SDL2=1 `$(SDLCONFIG) --cflags` + +ifeq ($(SDL2_USED),1) + SDLCONFIG := $(CROSS)sdl2-config + BACKEND_CFLAGS += -DHAVE_SDL2=1 +else ifeq ($(SDL1_USED),1) + SDLCONFIG := $(CROSS)sdl-config + BACKEND_CFLAGS += -DHAVE_SDL1=1 +endif + +ifneq ($(SDL1_USED)$(SDL2_USED),00) + BACKEND_CFLAGS += `$(SDLCONFIG) --cflags` ifeq ($(WINDOWS_BUILD),1) BACKEND_LDFLAGS += `$(SDLCONFIG) --static-libs` -lsetupapi -luser32 -limm32 -lole32 -loleaut32 -lshell32 -lwinmm -lversion else diff --git a/src/pc/audio/audio_sdl1.c b/src/pc/audio/audio_sdl1.c new file mode 100644 index 000000000..6cbf5c56b --- /dev/null +++ b/src/pc/audio/audio_sdl1.c @@ -0,0 +1,190 @@ +#ifdef AAPI_SDL1 + +#include +#include +#include +#include + +#include "audio_api.h" + +#define SNDPACKETLEN (8 * 1024) + +// this is basically SDL_dataqueue but slightly less generic + +typedef struct sndpacket { + size_t datalen; /* bytes currently in use in this packet. */ + size_t startpos; /* bytes currently consumed in this packet. */ + struct sndpacket *next; /* next item in linked list. */ + Uint8 data[]; /* packet data */ +} sndpacket_t; + +static sndpacket_t *qhead; +static sndpacket_t *qtail; +static sndpacket_t *qpool; +static size_t queued; + +static SDL_AudioSpec aspec; +static int was_init = 0; + +static void sndqueue_init(const size_t bufsize) { + const size_t wantpackets = (bufsize + (SNDPACKETLEN - 1)) / SNDPACKETLEN; + for (size_t i = 0; i < wantpackets; ++i) { + sndpacket_t *packet = malloc(sizeof(sndpacket_t) + SNDPACKETLEN); + if (packet) { + packet->datalen = 0; + packet->startpos = 0; + packet->next = qpool; + qpool = packet; + } + } +} + +static size_t sndqueue_read(void *buf, size_t len) { + sndpacket_t *packet; + Uint8 *ptr = buf; + + while ((len > 0) && ((packet = qhead) != NULL)) { + const size_t avail = packet->datalen - packet->startpos; + const size_t tx = (len < avail) ? len : avail; + + memcpy(ptr, packet->data + packet->startpos, tx); + packet->startpos += tx; + ptr += tx; + queued -= tx; + len -= tx; + + if (packet->startpos == packet->datalen) { + qhead = packet->next; + packet->next = qpool; + qpool = packet; + } + } + + if (qhead == NULL) + qtail = NULL; + + return (size_t)(ptr - (Uint8*)buf); +} + +static inline sndpacket_t *alloc_sndpacket(void) { + sndpacket_t *packet = qpool; + + if (packet) { + qpool = packet->next; + } else { + packet = malloc(sizeof(sndpacket_t) + SNDPACKETLEN); + if (!packet) return NULL; + } + + packet->datalen = 0; + packet->startpos = 0; + packet->next = NULL; + + if (qtail == NULL) + qhead = packet; + else + qtail->next = packet; + qtail = packet; + + return packet; +} + +static int sndqueue_push(const void *data, size_t len) { + sndpacket_t *orighead = qhead; + sndpacket_t *origtail = qtail; + size_t origlen = origtail ? origtail->datalen : 0; + Uint8 *ptr = data; + + while (len > 0) { + sndpacket_t *packet = qtail; + if (!packet || (packet->datalen >= SNDPACKETLEN)) { + packet = alloc_sndpacket(); + if (!packet) { + // out of memory, fuck everything + return -1; + } + } + + const size_t room = SNDPACKETLEN - packet->datalen; + const size_t datalen = (len < room) ? len : room; + memcpy(packet->data + packet->datalen, ptr, datalen); + ptr += datalen; + len -= datalen; + packet->datalen += datalen; + queued += datalen; + } + + return 0; +} + +static void audio_drain(void *user, Uint8 *buf, int len) { + const size_t tx = sndqueue_read(buf, len); + buf += tx; + len -= (int)tx; + if (len > 0) memset(buf, aspec.silence, len); +} + +static bool audio_sdl_init(void) { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { + fprintf(stderr, "SDL init error: %s\n", SDL_GetError()); + return false; + } + + SDL_AudioSpec want, have; + memset(&want, 0, sizeof(want)); + want.freq = 32000; + want.format = AUDIO_S16SYS; + want.channels = 2; + want.samples = 512; + want.callback = audio_drain; + if (SDL_OpenAudio(&want, &have) == -1) { + fprintf(stderr, "SDL_OpenAudio error: %s\n", SDL_GetError()); + return false; + } + + aspec = have; + + was_init = 1; + SDL_PauseAudio(0); + + return true; +} + +static int audio_sdl_buffered(void) { + SDL_LockAudio(); + int len = queued / 4; + SDL_UnlockAudio(); + return len; +} + +static int audio_sdl_get_desired_buffered(void) { + return 1100; +} + +static void audio_sdl_play(const uint8_t *buf, size_t len) { + SDL_LockAudio(); + // Don't fill the audio buffer too much in case this happens + if (queued / 4 < 6000) + sndqueue_push(buf, len); + SDL_UnlockAudio(); +} + +static void audio_sdl_shutdown(void) { + if (SDL_WasInit(SDL_INIT_AUDIO)) { + if (was_init) { + SDL_CloseAudio(); + was_init = 0; + } + SDL_QuitSubSystem(SDL_INIT_AUDIO); + } +} + +struct AudioAPI audio_sdl = { + audio_sdl_init, + audio_sdl_buffered, + audio_sdl_get_desired_buffered, + audio_sdl_play, + audio_sdl_shutdown +}; + +#endif diff --git a/src/pc/audio/audio_sdl.c b/src/pc/audio/audio_sdl2.c similarity index 97% rename from src/pc/audio/audio_sdl.c rename to src/pc/audio/audio_sdl2.c index 0701b0378..b0abea7aa 100644 --- a/src/pc/audio/audio_sdl.c +++ b/src/pc/audio/audio_sdl2.c @@ -11,10 +11,11 @@ static bool audio_sdl_init(void) { fprintf(stderr, "SDL init error: %s\n", SDL_GetError()); return false; } + SDL_AudioSpec want, have; SDL_zero(want); want.freq = 32000; - want.format = AUDIO_S16; + want.format = AUDIO_S16SYS; want.channels = 2; want.samples = 512; want.callback = NULL; diff --git a/src/pc/controller/controller_entry_point.c b/src/pc/controller/controller_entry_point.c index c2e5207ab..e5ff3eb84 100644 --- a/src/pc/controller/controller_entry_point.c +++ b/src/pc/controller/controller_entry_point.c @@ -13,7 +13,9 @@ static struct ControllerAPI *controller_implementations[] = { &controller_recorded_tas, + #ifdef CAPI_SDL2 &controller_sdl, + #endif &controller_keyboard, }; diff --git a/src/pc/controller/controller_sdl.c b/src/pc/controller/controller_sdl2.c similarity index 99% rename from src/pc/controller/controller_sdl.c rename to src/pc/controller/controller_sdl2.c index a71a82ebc..a45a55de9 100644 --- a/src/pc/controller/controller_sdl.c +++ b/src/pc/controller/controller_sdl2.c @@ -13,7 +13,7 @@ #include #include "controller_api.h" -#include "controller_sdl.h" +#include "controller_sdl2.h" #include "../configfile.h" #include "../platform.h" #include "../fs/fs.h" diff --git a/src/pc/gfx/gfx_opengl.c b/src/pc/gfx/gfx_opengl.c index b3d090b0a..324f082ed 100644 --- a/src/pc/gfx/gfx_opengl.c +++ b/src/pc/gfx/gfx_opengl.c @@ -19,13 +19,20 @@ # include #endif -#include - #define GL_GLEXT_PROTOTYPES 1 -#ifdef USE_GLES -# include -#else -# include + +#ifdef WAPI_SDL2 +# include +# ifdef USE_GLES +# include +# else +# include +# endif +#elif defined(WAPI_SDL1) +# include +# ifndef GLEW_STATIC +# include +# endif #endif #include "../platform.h" diff --git a/src/pc/gfx/gfx_opengl_legacy.c b/src/pc/gfx/gfx_opengl_legacy.c index 5f7c7daa8..4547b3052 100644 --- a/src/pc/gfx/gfx_opengl_legacy.c +++ b/src/pc/gfx/gfx_opengl_legacy.c @@ -15,15 +15,22 @@ # define FOR_WINDOWS 0 #endif -#include - #if FOR_WINDOWS || defined(OSX_BUILD) # define GLEW_STATIC # include #endif #define GL_GLEXT_PROTOTYPES 1 -#include + +#ifdef WAPI_SDL2 +# include +# include +#elif defined(WAPI_SDL1) +# include +# ifndef GLEW_STATIC +# include +# endif +#endif // redefine this if using a different GL loader #define mglGetProcAddress(name) SDL_GL_GetProcAddress(name) diff --git a/src/pc/gfx/gfx_sdl1.c b/src/pc/gfx/gfx_sdl1.c new file mode 100644 index 000000000..e8c6fa4e9 --- /dev/null +++ b/src/pc/gfx/gfx_sdl1.c @@ -0,0 +1,280 @@ +#ifdef WAPI_SDL1 + +#ifdef __MINGW32__ +#define FOR_WINDOWS 1 +#else +#define FOR_WINDOWS 0 +#endif + +#include + +#include +#include + +#include "gfx_window_manager_api.h" +#include "gfx_screen_config.h" +#include "../pc_main.h" +#include "../configfile.h" +#include "../cliopts.h" +#include "../platform.h" + +#include "src/pc/controller/controller_keyboard.h" + +// TODO: figure out if this shit even works +#ifdef VERSION_EU +# define FRAMERATE 25 +#else +# define FRAMERATE 30 +#endif + +static int inverted_scancode_table[512]; + +static kb_callback_t kb_key_down = NULL; +static kb_callback_t kb_key_up = NULL; +static void (*kb_all_keys_up)(void) = NULL; + +// time between consequtive game frames +static const int frame_time = 1000 / FRAMERATE; + +static int desktop_w = 640; +static int desktop_h = 480; +static int desktop_bpp = 24; + +static int window_w = 0; +static int window_h = 0; + +const SDLKey windows_scancode_table[] = { + /* 0 1 2 3 4 5 6 7 */ + /* 8 9 A B C D E F */ + SDLK_UNKNOWN, SDLK_ESCAPE, SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6, /* 0 */ + SDLK_7, SDLK_8, SDLK_9, SDLK_0, SDLK_MINUS, SDLK_EQUALS, SDLK_BACKSPACE, SDLK_TAB, /* 0 */ + + SDLK_q, SDLK_w, SDLK_e, SDLK_r, SDLK_t, SDLK_y, SDLK_u, SDLK_i, /* 1 */ + SDLK_o, SDLK_p, SDLK_LEFTBRACKET, SDLK_RIGHTBRACKET, SDLK_RETURN, SDLK_LCTRL, SDLK_a, SDLK_s, /* 1 */ + + SDLK_d, SDLK_f, SDLK_g, SDLK_h, SDLK_j, SDLK_k, SDLK_l, SDLK_SEMICOLON, /* 2 */ + SDLK_UNKNOWN, SDLK_BACKQUOTE, SDLK_LSHIFT, SDLK_BACKSLASH, SDLK_z, SDLK_x, SDLK_c, SDLK_v, /* 2 */ + + SDLK_b, SDLK_n, SDLK_m, SDLK_COMMA, SDLK_PERIOD, SDLK_SLASH, SDLK_RSHIFT, SDLK_PRINT, /* 3 */ + SDLK_LALT, SDLK_SPACE, SDLK_CAPSLOCK, SDLK_F1, SDLK_F2, SDLK_F3, SDLK_F4, SDLK_F5, /* 3 */ + + SDLK_F6, SDLK_F7, SDLK_F8, SDLK_F9, SDLK_F10, SDLK_NUMLOCK, SDLK_SCROLLOCK, SDLK_HOME, /* 4 */ + SDLK_UP, SDLK_PAGEUP, SDLK_KP_MINUS, SDLK_LEFT, SDLK_KP5, SDLK_RIGHT, SDLK_KP_PLUS, SDLK_END, /* 4 */ + + SDLK_DOWN, SDLK_PAGEDOWN, SDLK_INSERT, SDLK_DELETE, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_F11, /* 5 */ + SDLK_F12, SDLK_PAUSE, SDLK_UNKNOWN, SDLK_LSUPER, SDLK_RSUPER, SDLK_MODE, SDLK_UNKNOWN, SDLK_UNKNOWN, /* 5 */ + + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_F13, SDLK_F14, SDLK_F15, SDLK_UNKNOWN, /* 6 */ + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, /* 6 */ + + SDLK_WORLD_2, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_WORLD_1, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, /* 7 */ + SDLK_UNKNOWN, SDLK_WORLD_4, SDLK_UNKNOWN, SDLK_WORLD_5, SDLK_UNKNOWN, SDLK_WORLD_3, SDLK_UNKNOWN, SDLK_UNKNOWN /* 7 */ +}; + +const SDLKey scancode_rmapping_extended[][2] = { + { SDLK_KP_ENTER, SDLK_RETURN }, + { SDLK_RALT, SDLK_LALT }, + { SDLK_RCTRL, SDLK_LCTRL }, + { SDLK_KP_DIVIDE, SDLK_SLASH }, + //{ SDLK_KPPLUS, SDLK_CAPSLOCK } +}; + +const SDLKey scancode_rmapping_nonextended[][2] = { + { SDLK_KP7, SDLK_HOME }, + { SDLK_KP8, SDLK_UP }, + { SDLK_KP9, SDLK_PAGEUP }, + { SDLK_KP4, SDLK_LEFT }, + { SDLK_KP6, SDLK_RIGHT }, + { SDLK_KP1, SDLK_END }, + { SDLK_KP2, SDLK_DOWN }, + { SDLK_KP3, SDLK_PAGEDOWN }, + { SDLK_KP0, SDLK_INSERT }, + { SDLK_KP_PERIOD, SDLK_DELETE }, + { SDLK_KP_MULTIPLY, SDLK_PRINT } +}; + +static void gfx_sdl_set_mode(void) { + if (configWindow.exiting_fullscreen) + configWindow.exiting_fullscreen = false; + + if (configWindow.reset) { + configWindow.fullscreen = false; + configWindow.x = WAPI_WIN_CENTERPOS; + configWindow.y = WAPI_WIN_CENTERPOS; + configWindow.w = DESIRED_SCREEN_WIDTH; + configWindow.h = DESIRED_SCREEN_HEIGHT; + configWindow.reset = false; + } + + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + + uint32_t flags = SDL_OPENGL; + if (configWindow.fullscreen) + flags |= SDL_FULLSCREEN; + else + flags |= SDL_RESIZABLE; + + if (!SDL_VideoModeOK(configWindow.w, configWindow.h, desktop_bpp, flags)) { + printf( + "video mode [%dx%d fullscreen %d] not available, falling back to default\n", + configWindow.w, configWindow.h, configWindow.fullscreen + ); + configWindow.w = DESIRED_SCREEN_WIDTH; + configWindow.h = DESIRED_SCREEN_HEIGHT; + configWindow.fullscreen = false; + flags = SDL_OPENGL | SDL_RESIZABLE; + } + + if (!SDL_SetVideoMode(configWindow.w, configWindow.h, desktop_bpp, flags)) { + sys_fatal( + "could not set video mode [%dx%d fullscreen %d]: %s\n", + configWindow.w, configWindow.h, configWindow.fullscreen, SDL_GetError() + ); + } + + window_w = configWindow.w; + window_h = configWindow.h; +} + +static void gfx_sdl_init(const char *window_title) { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) + sys_fatal("Could not init SDL1 video: %s\n", SDL_GetError()); + + const SDL_VideoInfo *vinfo = SDL_GetVideoInfo(); + desktop_w = vinfo->current_w; + desktop_h = vinfo->current_h; + desktop_bpp = vinfo->vfmt->BitsPerPixel; + + SDL_WM_SetCaption(window_title, NULL); + + // set actual desired video mode + + gfx_sdl_set_mode(); + + SDL_ShowCursor(0); + + for (size_t i = 0; i < sizeof(windows_scancode_table) / sizeof(SDLKey); i++) { + inverted_scancode_table[windows_scancode_table[i]] = i; + } + + for (size_t i = 0; i < sizeof(scancode_rmapping_extended) / sizeof(scancode_rmapping_extended[0]); i++) { + inverted_scancode_table[scancode_rmapping_extended[i][0]] = inverted_scancode_table[scancode_rmapping_extended[i][1]] + 0x100; + } + + for (size_t i = 0; i < sizeof(scancode_rmapping_nonextended) / sizeof(scancode_rmapping_nonextended[0]); i++) { + inverted_scancode_table[scancode_rmapping_nonextended[i][0]] = inverted_scancode_table[scancode_rmapping_nonextended[i][1]]; + inverted_scancode_table[scancode_rmapping_nonextended[i][1]] += 0x100; + } +} + +static void gfx_sdl_main_loop(void (*run_one_game_iter)(void)) { + run_one_game_iter(); +} + +static void gfx_sdl_get_dimensions(uint32_t *width, uint32_t *height) { + if (width) *width = window_w; + if (height) *height = window_h; +} + +static int translate_scancode(int scancode) { + if (scancode < 512) { + return inverted_scancode_table[scancode]; + } else { + return 0; + } +} + +static void gfx_sdl_onkeydown(int scancode) { + if (kb_key_down) + kb_key_down(translate_scancode(scancode)); +} + +static void gfx_sdl_onkeyup(int scancode) { + if (kb_key_up) + kb_key_up(translate_scancode(scancode)); +} + +static void gfx_sdl_handle_events(void) { + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { +#ifndef TARGET_WEB + // Scancodes are broken in Emscripten SDL2: https://bugzilla.libsdl.org/show_bug.cgi?id=3259 + case SDL_KEYDOWN: + gfx_sdl_onkeydown(event.key.keysym.sym); + // ALT+F4 in case the OS doesn't do it (SDL1 doesn't seem to do it on my machine) + if (event.key.keysym.sym == SDLK_F4 && (event.key.keysym.mod & (KMOD_LALT | KMOD_RALT))) + game_exit(); + break; + case SDL_KEYUP: + gfx_sdl_onkeyup(event.key.keysym.sym); + break; +#endif + case SDL_VIDEORESIZE: + window_w = configWindow.w = event.resize.w; + window_h = configWindow.h = event.resize.h; + break; + case SDL_QUIT: + game_exit(); + break; + } + } +} + +static void gfx_sdl_set_keyboard_callbacks(kb_callback_t on_key_down, kb_callback_t on_key_up, void (*on_all_keys_up)(void)) { + kb_key_down = on_key_down; + kb_key_up = on_key_up; + kb_all_keys_up = on_all_keys_up; +} + +static bool gfx_sdl_start_frame(void) { + return true; +} + +static inline void sync_framerate_with_timer(void) { + static Uint32 last_time = 0; + // get base timestamp on the first frame (might be different from 0) + if (last_time == 0) last_time = SDL_GetTicks(); + const int elapsed = SDL_GetTicks() - last_time; + if (elapsed < frame_time) + SDL_Delay(frame_time - elapsed); + last_time += frame_time; +} + +static void gfx_sdl_swap_buffers_begin(void) { + sync_framerate_with_timer(); + SDL_GL_SwapBuffers(); +} + +static void gfx_sdl_swap_buffers_end(void) { +} + +static double gfx_sdl_get_time(void) { + return 0.0; +} + + +static void gfx_sdl_shutdown(void) { + if (SDL_WasInit(0)) + SDL_Quit(); +} + +struct GfxWindowManagerAPI gfx_sdl = { + gfx_sdl_init, + gfx_sdl_set_keyboard_callbacks, + gfx_sdl_main_loop, + gfx_sdl_get_dimensions, + gfx_sdl_handle_events, + gfx_sdl_start_frame, + gfx_sdl_swap_buffers_begin, + gfx_sdl_swap_buffers_end, + gfx_sdl_get_time, + gfx_sdl_shutdown +}; + +#endif // BACKEND_WM diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index ed6ee746a..ada661f1e 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -222,8 +222,10 @@ void main_func(void) { gfx_init(wm_api, rendering_api, window_title); wm_api->set_keyboard_callbacks(keyboard_on_key_down, keyboard_on_key_up, keyboard_on_all_keys_up); + #if defined(AAPI_SDL1) || defined(AAPI_SDL2) if (audio_api == NULL && audio_sdl.init()) audio_api = &audio_sdl; + #endif if (audio_api == NULL) { audio_api = &audio_null; From e18b3c5b97c45f93cb7a2c89f2fb4940c276abd2 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sat, 15 Aug 2020 16:04:40 +0300 Subject: [PATCH 20/26] fix controller_sdl2 --- src/pc/controller/controller_sdl2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/controller/controller_sdl2.c b/src/pc/controller/controller_sdl2.c index a45a55de9..a71a82ebc 100644 --- a/src/pc/controller/controller_sdl2.c +++ b/src/pc/controller/controller_sdl2.c @@ -13,7 +13,7 @@ #include #include "controller_api.h" -#include "controller_sdl2.h" +#include "controller_sdl.h" #include "../configfile.h" #include "../platform.h" #include "../fs/fs.h" From 655427f10ff9455bb382442b9350f051d6b64b0c Mon Sep 17 00:00:00 2001 From: fgsfds Date: Fri, 28 Aug 2020 22:55:40 +0300 Subject: [PATCH 21/26] add --poolsize arg for modifying main pool size --- src/game/memory.h | 1 + src/pc/cliopts.c | 9 +++++++++ src/pc/cliopts.h | 1 + src/pc/pc_main.c | 10 ++++++---- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/game/memory.h b/src/game/memory.h index 48cd703c7..a1eba8a72 100644 --- a/src/game/memory.h +++ b/src/game/memory.h @@ -9,6 +9,7 @@ #define MEMORY_POOL_RIGHT 1 #define GFX_POOL_SIZE (512 * 1024) +#define DEFAULT_POOL_SIZE (0x165000 * 8) struct AllocOnlyPool { diff --git a/src/pc/cliopts.c b/src/pc/cliopts.c index b9e9ee77f..54dbb5c71 100644 --- a/src/pc/cliopts.c +++ b/src/pc/cliopts.c @@ -34,6 +34,12 @@ static inline int arg_string(const char *name, const char *value, char *target) return 1; } +static inline int arg_uint(const char *name, const char *value, unsigned int *target) { + const unsigned long int v = strtoul(value, NULL, 0); + *target = v; + return 1; +} + void parse_cli_opts(int argc, char* argv[]) { // Initialize options with false values. memset(&gCLIOpts, 0, sizeof(gCLIOpts)); @@ -51,6 +57,9 @@ void parse_cli_opts(int argc, char* argv[]) { else if (strcmp(argv[i], "--cheats") == 0) // Enable cheats menu Cheats.EnableCheats = true; + else if (strcmp(argv[i], "--poolsize") == 0) // Main pool size + arg_uint("--poolsize", argv[++i], &gCLIOpts.PoolSize); + else if (strcmp(argv[i], "--configfile") == 0 && (i + 1) < argc) arg_string("--configfile", argv[++i], gCLIOpts.ConfigFile); diff --git a/src/pc/cliopts.h b/src/pc/cliopts.h index f9a45e189..a0281e22a 100644 --- a/src/pc/cliopts.h +++ b/src/pc/cliopts.h @@ -6,6 +6,7 @@ struct PCCLIOptions { unsigned int SkipIntro; unsigned int FullScreen; + unsigned int PoolSize; char ConfigFile[SYS_MAX_PATH]; char SavePath[SYS_MAX_PATH]; char GameDir[SYS_MAX_PATH]; diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index ada661f1e..923e7ea84 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -172,10 +172,6 @@ static void on_anim_frame(double time) { #endif void main_func(void) { - static u64 pool[0x165000/8 / 4 * sizeof(void *)]; - main_pool_init(pool, pool + sizeof(pool) / sizeof(pool[0])); - gEffectsMemoryPool = mem_pool_init(0x4000, MEMORY_POOL_LEFT); - const char *gamedir = gCLIOpts.GameDir[0] ? gCLIOpts.GameDir : FS_BASEDIR; const char *userpath = gCLIOpts.SavePath[0] ? gCLIOpts.SavePath : sys_user_path(); fs_init(sys_ropaths, gamedir, userpath); @@ -187,6 +183,12 @@ void main_func(void) { else if (gCLIOpts.FullScreen == 2) configWindow.fullscreen = false; + const size_t poolsize = gCLIOpts.PoolSize ? gCLIOpts.PoolSize : DEFAULT_POOL_SIZE; + u64 *pool = malloc(poolsize); + if (!pool) sys_fatal("Could not alloc %u bytes for main pool.\n", poolsize); + main_pool_init(pool, pool + poolsize / sizeof(pool[0])); + gEffectsMemoryPool = mem_pool_init(0x4000, MEMORY_POOL_LEFT); + #if defined(WAPI_SDL1) || defined(WAPI_SDL2) wm_api = &gfx_sdl; #elif defined(WAPI_DXGI) From 73c6c9105f661412a389c86f8086a72d836c1f8d Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sat, 29 Aug 2020 02:13:07 +0300 Subject: [PATCH 22/26] change GL_LEGACY to only use GL1.1 (1.2?) features --- src/pc/gfx/gfx_opengl_legacy.c | 587 ++++++++++++++++----------------- 1 file changed, 283 insertions(+), 304 deletions(-) diff --git a/src/pc/gfx/gfx_opengl_legacy.c b/src/pc/gfx/gfx_opengl_legacy.c index 4547b3052..a069f3497 100644 --- a/src/pc/gfx/gfx_opengl_legacy.c +++ b/src/pc/gfx/gfx_opengl_legacy.c @@ -3,6 +3,7 @@ #include #include #include +#include #ifndef _LANGUAGE_C # define _LANGUAGE_C @@ -32,40 +33,11 @@ # endif #endif -// redefine this if using a different GL loader -#define mglGetProcAddress(name) SDL_GL_GetProcAddress(name) - -// we'll define and load it manually in init, just in case -typedef void (*PFNMGLFOGCOORDPOINTERPROC)(GLenum type, GLsizei stride, const void *pointer); -static PFNMGLFOGCOORDPOINTERPROC mglFogCoordPointer = NULL; - -// since these can have different names, might as well redefine them to a single one -#undef GL_FOG_COORD_SRC -#undef GL_FOG_COORD -#undef GL_FOG_COORD_ARRAY -#define GL_FOG_COORD_SRC 0x8450 -#define GL_FOG_COORD 0x8451 -#define GL_FOG_COORD_ARRAY 0x8457 - #include "../platform.h" #include "gfx_cc.h" #include "gfx_rendering_api.h" #include "macros.h" -enum MixFlags { - SH_MF_OVERRIDE_ALPHA = 1, - - SH_MF_MULTIPLY = 2, - SH_MF_MIX = 4, - SH_MF_SINGLE = 8, - - SH_MF_MULTIPLY_ALPHA = 16, - SH_MF_MIX_ALPHA = 32, - SH_MF_SINGLE_ALPHA = 64, - - SH_MF_INPUT_ALPHA = 128, -}; - enum MixType { SH_MT_NONE, SH_MT_TEXTURE, @@ -76,119 +48,121 @@ enum MixType { }; struct ShaderProgram { + bool enabled; uint32_t shader_id; + struct CCFeatures cc; enum MixType mix; - uint32_t mix_flags; bool texture_used[2]; + int texture_ord[2]; int num_inputs; }; +struct SamplerState { + GLenum min_filter; + GLenum mag_filter; + GLenum wrap_s; + GLenum wrap_t; + GLuint tex; +}; + static struct ShaderProgram shader_program_pool[64]; static uint8_t shader_program_pool_size; static struct ShaderProgram *cur_shader = NULL; +static struct SamplerState tmu_state[2]; + static const float *cur_buf = NULL; static const float *cur_fog_ofs = NULL; static size_t cur_buf_size = 0; static size_t cur_buf_num_tris = 0; static size_t cur_buf_stride = 0; static bool gl_blend = false; -static bool gl_adv_fog = false; +static bool gl_npot = false; +static bool gl_multitexture = false; + +static void *scale_buf = NULL; +static int scale_buf_size = 0; + +static float c_mix[] = { 0.f, 0.f, 0.f, 1.f }; +static float c_invmix[] = { 1.f, 1.f, 1.f, 1.f }; static const float c_white[] = { 1.f, 1.f, 1.f, 1.f }; +// from https://github.com/z2442/sm64-port + +static void resample_32bit(const uint32_t *in, const int inwidth, const int inheight, uint32_t *out, const int outwidth, const int outheight) { + int i, j; + const uint32_t *inrow; + uint32_t frac, fracstep; + + fracstep = inwidth * 0x10000 / outwidth; + for (i = 0; i < outheight; i++, out += outwidth) { + inrow = in + inwidth * (i * inheight / outheight); + frac = fracstep >> 1; + for (j = 0; j < outwidth; j += 4) { + out[j] = inrow[frac >> 16]; + frac += fracstep; + out[j + 1] = inrow[frac >> 16]; + frac += fracstep; + out[j + 2] = inrow[frac >> 16]; + frac += fracstep; + out[j + 3] = inrow[frac >> 16]; + frac += fracstep; + } + } +} + +static inline uint32_t next_pot(uint32_t v) { + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; +} + +static inline uint32_t is_pot(const uint32_t v) { + return (v & (v - 1)) == 0; +} + static bool gfx_opengl_z_is_from_0_to_1(void) { return false; } -#define TEXENV_COMBINE_ON() glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE) -#define TEXENV_COMBINE_OFF() glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE) - -#define TEXENV_COMBINE_OP(num, cval, aval) \ - do { \ - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND ## num ## _RGB, cval); \ - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND ## num ## _ALPHA, aval); \ - } while (0) - -#define TEXENV_COMBINE_SET1(what, mode, val) \ - do { \ - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ ## what, mode); \ - glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ ## what, val); \ - } while (0) - -#define TEXENV_COMBINE_SET2(what, mode, val1, val2) \ - do { \ - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ ## what, mode); \ - glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ ## what, val1); \ - glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ ## what, val2); \ - } while (0) - -#define TEXENV_COMBINE_SET3(what, mode, val1, val2, val3) \ - do { \ - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ ## what, mode); \ - glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ ## what, val1); \ - glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ ## what, val2); \ - glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_ ## what, val3); \ - } while (0) - -static inline void texenv_set_texture_color(struct ShaderProgram *prg) { - glActiveTexture(GL_TEXTURE0); - - if (prg->mix_flags & SH_MF_OVERRIDE_ALPHA) { - TEXENV_COMBINE_ON(); - if (prg->mix_flags & SH_MF_SINGLE_ALPHA) { - if (prg->mix_flags & SH_MF_MULTIPLY) { - // keep the alpha but modulate the color - const GLenum alphasrc = (prg->mix_flags & SH_MF_INPUT_ALPHA) ? GL_PRIMARY_COLOR : GL_TEXTURE; - TEXENV_COMBINE_SET2(RGB, GL_MODULATE, GL_TEXTURE, GL_PRIMARY_COLOR); - TEXENV_COMBINE_SET1(ALPHA, GL_REPLACE, alphasrc); - } else { - // somehow makes it keep the color while taking the alpha from primary color - TEXENV_COMBINE_SET1(RGB, GL_REPLACE, GL_TEXTURE); - } - } else { // if (prg->mix_flags & SH_MF_SINGLE) { - if (prg->mix_flags & SH_MF_MULTIPLY_ALPHA) { - // modulate the alpha but keep the color - TEXENV_COMBINE_SET2(ALPHA, GL_MODULATE, GL_TEXTURE, GL_PRIMARY_COLOR); - TEXENV_COMBINE_SET1(RGB, GL_REPLACE, GL_TEXTURE); - } else { - // somehow makes it keep the alpha - TEXENV_COMBINE_SET1(ALPHA, GL_REPLACE, GL_TEXTURE); - } - } - // TODO: MIX and the other one - } else if (prg->mix_flags & SH_MF_MULTIPLY) { - // TODO: is this right? - TEXENV_COMBINE_OFF(); - } else if (prg->mix_flags & SH_MF_MIX) { - TEXENV_COMBINE_ON(); - // HACK: determine this using flags and not this crap - if (prg->num_inputs > 1) { - // out.rgb = mix(color0.rgb, color1.rgb, texel0.rgb); - // no color1 tho, so mix with white (texenv color is set in init()) - TEXENV_COMBINE_OP(2, GL_SRC_COLOR, GL_SRC_ALPHA); - TEXENV_COMBINE_SET3(RGB, GL_INTERPOLATE, GL_CONSTANT, GL_PRIMARY_COLOR, GL_TEXTURE); - TEXENV_COMBINE_SET1(ALPHA, GL_REPLACE, GL_CONSTANT); - } else { - // out.rgb = mix(color0.rgb, texel0.rgb, texel0.a); - TEXENV_COMBINE_OP(2, GL_SRC_ALPHA, GL_SRC_ALPHA); - TEXENV_COMBINE_SET3(RGB, GL_INTERPOLATE, GL_TEXTURE, GL_PRIMARY_COLOR, GL_TEXTURE); - } - } else { - TEXENV_COMBINE_OFF(); - } +static inline GLenum texenv_set_color(UNUSED struct ShaderProgram *prg) { + return GL_MODULATE; } -static inline void texenv_set_texture_texture(UNUSED struct ShaderProgram *prg) { - glActiveTexture(GL_TEXTURE0); - TEXENV_COMBINE_OFF(); - glActiveTexture(GL_TEXTURE1); - TEXENV_COMBINE_ON(); - // out.rgb = mix(texel0.rgb, texel1.rgb, color0.rgb); - TEXENV_COMBINE_OP(2, GL_SRC_COLOR, GL_SRC_ALPHA); - TEXENV_COMBINE_SET3(RGB, GL_INTERPOLATE, GL_PREVIOUS, GL_TEXTURE, GL_PRIMARY_COLOR); - // out.a = texel0.a; - TEXENV_COMBINE_SET1(ALPHA, GL_REPLACE, GL_PREVIOUS); +static inline GLenum texenv_set_texture(UNUSED struct ShaderProgram *prg) { + return GL_MODULATE; +} + +static inline GLenum texenv_set_texture_color(struct ShaderProgram *prg) { + GLenum mode; + + // HACK: lord forgive me for this, but this is easier + + switch (prg->shader_id) { + case 0x0000038D: // mario's eyes + case 0x01045A00: // peach letter + case 0x01200A00: // intro copyright fade in + mode = GL_DECAL; + break; + case 0x00000551: // goddard + mode = GL_BLEND; + break; + default: + mode = GL_MODULATE; + break; + } + + return mode; +} + +static inline GLenum texenv_set_texture_texture(UNUSED struct ShaderProgram *prg) { + return GL_MODULATE; } static void gfx_opengl_apply_shader(struct ShaderProgram *prg) { @@ -199,31 +173,19 @@ static void gfx_opengl_apply_shader(struct ShaderProgram *prg) { ofs += 4; // have texture(s), specify same texcoords for every active texture - for (int i = 0; i < 2; ++i) { - if (prg->texture_used[i]) { - glEnable(GL_TEXTURE0 + i); - glClientActiveTexture(GL_TEXTURE0 + i); - glActiveTexture(GL_TEXTURE0 + i); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glTexCoordPointer(2, GL_FLOAT, cur_buf_stride, ofs); - glEnable(GL_TEXTURE_2D); - ofs += 2; - } + if (prg->texture_used[0] || prg->texture_used[1]) { + glEnable(GL_TEXTURE_2D); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, cur_buf_stride, ofs); + ofs += 2; + } else { + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisable(GL_TEXTURE_2D); } if (prg->shader_id & SHADER_OPT_FOG) { - // fog requested, we can deal with it in one of two ways - if (gl_adv_fog) { - // if GL_EXT_fog_coord is available, use the provided fog factor as scaled depth for GL fog - const float fogrgb[] = { ofs[0], ofs[1], ofs[2] }; - glEnable(GL_FOG); - glFogfv(GL_FOG_COLOR, fogrgb); // color is the same for all verts, only intensity is different - glEnableClientState(GL_FOG_COORD_ARRAY); - mglFogCoordPointer(GL_FLOAT, cur_buf_stride, ofs + 3); // point it to alpha, which is fog factor - } else { - // if there's no fog coords available, blend it on top of normal tris later - cur_fog_ofs = ofs; - } + // blend it on top of normal tris later + cur_fog_ofs = ofs; ofs += 4; } @@ -232,139 +194,112 @@ static void gfx_opengl_apply_shader(struct ShaderProgram *prg) { // TODO: more than one color (maybe glSecondaryColorPointer?) // HACK: if there's a texture and two colors, one of them is likely for speculars or some shit (see mario head) // if there's two colors but no texture, the real color is likely the second one - const int hack = (prg->num_inputs > 1) * (4 - (int)prg->texture_used[0]); - glEnableClientState(GL_COLOR_ARRAY); - glColorPointer(4, GL_FLOAT, cur_buf_stride, ofs + hack); - ofs += 4 * prg->num_inputs; + // HACKHACK: alpha is 0 in the transition shader (0x01A00045), maybe figure out the flags instead + const int vlen = (prg->cc.opt_alpha && prg->shader_id != 0x01A00045) ? 4 : 3; + const int hack = vlen * (prg->num_inputs > 1); + + if (prg->texture_used[1] && prg->cc.do_mix[0]) { + // HACK: when two textures are mixed by vertex color, store the color + // it will be used later when rendering two texture passes + c_mix[0] = *(ofs + hack + 0); + c_mix[1] = *(ofs + hack + 1); + c_mix[2] = *(ofs + hack + 2); + c_invmix[0] = 1.f - c_mix[0]; + c_invmix[1] = 1.f - c_mix[1]; + c_invmix[2] = 1.f - c_mix[2]; + glDisableClientState(GL_COLOR_ARRAY); + glColor3f(c_mix[0], c_mix[1], c_mix[2]); + } else { + // otherwise use vertex colors as normal + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(vlen, GL_FLOAT, cur_buf_stride, ofs + hack); + } + + ofs += prg->num_inputs * vlen; + } else { + glDisableClientState(GL_COLOR_ARRAY); } - if (prg->shader_id & SHADER_OPT_TEXTURE_EDGE) { - // (horrible) alpha discard - glEnable(GL_ALPHA_TEST); - glAlphaFunc(GL_GREATER, 0.3f); - } + if (!prg->enabled) { + // we only need to do this once + prg->enabled = true; - // configure formulae - switch (prg->mix) { - case SH_MT_TEXTURE: - glActiveTexture(GL_TEXTURE0); - TEXENV_COMBINE_OFF(); - break; + if (prg->shader_id & SHADER_OPT_TEXTURE_EDGE) { + // (horrible) alpha discard + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.666f); + } else { + glDisable(GL_ALPHA_TEST); + } - case SH_MT_TEXTURE_COLOR: - texenv_set_texture_color(prg); - break; - - case SH_MT_TEXTURE_TEXTURE: - texenv_set_texture_texture(prg); - break; - - default: - break; + // configure texenv + GLenum mode; + switch (prg->mix) { + case SH_MT_TEXTURE: mode = texenv_set_texture(prg); break; + case SH_MT_TEXTURE_TEXTURE: mode = texenv_set_texture_texture(prg); break; + case SH_MT_TEXTURE_COLOR: mode = texenv_set_texture_color(prg); break; + default: mode = texenv_set_color(prg); break; + } + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, mode); } } static void gfx_opengl_unload_shader(struct ShaderProgram *old_prg) { - if (cur_shader == old_prg || old_prg == NULL) + if (cur_shader && (cur_shader == old_prg || !old_prg)) { + cur_shader->enabled = false; cur_shader = NULL; - - glClientActiveTexture(GL_TEXTURE0); - glActiveTexture(GL_TEXTURE0); - glDisable(GL_TEXTURE_2D); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - - glClientActiveTexture(GL_TEXTURE1); - glActiveTexture(GL_TEXTURE1); - glDisable(GL_TEXTURE_2D); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - - glDisable(GL_TEXTURE1); - glDisable(GL_TEXTURE0); - glDisable(GL_TEXTURE_2D); - glDisable(GL_ALPHA_TEST); - glDisable(GL_FOG); + } cur_fog_ofs = NULL; // clear fog colors - - glDisableClientState(GL_COLOR_ARRAY); - if (gl_adv_fog) glDisableClientState(GL_FOG_COORD_ARRAY); } static void gfx_opengl_load_shader(struct ShaderProgram *new_prg) { cur_shader = new_prg; - // gfx_opengl_apply_shader(cur_shader); + if (cur_shader) + cur_shader->enabled = false; } static struct ShaderProgram *gfx_opengl_create_and_load_new_shader(uint32_t shader_id) { - uint8_t c[2][4]; - for (int i = 0; i < 4; i++) { - c[0][i] = (shader_id >> (i * 3)) & 7; - c[1][i] = (shader_id >> (12 + i * 3)) & 7; - } - - bool used_textures[2] = {0, 0}; - int num_inputs = 0; - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 4; j++) { - if (c[i][j] >= SHADER_INPUT_1 && c[i][j] <= SHADER_INPUT_4) { - if (c[i][j] > num_inputs) { - num_inputs = c[i][j]; - } - } - if (c[i][j] == SHADER_TEXEL0 || c[i][j] == SHADER_TEXEL0A) { - used_textures[0] = true; - } - if (c[i][j] == SHADER_TEXEL1) { - used_textures[1] = true; - } - } - } - - const bool color_alpha_same = (shader_id & 0xfff) == ((shader_id >> 12) & 0xfff); - const bool do_multiply[2] = {c[0][1] == 0 && c[0][3] == 0, c[1][1] == 0 && c[1][3] == 0}; - const bool do_mix[2] = {c[0][1] == c[0][3], c[1][1] == c[1][3]}; - const bool do_single[2] = {c[0][2] == 0, c[1][2] == 0}; + struct CCFeatures ccf; + gfx_cc_get_features(shader_id, &ccf); struct ShaderProgram *prg = &shader_program_pool[shader_program_pool_size++]; prg->shader_id = shader_id; - prg->num_inputs = num_inputs; - prg->texture_used[0] = used_textures[0]; - prg->texture_used[1] = used_textures[1]; + prg->cc = ccf; + prg->num_inputs = ccf.num_inputs; + prg->texture_used[0] = ccf.used_textures[0]; + prg->texture_used[1] = ccf.used_textures[1]; - if (used_textures[0] && used_textures[1]) + if (ccf.used_textures[0] && ccf.used_textures[1]) { prg->mix = SH_MT_TEXTURE_TEXTURE; - else if (used_textures[0] && num_inputs) + if (ccf.do_single[1]) { + prg->texture_ord[0] = 1; + prg->texture_ord[1] = 0; + } else { + prg->texture_ord[0] = 0; + prg->texture_ord[1] = 1; + } + } else if (ccf.used_textures[0] && ccf.num_inputs) { prg->mix = SH_MT_TEXTURE_COLOR; - else if (used_textures[0]) + } else if (ccf.used_textures[0]) { prg->mix = SH_MT_TEXTURE; - else if (num_inputs > 1) + } else if (ccf.num_inputs > 1) { prg->mix = SH_MT_COLOR_COLOR; - else if (num_inputs) + } else if (ccf.num_inputs) { prg->mix = SH_MT_COLOR; - - if (do_single[0]) prg->mix_flags |= SH_MF_SINGLE; - if (do_multiply[0]) prg->mix_flags |= SH_MF_MULTIPLY; - if (do_mix[0]) prg->mix_flags |= SH_MF_MIX; - - if (!color_alpha_same && (shader_id & SHADER_OPT_ALPHA)) { - prg->mix_flags |= SH_MF_OVERRIDE_ALPHA; - if (do_single[1]) prg->mix_flags |= SH_MF_SINGLE_ALPHA; - if (do_multiply[1]) prg->mix_flags |= SH_MF_MULTIPLY_ALPHA; - if (do_mix[1]) prg->mix_flags |= SH_MF_MIX_ALPHA; - if (c[1][3] < SHADER_TEXEL0) prg->mix_flags |= SH_MF_INPUT_ALPHA; } + prg->enabled = false; + gfx_opengl_load_shader(prg); return prg; } static struct ShaderProgram *gfx_opengl_lookup_shader(uint32_t shader_id) { - for (size_t i = 0; i < shader_program_pool_size; i++) { - if (shader_program_pool[i].shader_id == shader_id) { + for (size_t i = 0; i < shader_program_pool_size; i++) + if (shader_program_pool[i].shader_id == shader_id) return &shader_program_pool[i]; - } - } return NULL; } @@ -381,35 +316,72 @@ static GLuint gfx_opengl_new_texture(void) { } static void gfx_opengl_select_texture(int tile, GLuint texture_id) { - glActiveTexture(GL_TEXTURE0 + tile); + tmu_state[tile].tex = texture_id; // remember this for multitexturing later glBindTexture(GL_TEXTURE_2D, texture_id); } -static void gfx_opengl_upload_texture(uint8_t *rgba32_buf, int width, int height) { +static inline void *gfx_opengl_scale_texture(const uint8_t *data, const int w, const int h, const int to_w, const int to_h) { + const int psize = to_w * to_h * 4; + + // realloc scale buffer if it's too small + if (psize > scale_buf_size) { + scale_buf = realloc(scale_buf, psize); + if (!scale_buf) sys_fatal("Out of memory allocating NPOT scale buffer\n"); + scale_buf_size = psize; + } + + resample_32bit((const uint32_t *)data, w, h, scale_buf, to_w, to_h); + + return scale_buf; +} + +static void gfx_opengl_upload_texture(const uint8_t *rgba32_buf, int width, int height) { + if (!gl_npot) { + // we don't support non power of two textures, scale to next power of two if necessary + if (!is_pot(width) || !is_pot(height)) { + const int pwidth = next_pot(width); + const int pheight = next_pot(height); + rgba32_buf = gfx_opengl_scale_texture(rgba32_buf, width, height, pwidth, pheight); + width = pwidth; + height = pheight; + } + } + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba32_buf); } -static uint32_t gfx_cm_to_opengl(uint32_t val) { - if (val & G_TX_CLAMP) - return GL_CLAMP_TO_EDGE; +static inline GLenum gfx_cm_to_opengl(uint32_t val) { + if (val & G_TX_CLAMP) return GL_CLAMP_TO_EDGE; return (val & G_TX_MIRROR) ? GL_MIRRORED_REPEAT : GL_REPEAT; } +static inline void gfx_opengl_apply_tmu_state(const int tile) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tmu_state[tile].min_filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, tmu_state[tile].mag_filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, tmu_state[tile].wrap_s); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tmu_state[tile].wrap_t); +} + static void gfx_opengl_set_sampler_parameters(int tile, bool linear_filter, uint32_t cms, uint32_t cmt) { const GLenum filter = linear_filter ? GL_LINEAR : GL_NEAREST; - glActiveTexture(GL_TEXTURE0 + tile); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gfx_cm_to_opengl(cms)); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gfx_cm_to_opengl(cmt)); + + const GLenum wrap_s = gfx_cm_to_opengl(cms); + const GLenum wrap_t = gfx_cm_to_opengl(cmt); + + tmu_state[tile].min_filter = filter; + tmu_state[tile].mag_filter = filter; + tmu_state[tile].wrap_s = wrap_s; + tmu_state[tile].wrap_t = wrap_t; + + // set state for the first texture right away + if (!tile) gfx_opengl_apply_tmu_state(tile); } static void gfx_opengl_set_depth_test(bool depth_test) { - if (depth_test) { + if (depth_test) glEnable(GL_DEPTH_TEST); - } else { + else glDisable(GL_DEPTH_TEST); - } } static void gfx_opengl_set_depth_mask(bool z_upd) { @@ -436,26 +408,19 @@ static void gfx_opengl_set_scissor(int x, int y, int width, int height) { static void gfx_opengl_set_use_alpha(bool use_alpha) { gl_blend = use_alpha; - if (use_alpha) { + if (use_alpha) glEnable(GL_BLEND); - } else { + else glDisable(GL_BLEND); - } } // draws the same triangles as plain fog color + fog intensity as alpha // on top of the normal tris and blends them to achieve sort of the same effect // as fog would -static inline void gfx_opengl_blend_fog_tris(void) { - // if a texture was used, replace it with fog color instead, but still keep the alpha - if (cur_shader->texture_used[0]) { - glActiveTexture(GL_TEXTURE0); - TEXENV_COMBINE_ON(); - // out.rgb = input0.rgb - TEXENV_COMBINE_SET1(RGB, GL_REPLACE, GL_PRIMARY_COLOR); - // out.a = texel0.a * input0.a - TEXENV_COMBINE_SET2(ALPHA, GL_MODULATE, GL_TEXTURE, GL_PRIMARY_COLOR); - } +static inline void gfx_opengl_pass_fog(void) { + // if texturing is enabled, disable it, since we're blending colors + if (cur_shader->texture_used[0] || cur_shader->texture_used[1]) + glDisable(GL_TEXTURE_2D); glEnableClientState(GL_COLOR_ARRAY); // enable color array temporarily glColorPointer(4, GL_FLOAT, cur_buf_stride, cur_fog_ofs); // set fog colors as primary colors @@ -467,11 +432,38 @@ static inline void gfx_opengl_blend_fog_tris(void) { glDepthFunc(GL_LESS); // set back to default if (!gl_blend) glDisable(GL_BLEND); // disable blending if it was disabled glDisableClientState(GL_COLOR_ARRAY); // will get reenabled later anyway + + // if texturing was enabled, re-enable it + if (cur_shader->texture_used[0] || cur_shader->texture_used[1]) + glEnable(GL_TEXTURE_2D); +} + +// this assumes the two textures are combined like so: +// result = mix(tex0.rgb, tex1.rgb, vertex.rgb) +static inline void gfx_opengl_pass_mix_texture(void) { + // set second texture + glBindTexture(GL_TEXTURE_2D, tmu_state[cur_shader->texture_ord[1]].tex); + gfx_opengl_apply_tmu_state(cur_shader->texture_ord[1]); + + if (!gl_blend) glEnable(GL_BLEND); // enable blending temporarily + glBlendFunc(GL_ONE, GL_ONE); // additive blending + glDepthFunc(GL_LEQUAL); // Z is the same as the base triangles + + // draw the same triangles, but with the inverse of the mix color + glColor3f(c_invmix[0], c_invmix[1], c_invmix[2]); + glDrawArrays(GL_TRIANGLES, 0, 3 * cur_buf_num_tris); + glColor3f(1.f, 1.f, 1.f); // reset color + + glDepthFunc(GL_LESS); // set back to default + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // same here + if (!gl_blend) glDisable(GL_BLEND); // disable blending if it was disabled + + // set old texture + glBindTexture(GL_TEXTURE_2D, tmu_state[cur_shader->texture_ord[0]].tex); + gfx_opengl_apply_tmu_state(cur_shader->texture_ord[0]); } static void gfx_opengl_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris) { - //printf("flushing %d tris\n", buf_vbo_num_tris); - cur_buf = buf_vbo; cur_buf_size = buf_vbo_len * 4; cur_buf_num_tris = buf_vbo_num_tris; @@ -479,10 +471,17 @@ static void gfx_opengl_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_ gfx_opengl_apply_shader(cur_shader); + // if there's two textures, set primary texture first + if (cur_shader->texture_used[1]) + glBindTexture(GL_TEXTURE_2D, tmu_state[cur_shader->texture_ord[0]].tex); + glDrawArrays(GL_TRIANGLES, 0, 3 * cur_buf_num_tris); + // if there's two textures, draw polys with the second texture + if (cur_shader->texture_used[1]) gfx_opengl_pass_mix_texture(); + // cur_fog_ofs is only set if GL_EXT_fog_coord isn't used - if (cur_fog_ofs) gfx_opengl_blend_fog_tris(); + if (cur_fog_ofs) gfx_opengl_pass_fog(); } static inline bool gl_check_ext(const char *name) { @@ -492,7 +491,7 @@ static inline bool gl_check_ext(const char *name) { extstr = (const char *)glGetString(GL_EXTENSIONS); if (!strstr(extstr, name)) { - fprintf(stderr, "GL extension not supported: %s\n", name); + printf("GL extension not supported: %s\n", name); return false; } @@ -526,54 +525,34 @@ static void gfx_opengl_init(void) { int vmajor, vminor; bool is_es = false; gl_get_version(&vmajor, &vminor, &is_es); - if (vmajor < 2 && vminor < 2 && !is_es) - sys_fatal("OpenGL 1.2+ is required.\nReported version: %s%d.%d", is_es ? "ES" : "", vmajor, vminor); + if ((vmajor < 2 && vminor < 1) || is_es) + sys_fatal("OpenGL 1.1+ is required.\nReported version: %s%d.%d\n", is_es ? "ES" : "", vmajor, vminor); - // check extensions that we need - const bool supported = - gl_check_ext("GL_ARB_multitexture") && - gl_check_ext("GL_ARB_texture_env_combine"); - - if (!supported) - sys_fatal("required GL extensions are not supported"); - - gl_adv_fog = false; - - // check whether we can use advanced fog shit - const bool fog_ext = - vmajor > 1 || vminor > 3 || - gl_check_ext("GL_EXT_fog_coord") || - gl_check_ext("GL_ARB_fog_coord"); - - if (fog_ext) { - // try to load manually, as this might be an extension, and even then the ext list may lie - mglFogCoordPointer = mglGetProcAddress("glFogCoordPointer"); - if (!mglFogCoordPointer) mglFogCoordPointer = mglGetProcAddress("glFogCoordPointerEXT"); - if (!mglFogCoordPointer) mglFogCoordPointer = mglGetProcAddress("glFogCoordPointerARB"); - if (!mglFogCoordPointer) - printf("glFogCoordPointer is not actually available, it won't be used.\n"); - else - gl_adv_fog = true; // appears to be all good + // check if we support non power of two textures + gl_npot = gl_check_ext("GL_ARB_texture_non_power_of_two"); + if (!gl_npot) { + // don't support NPOT textures, prepare buffer for rescaling + // this will be realloc'd as necessary + scale_buf_size = 64 * 64 * 4; + scale_buf = malloc(scale_buf_size); + if (!scale_buf) sys_fatal("Out of memory allocating for NPOT scale buffer\n"); } + // check if we support multitexturing + gl_multitexture = vmajor > 1 || vminor > 2 || gl_check_ext("GL_ARB_multitexture"); + printf("GL_VERSION = %s\n", glGetString(GL_VERSION)); printf("GL_EXTENSIONS =\n%s\n", glGetString(GL_EXTENSIONS)); - if (gl_adv_fog) { - // set fog params, they never change - printf("GL_EXT_fog_coord available, using that for fog\n"); - glFogi(GL_FOG_COORD_SRC, GL_FOG_COORD); - glFogi(GL_FOG_MODE, GL_LINEAR); - glFogf(GL_FOG_START, 0.0f); - glFogf(GL_FOG_END, 1.0f); - } - // these also never change + glDisable(GL_LIGHTING); + glDisable(GL_CULL_FACE); + // glDisable(GL_DITHER); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glEnableClientState(GL_VERTEX_ARRAY); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, c_white); - TEXENV_COMBINE_OP(0, GL_SRC_COLOR, GL_SRC_ALPHA); - TEXENV_COMBINE_OP(1, GL_SRC_COLOR, GL_SRC_ALPHA); } static void gfx_opengl_on_resize(void) { From 468887a6f97c08137784443bd5841ec826e793ba Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 28 Aug 2020 23:36:56 -0400 Subject: [PATCH 23/26] Check for zero rumble setting before allowing rumble Fixes controllers which don't check for rumble_strength and have constant rumble from rumbling even when set to 0 in the config. --- src/pc/controller/controller_entry_point.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pc/controller/controller_entry_point.c b/src/pc/controller/controller_entry_point.c index d5403f938..bc5e9fe6c 100644 --- a/src/pc/controller/controller_entry_point.c +++ b/src/pc/controller/controller_entry_point.c @@ -33,12 +33,16 @@ s32 osContInit(UNUSED OSMesgQueue *mq, u8 *controllerBits, UNUSED OSContStatus * s32 osMotorStart(UNUSED void *pfs) { // Since rumble stops by osMotorStop, its duration is not nessecary. // Set it to 5 seconds and hope osMotorStop() is called in time. - controller_rumble_play(configRumbleStrength / 100.0f, 5.0f); + if (configRumbleStrength>0){ + controller_rumble_play(configRumbleStrength / 100.0f, 5.0f); + } return 0; } s32 osMotorStop(UNUSED void *pfs) { - controller_rumble_stop(); + if (configRumbleStrength>0){ + controller_rumble_stop(); + } return 0; } From 46c57457b1908bdf610a5695f90d5796e1634472 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sun, 30 Aug 2020 01:29:26 +0300 Subject: [PATCH 24/26] GL_LEGACY: this should've been GL_REPLACE all along --- src/pc/gfx/gfx_opengl_legacy.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pc/gfx/gfx_opengl_legacy.c b/src/pc/gfx/gfx_opengl_legacy.c index a069f3497..2e3f12160 100644 --- a/src/pc/gfx/gfx_opengl_legacy.c +++ b/src/pc/gfx/gfx_opengl_legacy.c @@ -132,11 +132,11 @@ static bool gfx_opengl_z_is_from_0_to_1(void) { } static inline GLenum texenv_set_color(UNUSED struct ShaderProgram *prg) { - return GL_MODULATE; + return GL_REPLACE; } static inline GLenum texenv_set_texture(UNUSED struct ShaderProgram *prg) { - return GL_MODULATE; + return GL_REPLACE; } static inline GLenum texenv_set_texture_color(struct ShaderProgram *prg) { From cc26cc9bf9627e6d0bf38ab8428f8a4926beeaff Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sun, 30 Aug 2020 15:39:41 +0300 Subject: [PATCH 25/26] fix spaces --- src/pc/controller/controller_entry_point.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pc/controller/controller_entry_point.c b/src/pc/controller/controller_entry_point.c index bc5e9fe6c..f1eeeedcf 100644 --- a/src/pc/controller/controller_entry_point.c +++ b/src/pc/controller/controller_entry_point.c @@ -33,16 +33,14 @@ s32 osContInit(UNUSED OSMesgQueue *mq, u8 *controllerBits, UNUSED OSContStatus * s32 osMotorStart(UNUSED void *pfs) { // Since rumble stops by osMotorStop, its duration is not nessecary. // Set it to 5 seconds and hope osMotorStop() is called in time. - if (configRumbleStrength>0){ + if (configRumbleStrength) controller_rumble_play(configRumbleStrength / 100.0f, 5.0f); - } return 0; } s32 osMotorStop(UNUSED void *pfs) { - if (configRumbleStrength>0){ + if (configRumbleStrength) controller_rumble_stop(); - } return 0; } From 8a23a8a5c5c19c91ef44053942d8923aa0e6b7c7 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sun, 30 Aug 2020 16:06:22 +0300 Subject: [PATCH 26/26] add rudimentary SDL1 controller backend it's more to deal with the mouse not working I suppose --- Makefile | 2 +- src/pc/controller/controller_entry_point.c | 5 +- src/pc/controller/controller_sdl1.c | 285 +++++++++++++++++++++ 3 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 src/pc/controller/controller_sdl1.c diff --git a/Makefile b/Makefile index fc8f8e61d..aa9c3d7a3 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ RENDER_API ?= GL WINDOW_API ?= SDL2 # Audio backends: SDL1, SDL2 AUDIO_API ?= SDL2 -# Controller backends (can have multiple, space separated): SDL2 +# Controller backends (can have multiple, space separated): SDL2, SDL1 CONTROLLER_API ?= SDL2 # Misc settings for EXTERNAL_DATA diff --git a/src/pc/controller/controller_entry_point.c b/src/pc/controller/controller_entry_point.c index 5e8f16c90..34ca9ca05 100644 --- a/src/pc/controller/controller_entry_point.c +++ b/src/pc/controller/controller_entry_point.c @@ -13,16 +13,15 @@ static struct ControllerAPI *controller_implementations[] = { &controller_recorded_tas, - #ifdef CAPI_SDL2 + #if defined(CAPI_SDL2) || defined(CAPI_SDL1) &controller_sdl, #endif &controller_keyboard, }; s32 osContInit(UNUSED OSMesgQueue *mq, u8 *controllerBits, UNUSED OSContStatus *status) { - for (size_t i = 0; i < sizeof(controller_implementations) / sizeof(struct ControllerAPI *); i++) { + for (size_t i = 0; i < sizeof(controller_implementations) / sizeof(struct ControllerAPI *); i++) controller_implementations[i]->init(); - } *controllerBits = 1; return 0; } diff --git a/src/pc/controller/controller_sdl1.c b/src/pc/controller/controller_sdl1.c new file mode 100644 index 000000000..37316674c --- /dev/null +++ b/src/pc/controller/controller_sdl1.c @@ -0,0 +1,285 @@ +#ifdef CAPI_SDL1 + +#include +#include +#include +#include + +#include + +// Analog camera movement by Pathétique (github.com/vrmiguel), y0shin and Mors +// Contribute or communicate bugs at github.com/vrmiguel/sm64-analog-camera + +#include + +#include "controller_api.h" +#include "controller_sdl.h" +#include "../configfile.h" +#include "../platform.h" +#include "../fs/fs.h" + +#include "game/level_update.h" + +// mouse buttons are also in the controller namespace (why), just offset 0x100 +#define VK_OFS_SDL_MOUSE 0x0100 +#define VK_BASE_SDL_MOUSE (VK_BASE_SDL_GAMEPAD + VK_OFS_SDL_MOUSE) +#define MAX_JOYBINDS 32 +#define MAX_MOUSEBUTTONS 8 // arbitrary +#define MAX_JOYBUTTONS 32 // arbitrary; includes virtual keys for triggers +#define AXIS_THRESHOLD (30 * 256) + +enum { + JOY_AXIS_LEFTX, + JOY_AXIS_LEFTY, + JOY_AXIS_RIGHTX, + JOY_AXIS_RIGHTY, + JOY_AXIS_LTRIG, + JOY_AXIS_RTRIG, + MAX_AXES, +}; + +int mouse_x; +int mouse_y; + +#ifdef BETTERCAMERA +extern u8 newcam_mouse; +#endif + +static bool init_ok; +static SDL_Joystick *sdl_joy; + +static u32 num_joy_binds = 0; +static u32 num_mouse_binds = 0; +static u32 joy_binds[MAX_JOYBINDS][2]; +static u32 mouse_binds[MAX_JOYBINDS][2]; +static int joy_axis_binds[MAX_AXES] = { 0, 1, 2, 3, 4, 5 }; + +static bool joy_buttons[MAX_JOYBUTTONS] = { false }; +static u32 mouse_buttons = 0; +static u32 last_mouse = VK_INVALID; +static u32 last_joybutton = VK_INVALID; + +static int num_joy_axes = 0; +static int num_joy_buttons = 0; +static int num_joy_hats = 0; + +static inline void controller_add_binds(const u32 mask, const u32 *btns) { + for (u32 i = 0; i < MAX_BINDS; ++i) { + if (btns[i] >= VK_BASE_SDL_GAMEPAD && btns[i] <= VK_BASE_SDL_GAMEPAD + VK_SIZE) { + if (btns[i] >= VK_BASE_SDL_MOUSE && num_joy_binds < MAX_JOYBINDS) { + mouse_binds[num_mouse_binds][0] = btns[i] - VK_BASE_SDL_MOUSE; + mouse_binds[num_mouse_binds][1] = mask; + ++num_mouse_binds; + } else if (num_mouse_binds < MAX_JOYBINDS) { + joy_binds[num_joy_binds][0] = btns[i] - VK_BASE_SDL_GAMEPAD; + joy_binds[num_joy_binds][1] = mask; + ++num_joy_binds; + } + } + } +} + +static void controller_sdl_bind(void) { + bzero(joy_binds, sizeof(joy_binds)); + bzero(mouse_binds, sizeof(mouse_binds)); + num_joy_binds = 0; + num_mouse_binds = 0; + + controller_add_binds(A_BUTTON, configKeyA); + controller_add_binds(B_BUTTON, configKeyB); + controller_add_binds(Z_TRIG, configKeyZ); + controller_add_binds(STICK_UP, configKeyStickUp); + controller_add_binds(STICK_LEFT, configKeyStickLeft); + controller_add_binds(STICK_DOWN, configKeyStickDown); + controller_add_binds(STICK_RIGHT, configKeyStickRight); + controller_add_binds(U_CBUTTONS, configKeyCUp); + controller_add_binds(L_CBUTTONS, configKeyCLeft); + controller_add_binds(D_CBUTTONS, configKeyCDown); + controller_add_binds(R_CBUTTONS, configKeyCRight); + controller_add_binds(L_TRIG, configKeyL); + controller_add_binds(R_TRIG, configKeyR); + controller_add_binds(START_BUTTON, configKeyStart); +} + +static void controller_sdl_init(void) { + if (SDL_Init(SDL_INIT_JOYSTICK) != 0) { + fprintf(stderr, "SDL init error: %s\n", SDL_GetError()); + return; + } + + if (SDL_NumJoysticks() > 0) + sdl_joy = SDL_JoystickOpen(0); + + if (sdl_joy) { + num_joy_axes = SDL_JoystickNumAxes(sdl_joy); + num_joy_buttons = SDL_JoystickNumButtons(sdl_joy); + num_joy_hats = SDL_JoystickNumHats(sdl_joy); + + for (int i = 0; i < MAX_AXES; ++i) + if (i >= num_joy_axes) + joy_axis_binds[i] = -1; + } + +#ifdef BETTERCAMERA + if (newcam_mouse == 1) + SDL_WM_GrabInput(SDL_GRAB_ON); + SDL_GetRelativeMouseState(&mouse_x, &mouse_y); +#endif + + controller_sdl_bind(); + + init_ok = true; +} + +static inline void update_button(const int i, const bool new) { + const bool pressed = !joy_buttons[i] && new; + joy_buttons[i] = new; + if (pressed) last_joybutton = i; +} + +static inline int16_t get_axis(const int i) { + if (joy_axis_binds[i] >= 0) + return SDL_JoystickGetAxis(sdl_joy, i); + else + return 0; +} + +static void controller_sdl_read(OSContPad *pad) { + if (!init_ok) return; + +#ifdef BETTERCAMERA + if (newcam_mouse == 1 && sCurrPlayMode != 2) + SDL_WM_GrabInput(SDL_GRAB_ON); + else + SDL_WM_GrabInput(SDL_GRAB_OFF); + + u32 mouse = SDL_GetRelativeMouseState(&mouse_x, &mouse_y); + + for (u32 i = 0; i < num_mouse_binds; ++i) + if (mouse & SDL_BUTTON(mouse_binds[i][0])) + pad->button |= mouse_binds[i][1]; + + // remember buttons that changed from 0 to 1 + last_mouse = (mouse_buttons ^ mouse) & mouse; + mouse_buttons = mouse; +#endif + + if (!sdl_joy) return; + + SDL_JoystickUpdate(); + + int16_t leftx = get_axis(JOY_AXIS_LEFTX); + int16_t lefty = get_axis(JOY_AXIS_LEFTY); + int16_t rightx = get_axis(JOY_AXIS_RIGHTX); + int16_t righty = get_axis(JOY_AXIS_RIGHTY); + + int16_t ltrig = get_axis(JOY_AXIS_LTRIG); + int16_t rtrig = get_axis(JOY_AXIS_RTRIG); + +#ifdef TARGET_WEB + // Firefox has a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1606562 + // It sets down y to 32768.0f / 32767.0f, which is greater than the allowed 1.0f, + // which SDL then converts to a int16_t by multiplying by 32767.0f, which overflows into -32768. + // Maximum up will hence never become -32768 with the current version of SDL2, + // so this workaround should be safe in compliant browsers. + if (lefty == -32768) { + lefty = 32767; + } + if (righty == -32768) { + righty = 32767; + } +#endif + + for (int i = 0; i < num_joy_buttons; ++i) { + const bool new = SDL_JoystickGetButton(sdl_joy, i); + update_button(i, new); + } + + update_button(VK_LTRIGGER - VK_BASE_SDL_GAMEPAD, ltrig > AXIS_THRESHOLD); + update_button(VK_RTRIGGER - VK_BASE_SDL_GAMEPAD, rtrig > AXIS_THRESHOLD); + + u32 buttons_down = 0; + for (u32 i = 0; i < num_joy_binds; ++i) + if (joy_buttons[joy_binds[i][0]]) + buttons_down |= joy_binds[i][1]; + + pad->button |= buttons_down; + + const u32 xstick = buttons_down & STICK_XMASK; + const u32 ystick = buttons_down & STICK_YMASK; + if (xstick == STICK_LEFT) + pad->stick_x = -128; + else if (xstick == STICK_RIGHT) + pad->stick_x = 127; + if (ystick == STICK_DOWN) + pad->stick_y = -128; + else if (ystick == STICK_UP) + pad->stick_y = 127; + + if (rightx < -0x4000) pad->button |= L_CBUTTONS; + if (rightx > 0x4000) pad->button |= R_CBUTTONS; + if (righty < -0x4000) pad->button |= U_CBUTTONS; + if (righty > 0x4000) pad->button |= D_CBUTTONS; + + uint32_t magnitude_sq = (uint32_t)(leftx * leftx) + (uint32_t)(lefty * lefty); + uint32_t stickDeadzoneActual = configStickDeadzone * DEADZONE_STEP; + if (magnitude_sq > (uint32_t)(stickDeadzoneActual * stickDeadzoneActual)) { + pad->stick_x = leftx / 0x100; + int stick_y = -lefty / 0x100; + pad->stick_y = stick_y == 128 ? 127 : stick_y; + } + + magnitude_sq = (uint32_t)(rightx * rightx) + (uint32_t)(righty * righty); + stickDeadzoneActual = configStickDeadzone * DEADZONE_STEP; + if (magnitude_sq > (uint32_t)(stickDeadzoneActual * stickDeadzoneActual)) { + pad->ext_stick_x = rightx / 0x100; + int stick_y = -righty / 0x100; + pad->ext_stick_y = stick_y == 128 ? 127 : stick_y; + } +} + +static void controller_sdl_rumble_play(f32 strength, f32 length) { } + +static void controller_sdl_rumble_stop(void) { } + +static u32 controller_sdl_rawkey(void) { + if (last_joybutton != VK_INVALID) { + const u32 ret = last_joybutton; + last_joybutton = VK_INVALID; + return ret; + } + + for (int i = 0; i < MAX_MOUSEBUTTONS; ++i) { + if (last_mouse & SDL_BUTTON(i)) { + const u32 ret = VK_OFS_SDL_MOUSE + i; + last_mouse = 0; + return ret; + } + } + return VK_INVALID; +} + +static void controller_sdl_shutdown(void) { + if (SDL_WasInit(SDL_INIT_JOYSTICK)) { + if (sdl_joy) { + SDL_JoystickClose(sdl_joy); + sdl_joy = NULL; + } + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + } + + init_ok = false; +} + +struct ControllerAPI controller_sdl = { + VK_BASE_SDL_GAMEPAD, + controller_sdl_init, + controller_sdl_read, + controller_sdl_rawkey, + controller_sdl_rumble_play, + controller_sdl_rumble_stop, + controller_sdl_bind, + controller_sdl_shutdown +}; + +#endif // CAPI_SDL1