feat(glfw): adapter and 1 MS/s streaming demo
This commit is contained in:
1 parent
1f5aa11de2
commit
4b649a2783
7 files changed
+884
-2
No files matched your search
@@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
#include <plot/core/rect.hpp>
|
||||
#include <plot/core/status.hpp>
|
||||
#include <plot/core/types.hpp>
|
||||
#include <plot/input/controller.hpp>
|
||||
#include <plot/plot/plot.hpp>
|
||||
#include <plot/render/renderer.hpp>
|
||||
|
||||
/// GLFW's window type. Forward-declared on purpose: this header is
|
||||
/// self-contained and needs no GLFW include directory, so a consumer of the
|
||||
/// adapter does not inherit the toolkit's headers (and their `<GL/gl.h>`, whose
|
||||
/// macros would rewrite this library's enumerators).
|
||||
struct GLFWwindow;
|
||||
|
||||
namespace plot::glfw {
|
||||
|
||||
/// What `Host::run()` opens the window with.
|
||||
struct RunConfig {
|
||||
int width = 1280;
|
||||
int height = 720;
|
||||
const char* title = "plot";
|
||||
bool vsync = true;
|
||||
};
|
||||
|
||||
/// One frame of application code, called by `Host::run()` after the viewport is
|
||||
/// set and before the frame is staged, so it is where data is refreshed.
|
||||
/// `dt` is the wall time since the previous call, in seconds.
|
||||
using FrameFn = std::function<void(plot::Plot&, f64 dt)>;
|
||||
|
||||
/// The reference host: owns the GLFW window and its OpenGL 3.3 core context, the
|
||||
/// GL entry points, the `Renderer`, the `Plot`, the `input::Controller` and the
|
||||
/// frame loop, so nothing above it names GLFW or GL.
|
||||
///
|
||||
/// Per-frame sequence, in this order:
|
||||
///
|
||||
/// poll events -> controller.set_viewport(framebuffer-pixel rect) ->
|
||||
/// frame(plot, dt) -> begin_frame -> draw -> [screenshot] -> end_frame ->
|
||||
/// swap buffers
|
||||
///
|
||||
/// Two pixel spaces meet here and must not be confused. GLFW reports the cursor
|
||||
/// and the window in *window* coordinates, while the framebuffer -- what
|
||||
/// `begin_frame` takes and what every `Rect` in this library speaks -- can be
|
||||
/// larger under a fractional or HiDPI scale, so `glfwGetFramebufferSize()` and
|
||||
/// `glfwGetWindowSize()` disagree. The host calls `begin_frame` with the
|
||||
/// framebuffer size and scales every pointer coordinate by the ratio
|
||||
/// `framebuffer / window`, so a gesture lands on the pixels the plot was laid out
|
||||
/// in. On an unscaled display the ratio is 1.0 and nothing changes.
|
||||
///
|
||||
/// Headless smoke mode, read once per `run()`:
|
||||
///
|
||||
/// `PLOT_DEMO_EXIT_AFTER_MS=<n>` stop the loop after n ms and return
|
||||
/// (default 0: run until the window is closed)
|
||||
/// `PLOT_DEMO_SCREENSHOT=<path>` on that final frame, write it with
|
||||
/// `Renderer::dump_frame_ppm()` before the swap
|
||||
///
|
||||
/// That is what lets a CI run a GUI library under `xvfb-run`: no display access
|
||||
/// and no screen capture, just a PPM on disk.
|
||||
///
|
||||
/// A run with a deadline is that automated path, so its window is created hidden
|
||||
/// (`GLFW_VISIBLE` false): an automated run must not flash a window on whatever
|
||||
/// display it lands on. It also means the loop is not throttled by a display
|
||||
/// refresh, so the frame times it reports are the CPU's, not a vblank's. A run
|
||||
/// without a deadline shows its window, as an interactive application must.
|
||||
///
|
||||
/// Not thread-safe, and confined to the thread that runs `run()`.
|
||||
class Host {
|
||||
public:
|
||||
Host() noexcept;
|
||||
~Host();
|
||||
Host(const Host&) = delete;
|
||||
Host& operator=(const Host&) = delete;
|
||||
|
||||
/// Opens the window, makes a 3.3 core context current, resolves the GL entry
|
||||
/// points and initialises the renderer, then runs the loop until the window is
|
||||
/// closed or the exit deadline passes. Returns false with `st` filled in when
|
||||
/// GLFW, the context or `Renderer::init()` fails; a failed run leaves nothing
|
||||
/// open.
|
||||
///
|
||||
/// On success the window *stays open*, so a caller can stop and join whatever
|
||||
/// feeds the plot before the GL resources go away: `close()` (or the
|
||||
/// destructor) releases the renderer, the context and the window, in that
|
||||
/// order, on this thread. Calling `run()` again reopens the window and keeps
|
||||
/// the plot.
|
||||
bool run(const RunConfig& cfg, const FrameFn& frame, Status* st = nullptr);
|
||||
|
||||
/// Releases the renderer and the window. Idempotent, and safe on a host that
|
||||
/// never ran.
|
||||
void close() noexcept;
|
||||
|
||||
/// The plot the frame callback populates. Valid for the host's whole life.
|
||||
Plot& plot() noexcept { return plot_; }
|
||||
/// The gesture controller the host feeds. Valid for the host's whole life.
|
||||
input::Controller& controller() noexcept { return controller_; }
|
||||
|
||||
/// The renderer, or null while no window is open. Valid from inside the frame
|
||||
/// callback and after a successful `run()`; `close()` releases it.
|
||||
Renderer* renderer() noexcept { return renderer_ ? &*renderer_ : nullptr; }
|
||||
|
||||
/// Retitles the open window -- from the frame callback, for a live
|
||||
/// samples/second and frame-time readout. A no-op while no window is open.
|
||||
void set_title(const char* title) noexcept;
|
||||
|
||||
/// Framebuffer pixels per window pixel, one ratio per axis, as of the last
|
||||
/// event or frame. 1.0 on an unscaled display.
|
||||
f32 framebuffer_scale_x() const noexcept { return fb_scale_x_; }
|
||||
f32 framebuffer_scale_y() const noexcept { return fb_scale_y_; }
|
||||
/// The window's content scale, one per axis, as of the last frame.
|
||||
f32 content_scale_x() const noexcept { return content_scale_x_; }
|
||||
f32 content_scale_y() const noexcept { return content_scale_y_; }
|
||||
|
||||
private:
|
||||
/// Reads the framebuffer and window sizes and the content scale, refreshing
|
||||
/// the window -> framebuffer ratios the pointer translation uses.
|
||||
void refresh_sizes() noexcept;
|
||||
|
||||
/// Callbacks find their host through GLFW's per-window user pointer.
|
||||
static Host* from(GLFWwindow* w) noexcept;
|
||||
static void cursor_pos_cb(GLFWwindow* w, double x, double y);
|
||||
static void mouse_button_cb(GLFWwindow* w, int button, int action, int mods);
|
||||
static void scroll_cb(GLFWwindow* w, double dx, double dy);
|
||||
static void key_cb(GLFWwindow* w, int key, int scancode, int action, int mods);
|
||||
static void framebuffer_size_cb(GLFWwindow* w, int width, int height);
|
||||
|
||||
void on_cursor_pos(f64 wx, f64 wy) noexcept;
|
||||
void on_mouse_button(int button, int action, int mods) noexcept;
|
||||
void on_scroll(f64 dx, f64 dy) noexcept;
|
||||
void on_key(int key, int action, int mods) noexcept;
|
||||
|
||||
Plot plot_;
|
||||
input::Controller controller_; ///< forwards its viewport to `plot_`
|
||||
std::optional<Renderer> renderer_;
|
||||
GLFWwindow* window_ = nullptr;
|
||||
|
||||
u32 fb_width_ = 0; ///< framebuffer size of the last refresh_sizes()
|
||||
u32 fb_height_ = 0;
|
||||
/// Framebuffer pixels per window pixel. Taken from the two sizes GLFW reports
|
||||
/// rather than from the content scale, which under a fractional scale does not
|
||||
/// round back to the framebuffer size.
|
||||
f32 fb_scale_x_ = 1.0f;
|
||||
f32 fb_scale_y_ = 1.0f;
|
||||
f32 content_scale_x_ = 1.0f;
|
||||
f32 content_scale_y_ = 1.0f;
|
||||
/// Last pointer position in framebuffer pixels: the deltas the controller pans
|
||||
/// with are the movements since the previous event, which GLFW does not report.
|
||||
f32 last_pointer_x_ = 0.0f;
|
||||
f32 last_pointer_y_ = 0.0f;
|
||||
bool have_pointer_ = false;
|
||||
};
|
||||
|
||||
} // namespace plot::glfw
|
||||
Reference in new issue
Block a user