terminal_demo: Add Draw metrics to upper-left corner.

Drawing the FPS, draw time, and flush time metrics to the
top left corner of the display. The text is:

    FPS: %d, Draw:%dms, Flush:%dms

Flush time is the time the display/driver takes to transport the
framebuffer over the bus (currently SPI) to the display controller.

Change-Id: I6e2a967ddb764aa117c9a112791fbeb6ad17d41f
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/experimental/+/125830
Commit-Queue: Chris Mumford <cmumford@google.com>
Reviewed-by: Anthony DiGirolamo <tonymd@google.com>
diff --git a/applications/terminal_display/BUILD.gn b/applications/terminal_display/BUILD.gn
index fcd69da..718be6d 100644
--- a/applications/terminal_display/BUILD.gn
+++ b/applications/terminal_display/BUILD.gn
@@ -57,6 +57,7 @@
     "$dir_pw_framebuffer",
     "$dir_pw_log",
     "$dir_pw_random",
+    "$dir_pw_ring_buffer",
     "$dir_pw_string",
     "$dir_pw_sys_io",
     "$dir_pw_touchscreen",
diff --git a/applications/terminal_display/main.cc b/applications/terminal_display/main.cc
index c03b24f..cf2a178 100644
--- a/applications/terminal_display/main.cc
+++ b/applications/terminal_display/main.cc
@@ -13,6 +13,7 @@
 // the License.
 #include <array>
 #include <cstdint>
+#include <cwchar>
 #include <forward_list>
 #include <memory>
 #include <string_view>
@@ -22,6 +23,7 @@
 
 #include "ansi.h"
 #include "app_common/common.h"
+#include "pw_assert/assert.h"
 #include "pw_assert/check.h"
 #include "pw_board_led/led.h"
 #include "pw_color/color.h"
@@ -34,6 +36,7 @@
 #include "pw_draw/pigweed_farm.h"
 #include "pw_framebuffer/rgb565.h"
 #include "pw_log/log.h"
+#include "pw_ring_buffer/prefixed_entry_ring_buffer.h"
 #include "pw_spin_delay/delay.h"
 #include "pw_string/string_builder.h"
 #include "pw_sys_io/sys_io.h"
@@ -52,6 +55,7 @@
 using pw::display::Display;
 using pw::draw::FontSet;
 using pw::framebuffer::FramebufferRgb565;
+using pw::ring_buffer::PrefixedEntryRingBuffer;
 
 // TODO(cmumford): move this code into a pre_init section (i.e. boot.cc) which
 //                 is part of the target. Not all targets currently have this.
@@ -297,6 +301,20 @@
   return 76;
 }
 
+void DrawFPS(Vector2<int> tl,
+             FramebufferRgb565& framebuffer,
+             std::wstring_view fps_msg) {
+  if (fps_msg.empty())
+    return;
+
+  DrawString(fps_msg,
+             tl,
+             colors_pico8_rgb565[COLOR_PEACH],
+             kBlack,
+             pw::draw::font6x8,
+             framebuffer);
+}
+
 // Draw the pigweed text banner.
 // Returns the bottom Y coordinate of the bottommost pixel set.
 int DrawPigweedBanner(Vector2<int> tl, FramebufferRgb565& framebuffer) {
@@ -367,7 +385,7 @@
 
 // Draw the application header section which is mostly static text/graphics.
 // Return the height (in pixels) of the header.
-int DrawHeader(FramebufferRgb565& framebuffer) {
+int DrawHeader(FramebufferRgb565& framebuffer, std::wstring_view fps_msg) {
   DrawButton(
       g_button, /*bg_color=*/colors_pico8_rgb565[COLOR_BLUE], framebuffer);
   Vector2<int> tl = {0, 0};
@@ -377,6 +395,8 @@
   constexpr int kFontSheetMargin = 4;
   tl.y += kFontSheetMargin;
 
+  DrawFPS({1, 2}, framebuffer, fps_msg);
+
   return DrawFontSheets(tl, framebuffer);
 }
 
@@ -405,9 +425,9 @@
   }
 }
 
-void DrawFrame(FramebufferRgb565& framebuffer) {
+void DrawFrame(FramebufferRgb565& framebuffer, std::wstring_view fps_msg) {
   constexpr int kHeaderMargin = 4;
-  int header_bottom = DrawHeader(framebuffer);
+  int header_bottom = DrawHeader(framebuffer, fps_msg);
   DrawLogTextBuffer(
       header_bottom + kHeaderMargin, pw::draw::font6x8, framebuffer);
 }
@@ -420,6 +440,25 @@
   PW_LOG_DEBUG("Debug output");
 }
 
+// Given a ring buffer full of uint32_t values, return the average value
+// or zero if empty.
+uint32_t CalcAverageUint32Value(PrefixedEntryRingBuffer& ring_buffer) {
+  if (!ring_buffer.EntryCount())
+    return 0;
+  uint64_t sum = 0;
+  uint32_t count = 0;
+  pw::ring_buffer::PrefixedEntryRingBufferMulti::iterator it =
+      ring_buffer.begin();
+  for (; it != ring_buffer.end(); ++it) {
+    PW_ASSERT(it->buffer.size() == sizeof(uint32_t));
+    uint32_t val;
+    std::memcpy(&val, it->buffer.data(), sizeof(val));
+    sum += val;
+    count++;
+  }
+  return sum / count;
+}
+
 }  // namespace
 
 void MainTask(void* pvParameters) {
@@ -427,6 +466,15 @@
   uint32_t frame_start_millis = pw::spin_delay::Millis();
   uint32_t frames = 0;
   int frames_per_second = 0;
+  std::array<wchar_t, 40> fps_buffer = {0};
+  std::wstring_view fps_view(fps_buffer.data(), 0);
+  std::byte draw_buffer[30 * sizeof(uint32_t)];
+  std::byte flush_buffer[30 * sizeof(uint32_t)];
+  PrefixedEntryRingBuffer draw_times;
+  PrefixedEntryRingBuffer flush_times;
+
+  draw_times.SetBuffer(draw_buffer);
+  flush_times.SetBuffer(flush_buffer);
 
   pw::log_basic::SetOutput(LogCallback);
 
@@ -444,7 +492,7 @@
 
   pw::coordinates::Vec3Int last_frame_touch_state(0, 0, 0);
 
-  DrawFrame(framebuffer);
+  DrawFrame(framebuffer, fps_view);
   // Push the frame buffer to the screen.
   display.ReleaseFramebuffer(std::move(framebuffer));
 
@@ -472,21 +520,34 @@
     last_frame_touch_state.y = point.y;
     last_frame_touch_state.z = point.z;
 
+    uint32_t start = pw::spin_delay::Millis();
     framebuffer = display.GetFramebuffer();
     pw::draw::Fill(&framebuffer, kBlack);
-    DrawFrame(framebuffer);
+    DrawFrame(framebuffer, fps_view);
+    uint32_t end = pw::spin_delay::Millis();
+    uint32_t time = end - start;
+    draw_times.PushBack(pw::as_bytes(pw::span{std::addressof(time), 1}));
+    start = end;
 
     display.ReleaseFramebuffer(std::move(framebuffer));
+    time = pw::spin_delay::Millis() - start;
+    flush_times.PushBack(pw::as_bytes(pw::span{std::addressof(time), 1}));
 
     // Every second make a log message.
     frames++;
     if (pw::spin_delay::Millis() > frame_start_millis + 1000) {
+      frames_per_second = frames;
+      frames = 0;
       PW_LOG_INFO("Time: %lu - FPS: %d",
                   static_cast<unsigned long>(pw::spin_delay::Millis()),
                   frames_per_second);
-
-      frames_per_second = frames;
-      frames = 0;
+      int len = std::swprintf(fps_buffer.data(),
+                              fps_buffer.size(),
+                              L"FPS:%d, Draw:%dms, Flush:%dms",
+                              frames_per_second,
+                              CalcAverageUint32Value(draw_times),
+                              CalcAverageUint32Value(flush_times));
+      fps_view = std::wstring_view(fps_buffer.data(), len);
 
       frame_start_millis = pw::spin_delay::Millis();
     }