diff --git a/src/game/game_init.c b/src/game/game_init.c
index b961ca52..531231cf 100644
--- a/src/game/game_init.c
+++ b/src/game/game_init.c
@@ -82,6 +82,47 @@ struct DemoInput gRecordedDemoInput = { 0 };
 // Display
 // ----------------------------------------------------------------------------------------------------
 
+// SDK states that 1 cycle takes about 21.33 nanoseconds
+#define SECONDS_PER_CYCLE 0.00000002133f
+
+#define FPS_COUNTER_X_POS 24
+#define FPS_COUNTER_Y_POS 190
+
+static OSTime gLastOSTime = 0;
+static float gFrameTime = 0.0f;
+static u16 gFrames = 0;
+static u16 gFPS = 0;
+static u8 gRenderFPS = FALSE;
+
+static void calculate_frameTime_from_OSTime(OSTime diff) {
+    gFrameTime += diff * SECONDS_PER_CYCLE;
+    gFrames++;
+}
+
+static void render_fps(void) {
+    // Toggle rendering framerate with the L button.
+    if (gPlayer1Controller->buttonPressed & L_TRIG) {
+        gRenderFPS ^= 1;
+    }
+
+    if (gRenderFPS) {
+        OSTime newTime = osGetTime();
+
+        calculate_frameTime_from_OSTime(newTime - gLastOSTime);
+
+        // If frame time is longer or equal to a second, update FPS counter.
+        if (gFrameTime >= 1.0f) {
+            gFPS = gFrames;
+            gFrames = 0;
+            gFrameTime -= 1.0f;
+        }
+
+        print_text_fmt_int(FPS_COUNTER_X_POS, FPS_COUNTER_Y_POS, "FPS %d", gFPS);
+
+        gLastOSTime = newTime;
+    }
+}
+
 /**
  * Sets the initial RDP (Reality Display Processor) rendering settings.
  */
@@ -694,5 +735,7 @@ void thread5_game_loop(UNUSED void *arg) {
             // amount of free space remaining.
             print_text_fmt_int(180, 20, "BUF %d", gGfxPoolEnd - (u8 *) gDisplayListHead);
         }
+
+        render_fps();
     }
 }
