mirror of
https://github.com/italicsjenga/mini_gl_fb.git
synced 2024-11-21 23:11:30 +11:00
Update glutin, docs, and adds a few other features (including multi window support) (#5)
Upgrades glutin, docs, and adds a few other features (including multi window support). But not the readme. That comes next.
This commit is contained in:
parent
484ff092ba
commit
02b3b62520
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "mini_gl_fb"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
authors = ["shivshank"]
|
||||
description = "Quick and easy window creation, input, and high speed bitmap rendering"
|
||||
repository = "https://github.com/shivshank/mini_gl_fb"
|
||||
|
@ -12,6 +12,6 @@ categories = ["rendering"]
|
|||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
glutin = "0.19.0"
|
||||
glutin = "0.26.0"
|
||||
gl = "0.10.0"
|
||||
rustic_gl = "0.3.2"
|
||||
|
|
44
README.md
44
README.md
|
@ -4,12 +4,32 @@
|
|||
[![Docs.rs](https://docs.rs/mini_gl_fb/badge.svg)](https://docs.rs/mini_gl_fb)
|
||||
|
||||
Mini GL Framebuffer provides an easy way to draw to a window from a pixel buffer. OpenGL
|
||||
alternative to other easy framebuffer libraries.
|
||||
alternative to other easy framebuffer libraries like `minifb` and `pixels`.
|
||||
|
||||
It's designed to be dead simple and easy to remember when you just want to get something on the
|
||||
screen ASAP. It's also built to be super flexible and easy to grow out of in case your project
|
||||
gets serious (MGlFb exposes all of its internals so you can iteratively remove it as a
|
||||
dependency over time!).
|
||||
gets serious. MGlFb exposes all of its internals so you can iteratively remove it as a
|
||||
dependency over time!
|
||||
|
||||
You can also use `MiniGlFb::glutin_breakout` to do rad things like multi-window while keeping
|
||||
the useful `Framebuffer` helper around. There's an example called `multi_window` which shows
|
||||
this in action.
|
||||
|
||||
MGlFb should run on any platform you throw it at, thanks to `winit` and `glutin`'s
|
||||
cross-platform compatibility. However, you do need proper GPU drivers which support OpenGL.
|
||||
That means MGlFb won't work in certain virtual machines or on servers without a GPU.
|
||||
Unfortunately, this isn't something that can be helped because MGlFb can't function without
|
||||
OpenGL.
|
||||
|
||||
# Screenies
|
||||
Here are some screenies of the `multi_window` example running on different platforms:
|
||||
|
||||
![Windows](screenies/multi_window_windows.png)
|
||||
![Arch Linux (X11)](screenies/multi_window_x11.png)
|
||||
![macOS](screenies/multi_window_macos.png)
|
||||
|
||||
It is a showcase of the advanced functionality that MGlFb can support, but it's not at all
|
||||
representative of how little work is required to get started.
|
||||
|
||||
# Usage
|
||||
|
||||
|
@ -17,10 +37,10 @@ dependency over time!).
|
|||
extern crate mini_gl_fb;
|
||||
|
||||
fn main() {
|
||||
let mut fb = mini_gl_fb::gotta_go_fast("Hello world!", 800.0, 600.0);
|
||||
let (mut event_loop, mut fb) = mini_gl_fb::gotta_go_fast("Hello world!", 800.0, 600.0);
|
||||
let buffer = vec![[128u8, 0, 0, 255]; 800 * 600];
|
||||
fb.update_buffer(&buffer);
|
||||
fb.persist();
|
||||
fb.persist(&mut event_loop);
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -34,13 +54,15 @@ Get access to mouse position and key inputs with no hassle. The following is ext
|
|||
Game of Life example:
|
||||
|
||||
```rust
|
||||
use mini_gl_fb::glutin::event_loop::EventLoop;
|
||||
use mini_gl_fb::Config;
|
||||
|
||||
let mut fb = mini_gl_fb::gotta_go_fast("Hello world!", 800.0, 600.0);
|
||||
let (mut event_loop, mut fb) = mini_gl_fb::gotta_go_fast("Hello, World!", 800., 600.);
|
||||
let buffer = vec![[128u8, 0, 0, 255]; 800 * 600];
|
||||
|
||||
// ...
|
||||
|
||||
fb.glutin_handle_basic_input(|fb, input| {
|
||||
fb.glutin_handle_basic_input(&mut event_loop, |fb, input| {
|
||||
let elapsed = previous.elapsed().unwrap();
|
||||
let seconds = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 * 1e-9;
|
||||
|
||||
|
@ -93,17 +115,19 @@ You can also "breakout" and get access to the underlying glutin window while sti
|
|||
setup:
|
||||
|
||||
```rust
|
||||
let mut fb = mini_gl_fb::gotta_go_fast("Hello world!", 800.0, 600.0);
|
||||
let (_, fb) = mini_gl_fb::gotta_go_fast("Hello world!", 800.0, 600.0);
|
||||
|
||||
let GlutinBreakout {
|
||||
mut events_loop,
|
||||
gl_window,
|
||||
context,
|
||||
mut fb,
|
||||
} = fb.glutin_breakout();
|
||||
|
||||
fb.update_buffer(/*...*/);
|
||||
```
|
||||
|
||||
The `multi_window` example works by running the winit event loop manually and handling events
|
||||
for multiple `GlutinBreakout`s at once. You can do this too!
|
||||
|
||||
# Other features
|
||||
|
||||
- Black and white rendering, specifying one byte per pixel
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
#[macro_use]
|
||||
extern crate mini_gl_fb;
|
||||
|
||||
use mini_gl_fb::{Config, BufferFormat};
|
||||
use mini_gl_fb::BufferFormat;
|
||||
use mini_gl_fb::glutin::event_loop::EventLoop;
|
||||
use mini_gl_fb::glutin::dpi::LogicalSize;
|
||||
|
||||
fn main() {
|
||||
let mut fb = mini_gl_fb::get_fancy(Config {
|
||||
window_title: "Hello world!",
|
||||
window_size: (800.0, 600.0),
|
||||
buffer_size: (2, 2),
|
||||
.. Default::default()
|
||||
});
|
||||
let mut event_loop = EventLoop::new();
|
||||
let mut fb = mini_gl_fb::get_fancy(config! {
|
||||
window_title: String::from("Hello world!"),
|
||||
window_size: LogicalSize::new(800.0, 600.0),
|
||||
buffer_size: Some(LogicalSize::new(2, 2))
|
||||
}, &event_loop);
|
||||
|
||||
fb.change_buffer_format::<u8>(BufferFormat::R);
|
||||
fb.use_grayscale_shader();
|
||||
|
@ -16,5 +19,5 @@ fn main() {
|
|||
let buffer = [128u8, 255, 50, 25];
|
||||
fb.update_buffer(&buffer);
|
||||
|
||||
fb.persist();
|
||||
fb.persist(&mut event_loop);
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ fn main() {
|
|||
let width = 800.0;
|
||||
let height = 600.0;
|
||||
|
||||
let mut fb = mini_gl_fb::gotta_go_fast("Hello shaders!", width, height);
|
||||
let (mut event_loop, mut fb) = mini_gl_fb::gotta_go_fast("Hello shaders!", width, height);
|
||||
|
||||
let mut buffer = vec![[128u8, 0, 0, 255]; (width * height) as usize];
|
||||
// let's write a red line into the buffer roughly along the diagonal (misses many pixels)
|
||||
|
@ -119,5 +119,5 @@ fn main() {
|
|||
|
||||
fb.update_buffer(&buffer);
|
||||
|
||||
fb.persist_and_redraw(true);
|
||||
fb.persist_and_redraw(&mut event_loop, true);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
#[macro_use]
|
||||
extern crate mini_gl_fb;
|
||||
|
||||
use mini_gl_fb::{Config, BufferFormat};
|
||||
use mini_gl_fb::glutin::{MouseButton, VirtualKeyCode};
|
||||
use mini_gl_fb::BufferFormat;
|
||||
use mini_gl_fb::glutin::event::{VirtualKeyCode, MouseButton};
|
||||
use mini_gl_fb::glutin::event_loop::EventLoop;
|
||||
use mini_gl_fb::glutin::dpi::LogicalSize;
|
||||
|
||||
use std::time::SystemTime;
|
||||
|
||||
|
@ -9,12 +12,12 @@ const WIDTH: usize = 200;
|
|||
const HEIGHT: usize = 200;
|
||||
|
||||
fn main() {
|
||||
let mut fb = mini_gl_fb::get_fancy(Config {
|
||||
window_title: "PSA: Conway wants you to appreciate group theory instead",
|
||||
window_size: (800.0, 800.0),
|
||||
buffer_size: (WIDTH as _, HEIGHT as _),
|
||||
.. Default::default()
|
||||
});
|
||||
let mut event_loop = EventLoop::new();
|
||||
let mut fb = mini_gl_fb::get_fancy(config! {
|
||||
window_title: String::from("PSA: Conway wants you to appreciate group theory instead"),
|
||||
window_size: LogicalSize::new(800.0, 800.0),
|
||||
buffer_size: Some(LogicalSize::new(WIDTH as _, HEIGHT as _))
|
||||
}, &event_loop);
|
||||
|
||||
fb.change_buffer_format::<u8>(BufferFormat::R);
|
||||
fb.use_post_process_shader(POST_PROCESS);
|
||||
|
@ -35,7 +38,7 @@ fn main() {
|
|||
let mut previous = SystemTime::now();
|
||||
let mut extra_delay: f64 = 0.0;
|
||||
|
||||
fb.glutin_handle_basic_input(|fb, input| {
|
||||
fb.glutin_handle_basic_input(&mut event_loop, |fb, input| {
|
||||
let elapsed = previous.elapsed().unwrap();
|
||||
let seconds = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 * 1e-9;
|
||||
|
||||
|
|
|
@ -1,68 +1,63 @@
|
|||
extern crate mini_gl_fb;
|
||||
|
||||
use mini_gl_fb::{glutin, GlutinBreakout};
|
||||
|
||||
use glutin::GlContext;
|
||||
extern crate glutin;
|
||||
|
||||
use std::cmp::{min, max};
|
||||
use mini_gl_fb::GlutinBreakout;
|
||||
use mini_gl_fb::glutin::event::WindowEvent::KeyboardInput;
|
||||
use mini_gl_fb::glutin::event::{Event, VirtualKeyCode, ElementState, WindowEvent};
|
||||
use mini_gl_fb::glutin::event_loop::ControlFlow;
|
||||
|
||||
fn main() {
|
||||
let mut fb = mini_gl_fb::gotta_go_fast("Hello world!", 800.0, 600.0);
|
||||
let (event_loop, mut fb) = mini_gl_fb::gotta_go_fast("Hello world!", 800.0, 600.0);
|
||||
let mut buffer = vec![[128u8, 0, 0, 255]; 800 * 600];
|
||||
fb.update_buffer(&buffer);
|
||||
|
||||
let GlutinBreakout {
|
||||
mut events_loop,
|
||||
gl_window,
|
||||
context,
|
||||
mut fb,
|
||||
} = fb.glutin_breakout();
|
||||
|
||||
let mut running = true;
|
||||
let mut mouse_x = 0;
|
||||
let mut mouse_y = 0;
|
||||
let mut mouse_down = false;
|
||||
while running {
|
||||
events_loop.poll_events(|event| {
|
||||
use glutin::{Event, ElementState, VirtualKeyCode};
|
||||
use glutin::WindowEvent::*;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event: CloseRequested, .. } => {
|
||||
running = false;
|
||||
}
|
||||
Event::WindowEvent { event: KeyboardInput { input, .. }, .. } => {
|
||||
if let Some(k) = input.virtual_keycode {
|
||||
if k == VirtualKeyCode::Escape && input.state == ElementState::Released {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::WindowEvent { event: Resized(logical_size), .. } => {
|
||||
let dpi_factor = gl_window.get_hidpi_factor();
|
||||
gl_window.resize(logical_size.to_physical(dpi_factor));
|
||||
}
|
||||
Event::WindowEvent { event: CursorMoved { position, .. }, .. } => {
|
||||
let dpi_factor = gl_window.get_hidpi_factor();
|
||||
let (x, y) = position.to_physical(dpi_factor).into();
|
||||
mouse_x = min(max(x, 0), 800 - 1);
|
||||
mouse_y = min(max(y, 0), 600 - 1);
|
||||
if mouse_down {
|
||||
buffer[(mouse_x + mouse_y * 800) as usize] = [64, 128, 255, 255];
|
||||
fb.update_buffer(&buffer);
|
||||
}
|
||||
}
|
||||
Event::WindowEvent { event: MouseInput { state, .. }, .. } => {
|
||||
if state == ElementState::Pressed {
|
||||
mouse_down = true;
|
||||
} else {
|
||||
mouse_down = false;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
event_loop.run(move |event, _, flow| {
|
||||
match event {
|
||||
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
|
||||
*flow = ControlFlow::Exit;
|
||||
}
|
||||
});
|
||||
|
||||
fb.redraw();
|
||||
gl_window.swap_buffers().unwrap();
|
||||
}
|
||||
Event::WindowEvent { event: KeyboardInput { input, .. }, .. } => {
|
||||
if let Some(k) = input.virtual_keycode {
|
||||
if k == VirtualKeyCode::Escape && input.state == ElementState::Pressed {
|
||||
*flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::WindowEvent { event: WindowEvent::Resized(size), .. } => {
|
||||
context.resize(size);
|
||||
context.window().request_redraw();
|
||||
}
|
||||
Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. } => {
|
||||
let (x, y) = position.to_logical::<f64>(context.window().scale_factor()).into();
|
||||
println!("({}, {})", x, y);
|
||||
let mouse_x = min(max(x, 0), 800 - 1);
|
||||
let mouse_y = min(max(fb.buffer_size.height - y, 0), 600 - 1);
|
||||
if mouse_down {
|
||||
buffer[(mouse_x + mouse_y * 800) as usize] = [64, 128, 255, 255];
|
||||
fb.update_buffer(&buffer);
|
||||
context.window().request_redraw();
|
||||
}
|
||||
}
|
||||
Event::WindowEvent { event: WindowEvent::MouseInput { state, .. }, .. } => {
|
||||
if state == ElementState::Pressed {
|
||||
mouse_down = true;
|
||||
} else {
|
||||
mouse_down = false;
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
fb.redraw();
|
||||
context.swap_buffers().unwrap();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
273
examples/multi_window.rs
Normal file
273
examples/multi_window.rs
Normal file
|
@ -0,0 +1,273 @@
|
|||
#[macro_use]
|
||||
extern crate mini_gl_fb;
|
||||
extern crate glutin;
|
||||
|
||||
use mini_gl_fb::glutin::event_loop::EventLoop;
|
||||
use mini_gl_fb::glutin::event::{Event, WindowEvent, MouseButton, VirtualKeyCode, KeyboardInput, ElementState};
|
||||
use mini_gl_fb::{get_fancy, GlutinBreakout};
|
||||
use mini_gl_fb::glutin::dpi::{LogicalSize, LogicalPosition};
|
||||
use mini_gl_fb::glutin::window::{Window, WindowId, CursorIcon};
|
||||
use mini_gl_fb::glutin::event_loop::ControlFlow;
|
||||
use mini_gl_fb::glutin::platform::run_return::EventLoopExtRunReturn;
|
||||
|
||||
/// Turn up this number to make the pixels bigger. 1 is one logical pixel
|
||||
const SCALE_FACTOR: f64 = 2.;
|
||||
|
||||
/// A window being tracked by a `MultiWindow`. All tracked windows will be forwarded all events
|
||||
/// received on the `MultiWindow`'s event loop.
|
||||
trait TrackedWindow {
|
||||
/// Handles one event from the event loop. Returns true if the window needs to be kept alive,
|
||||
/// otherwise it will be closed. Window events should be checked to ensure that their ID is one
|
||||
/// that the TrackedWindow is interested in.
|
||||
fn handle_event(&mut self, event: &Event<()>) -> bool;
|
||||
}
|
||||
|
||||
/// Manages multiple `TrackedWindow`s by forwarding events to them.
|
||||
struct MultiWindow {
|
||||
windows: Vec<Option<Box<dyn TrackedWindow>>>,
|
||||
}
|
||||
|
||||
impl MultiWindow {
|
||||
/// Creates a new `MultiWindow`.
|
||||
pub fn new() -> Self {
|
||||
MultiWindow {
|
||||
windows: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a new `TrackedWindow` to the `MultiWindow`.
|
||||
pub fn add(&mut self, window: Box<dyn TrackedWindow>) {
|
||||
self.windows.push(Some(window))
|
||||
}
|
||||
|
||||
/// Runs the event loop until all `TrackedWindow`s are closed.
|
||||
pub fn run(&mut self, event_loop: &mut EventLoop<()>) {
|
||||
if !self.windows.is_empty() {
|
||||
event_loop.run_return(|event, _, flow| {
|
||||
*flow = ControlFlow::Wait;
|
||||
|
||||
for option in &mut self.windows {
|
||||
if let Some(window) = option.as_mut() {
|
||||
if !window.handle_event(&event) {
|
||||
option.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.windows.retain(Option::is_some);
|
||||
|
||||
if self.windows.is_empty() {
|
||||
*flow = ControlFlow::Exit;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A basic window that allows you to draw in it. An example of how to implement a `TrackedWindow`.
|
||||
struct DrawWindow {
|
||||
pub breakout: GlutinBreakout,
|
||||
pub buffer: Vec<[u8; 4]>,
|
||||
pub buffer_size: LogicalSize<u32>,
|
||||
pub bg: [u8; 4],
|
||||
pub fg: [u8; 4],
|
||||
mouse_state: ElementState,
|
||||
line_start: Option<LogicalPosition<i32>>,
|
||||
}
|
||||
|
||||
impl DrawWindow {
|
||||
fn window(&self) -> &Window {
|
||||
self.breakout.context.window()
|
||||
}
|
||||
|
||||
pub fn matches_id(&self, id: WindowId) -> bool {
|
||||
id == self.window().id()
|
||||
}
|
||||
|
||||
/// Updates the window's buffer. Should only be done inside of RedrawRequested events; outside
|
||||
/// of them, use `request_redraw` instead.
|
||||
fn redraw(&mut self) {
|
||||
self.breakout.fb.update_buffer(&self.buffer);
|
||||
self.breakout.context.swap_buffers().unwrap();
|
||||
}
|
||||
|
||||
/// Requests a redraw event for this window.
|
||||
fn request_redraw(&self) {
|
||||
self.window().request_redraw();
|
||||
}
|
||||
|
||||
/// Resizes the window's buffer to a new size, attempting to preserve the current content as
|
||||
/// much as possible. Fills new space with the background color, and deletes overflowing space.
|
||||
fn resize(&mut self, new_size: LogicalSize<u32>) {
|
||||
let mut new_buffer = vec![self.bg; new_size.width as usize * new_size.height as usize];
|
||||
|
||||
if self.buffer_size.width > 0 {
|
||||
// use rchunks for inverted y
|
||||
for (old_line, new_line) in self.buffer.chunks_exact(self.buffer_size.width as usize)
|
||||
.zip(new_buffer.chunks_exact_mut(new_size.width as usize)) {
|
||||
if old_line.len() <= new_line.len() {
|
||||
new_line[0..old_line.len()].copy_from_slice(old_line)
|
||||
} else {
|
||||
new_line.copy_from_slice(&old_line[0..new_line.len()])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.buffer = new_buffer;
|
||||
self.buffer_size = new_size;
|
||||
self.breakout.fb.resize_buffer(new_size.width, new_size.height);
|
||||
}
|
||||
|
||||
fn plot(&mut self, position: LogicalPosition<i32>) {
|
||||
if position.x < 0 || position.x >= self.buffer_size.width as i32 ||
|
||||
position.y < 0 || position.y >= self.buffer_size.height as i32 {
|
||||
return
|
||||
}
|
||||
|
||||
let position = position.cast::<u32>();
|
||||
let index = (position.x + position.y * self.buffer_size.width) as usize;
|
||||
self.buffer[index] = self.fg;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
|
||||
fn plot_line(&mut self, start: LogicalPosition<i32>, end: LogicalPosition<i32>) {
|
||||
let (mut x0, mut y0): (i32, i32) = start.into();
|
||||
let (x1, y1): (i32, i32) = end.into();
|
||||
let dx = (x1 - x0).abs();
|
||||
let sx = if x0 < x1 { 1 } else { -1 };
|
||||
let dy = -(y1 - y0).abs();
|
||||
let sy = if y0 < y1 { 1 } else { -1 };
|
||||
let mut err = dx + dy;
|
||||
|
||||
while x0 != x1 || y0 != y1 {
|
||||
self.plot(LogicalPosition::new(x0, y0));
|
||||
let e2 = err * 2;
|
||||
if e2 > dy {
|
||||
err += dy;
|
||||
x0 += sx;
|
||||
}
|
||||
if e2 <= dx {
|
||||
err += dx;
|
||||
y0 += sy;
|
||||
}
|
||||
}
|
||||
|
||||
self.plot(end);
|
||||
}
|
||||
|
||||
/// Creates a new `DrawWindow` for the specified event loop, using the specified background and
|
||||
/// foreground colors.
|
||||
pub fn new(event_loop: &EventLoop<()>, bg: [u8; 4], fg: [u8; 4]) -> Self {
|
||||
let mut new = Self {
|
||||
breakout: get_fancy(config! {
|
||||
resizable: true,
|
||||
invert_y: false
|
||||
}, &event_loop).glutin_breakout(),
|
||||
buffer: vec![],
|
||||
buffer_size: LogicalSize::new(0, 0),
|
||||
bg,
|
||||
fg,
|
||||
mouse_state: ElementState::Released,
|
||||
line_start: None,
|
||||
};
|
||||
new.resize(new.window().inner_size().to_logical(new.window().scale_factor() * SCALE_FACTOR));
|
||||
new.window().set_cursor_icon(CursorIcon::Crosshair);
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
impl TrackedWindow for DrawWindow {
|
||||
fn handle_event(&mut self, event: &Event<()>) -> bool {
|
||||
match *event {
|
||||
Event::WindowEvent {
|
||||
window_id: id,
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} if self.matches_id(id) => {
|
||||
return false;
|
||||
}
|
||||
Event::WindowEvent {
|
||||
window_id: id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
input: KeyboardInput {
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} if self.matches_id(id) => {
|
||||
if let Some(_) = self.window().fullscreen() {
|
||||
self.window().set_fullscreen(None);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(id) if self.matches_id(id) => {
|
||||
unsafe { self.breakout.make_current().unwrap(); }
|
||||
self.redraw();
|
||||
}
|
||||
Event::WindowEvent {
|
||||
window_id: id,
|
||||
event: WindowEvent::Resized(size),
|
||||
..
|
||||
} if self.matches_id(id) => {
|
||||
unsafe { self.breakout.make_current().unwrap(); }
|
||||
self.breakout.fb.resize_viewport(size.width, size.height);
|
||||
self.resize(size.to_logical(self.window().scale_factor() * SCALE_FACTOR));
|
||||
self.request_redraw();
|
||||
}
|
||||
Event::WindowEvent {
|
||||
window_id: id,
|
||||
event: WindowEvent::MouseInput {
|
||||
button: MouseButton::Left,
|
||||
state,
|
||||
..
|
||||
},
|
||||
..
|
||||
} if self.matches_id(id) => {
|
||||
self.mouse_state = state;
|
||||
self.line_start = None;
|
||||
}
|
||||
Event::WindowEvent {
|
||||
window_id: id,
|
||||
event: WindowEvent::CursorMoved {
|
||||
position,
|
||||
..
|
||||
},
|
||||
..
|
||||
} if self.matches_id(id) => {
|
||||
if self.mouse_state == ElementState::Pressed {
|
||||
let inner_size = self.window().inner_size();
|
||||
let position = LogicalPosition::new(
|
||||
((position.x / inner_size.width as f64) * self.buffer_size.width as f64).floor(),
|
||||
((position.y / inner_size.height as f64) * self.buffer_size.height as f64).floor()
|
||||
).cast::<i32>();
|
||||
|
||||
if let Some(line_start) = self.line_start {
|
||||
self.plot_line(line_start, position);
|
||||
} else {
|
||||
self.plot(position);
|
||||
}
|
||||
|
||||
self.line_start = Some(position);
|
||||
|
||||
self.request_redraw();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut event_loop = EventLoop::new();
|
||||
let mut multi_window = MultiWindow::new();
|
||||
multi_window.add(Box::new(DrawWindow::new(&event_loop, [25u8, 33, 40, 255], [54u8, 165, 209, 255])));
|
||||
multi_window.add(Box::new(DrawWindow::new(&event_loop, [25u8, 40, 33, 255], [54u8, 209, 82, 255])));
|
||||
multi_window.add(Box::new(DrawWindow::new(&event_loop, [40u8, 33, 25, 255], [209u8, 82, 54, 255])));
|
||||
multi_window.run(&mut event_loop);
|
||||
}
|
|
@ -1,15 +1,18 @@
|
|||
#[macro_use]
|
||||
extern crate mini_gl_fb;
|
||||
|
||||
use mini_gl_fb::{Config, BufferFormat};
|
||||
use mini_gl_fb::BufferFormat;
|
||||
use mini_gl_fb::glutin::event_loop::EventLoop;
|
||||
use mini_gl_fb::glutin::dpi::LogicalSize;
|
||||
|
||||
fn main() {
|
||||
let mut fb = mini_gl_fb::get_fancy(Config {
|
||||
window_title: "Hello world!",
|
||||
window_size: (800.0, 600.0),
|
||||
buffer_size: (2, 2),
|
||||
resizable: true,
|
||||
.. Default::default()
|
||||
});
|
||||
let mut event_loop = EventLoop::new();
|
||||
let mut fb = mini_gl_fb::get_fancy(config! {
|
||||
window_title: String::from("Hello world!"),
|
||||
window_size: LogicalSize::new(800.0, 600.0),
|
||||
buffer_size: Some(LogicalSize::new(2, 2)),
|
||||
resizable: true
|
||||
}, &event_loop);
|
||||
|
||||
|
||||
fb.change_buffer_format::<u8>(BufferFormat::R);
|
||||
|
@ -21,5 +24,5 @@ fn main() {
|
|||
// This can also be configured at creation
|
||||
// fb.set_resizable(true);
|
||||
|
||||
fb.persist();
|
||||
fb.persist(&mut event_loop);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
#[macro_use]
|
||||
extern crate mini_gl_fb;
|
||||
|
||||
use mini_gl_fb::Config;
|
||||
use mini_gl_fb::glutin::event_loop::EventLoop;
|
||||
use mini_gl_fb::glutin::dpi::LogicalSize;
|
||||
|
||||
fn main() {
|
||||
let mut fb = mini_gl_fb::get_fancy(Config {
|
||||
window_title: "Hello world!",
|
||||
window_size: (800.0, 600.0),
|
||||
buffer_size: (2, 2),
|
||||
.. Default::default()
|
||||
});
|
||||
let mut event_loop = EventLoop::new();
|
||||
let mut fb = mini_gl_fb::get_fancy(config! {
|
||||
window_title: String::from("Hello world!"),
|
||||
window_size: LogicalSize::new(800.0, 600.0),
|
||||
buffer_size: Some(LogicalSize::new(2, 2))
|
||||
}, &event_loop);
|
||||
|
||||
let mut buffer = vec![[128u8, 0, 0, 255]; 4];
|
||||
buffer[3] = [255, 255, 255, 255];
|
||||
|
||||
fb.update_buffer(&buffer);
|
||||
|
||||
fb.persist();
|
||||
fb.persist(&mut event_loop);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
extern crate mini_gl_fb;
|
||||
|
||||
fn main() {
|
||||
let mut fb = mini_gl_fb::gotta_go_fast("Hello world!", 800.0, 600.0);
|
||||
let (mut event_loop, mut fb) = mini_gl_fb::gotta_go_fast("Hello world!", 800.0, 600.0);
|
||||
let buffer = vec![[128u8, 0, 0, 255]; 800 * 600];
|
||||
fb.update_buffer(&buffer);
|
||||
fb.persist();
|
||||
fb.persist(&mut event_loop);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
#[macro_use]
|
||||
extern crate mini_gl_fb;
|
||||
|
||||
use mini_gl_fb::{Config, BufferFormat};
|
||||
use mini_gl_fb::BufferFormat;
|
||||
use mini_gl_fb::glutin::event_loop::EventLoop;
|
||||
use mini_gl_fb::glutin::dpi::LogicalSize;
|
||||
|
||||
fn main() {
|
||||
let mut fb = mini_gl_fb::get_fancy(Config {
|
||||
window_title: "Hello world!",
|
||||
window_size: (800.0, 600.0),
|
||||
buffer_size: (2, 2),
|
||||
.. Default::default()
|
||||
});
|
||||
let mut event_loop = EventLoop::new();
|
||||
let mut fb = mini_gl_fb::get_fancy(config! {
|
||||
window_title: String::from("Hello world!"),
|
||||
window_size: LogicalSize::new(800.0, 600.0),
|
||||
buffer_size: Some(LogicalSize::new(2, 2))
|
||||
}, &event_loop);
|
||||
|
||||
fb.change_buffer_format::<u8>(BufferFormat::RG);
|
||||
|
||||
|
@ -16,5 +19,5 @@ fn main() {
|
|||
let buffer = vec![[0u8, 50, 128, 255]; 4];
|
||||
fb.update_buffer(&buffer);
|
||||
|
||||
fb.persist();
|
||||
fb.persist(&mut event_loop);
|
||||
}
|
||||
|
|
BIN
screenies/multi_window_macos.png
Normal file
BIN
screenies/multi_window_macos.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 294 KiB |
BIN
screenies/multi_window_windows.png
Normal file
BIN
screenies/multi_window_windows.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 128 KiB |
BIN
screenies/multi_window_x11.png
Normal file
BIN
screenies/multi_window_x11.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 161 KiB |
228
src/breakout.rs
228
src/breakout.rs
|
@ -1,20 +1,224 @@
|
|||
use glutin::{
|
||||
GlWindow,
|
||||
EventsLoop,
|
||||
VirtualKeyCode,
|
||||
MouseButton,
|
||||
ModifiersState,
|
||||
};
|
||||
//! Contains the [`GlutinBreakout`] struct, which is a way to "break out" the Glutin context and
|
||||
//! [`Framebuffer`] object and manipulate them directly.
|
||||
|
||||
use glutin::{WindowedContext, PossiblyCurrent, ContextError};
|
||||
use core::Framebuffer;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use glutin::event::{MouseButton, VirtualKeyCode, ModifiersState};
|
||||
|
||||
/// `GlutinBreakout` is useful when you are growing out of the basic input methods and synchronous
|
||||
/// nature of [`MiniGlFb`][crate::MiniGlFb], since it's more powerful than the the higher-level
|
||||
/// abstrations. You can obtain it by calling
|
||||
/// [`MiniGlFb::glutin_breakout()`][crate::MiniGlFb::glutin_breakout].
|
||||
///
|
||||
/// # Usage for multiple windows
|
||||
/// The basic idea for managing multiple windows is to check each incoming event to determine which
|
||||
/// window it's for. In order to draw to multiple windows individually, you have to switch the
|
||||
/// context using [`make_current`][GlutinBreakout::make_current] before updating the window.
|
||||
///
|
||||
/// Here's a basic implementation (there's a lot of boilerplate because we're not using the
|
||||
/// [`MiniGlFb`][crate::MiniGlFb] API - it's closer to using
|
||||
/// [`winit`](https://docs.rs/winit/0.24.0/winit/index.html) directly):
|
||||
///
|
||||
/// ```
|
||||
/// use mini_gl_fb::GlutinBreakout;
|
||||
/// use mini_gl_fb::glutin::window::{Window, WindowId};
|
||||
/// use mini_gl_fb::glutin::event::{Event, WindowEvent, KeyboardInput, VirtualKeyCode, ElementState};
|
||||
/// use mini_gl_fb::glutin::event_loop::{EventLoop, ControlFlow};
|
||||
/// use mini_gl_fb::config;
|
||||
///
|
||||
/// struct TrackedWindow {
|
||||
/// pub breakout: GlutinBreakout,
|
||||
/// pub background: [u8; 4]
|
||||
/// }
|
||||
///
|
||||
/// impl TrackedWindow {
|
||||
/// fn window(&self) -> &Window { self.breakout.context.window() }
|
||||
/// fn matches_id(&self, id: WindowId) -> bool { id == self.breakout.context.window().id() }
|
||||
///
|
||||
/// pub fn handle_event(&mut self, event: &Event<()>) -> bool {
|
||||
/// match event {
|
||||
/// Event::WindowEvent { window_id: id, event, .. } if self.matches_id(*id) => {
|
||||
/// match event {
|
||||
/// WindowEvent::CloseRequested |
|
||||
/// WindowEvent::KeyboardInput {
|
||||
/// input: KeyboardInput {
|
||||
/// virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
/// state: ElementState::Pressed,
|
||||
/// ..
|
||||
/// },
|
||||
/// ..
|
||||
/// } => return false,
|
||||
/// WindowEvent::Resized(size) => {
|
||||
/// self.breakout.fb.resize_viewport(size.width, size.height);
|
||||
/// let size = size.to_logical(self.window().scale_factor());
|
||||
/// self.breakout.fb.resize_buffer(size.width, size.height);
|
||||
/// }
|
||||
/// _ => {
|
||||
/// // do other stuff?
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// Event::RedrawRequested(id) if self.matches_id(*id) => {
|
||||
/// // If you don't do this, OpenGL will get confused and only draw to one window.
|
||||
/// unsafe { self.breakout.make_current().unwrap(); }
|
||||
///
|
||||
/// let size = self.window().inner_size().to_logical::<f64>(self.window().scale_factor());
|
||||
///
|
||||
/// // Unfortunately the performance of this is abysmal. Usually you should cache
|
||||
/// // your buffer and only update it when needed or when the window is resized.
|
||||
/// let pixels = size.width.floor() as usize * size.height.floor() as usize;
|
||||
/// self.breakout.fb.update_buffer(&vec![self.background; pixels]);
|
||||
/// self.breakout.context.swap_buffers();
|
||||
/// }
|
||||
/// _ => {}
|
||||
/// }
|
||||
///
|
||||
/// true
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let event_loop = EventLoop::new();
|
||||
/// let mut windows: Vec<Option<TrackedWindow>> = vec![];
|
||||
///
|
||||
/// let config = config! {
|
||||
/// resizable: true
|
||||
/// };
|
||||
///
|
||||
/// windows.push(Some(TrackedWindow {
|
||||
/// breakout: mini_gl_fb::get_fancy(config.clone(), &event_loop).glutin_breakout(),
|
||||
/// background: [224u8, 66, 26, 255]
|
||||
/// }));
|
||||
///
|
||||
/// windows.push(Some(TrackedWindow {
|
||||
/// breakout: mini_gl_fb::get_fancy(config.clone(), &event_loop).glutin_breakout(),
|
||||
/// background: [26u8, 155, 224, 255]
|
||||
/// }));
|
||||
///
|
||||
/// // run event loop
|
||||
/// event_loop.run(move |event, _, flow| {
|
||||
/// *flow = ControlFlow::Wait;
|
||||
///
|
||||
/// for option in &mut windows {
|
||||
/// if let Some(window) = option {
|
||||
/// if !window.handle_event(&event) {
|
||||
/// option.take();
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// windows.retain(Option::is_some);
|
||||
///
|
||||
/// if windows.is_empty() {
|
||||
/// *flow = ControlFlow::Exit;
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// It's hard to come up with a generalized, flexible implementation of this, especially if you need
|
||||
/// to open more windows based on user input, or run tasks in other threads, etc. Basically, it's
|
||||
/// open for you to play with, but it's not functionality that MGlFb wants to include first-class
|
||||
/// just yet.
|
||||
#[derive(Debug)]
|
||||
pub struct GlutinBreakout {
|
||||
pub events_loop: EventsLoop,
|
||||
pub gl_window: GlWindow,
|
||||
/// Contains the OpenGL context and its associated window. This is a
|
||||
/// [`glutin`](https://docs.rs/glutin/0.26.0/glutin/) struct; go see their documentation on
|
||||
/// [`WindowedContext`] for more information.
|
||||
pub context: WindowedContext<PossiblyCurrent>,
|
||||
/// Contains the [`Framebuffer`] for that context. Consult its documentation for information on
|
||||
/// how to use it.
|
||||
pub fb: Framebuffer,
|
||||
}
|
||||
|
||||
impl GlutinBreakout {
|
||||
/// Sets the current thread's OpenGL context to the one contained in this breakout.
|
||||
///
|
||||
/// Historically, MGlFb did not support multiple windows. It owned its own event loop and you
|
||||
/// weren't allowed to use the library with your own. However, as of version 0.8, you are now
|
||||
/// expected to bring your own event loop to all functions that involve one. This means that
|
||||
/// multiple windows are very possible, and even supported, as long as you're willing to route
|
||||
/// events yourself... and manage all the OpenGL contexts.
|
||||
///
|
||||
/// The problem with managing multiple OpenGL contexts from one thread is that the "current"
|
||||
/// context is set per-thread. That means you basically have to switch through them really
|
||||
/// quickly if you want to update multiple windows in "parallel". But how do you switch?
|
||||
///
|
||||
/// Glutin has you partially covered on this one - it has
|
||||
/// [`make_current`][glutin::ContextWrapper<PossiblyCurrent, Window>::make_current]. However,
|
||||
/// that method takes `self` and emits a new `WindowedContext`, and you can't really move `self`
|
||||
/// into that function without unsafe code.
|
||||
///
|
||||
/// Here is an unsafe function containing code that makes the context current, in-place. That
|
||||
/// way, you can switch contexts in one line of code, and focus on other stuff.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ```
|
||||
/// # use mini_gl_fb::glutin::event_loop::{EventLoop, ControlFlow};
|
||||
/// # use mini_gl_fb::glutin::event::{Event, WindowEvent, KeyboardInput, VirtualKeyCode, ElementState};
|
||||
/// # use mini_gl_fb::{config, get_fancy};
|
||||
/// #
|
||||
/// # let mut event_loop = EventLoop::new();
|
||||
/// # let mut breakout = get_fancy(config! {
|
||||
/// # window_title: String::from("GlutinBreakout::make_current()")
|
||||
/// # }, &event_loop).glutin_breakout();
|
||||
/// #
|
||||
/// event_loop.run(move |event, _, flow| {
|
||||
/// # *flow = ControlFlow::Wait;
|
||||
/// #
|
||||
/// match event {
|
||||
/// // ...
|
||||
/// # Event::WindowEvent { event, .. } => {
|
||||
/// # match event {
|
||||
/// # WindowEvent::CloseRequested |
|
||||
/// # WindowEvent::KeyboardInput {
|
||||
/// # input: KeyboardInput {
|
||||
/// # virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
/// # state: ElementState::Pressed,
|
||||
/// # ..
|
||||
/// # },
|
||||
/// # ..
|
||||
/// # } => *flow = ControlFlow::Exit,
|
||||
/// # _ => ()
|
||||
/// # }
|
||||
/// # },
|
||||
/// Event::RedrawRequested(..) => {
|
||||
/// unsafe { breakout.make_current().unwrap(); }
|
||||
/// // ...
|
||||
/// # let window = breakout.context.window();
|
||||
/// # let size = window.inner_size().to_logical::<f64>(window.scale_factor());
|
||||
/// # let pixels = size.width.floor() as usize * size.height.floor() as usize;
|
||||
/// # let your_buffer_here = vec![[0u8, 200, 240, 255]; pixels];
|
||||
/// breakout.fb.update_buffer(&your_buffer_here);
|
||||
/// breakout.context.swap_buffers();
|
||||
/// }
|
||||
/// // ...
|
||||
/// # _ => {}
|
||||
/// }
|
||||
/// })
|
||||
/// ```
|
||||
pub unsafe fn make_current(&mut self) -> Result<(), ContextError> {
|
||||
let context_ptr: *mut _ = &mut self.context;
|
||||
let context = std::ptr::read(context_ptr);
|
||||
let result = context.make_current();
|
||||
|
||||
if let Err((context, err)) = result {
|
||||
std::ptr::write(context_ptr, context);
|
||||
Err(err)
|
||||
} else {
|
||||
std::ptr::write(context_ptr, result.unwrap());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for [`MiniGlFb::glutin_handle_basic_input`][crate::MiniGlFb::glutin_handle_basic_input].
|
||||
/// Contains the current state of the window in a polling-like fashion.
|
||||
#[non_exhaustive]
|
||||
#[derive(Default, Clone, PartialEq, Debug)]
|
||||
pub struct BasicInput {
|
||||
/// The mouse position in buffer coordinates.
|
||||
///
|
||||
|
@ -33,8 +237,14 @@ pub struct BasicInput {
|
|||
///
|
||||
/// If a key has not been pressed yet it will not be in the map.
|
||||
pub keys: HashMap<VirtualKeyCode, (bool, bool)>,
|
||||
/// The current modifier keys that are being pressed.
|
||||
pub modifiers: ModifiersState,
|
||||
/// This is set to `true` when the window is resized outside of your callback. If you do not
|
||||
/// update the buffer in your callback, you should still draw it if this is `true`.
|
||||
pub resized: bool,
|
||||
/// If this is set to `true` by your callback, it will not be called as fast as possible, but
|
||||
/// rather only when the input changes.
|
||||
pub wait: bool,
|
||||
}
|
||||
|
||||
impl BasicInput {
|
||||
|
|
129
src/config.rs
129
src/config.rs
|
@ -1,48 +1,123 @@
|
|||
/// Configuration for "advanced" use cases, when `gotta_go_fast` isn't doing what you need.
|
||||
use glutin::dpi::LogicalSize;
|
||||
|
||||
/// Configuration for "advanced" use cases, when [`gotta_go_fast`][crate::gotta_go_fast] isn't doing
|
||||
/// what you need.
|
||||
///
|
||||
/// The following pattern is reccomended when creating a config:
|
||||
/// The following pattern is recommended when creating a config:
|
||||
///
|
||||
/// ```
|
||||
/// use mini_gl_fb::Config;
|
||||
/// use mini_gl_fb::config;
|
||||
/// use mini_gl_fb::glutin::dpi::LogicalSize;
|
||||
///
|
||||
/// let config: Config<&str> = Config {
|
||||
/// let config = config! {
|
||||
/// /* specify whichever fields you need to set, for example: */
|
||||
/// window_size: (100.0, 100.0),
|
||||
/// resizable: false,
|
||||
/// .. Default::default()
|
||||
/// window_size: LogicalSize::new(100.0, 100.0),
|
||||
/// resizable: true,
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// Since `Config` is `#[non_exhaustive]`, you cannot construct it directly, and can only obtain one
|
||||
/// from a trait like [`Default`]. The [`config!`][config] macro makes it much less tedious to
|
||||
/// construct custom configs. See its documentation for more information.
|
||||
///
|
||||
/// If there's a config option you want to see or think is missing, please open an issue!
|
||||
pub struct Config<S: ToString> {
|
||||
/// Sets the scale of the buffer. The buffer will automatically scale to the size of the
|
||||
/// window. By default this will be the same size as the window_size.
|
||||
pub buffer_size: (u32, u32),
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct Config {
|
||||
/// Sets the pixel dimensions of the buffer. The buffer will automatically stretch to fill the
|
||||
/// whole window. By default this will be the same as the window_size.
|
||||
pub buffer_size: Option<LogicalSize<u32>>,
|
||||
/// If this is true, the window created by mini_gl_fb will be set to resizable. This can be
|
||||
/// changed later. Please note that the buffer itself will not be automatically resized, only
|
||||
/// the viewport.
|
||||
pub resizable: bool,
|
||||
pub window_title: S,
|
||||
pub window_size: (f64, f64),
|
||||
/// The title of the window that will be created.
|
||||
pub window_title: String,
|
||||
/// The logical size of the window that gets created. On HiDPI screens the actual size may be
|
||||
/// larger than this
|
||||
pub window_size: LogicalSize<f64>,
|
||||
/// By default, the origin of the buffer is the bottom-left. This is known as "inverted Y", as
|
||||
/// most screen-space coordinate systems begin from the top-left. By explicitly setting this
|
||||
/// option to `false`, you can switch to screen-space coordinates rather than OpenGL
|
||||
/// coordinates. Otherwise, you will have to invert all mouse events received from winit/glutin.
|
||||
pub invert_y: bool
|
||||
}
|
||||
|
||||
impl<'a> Default for Config<&'a str> {
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
buffer_size: (0, 0),
|
||||
buffer_size: None,
|
||||
resizable: false,
|
||||
// :^)
|
||||
window_title: "Super Mini GL Framebufferer 3!",
|
||||
window_size: (600.0, 480.0),
|
||||
window_title: String::from("Super Mini GL Framebufferer 3!"),
|
||||
window_size: LogicalSize::new(600.0, 480.0),
|
||||
invert_y: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config<String> {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
buffer_size: (0, 0),
|
||||
resizable: false,
|
||||
// :^)
|
||||
window_title: "Super Mini GL Framebufferer 3!".to_string(),
|
||||
window_size: (600.0, 480.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// The `config!` macro is intended to make it easy for us to add new fields in the future while
|
||||
/// staying backwards-compatible. This is done by making [`Config`] `#[non_exhaustive]` but still
|
||||
/// providing [`Default`], so that users can obtain the defaults and modify it to their liking. The
|
||||
/// `config!` macro automates this, and makes custom configs just as easy as constructing `Config`
|
||||
/// directly would be.
|
||||
///
|
||||
/// You can use the macro like this:
|
||||
///
|
||||
/// ```
|
||||
/// # use mini_gl_fb::config;
|
||||
/// #
|
||||
/// let config = config! {
|
||||
/// resizable: true,
|
||||
/// invert_y: false
|
||||
/// };
|
||||
///
|
||||
/// assert_eq!(config.resizable, true);
|
||||
/// assert_eq!(config.invert_y, false);
|
||||
/// ```
|
||||
///
|
||||
/// As you can see, it's almost identical to a struct construction. You just use this macro in place
|
||||
/// of `Config`. As such, it has a minimal impact on user code. That invocation roughly expands to:
|
||||
///
|
||||
/// ```
|
||||
/// # use mini_gl_fb::Config;
|
||||
/// #
|
||||
/// let config = {
|
||||
/// let mut config = Config::default();
|
||||
/// config.resizable = true;
|
||||
/// config.invert_y = false;
|
||||
/// config
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// This way, adding new fields will not affect existing code.
|
||||
///
|
||||
/// You can also create a copy of an existing config, with only a couple options changed:
|
||||
///
|
||||
/// ```
|
||||
/// # use mini_gl_fb::config;
|
||||
/// #
|
||||
/// let original = config! {};
|
||||
/// let copy = config! {
|
||||
/// invert_y: false,
|
||||
/// ..original
|
||||
/// };
|
||||
///
|
||||
/// assert_eq!(original.invert_y, true);
|
||||
/// assert_eq!(copy.invert_y, false);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! config {
|
||||
{$($k:ident: $v:expr),+,..$from:expr$(,)?} => {{
|
||||
let mut config: $crate::Config = ::std::clone::Clone::clone(&$from);
|
||||
$(config.$k = $v;
|
||||
)*config
|
||||
}};
|
||||
{$($k:ident: $v:expr),+$(,)?} => {{
|
||||
let mut config: $crate::Config = ::std::default::Default::default();
|
||||
$(config.$k = $v;
|
||||
)*config
|
||||
}};
|
||||
{} => { <$crate::Config as ::std::default::Default>::default() }
|
||||
}
|
||||
|
|
469
src/core.rs
469
src/core.rs
|
@ -2,49 +2,44 @@ use breakout::{GlutinBreakout, BasicInput};
|
|||
|
||||
use rustic_gl;
|
||||
|
||||
use glutin::{
|
||||
EventsLoop,
|
||||
WindowBuilder,
|
||||
ContextBuilder,
|
||||
GlWindow,
|
||||
GlContext,
|
||||
Event,
|
||||
WindowEvent,
|
||||
VirtualKeyCode,
|
||||
ElementState,
|
||||
};
|
||||
use glutin::dpi::{LogicalSize, LogicalPosition};
|
||||
use glutin::{ContextBuilder, WindowedContext, PossiblyCurrent};
|
||||
use glutin::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
|
||||
|
||||
use gl;
|
||||
use gl::types::*;
|
||||
|
||||
use std::mem::size_of_val;
|
||||
use std::collections::HashMap;
|
||||
use glutin::window::WindowBuilder;
|
||||
use glutin::event_loop::{EventLoop, ControlFlow};
|
||||
use glutin::platform::run_return::EventLoopExtRunReturn;
|
||||
use glutin::event::{Event, WindowEvent, VirtualKeyCode, ElementState, KeyboardInput};
|
||||
|
||||
/// Create a context using glutin given a configuration.
|
||||
pub fn init_glutin_context<S: ToString>(
|
||||
pub fn init_glutin_context<S: ToString, ET: 'static>(
|
||||
window_title: S,
|
||||
window_width: f64,
|
||||
window_height: f64,
|
||||
resizable: bool,
|
||||
) -> (EventsLoop, GlWindow) {
|
||||
event_loop: &EventLoop<ET>
|
||||
) -> WindowedContext<PossiblyCurrent> {
|
||||
let window_size = LogicalSize::new(window_width, window_height);
|
||||
|
||||
let events_loop = EventsLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title(window_title.to_string())
|
||||
.with_dimensions(window_size)
|
||||
.with_inner_size(window_size)
|
||||
.with_resizable(resizable);
|
||||
|
||||
let context = ContextBuilder::new();
|
||||
let gl_window = GlWindow::new(window, context, &events_loop).unwrap();
|
||||
let context: WindowedContext<PossiblyCurrent> = unsafe {
|
||||
ContextBuilder::new()
|
||||
.build_windowed(window, event_loop)
|
||||
.unwrap()
|
||||
.make_current()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
gl_window.make_current().unwrap();
|
||||
gl::load_with(|symbol| gl_window.get_proc_address(symbol) as *const _);
|
||||
}
|
||||
gl::load_with(|symbol| context.get_proc_address(symbol) as *const _);
|
||||
|
||||
(events_loop, gl_window)
|
||||
context
|
||||
}
|
||||
|
||||
type VertexFormat = buffer_layout!([f32; 2], [f32; 2]);
|
||||
|
@ -54,7 +49,8 @@ pub fn init_framebuffer(
|
|||
buffer_width: u32,
|
||||
buffer_height: u32,
|
||||
viewport_width: u32,
|
||||
viewport_height: u32
|
||||
viewport_height: u32,
|
||||
invert_y: bool
|
||||
) -> Framebuffer {
|
||||
// The config takes the size in u32 because that's all that actually makes sense but since
|
||||
// OpenGL is from the Land of C where a Working Type System doesn't exist, we work with i32s
|
||||
|
@ -98,15 +94,25 @@ pub fn init_framebuffer(
|
|||
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
|
||||
VertexFormat::declare(0);
|
||||
|
||||
let verts: [[f32; 2]; 12] = [
|
||||
[-1., 1.], [0., 1.], // top left
|
||||
[-1., -1.], [0., 0.], // bottom left
|
||||
[1., -1.], [1., 0.], // bottom right
|
||||
[1., -1.], [1., 0.], // bottom right
|
||||
[1., 1.], [1., 1.], // top right
|
||||
[-1., 1.], [0., 1.], // top left
|
||||
];
|
||||
use std::mem::size_of_val;
|
||||
let verts: [[f32; 2]; 12] = if invert_y {
|
||||
[
|
||||
[-1., 1.], [0., 1.], // top left
|
||||
[-1., -1.], [0., 0.], // bottom left
|
||||
[1., -1.], [1., 0.], // bottom right
|
||||
[1., -1.], [1., 0.], // bottom right
|
||||
[1., 1.], [1., 1.], // top right
|
||||
[-1., 1.], [0., 1.], // top left
|
||||
]
|
||||
} else {
|
||||
[
|
||||
[-1., -1.], [0., 1.], // bottom left
|
||||
[1., 1.], [1., 0.], // top right
|
||||
[-1., 1.], [0., 0.], // top left
|
||||
[1., 1.], [1., 0.], // top right
|
||||
[-1., -1.], [0., 1.], // bottom left
|
||||
[1., -1.], [1., 1.], // bottom right
|
||||
]
|
||||
};
|
||||
gl::BufferData(gl::ARRAY_BUFFER,
|
||||
size_of_val(&verts) as _,
|
||||
verts.as_ptr() as *const _,
|
||||
|
@ -120,20 +126,21 @@ pub fn init_framebuffer(
|
|||
}
|
||||
|
||||
Framebuffer {
|
||||
buffer_width,
|
||||
buffer_height,
|
||||
vp_width,
|
||||
vp_height,
|
||||
program,
|
||||
sampler_location,
|
||||
vertex_shader: Some(vertex_shader),
|
||||
geometry_shader: None,
|
||||
fragment_shader: Some(fragment_shader),
|
||||
texture,
|
||||
vao,
|
||||
vbo,
|
||||
texture_format,
|
||||
buffer_size: LogicalSize::new(buffer_width, buffer_height),
|
||||
vp_size: PhysicalSize::new(vp_width, vp_height),
|
||||
did_draw: false,
|
||||
inverted_y: invert_y,
|
||||
internal: FramebufferInternal {
|
||||
program,
|
||||
sampler_location,
|
||||
vertex_shader: Some(vertex_shader),
|
||||
geometry_shader: None,
|
||||
fragment_shader: Some(fragment_shader),
|
||||
texture,
|
||||
vao,
|
||||
vbo,
|
||||
texture_format,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,214 +153,181 @@ pub fn init_framebuffer(
|
|||
/// is no documentation and you find the method is non-trivial, it's a bug! Feel free to submit an
|
||||
/// issue!
|
||||
pub struct Internal {
|
||||
pub events_loop: EventsLoop,
|
||||
pub gl_window: GlWindow,
|
||||
pub context: WindowedContext<PossiblyCurrent>,
|
||||
pub fb: Framebuffer,
|
||||
}
|
||||
|
||||
impl Internal {
|
||||
pub fn update_buffer<T>(&mut self, image_data: &[T]) {
|
||||
self.fb.update_buffer(image_data);
|
||||
self.gl_window.swap_buffers().unwrap();
|
||||
self.context.swap_buffers().unwrap();
|
||||
}
|
||||
|
||||
pub fn set_resizable(&mut self, resizable: bool) {
|
||||
self.gl_window.set_resizable(resizable);
|
||||
self.context.window().set_resizable(resizable);
|
||||
}
|
||||
|
||||
pub fn resize_viewport(&mut self, width: u32, height: u32) {
|
||||
self.gl_window.resize((width, height).into());
|
||||
self.context.resize((width, height).into());
|
||||
self.fb.resize_viewport(width, height);
|
||||
}
|
||||
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
let mut running = true;
|
||||
let mut resized = None;
|
||||
self.events_loop.poll_events(|event| {
|
||||
pub fn redraw(&mut self) {
|
||||
self.fb.redraw();
|
||||
self.context.swap_buffers().unwrap();
|
||||
}
|
||||
|
||||
pub fn persist<ET: 'static>(&mut self, event_loop: &mut EventLoop<ET>) {
|
||||
self.persist_and_redraw(event_loop, false);
|
||||
}
|
||||
|
||||
pub fn persist_and_redraw<ET: 'static>(&mut self, event_loop: &mut EventLoop<ET>, redraw: bool) {
|
||||
event_loop.run_return(|event, _, flow| {
|
||||
*flow = ControlFlow::Wait;
|
||||
|
||||
let mut new_size = None;
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => running = false,
|
||||
WindowEvent::CloseRequested => *flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput { input, .. } => {
|
||||
if let Some(k) = input.virtual_keycode {
|
||||
if k == VirtualKeyCode::Escape
|
||||
&& input.state == ElementState::Released {
|
||||
running = false;
|
||||
&& input.state == ElementState::Pressed {
|
||||
*flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowEvent::Resized(logical_size) => {
|
||||
resized = Some(logical_size);
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
new_size = Some(physical_size);
|
||||
}
|
||||
_ => {},
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
});
|
||||
if let Some(size) = resized {
|
||||
let dpi_factor = self.gl_window.get_hidpi_factor();
|
||||
let (x, y) = size.to_physical(dpi_factor).into();
|
||||
self.resize_viewport(x, y);
|
||||
}
|
||||
running
|
||||
}
|
||||
|
||||
pub fn redraw(&mut self) {
|
||||
self.fb.redraw();
|
||||
self.gl_window.swap_buffers().unwrap();
|
||||
}
|
||||
|
||||
pub fn persist(&mut self) {
|
||||
self.persist_and_redraw(false);
|
||||
}
|
||||
|
||||
pub fn persist_and_redraw(&mut self, redraw: bool) {
|
||||
let mut running = true;
|
||||
while running {
|
||||
let mut new_size = None;
|
||||
self.events_loop.poll_events(|event| {
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => running = false,
|
||||
WindowEvent::KeyboardInput { input, .. } => {
|
||||
if let Some(k) = input.virtual_keycode {
|
||||
if k == VirtualKeyCode::Escape
|
||||
&& input.state == ElementState::Released {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowEvent::Resized(logical_size) => {
|
||||
new_size = Some(logical_size);
|
||||
}
|
||||
_ => {},
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
});
|
||||
if let Some(size) = new_size {
|
||||
let dpi_factor = self.gl_window.get_hidpi_factor();
|
||||
let (x, y) = size.to_physical(dpi_factor).into();
|
||||
self.resize_viewport(x, y);
|
||||
// TODO: We should store window size in BasicInput, but as what type? OpenGL wants
|
||||
// integer window sizes but we also have to deal with physical vs. logical size
|
||||
self.resize_viewport(size.width, size.height);
|
||||
self.redraw();
|
||||
} else {
|
||||
if redraw {
|
||||
self.fb.redraw();
|
||||
self.gl_window.swap_buffers().unwrap();
|
||||
}
|
||||
} else if redraw {
|
||||
self.fb.redraw();
|
||||
self.context.swap_buffers().unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn glutin_handle_basic_input<F: FnMut(&mut Framebuffer, &BasicInput) -> bool>(
|
||||
&mut self, mut handler: F
|
||||
pub fn glutin_handle_basic_input<ET: 'static, F: FnMut(&mut Framebuffer, &BasicInput) -> bool>(
|
||||
&mut self, event_loop: &mut EventLoop<ET>, mut handler: F
|
||||
) {
|
||||
let mut running = true;
|
||||
let mut input = BasicInput {
|
||||
// Not sure how to set mouse pos at start
|
||||
mouse_pos: (0.0, 0.0),
|
||||
mouse: HashMap::new(),
|
||||
keys: HashMap::new(),
|
||||
modifiers: Default::default(),
|
||||
resized: false,
|
||||
};
|
||||
while running {
|
||||
let mut previous_input: Option<BasicInput> = None;
|
||||
let mut input = BasicInput::default();
|
||||
|
||||
event_loop.run_return(|event, _, flow| {
|
||||
let mut new_size = None;
|
||||
let mut new_mouse_pos: Option<LogicalPosition> = None;
|
||||
self.events_loop.poll_events(|event| {
|
||||
// Copy the current states into the previous state for input
|
||||
for (_, val) in &mut input.keys {
|
||||
val.0 = val.1;
|
||||
}
|
||||
for (_, val) in &mut input.mouse {
|
||||
val.0 = val.1;
|
||||
}
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => running = false,
|
||||
WindowEvent::KeyboardInput { input: event_input, .. } => {
|
||||
if let Some(vk) = event_input.virtual_keycode {
|
||||
let key = input.keys.entry(vk)
|
||||
.or_insert((false, false));
|
||||
key.1 = event_input.state == ElementState::Pressed;
|
||||
}
|
||||
input.modifiers = event_input.modifiers;
|
||||
}
|
||||
WindowEvent::CursorMoved { position, modifiers, ..} => {
|
||||
new_mouse_pos = Some(position);
|
||||
input.modifiers = modifiers;
|
||||
}
|
||||
WindowEvent::MouseInput { state, button, modifiers, .. } => {
|
||||
let button = input.mouse.entry(button)
|
||||
.or_insert((false, false));
|
||||
button.1 = state == ElementState::Pressed;
|
||||
input.modifiers = modifiers;
|
||||
}
|
||||
WindowEvent::Resized(logical_size) => {
|
||||
new_size = Some(logical_size);
|
||||
}
|
||||
_ => {},
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
});
|
||||
let mut new_mouse_pos: Option<PhysicalPosition<f64>> = None;
|
||||
|
||||
// Copy the current states into the previous state for input
|
||||
for (_, val) in &mut input.keys {
|
||||
val.0 = val.1;
|
||||
}
|
||||
|
||||
for (_, val) in &mut input.mouse {
|
||||
val.0 = val.1;
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input: KeyboardInput {
|
||||
virtual_keycode: Some(vk),
|
||||
state,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
let key = input.keys.entry(vk)
|
||||
.or_insert((false, false));
|
||||
key.1 = state == ElementState::Pressed;
|
||||
}
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
new_mouse_pos = Some(position);
|
||||
}
|
||||
WindowEvent::MouseInput { state, button, .. } => {
|
||||
let button = input.mouse.entry(button)
|
||||
.or_insert((false, false));
|
||||
button.1 = state == ElementState::Pressed;
|
||||
}
|
||||
WindowEvent::ModifiersChanged(modifiers) => {
|
||||
input.modifiers = modifiers;
|
||||
}
|
||||
WindowEvent::Resized(logical_size) => {
|
||||
new_size = Some(logical_size);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(size) = new_size {
|
||||
let dpi_factor = self.gl_window.get_hidpi_factor();
|
||||
let (x, y) = size.to_physical(dpi_factor).into();
|
||||
self.resize_viewport(x, y);
|
||||
self.resize_viewport(size.width, size.height);
|
||||
input.resized = false;
|
||||
}
|
||||
|
||||
if let Some(pos) = new_mouse_pos {
|
||||
let dpi_factor = self.gl_window.get_hidpi_factor();
|
||||
let (x, y): (f64, f64) = pos.to_physical(dpi_factor).into();
|
||||
let x_scale = self.fb.buffer_width as f64 / (self.fb.vp_width as f64);
|
||||
let y_scale = self.fb.buffer_height as f64 / (self.fb.vp_height as f64);
|
||||
let (x, y): (f64, f64) = pos.into();
|
||||
let x_scale = self.fb.buffer_size.width as f64 / (self.fb.vp_size.width as f64);
|
||||
let y_scale = self.fb.buffer_size.height as f64 / (self.fb.vp_size.height as f64);
|
||||
let mouse_pos = (
|
||||
x * x_scale,
|
||||
// use the OpenGL texture coordinate system instead of window coordinates
|
||||
self.fb.buffer_height as f64 - y * y_scale
|
||||
if self.fb.inverted_y {
|
||||
self.fb.buffer_size.height as f64 - y * y_scale
|
||||
} else {
|
||||
y * y_scale
|
||||
}
|
||||
);
|
||||
input.mouse_pos = mouse_pos;
|
||||
}
|
||||
|
||||
if running {
|
||||
running = handler(&mut self.fb, &input);
|
||||
if self.fb.did_draw {
|
||||
self.gl_window.swap_buffers().unwrap();
|
||||
self.fb.did_draw = false;
|
||||
if input.wait {
|
||||
*flow = ControlFlow::Wait;
|
||||
|
||||
// handler only wants to be notified when the input changes
|
||||
if previous_input.as_ref().map_or(true, |p| *p != input) {
|
||||
if !handler(&mut self.fb, &input) {
|
||||
*flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// handler wants to be notified regardless
|
||||
if !handler(&mut self.fb, &input) {
|
||||
*flow = ControlFlow::Exit;
|
||||
} else {
|
||||
*flow = ControlFlow::Poll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previous_input = Some(input.clone());
|
||||
|
||||
if self.fb.did_draw {
|
||||
self.context.swap_buffers().unwrap();
|
||||
self.fb.did_draw = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn glutin_breakout(self) -> GlutinBreakout {
|
||||
GlutinBreakout {
|
||||
events_loop: self.events_loop,
|
||||
gl_window: self.gl_window,
|
||||
context: self.context,
|
||||
fb: self.fb,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides the drawing functionality.
|
||||
///
|
||||
/// You can get direct access by using a breakout function, such as breakout_glutin.
|
||||
///
|
||||
/// # Disclaimer:
|
||||
///
|
||||
/// Accessing fields directly is not the intended usage. If a feature is missing please open an
|
||||
/// issue. The fields are public, however, so that while you are waiting for a feature to be
|
||||
/// exposed, if you need something in a pinch you can dig in easily and make it happen.
|
||||
///
|
||||
/// The internal fields may change.
|
||||
///
|
||||
/// TODO: Possibly create a FramebufferInternal struct?
|
||||
pub struct Framebuffer {
|
||||
pub buffer_width: i32,
|
||||
pub buffer_height: i32,
|
||||
pub vp_width: i32,
|
||||
pub vp_height: i32,
|
||||
/// Contains internal OpenGL things.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug)]
|
||||
pub struct FramebufferInternal {
|
||||
pub program: GLuint,
|
||||
pub sampler_location: GLint,
|
||||
pub vertex_shader: Option<GLuint>,
|
||||
|
@ -363,17 +337,70 @@ pub struct Framebuffer {
|
|||
pub vao: GLuint,
|
||||
pub vbo: GLuint,
|
||||
pub texture_format: (BufferFormat, GLenum),
|
||||
}
|
||||
|
||||
/// The Framebuffer struct manages the framebuffer of a MGlFb window. Through this struct, you can
|
||||
/// update the size and content of the buffer. Framebuffers are usually obtained through
|
||||
/// [`MiniGlFb::glutin_breakout`][crate::MiniGlFb::glutin_breakout], but they're also returned by
|
||||
/// [`init_framebuffer`].
|
||||
///
|
||||
/// # Basic usage
|
||||
/// Firstly, one of the most important things to do when managing a Framebuffer manually is to make
|
||||
/// sure that whenever the window is resized, the Framebuffer is the first to know. Usually, this is
|
||||
/// handled for you by [`MiniGlFb`][crate::MiniGlFb], but that isn't the case when using the
|
||||
/// [`GlutinBreakout`].
|
||||
///
|
||||
/// Whenever you receive a resize event for your window, make sure to call
|
||||
/// [`Framebuffer::resize_viewport`] with the new physical dimensions of your window. You can also
|
||||
/// figure out some logical dimensions and call [`Framebuffer::resize_buffer`] too.
|
||||
///
|
||||
/// Additionally, when managing multiple framebuffers at once, you should make sure to call
|
||||
/// [`GlutinBreakout::make_current`] when appropriate, before calling any `Framebuffer` methods.
|
||||
/// Forgetting to call `make_current` can cause OpenGL to get confused and draw to the wrong window,
|
||||
/// which is probably not what you want.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug)]
|
||||
pub struct Framebuffer {
|
||||
/// The logical size of the buffer. When you update the buffer via
|
||||
/// [`update_buffer`][Framebuffer::update_buffer], it is expected to contain
|
||||
/// `buffer_size.width * buffer_size.height` pixels.
|
||||
pub buffer_size: LogicalSize<i32>,
|
||||
|
||||
/// The physical size of the viewport. This should always be kept up to date with the size of
|
||||
/// the window, and there is no reason to set it otherwise unless you're drawing multiple
|
||||
/// buffers to one window or something funky like that.
|
||||
pub vp_size: PhysicalSize<i32>,
|
||||
|
||||
/// This is set to `true` every time [`draw`][Framebuffer::draw] is called. (or, by extension,
|
||||
/// [`update_buffer`][Framebuffer::update_buffer])
|
||||
///
|
||||
/// It's safe to set this to `false` afterwards, it's just a flag to let you know if code you're
|
||||
/// calling into has updated the buffer or not.
|
||||
pub did_draw: bool,
|
||||
|
||||
/// True if the origin should be the bottom left of the screen instead of the top left. For
|
||||
/// historical reasons, this is the default. This should only be configured by changing the
|
||||
/// [`Config`][crate::Config] passed to [`get_fancy`][crate::get_fancy].
|
||||
pub inverted_y: bool,
|
||||
|
||||
/// Contains internal OpenGL things.
|
||||
///
|
||||
/// Accessing fields directly is not the intended usage. If a feature is missing please open an
|
||||
/// issue. The fields are public, however, so that while you are waiting for a feature to be
|
||||
/// exposed, if you need something in a pinch you can dig in easily and make it happen.
|
||||
///
|
||||
/// The internal fields may change.
|
||||
pub internal: FramebufferInternal
|
||||
}
|
||||
|
||||
impl Framebuffer {
|
||||
pub fn update_buffer<T>(&mut self, image_data: &[T]) {
|
||||
// Check the length of the passed slice so this is actually a safe method.
|
||||
let (format, kind) = self.texture_format;
|
||||
let (format, kind) = self.internal.texture_format;
|
||||
let expected_size_in_bytes = size_of_gl_type_enum(kind)
|
||||
* format.components()
|
||||
* self.buffer_width as usize
|
||||
* self.buffer_height as usize;
|
||||
* self.buffer_size.width as usize
|
||||
* self.buffer_size.height as usize;
|
||||
let actual_size_in_bytes = size_of_val(image_data);
|
||||
if actual_size_in_bytes != expected_size_in_bytes {
|
||||
panic!(
|
||||
|
@ -388,8 +415,8 @@ impl Framebuffer {
|
|||
gl::TEXTURE_2D,
|
||||
0,
|
||||
gl::RGBA as _,
|
||||
fb.buffer_width,
|
||||
fb.buffer_height,
|
||||
fb.buffer_size.width,
|
||||
fb.buffer_size.height,
|
||||
0,
|
||||
format as GLenum,
|
||||
kind,
|
||||
|
@ -400,12 +427,12 @@ impl Framebuffer {
|
|||
}
|
||||
|
||||
pub fn use_vertex_shader(&mut self, source: &str) {
|
||||
rebuild_shader(&mut self.vertex_shader, gl::VERTEX_SHADER, source);
|
||||
rebuild_shader(&mut self.internal.vertex_shader, gl::VERTEX_SHADER, source);
|
||||
self.relink_program();
|
||||
}
|
||||
|
||||
pub fn use_fragment_shader(&mut self, source: &str) {
|
||||
rebuild_shader(&mut self.fragment_shader, gl::FRAGMENT_SHADER, source);
|
||||
rebuild_shader(&mut self.internal.fragment_shader, gl::FRAGMENT_SHADER, source);
|
||||
self.relink_program();
|
||||
}
|
||||
|
||||
|
@ -415,7 +442,7 @@ impl Framebuffer {
|
|||
}
|
||||
|
||||
pub fn use_geometry_shader(&mut self, source: &str) {
|
||||
rebuild_shader(&mut self.geometry_shader, gl::GEOMETRY_SHADER, source);
|
||||
rebuild_shader(&mut self.internal.geometry_shader, gl::GEOMETRY_SHADER, source);
|
||||
self.relink_program();
|
||||
}
|
||||
|
||||
|
@ -427,17 +454,15 @@ impl Framebuffer {
|
|||
&mut self,
|
||||
format: BufferFormat,
|
||||
) {
|
||||
self.texture_format = (format, T::to_gl_enum());
|
||||
self.internal.texture_format = (format, T::to_gl_enum());
|
||||
}
|
||||
|
||||
pub fn resize_buffer(&mut self, buffer_width: u32, buffer_height: u32) {
|
||||
self.buffer_width = buffer_width as _;
|
||||
self.buffer_height = buffer_height as _;
|
||||
self.buffer_size = LogicalSize::new(buffer_width, buffer_height).cast();
|
||||
}
|
||||
|
||||
pub fn resize_viewport(&mut self, width: u32, height: u32) {
|
||||
self.vp_width = width as _;
|
||||
self.vp_height = height as _;
|
||||
self.vp_size = PhysicalSize::new(width, height).cast();
|
||||
}
|
||||
|
||||
pub fn redraw(&mut self) {
|
||||
|
@ -445,16 +470,16 @@ impl Framebuffer {
|
|||
}
|
||||
|
||||
/// Draw the quad to the active context. Optionally issue other commands after binding
|
||||
/// everything but before
|
||||
/// everything but before drawing it.
|
||||
///
|
||||
/// You probably want `redraw` (equivalent to `.draw(|_| {})`).
|
||||
/// You probably want [`redraw`][Framebuffer::redraw] (equivalent to `.draw(|_| {})`).
|
||||
pub fn draw<F: FnOnce(&Framebuffer)>(&mut self, f: F) {
|
||||
unsafe {
|
||||
gl::Viewport(0, 0, self.vp_width, self.vp_height);
|
||||
gl::UseProgram(self.program);
|
||||
gl::BindVertexArray(self.vao);
|
||||
gl::Viewport(0, 0, self.vp_size.width, self.vp_size.height);
|
||||
gl::UseProgram(self.internal.program);
|
||||
gl::BindVertexArray(self.internal.vao);
|
||||
gl::ActiveTexture(0);
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.texture);
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.internal.texture);
|
||||
f(self);
|
||||
gl::DrawArrays(gl::TRIANGLES, 0, 6);
|
||||
gl::BindTexture(gl::TEXTURE_2D, 0);
|
||||
|
@ -466,17 +491,17 @@ impl Framebuffer {
|
|||
|
||||
pub fn relink_program(&mut self) {
|
||||
unsafe {
|
||||
gl::DeleteProgram(self.program);
|
||||
self.program = build_program(&[
|
||||
self.vertex_shader,
|
||||
self.fragment_shader,
|
||||
self.geometry_shader,
|
||||
gl::DeleteProgram(self.internal.program);
|
||||
self.internal.program = build_program(&[
|
||||
self.internal.vertex_shader.clone(),
|
||||
self.internal.fragment_shader.clone(),
|
||||
self.internal.geometry_shader.clone(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
#[repr(u32)]
|
||||
pub enum BufferFormat {
|
||||
R = gl::RED,
|
||||
|
|
157
src/lib.rs
157
src/lib.rs
|
@ -2,26 +2,33 @@
|
|||
//!
|
||||
//! # Basic Usage
|
||||
//!
|
||||
//! Start with the function `gotta_go_fast`. This will create a basic window and give you a buffer
|
||||
//! that you can draw to in one line. The main public API is available through the `MiniGlFb` type.
|
||||
//! Start with the function [`gotta_go_fast`]. This will create an [`EventLoop`], basic window, and
|
||||
//! a buffer that you can draw to, all in just one function call. The main public API is available
|
||||
//! through the [`MiniGlFb`] type.
|
||||
//!
|
||||
//! ```rust
|
||||
//! extern crate mini_gl_fb;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut fb = mini_gl_fb::gotta_go_fast("Hello world!", 800.0, 600.0);
|
||||
//! // Create the event loop and framebuffer
|
||||
//! let (mut event_loop, mut fb) = mini_gl_fb::gotta_go_fast("Hello world!", 800.0, 600.0);
|
||||
//!
|
||||
//! // Fill the buffer with something
|
||||
//! let buffer = vec![[128u8, 0, 0, 255]; 800 * 600];
|
||||
//! fb.update_buffer(&buffer);
|
||||
//! fb.persist();
|
||||
//!
|
||||
//! // Show the window until the user decides to quit (close button, or Esc)
|
||||
//! fb.persist(&mut event_loop);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! The default buffer format is 32bit RGBA, so every pixel is four bytes. Buffer[0] is the bottom
|
||||
//! left pixel. The buffer should be tightly packed with no padding after each row.
|
||||
//! The default buffer format is 32bit RGBA, so every pixel is four bytes. Buffer\[0\] is the bottom
|
||||
//! left pixel (not the top). See [`Config::invert_y`] for information on this. The buffer should be
|
||||
//! tightly packed with no padding after each row.
|
||||
//!
|
||||
//! # Interlude: Library philosophy
|
||||
//!
|
||||
//! All of the internals of this library are exposed. Any fields behind `mini_gl_fb.internal`
|
||||
//! All of the internals of this library are exposed. Any fields behind [`MiniGlFb::internal`]
|
||||
//! are not considered a part of the public API but are exposed in case the library is missing a
|
||||
//! feature that you need "right now." This library is not here to box you in.
|
||||
//!
|
||||
|
@ -35,23 +42,32 @@
|
|||
//!
|
||||
//! # More advanced configuration
|
||||
//!
|
||||
//! Use the `get_fancy` function for more settings. See `Config` for what's available. This allows
|
||||
//! you to, for instance, create a window with a buffer of a different size than the window.
|
||||
//! Use the [`get_fancy`] function for more settings. See [`Config`] for what's available. This
|
||||
//! allows you to, for instance, create a window with a buffer of a different size than the window.
|
||||
//! This is useful for HiDPI support, since you can take advantage of the full resolution of the
|
||||
//! screen.
|
||||
//!
|
||||
//! `get_fancy` (and all the functions in the library) require you to bring your own event loop.
|
||||
//! This allows for multiple windows. See the `multi_window` example.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use mini_gl_fb::{get_fancy, Config};
|
||||
//! use mini_gl_fb::{get_fancy, config};
|
||||
//! use mini_gl_fb::glutin::event_loop::EventLoop;
|
||||
//! use mini_gl_fb::glutin::dpi::LogicalSize;
|
||||
//! # let window_title = "foo";
|
||||
//! # let window_width = 800.0;
|
||||
//! # let window_height = 600.0;
|
||||
//!
|
||||
//! let config = Config {
|
||||
//! let event_loop = EventLoop::new();
|
||||
//! let config = config! {
|
||||
//! window_title: window_title.to_string(),
|
||||
//! window_size: (window_width, window_height),
|
||||
//! .. Default::default()
|
||||
//! window_size: LogicalSize::new(window_width, window_height)
|
||||
//! };
|
||||
//! let fb = get_fancy(config);
|
||||
//! let fb = get_fancy(config, &event_loop);
|
||||
//! ```
|
||||
//!
|
||||
//! (See the [`Config`] documentation for an explanation of the `config!` macro.)
|
||||
//!
|
||||
//! If you think something else should be exposed as an option, open an issue!
|
||||
//!
|
||||
//! # Bring your own context (and event handling)!
|
||||
|
@ -66,6 +82,26 @@
|
|||
//! Currently uses the `gl` crate for OpenGL loading. OpenGL context creation may fail if your
|
||||
//! setup does not support the newest OpenGL. This bug needs to be verified and is be fixable.
|
||||
//! OpenGL ~3 is currently required, but OpenGL 2.1 support should be feasible if requested.
|
||||
//!
|
||||
//! # Feature matrix
|
||||
//!
|
||||
//! MGlFb does not implement every feature and is not compatible with everything, but there are
|
||||
//! still reasons to choose it over other libraries.
|
||||
//!
|
||||
//! | Feature | `glutin`+`mini_gl_fb` | `winit`+`pixels` | `minifb` |
|
||||
//! |-------------------------------|-----------------------|----------------------|-----------------------|
|
||||
//! | `gotta_go_fast`-like function | Yes | No | No |
|
||||
//! | Event-based API | Yes | Yes | No |
|
||||
//! | Multi-window | Yes | Yes | Unsupported |
|
||||
//! | Vsync | Yes (use glutin) | Confusing wgpu stuff | Only FPS locking |
|
||||
//! | Buffer allocation | By user | Confusing wgpu stuff | By user |
|
||||
//! | Resizable | Yes | Requires restart | Yes |
|
||||
//! | Scalable | Yes | Integer | Buggy on Windows |
|
||||
//! | Hardware-accelerated | Yes | Very | macOS-only |
|
||||
//! | Basic input | Yes | No | Always |
|
||||
//! | Multiple rendering backends | No (OpenGL) | Yes, by wgpu | No (one per platform) |
|
||||
//! | Custom shaders | Yes | Pre-provided | No shaders |
|
||||
//! | Requires OpenGL | 3.3+ | No | No |
|
||||
|
||||
#[macro_use]
|
||||
pub extern crate rustic_gl;
|
||||
|
@ -82,65 +118,63 @@ pub use config::Config;
|
|||
pub use core::{Internal, BufferFormat, Framebuffer};
|
||||
|
||||
use core::ToGlType;
|
||||
use glutin::event_loop::EventLoop;
|
||||
use glutin::dpi::LogicalSize;
|
||||
|
||||
/// Creates a non resizable window and framebuffer with a given size in pixels.
|
||||
/// Creates a non-resizable window and framebuffer with a given size in logical pixels. On HiDPI
|
||||
/// screens, the physical size of the window may be larger or smaller than the provided values, but
|
||||
/// the buffer will be scaled to match.
|
||||
///
|
||||
/// Please note that the window size is in logical device pixels, so on a high DPI monitor the
|
||||
/// physical window size may be larger. In this case, the rendered buffer will be scaled it
|
||||
/// automatically by OpenGL.
|
||||
/// This function also creates an event loop for you. If you would like to create your own event
|
||||
/// loop, you can use the `get_fancy` function directly.
|
||||
pub fn gotta_go_fast<S: ToString>(
|
||||
window_title: S,
|
||||
window_width: f64,
|
||||
window_height: f64
|
||||
) -> MiniGlFb {
|
||||
let config = Config {
|
||||
) -> (EventLoop<()>, MiniGlFb) {
|
||||
let event_loop = EventLoop::new();
|
||||
let config = config! {
|
||||
window_title: window_title.to_string(),
|
||||
window_size: (window_width, window_height),
|
||||
resizable: false,
|
||||
.. Default::default()
|
||||
window_size: LogicalSize::from((window_width, window_height)),
|
||||
resizable: false
|
||||
};
|
||||
get_fancy(config)
|
||||
let fancy = get_fancy(config, &event_loop);
|
||||
(event_loop, fancy)
|
||||
}
|
||||
|
||||
/// Create a window with a custom configuration.
|
||||
///
|
||||
/// If this configuration is not sufficient for you, check out the source for this function.
|
||||
/// Creating the MiniGlFb instance is just a call to two functions!
|
||||
/// Creating the `MiniGlFb` instance is just a call to two functions!
|
||||
///
|
||||
/// Many window settings can be changed after creation, so you most likely don't ever need to call
|
||||
/// `get_fancy` with a custom config. However, if there is a bug in the OS/windowing system or
|
||||
/// glutin or in this library, this function exists as a possible work around (or in case for some
|
||||
/// reason everything must be absolutely correct at window creation)
|
||||
pub fn get_fancy<S: ToString>(config: Config<S>) -> MiniGlFb {
|
||||
let buffer_width = if config.buffer_size.0 == 0 { config.window_size.0.round() as _ }
|
||||
else { config.buffer_size.0 };
|
||||
let buffer_height = if config.buffer_size.1 == 0 { config.window_size.1.round() as _ }
|
||||
else { config.buffer_size.1 };
|
||||
pub fn get_fancy<ET: 'static>(config: Config, event_loop: &EventLoop<ET>) -> MiniGlFb {
|
||||
let buffer_size = config.buffer_size.unwrap_or_else(|| config.window_size.cast());
|
||||
|
||||
let (events_loop, gl_window) = core::init_glutin_context(
|
||||
let context = core::init_glutin_context(
|
||||
config.window_title,
|
||||
config.window_size.0,
|
||||
config.window_size.1,
|
||||
config.window_size.width,
|
||||
config.window_size.height,
|
||||
config.resizable,
|
||||
event_loop
|
||||
);
|
||||
|
||||
let dpi_factor = gl_window.get_hidpi_factor();
|
||||
let (vp_width, vp_height) = gl_window.get_inner_size()
|
||||
.unwrap()
|
||||
.to_physical(dpi_factor)
|
||||
.into();
|
||||
let (vp_width, vp_height) = context.window().inner_size().into();
|
||||
|
||||
let fb = core::init_framebuffer(
|
||||
buffer_width,
|
||||
buffer_height,
|
||||
buffer_size.width,
|
||||
buffer_size.height,
|
||||
vp_width,
|
||||
vp_height,
|
||||
config.invert_y
|
||||
);
|
||||
|
||||
MiniGlFb {
|
||||
internal: Internal {
|
||||
events_loop,
|
||||
gl_window,
|
||||
context,
|
||||
fb,
|
||||
}
|
||||
}
|
||||
|
@ -174,21 +208,6 @@ impl MiniGlFb {
|
|||
self.internal.update_buffer(image_data);
|
||||
}
|
||||
|
||||
/// Checks if escape has been pressed or the window has been asked to close.
|
||||
///
|
||||
/// This function is a good choice for a while loop condition when you are making a simulation
|
||||
/// that needs to progress over time but does not need to handle user input.
|
||||
///
|
||||
/// Calling this function clears the event queue and also handles resizes for you (if your
|
||||
/// window is resizable). This does not resize the image buffer; the rendered buffer will
|
||||
/// instead scale to fit the window.
|
||||
///
|
||||
/// Please note that if your window does change size, for buffer to appear scaled it must
|
||||
/// be redrawn, typically either by calling `redraw` or `update_buffer`.
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
self.internal.is_running()
|
||||
}
|
||||
|
||||
pub fn redraw(&mut self) {
|
||||
self.internal.redraw();
|
||||
}
|
||||
|
@ -205,7 +224,8 @@ impl MiniGlFb {
|
|||
///
|
||||
/// ```rust
|
||||
/// # use mini_gl_fb::get_fancy;
|
||||
/// # let mut fb = get_fancy::<&str>(Default::default());
|
||||
/// # use mini_gl_fb::glutin::event_loop::EventLoop;
|
||||
/// # let mut fb = get_fancy(Default::default(), &EventLoop::new());
|
||||
/// fb.use_post_process_shader("
|
||||
/// void main_image( out vec4 r_frag_color, in vec2 v_uv ) {
|
||||
/// r_frag_color = texture(u_buffer, v_uv);
|
||||
|
@ -251,7 +271,8 @@ impl MiniGlFb {
|
|||
/// ```rust
|
||||
/// use mini_gl_fb::BufferFormat;
|
||||
/// # use mini_gl_fb::get_fancy;
|
||||
/// # let mut fb = get_fancy::<&str>(Default::default());
|
||||
/// # use mini_gl_fb::glutin::event_loop::EventLoop;
|
||||
/// # let mut fb = get_fancy(Default::default(), &EventLoop::new());
|
||||
///
|
||||
/// fb.change_buffer_format::<u8>(BufferFormat::R);
|
||||
/// fb.use_grayscale_shader();
|
||||
|
@ -302,8 +323,8 @@ impl MiniGlFb {
|
|||
///
|
||||
/// Supports pressing escape to quit. Automatically scales the rendered buffer to the size of
|
||||
/// the window if the window is resiable (but this does not resize the buffer).
|
||||
pub fn persist(&mut self) {
|
||||
self.internal.persist();
|
||||
pub fn persist<ET: 'static>(&mut self, event_loop: &mut EventLoop<ET>) {
|
||||
self.internal.persist(event_loop);
|
||||
}
|
||||
|
||||
/// `persist` implementation.
|
||||
|
@ -311,8 +332,8 @@ impl MiniGlFb {
|
|||
/// When redraw is true, redraws as fast as possible. This function is primarily for debugging.
|
||||
///
|
||||
/// See `persist` method documentation for more info.
|
||||
pub fn persist_and_redraw(&mut self, redraw: bool) {
|
||||
self.internal.persist_and_redraw(redraw);
|
||||
pub fn persist_and_redraw<ET: 'static>(&mut self, event_loop: &mut EventLoop<ET>, redraw: bool) {
|
||||
self.internal.persist_and_redraw(event_loop, redraw);
|
||||
}
|
||||
|
||||
/// Provides an easy interface for rudimentary input handling.
|
||||
|
@ -329,16 +350,16 @@ impl MiniGlFb {
|
|||
/// You can cause the handler to exit by returning false from it. This does not kill the
|
||||
/// window, so as long as you still have it in scope, you can actually keep using it and,
|
||||
/// for example, resume handling input but with a different handler callback.
|
||||
pub fn glutin_handle_basic_input<F: FnMut(&mut Framebuffer, &BasicInput) -> bool>(
|
||||
&mut self, handler: F
|
||||
pub fn glutin_handle_basic_input<ET: 'static, F: FnMut(&mut Framebuffer, &BasicInput) -> bool>(
|
||||
&mut self, event_loop: &mut EventLoop<ET>, handler: F
|
||||
) {
|
||||
self.internal.glutin_handle_basic_input(handler);
|
||||
self.internal.glutin_handle_basic_input(event_loop, handler);
|
||||
}
|
||||
|
||||
/// Need full access to Glutin's event handling? No problem!
|
||||
///
|
||||
/// Hands you the event loop and the window we created, so you can handle events however you
|
||||
/// want, and the Framebuffer, so you can still draw easily!
|
||||
/// Hands you the window we created, so you can handle events however you want, and the
|
||||
/// Framebuffer, so you can still draw easily!
|
||||
///
|
||||
/// **IMPORTANT:** You should make sure to render something before swapping buffers or **the
|
||||
/// window may flash violently**. You can call `fb.redraw()` directly before if you are unsure
|
||||
|
|
Loading…
Reference in a new issue