Merge dev -> master (#119)

* Deprecated update_with_buffer and added a temporary (update_with_buffer_size) for now. This will later be removed and the update_with_buffer is requiring the size to bu suplied

* Reparation for 0.14 release

* Missed one case

* Minor cleanup

* Switch to C scalar for Unix + rename

Reason is so we can always use optimized scalar even in debug.
Also removed _size so only update_with_buffer(..) takes width, height of the input buffer

* Implemented AspectRatio aware scale on nix

* Implemented image center

* Added UpperLeft center mode for unix

* Moving macOS over to sized update

* Fixed resize not working on macOS

* WIP on macOS

* More WIP on macOS version

* Bunch of macOS updates and fixes

* Fixed broken bg color on macOS

* Windows fixes WIP

* Remove some spamming

* More windows fixes

* Windows fixes for cursor and warnings

* Some cleanup

* rustfmt pass

* Fixed typo

* Added support for limiting update rate

* Added update rate to Windows

* Added update rate to macOS

* Misc fixes

* Fixed resources and maintance badge

* Updated readme

* Updated changelog

* Added rate limit
This commit is contained in:
Daniel Collin 2019-12-16 08:24:48 +01:00 committed by GitHub
parent f181e2d67c
commit 8b3c2e9b37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1234 additions and 317 deletions

3
.vs/ProjectSettings.json Normal file
View file

@ -0,0 +1,3 @@
{
"CurrentProjectSetting": "No Configurations"
}

View file

@ -2,6 +2,16 @@
This project follows semantic versioning.
### v0.15 (2019-12-16)
- [API BREAKAGE] - update_with_buffer now always take width and height parameters.
- [added] scale_mode in WindowOptions now allow for aspect correct scaling, center of non-scaled buffers and more.
- [added] Added limit_update_rate(..) in order to reduce CPU usage and not hammer the native system calls.
- [changed] x11 now uses C for it's scaling in software mode in order to always have opts on even in debug build.
- [changed] Several fixes with rescaling on all platforms
- [fixed] Cursor was behaving bad on Windows. This has now been fixed
- [known issues] There are some flickering and various issues when resizing on most platforms. PRs/ideas welcome for this.
### v0.14 (2019-12-03)
- [changed] Deprecated update_with_buffer on favor of update_with_buffer_size. The idea is that a size in of the buffer will be more robust and allow for aspect scaling as well.

View file

@ -1,20 +1,25 @@
[package]
name = "minifb"
version = "0.14.0"
version = "0.15.0"
license = "MIT/Apache-2.0"
authors = ["Daniel Collin <daniel@collin.com>"]
description = "Cross-platform window setup with optional bitmap rendering"
keywords = ["windowing", "window", "framebuffer"]
categories = ["rendering"]
repository = "https://github.com/emoon/rust_minifb"
documentation = "https://docs.rs/minifb/0.12/minifb"
maintenance = { status = "actively-developed" }
documentation = "https://docs.rs/minifb/0.15/minifb"
build = "build.rs"
readme = "README.md"
exclude = [
"resources/"
]
[badges]
appveyor = { repository = "emoon/rust-minifb" }
travis-ci = { repository = "emoon/rust_minifb" }
maintenance = {status = "actively-developed"}
[dev-dependencies]
png = "0.15"
[build-dependencies]
cc = "1.0"

View file

@ -13,7 +13,7 @@ Usage
```toml
# Cargo.toml
[dependencies]
minifb = "0.14"
minifb = "0.15"
```
Example
@ -22,7 +22,7 @@ Example
```rust
extern crate minifb;
use minifb::{Key, WindowOptions, Window};
use minifb::{Key, Window, WindowOptions};
const WIDTH: usize = 640;
const HEIGHT: usize = 360;
@ -30,28 +30,36 @@ const HEIGHT: usize = 360;
fn main() {
let mut buffer: Vec<u32> = vec![0; WIDTH * HEIGHT];
let mut window = Window::new("Test - ESC to exit",
WIDTH,
HEIGHT,
WindowOptions::default()).unwrap_or_else(|e| {
let mut window = Window::new(
"Test - ESC to exit",
WIDTH,
HEIGHT,
WindowOptions::default(),
)
.unwrap_or_else(|e| {
panic!("{}", e);
});
// Limit to max ~60 fps update rate
window.limit_update_rate(Some(std::time::Duration::from_micros(16600)));
while window.is_open() && !window.is_key_down(Key::Escape) {
for i in buffer.iter_mut() {
*i = 0; // write something more funny here!
}
// We unwrap here as we want this code to exit if it fails. Real applications may want to handle this in a different way
window.update_with_buffer_size(&buffer, WIDTH, HEIGHT).unwrap();
window
.update_with_buffer(&buffer, WIDTH, HEIGHT)
.unwrap();
}
}
```
Status
------
Currently macOS, Redox, Linux and Windows (64-bit and 32-bit) are the current supported platforms. X11 (Linux/FreeBSD/etc) support has been tested on Ubuntu (x64). Bug report(s) for other OSes/CPUs are welcome!
Currently macOS, Linux and Windows (64-bit and 32-bit) are the current supported platforms. X11 (Linux/FreeBSD/etc) support has been tested on Ubuntu (x64). Bug report(s) for other OSes/CPUs are welcome!
Notice: That after 0.13 Redox hasn't been updated and some work is required to get that working again. PR are welcome.
Build instructions
------------------

View file

@ -12,5 +12,11 @@ fn main() {
.compile("libminifb_native.a");
println!("cargo:rustc-link-lib=framework=Metal");
println!("cargo:rustc-link-lib=framework=MetalKit");
} else if !env.contains("windows") {
// build scalar on non-windows and non-mac
cc::Build::new()
.file("src/native/unix/scalar.cpp")
.opt_level(3) // always build with opts for scaler so it's fast in debug also
.compile("libscalar.a")
}
}

42
examples/image.rs Normal file
View file

@ -0,0 +1,42 @@
extern crate minifb;
extern crate png;
use minifb::{Key, ScaleMode, Window, WindowOptions};
fn main() {
use std::fs::File;
// The decoder is a build for reader and can be used to set various decoding options
// via `Transformations`. The default output transformation is `Transformations::EXPAND
// | Transformations::STRIP_ALPHA`.
let decoder = png::Decoder::new(File::open("resources/uv.png").unwrap());
let (info, mut reader) = decoder.read_info().unwrap();
// Allocate the output buffer.
let mut buf = vec![0; info.buffer_size()];
// Read the next frame. Currently this function should only called once.
// The default options
reader.next_frame(&mut buf).unwrap();
// convert buffer to u32
let u32_buffer: Vec<u32> = buf
.chunks(3)
.map(|v| ((v[0] as u32) << 16) | ((v[1] as u32) << 8) | v[2] as u32)
.collect();
let mut window = Window::new(
"Noise Test - Press ESC to exit",
info.width as usize,
info.height as usize,
WindowOptions {
resize: true,
scale_mode: ScaleMode::Center,
..WindowOptions::default()
},
)
.expect("Unable to open Window");
while window.is_open() && !window.is_key_down(Key::Escape) {
window
.update_with_buffer(&u32_buffer, info.width as usize, info.height as usize)
.unwrap();
}
}

View file

@ -1,27 +1,30 @@
extern crate minifb;
use minifb::{Key, Window, WindowOptions};
use minifb::{Key, Scale, ScaleMode, Window, WindowOptions};
const WIDTH: usize = 600;
const HEIGHT: usize = 600;
const WIDTH: usize = 100;
const HEIGHT: usize = 100;
const FRACTAL_DEPTH: u32 = 64;
const GENERATION_INFINITY: f64 = 16.;
fn main() {
let mut buffer: Vec<u32> = vec![0; WIDTH * HEIGHT];
let mut window = match Window::new(
let mut window = Window::new(
"Fractal - ESC to exit",
WIDTH,
HEIGHT,
WindowOptions::default(),
) {
Ok(win) => win,
Err(err) => {
println!("Unable to create window {}", err);
return;
}
};
WindowOptions {
resize: true,
scale: Scale::X2,
scale_mode: ScaleMode::AspectRatioStretch,
..WindowOptions::default()
},
)
.expect("Unable to Open Window");
// Limit to max ~60 fps update rate
window.limit_update_rate(Some(std::time::Duration::from_micros(16600)));
let range = 2.0;
let x_min = 0. - range;
@ -32,6 +35,8 @@ fn main() {
let mut angle: f64 = 0.0;
window.set_background_color(0, 0, 20);
while window.is_open() && !window.is_key_down(Key::Escape) {
for i in 0..buffer.len() {
let mut real = map((i % WIDTH) as f64, 0., WIDTH as f64, x_min, x_max);
@ -58,7 +63,7 @@ fn main() {
angle += 0.1;
// We unwrap here as we want this code to exit if it fails
window.update_with_buffer_size(&buffer, WIDTH, HEIGHT).unwrap();
window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap();
}
}

View file

@ -29,6 +29,7 @@ fn main() {
WIDTH,
HEIGHT,
WindowOptions {
resize: true,
scale: Scale::X2,
..WindowOptions::default()
},
@ -112,6 +113,6 @@ fn main() {
});
// We unwrap here as we want this code to exit if it fails
window.update_with_buffer_size(&buffer, WIDTH, HEIGHT).unwrap();
window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap();
}
}

View file

@ -13,7 +13,6 @@ fn main() {
WIDTH,
HEIGHT,
WindowOptions {
resize: true,
scale: Scale::X2,
..WindowOptions::default()
},
@ -31,8 +30,9 @@ fn main() {
{
let (new_width, new_height) = window.get_size();
if new_width != width || new_height != height {
// Div by / 2 here as we use 2x scaling for the buffer
// copy valid bits of old buffer to new buffer
let mut new_buffer = vec![0; new_width * new_height / 2 / 2];
let mut new_buffer = vec![0; (new_width / 2) * (new_height / 2)];
for y in 0..(height / 2).min(new_height / 2) {
for x in 0..(width / 2).min(new_width / 2) {
new_buffer[y * (new_width / 2) + x] = buffer[y * (width / 2) + x];
@ -45,11 +45,7 @@ fn main() {
}
window.get_mouse_pos(MouseMode::Discard).map(|(x, y)| {
let screen_pos = ((y as usize) * width / 2) + x as usize;
println!(
"{:?}",
window.get_unscaled_mouse_pos(MouseMode::Discard).unwrap()
);
let screen_pos = ((y as usize) * (width / 2)) + x as usize;
if window.get_mouse_down(MouseButton::Left) {
buffer[screen_pos] = 0x00ffffff;
@ -65,6 +61,8 @@ fn main() {
});
// We unwrap here as we want this code to exit if it fails
window.update_with_buffer_size(&buffer, width, height).unwrap();
window
.update_with_buffer(&buffer, width / 2, height / 2)
.unwrap();
}
}

View file

@ -34,8 +34,8 @@ fn main() {
&& !orig.is_key_down(Key::Escape)
&& !double.is_key_down(Key::Escape)
{
orig.update_with_buffer_size(&buffer, width, height).unwrap();
double.update_with_buffer_size(&buffer, width, height).unwrap();
orig.update_with_buffer(&buffer, width, height).unwrap();
double.update_with_buffer(&buffer, width, height).unwrap();
pos += 7;
pos *= 13;
pos %= buffer.len();

View file

@ -1,41 +1,39 @@
extern crate minifb;
use minifb::{Key, Scale, Window, WindowOptions};
use minifb::{Key, ScaleMode, Window, WindowOptions};
const WIDTH: usize = 640;
const HEIGHT: usize = 360;
const WIDTH: usize = 640 / 2;
const HEIGHT: usize = 360 / 2;
fn main() {
let mut noise;
let mut carry;
let mut seed = 0xbeefu32;
let mut window = match Window::new(
let mut window = Window::new(
"Noise Test - Press ESC to exit",
WIDTH,
HEIGHT,
WindowOptions {
resize: true,
scale: Scale::X2,
scale_mode: ScaleMode::UpperLeft,
..WindowOptions::default()
},
) {
Ok(win) => win,
Err(err) => {
println!("Unable to create window {}", err);
return;
}
};
)
.expect("Unable to create window");
// Limit to max ~60 fps update rate
window.limit_update_rate(Some(std::time::Duration::from_micros(16600)));
let mut buffer: Vec<u32> = Vec::with_capacity(WIDTH * HEIGHT);
let mut size = (0, 0);
while window.is_open() && !window.is_key_down(Key::Escape) {
let new_size = window.get_size();
let new_size = (window.get_size().0, window.get_size().1);
if new_size != size {
size = new_size;
buffer.resize(size.0 * size.1 / 2 / 2, 0);
buffer.resize(size.0 * size.1, 0);
}
for i in buffer.iter_mut() {
@ -60,7 +58,8 @@ fn main() {
}
});
// We unwrap here as we want this code to exit if it fails
window.update_with_buffer_size(&buffer, new_size.0, new_size.0).unwrap();
window
.update_with_buffer(&buffer, new_size.0, new_size.1)
.unwrap();
}
}

View file

@ -130,6 +130,6 @@ fn main() {
}
// We unwrap here as we want this code to exit if it fails
window.update_with_buffer_size(&buffer, WIDTH, HEIGHT).unwrap();
window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap();
}
}

BIN
resources/planet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

BIN
resources/uv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

View file

@ -2,17 +2,20 @@ use error::Error;
use Result;
pub fn check_buffer_size(
window_width: usize,
window_height: usize,
scale: usize,
buffer_width: usize,
buffer_height: usize,
buffer_stride: usize,
buffer: &[u32],
) -> Result<()> {
let width = usize::max(buffer_width, buffer_stride);
let buffer_size = buffer.len() * 4; // len is the number of entries so * 4 as we want bytes
let required_buffer_size = (window_width / scale) * (window_height / scale) * 4; // * 4 for 32-bit buffer
let required_buffer_size = width * buffer_height * 4; // * 4 for 32-bit buffer
if buffer_size < required_buffer_size {
let err = format!("Update failed because input buffer is too small. Required size for {} x {} window ({}x scale) is {} bytes but the size of the input buffer has the size {} bytes",
window_width, window_height, scale, required_buffer_size, buffer_size);
let err = format!(
"Update failed because input buffer is too small. Required size for {} ({} stride) x {} buffer is {}
bytes but the size of the input buffer has the size {} bytes",
buffer_width, buffer_stride, buffer_height, required_buffer_size, buffer_size);
Err(Error::UpdateFailed(err))
} else {
Ok(())

View file

@ -98,6 +98,7 @@ mod buffer_helper;
mod key_handler;
mod mouse_handler;
mod os;
mod rate;
mod window_flags;
//mod menu;
//pub use menu::Menu as Menu;
@ -133,6 +134,34 @@ impl fmt::Debug for Window {
}
}
pub fn clamp<T: PartialOrd>(low: T, value: T, high: T) -> T {
if value < low {
low
} else if value > high {
high
} else {
value
}
}
///
/// On some OS (X11 for example) it's possible a window can resize even if no resize has been set.
/// This causes some issues depending on how the content of an input buffer should be displayed then it's possible
/// to set this scaling mode to get a better behavior.
///
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ScaleMode {
/// Stretch the buffer in the whole window meaning if your buffer is 256x256 and window is 1024x1024 it will be scaled up 4 times
Stretch,
/// Keep the correct aspect ratio to be displayed while scaling up fully in the other axis. Fill area will be filed with Window::set_bg_color (default 0, 0, 0)
AspectRatioStretch,
/// Places the buffer in the middle of the window without any scaling. Fills the borders with color set Window::set_bg_color (default 0,0,0)
/// If the window is smaller than the buffer the center of the buffer will be displayed
Center,
/// Same as Center but places the buffer in the upper left corner of the window.
UpperLeft,
}
///
/// WindowOptions is creation settings for the window. By default the settings are defined for
/// displayng a 32-bit buffer (no scaling of window is possible)
@ -145,8 +174,10 @@ pub struct WindowOptions {
pub title: bool,
/// If it should be possible to resize the window (default: false)
pub resize: bool,
/// Scale of the window that used in conjunction with update_with_buffer_size (default: X1)
/// Scale of the window that used in conjunction with update_with_buffer (default: X1)
pub scale: Scale,
/// Adjust how the scaling of the buffer used with update_with_buffer should be done.
pub scale_mode: ScaleMode,
}
impl Window {
@ -172,17 +203,12 @@ impl Window {
///
/// ```no_run
/// # use minifb::*;
/// let mut window = match Window::new("Test", 640, 400,
/// WindowOptions {
/// resize: true,
/// ..WindowOptions::default()
/// }) {
/// Ok(win) => win,
/// Err(err) => {
/// println!("Unable to create window {}", err);
/// return;
/// }
///};
/// let mut window = Window::new("Test", 640, 400,
/// WindowOptions {
/// resize: true,
/// ..WindowOptions::default()
/// })
/// .expect("Unable to open Window");
/// ```
pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result<Window> {
imp::Window::new(name, width, height, opts).map(Window)
@ -219,38 +245,6 @@ impl Window {
self.0.get_window_handle()
}
///
/// Updates the window with a 32-bit pixel buffer. The encoding for each pixel is `0RGB`:
/// The upper 8-bits are ignored, the next 8-bits are for the red channel, the next 8-bits
/// afterwards for the green channel, and the lower 8-bits for the blue channel.
///
/// Notice that the buffer needs to be at least the size of the created window.
///
/// # Examples
///
/// ```no_run
/// # use minifb::*;
/// fn from_u8_rgb(r: u8, g: u8, b: u8) -> u32 {
/// let (r, g, b) = (r as u32, g as u32, b as u32);
/// (r << 16) | (g << 8) | b
/// }
/// let azure_blue = from_u8_rgb(0, 127, 255);
///
/// let mut buffer: Vec<u32> = vec![azure_blue; 640 * 400];
///
/// let mut window = Window::new("Test", 640, 400, WindowOptions::default()).unwrap();
///
/// window.update_with_buffer(&buffer).unwrap();
/// ```
#[inline]
#[deprecated(
since = "0.14.0",
note = "Please use the update_with_buffer_size instead. This function will be replaced with args given in update_with_buffer_size in the future."
)]
pub fn update_with_buffer(&mut self, buffer: &[u32]) -> Result<()> {
self.0.update_with_buffer(buffer)
}
///
/// Updates the window with a 32-bit pixel buffer. The encoding for each pixel is `0RGB`:
/// The upper 8-bits are ignored, the next 8-bits are for the red channel, the next 8-bits
@ -277,12 +271,18 @@ impl Window {
///
/// let mut window = Window::new("Test", window_width, window_height, WindowOptions::default()).unwrap();
///
/// window.update_with_buffer_size(&buffer, buffer_width, buffer_height).unwrap();
/// window.update_with_buffer(&buffer, buffer_width, buffer_height).unwrap();
/// ```
#[inline]
pub fn update_with_buffer_size(&mut self, buffer: &[u32], _width: usize, _height: usize) -> Result<()> {
// currently width / height isn't used but will be soon
self.0.update_with_buffer(buffer)
pub fn update_with_buffer(
&mut self,
buffer: &[u32],
width: usize,
height: usize,
) -> Result<()> {
self.0.update_rate();
self.0
.update_with_buffer_stride(buffer, width, height, width)
}
///
@ -300,6 +300,7 @@ impl Window {
/// ```
#[inline]
pub fn update(&mut self) {
self.0.update_rate();
self.0.update()
}
@ -340,6 +341,60 @@ impl Window {
self.0.set_position(x, y)
}
///
/// Sets the background color that is used with update_with_buffer.
/// In some cases there will be a blank area around the buffer depending on the ScaleMode that has been set.
/// This color will be used in the in that area.
/// The function takes 3 parameters in (red, green, blue) and each value is in the range of 0-255 where 255 is the brightest value
///
/// # Examples
///
/// ```no_run
/// # use minifb::*;
/// # let mut window = Window::new("Test", 640, 400, WindowOptions::default()).unwrap();
/// // Set background color to bright red
/// window.set_background_color(255, 0, 0);
/// ```
///
#[inline]
pub fn set_background_color(&mut self, red: usize, green: usize, blue: usize) {
let r = clamp(0, red, 255);
let g = clamp(0, green, 255);
let b = clamp(0, blue, 255);
self.0
.set_background_color(((r << 16) | (g << 8) | b) as u32);
}
///
/// Limits the update rate of polling for new events in order to reduce CPU usage.
/// The problem of having a tight loop that does something like this
///
/// ```no_run
/// # use minifb::*;
/// # let mut window = Window::new("Test", 640, 400, WindowOptions::default()).unwrap();
/// loop {
/// window.update();
/// }
/// ```
/// Is that lots of CPU time will be spent calling system functions to check for new events in a tight loop making the CPU time go up.
/// Using `limit_update_rate` minifb will check how much time has passed since the last time and if it's less than the selected time it will sleep for the remainder of it.
/// This means that if more time has spent than the set time (external code taking longer) minifb will not do any waiting at all so there is no loss in CPU performance with this feature.
/// By default it's set to 4 milliseconds. Setting this value to None and no waiting will be done
///
/// # Examples
///
/// ```no_run
/// # use minifb::*;
/// # let mut window = Window::new("Test", 640, 400, WindowOptions::default()).unwrap();
/// // Make sure that at least 4 ms has passed since the last event poll
/// window.limit_update_rate(Some(std::time::Duration::from_millis(4)));
/// ```
///
#[inline]
pub fn limit_update_rate(&mut self, time: Option<std::time::Duration>) {
self.0.set_rate(time)
}
///
/// Returns the current size of the window
///
@ -912,6 +967,7 @@ impl Default for WindowOptions {
title: true,
resize: false,
scale: Scale::X1,
scale_mode: ScaleMode::Stretch,
}
}
}

View file

@ -5,11 +5,12 @@
#include <MetalKit/MetalKit.h>
#include <unistd.h>
extern id<MTLDevice> g_metal_device;
extern id<MTLCommandQueue> g_command_queue;
extern id<MTLLibrary> g_library;
extern id<MTLRenderPipelineState> g_pipeline_state;
id<MTLDevice> g_metal_device;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
NSString* g_shadersSrc = @
@ -22,17 +23,18 @@ NSString* g_shadersSrc = @
"};\n"
"vertex VertexOutput vertFunc(\n"
"unsigned int vID[[vertex_id]])\n"
"unsigned int vID[[vertex_id]],\n"
"constant float4* vertexArray[[buffer(0)]])\n"
"{\n"
"VertexOutput out;\n"
"out.pos.x = (float)(vID / 2) * 4.0 - 1.0;\n"
"out.pos.y = (float)(vID % 2) * 4.0 - 1.0;\n"
"out.pos.x = (vertexArray[vID].x * 2.0) - 1.0;\n"
"out.pos.y = (vertexArray[vID].y * 2.0) - 1.0;\n"
"out.pos.z = 0.0;\n"
"out.pos.w = 1.0;\n"
"out.texcoord.x = (float)(vID / 2) * 2.0;\n"
"out.texcoord.y = 1.0 - (float)(vID % 2) * 2.0;\n"
"out.texcoord.x = vertexArray[vID].w;\n"
"out.texcoord.y = vertexArray[vID].z;\n"
"return out;\n"
"}\n"
@ -159,6 +161,7 @@ void* mfb_open(const char* name, int width, int height, uint32_t flags, int scal
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
create_standard_menu();
cursor_init();
g_metal_device = MTLCreateSystemDefaultDevice();
if (!g_metal_device) {
@ -191,9 +194,9 @@ void* mfb_open(const char* name, int width, int height, uint32_t flags, int scal
if (!window)
return 0;
window->draw_buffer = malloc((width * height * 4) * 8);
window->draw_parameters = calloc(sizeof(DrawParameters), 1);
if (!window->draw_buffer)
if (!window->draw_parameters)
return 0;
// Setup command queue
@ -211,18 +214,40 @@ void* mfb_open(const char* name, int width, int height, uint32_t flags, int scal
textureDescriptor.width = width;
textureDescriptor.height = height;
// Create the texture from the device by using the descriptor
const float scale_t = 1.00f;
const float uv_const = 1.0f;
static const Vertex intial_vertices[] = {
{ -1.0f * scale_t, 1.0f * scale_t, 0.0f, 0.0f },
{ 1.0f * scale_t, 1.0f * scale_t, uv_const, 0.0f },
{ 1.0f * scale_t, -1.0f * scale_t, uv_const, uv_const },
{ -1.0f * scale_t, 1.0f * scale_t, 0.0f, 0.0f },
{ 1.0f * scale_t, -1.0f * scale_t, uv_const, uv_const },
{ -1.0f * scale_t, -1.0f * scale_t, 0.0f, uv_const },
};
viewController->m_width = width;
viewController->m_height = height;
for (int i = 0; i < MaxBuffersInFlight; ++i) {
viewController->m_texture_buffers[i] = [g_metal_device newTextureWithDescriptor:textureDescriptor];
viewController->m_draw_state[i].texture_width = width;
viewController->m_draw_state[i].texture_height = height;
viewController->m_draw_state[i].texture =
[g_metal_device newTextureWithDescriptor:textureDescriptor];
viewController->m_draw_state[i].vertex_buffer =
[g_metal_device newBufferWithBytes:intial_vertices
length:sizeof(intial_vertices)
options:MTLResourceStorageModeShared];
viewController->m_delayed_delete_textures[i].frame_count = -1;
}
// Used for syncing the CPU and GPU
viewController->m_semaphore = dispatch_semaphore_create(MaxBuffersInFlight);
viewController->m_draw_buffer = window->draw_buffer;
viewController->m_width = width;
viewController->m_height = height;
viewController->m_delayed_delete_count = 0;
viewController->m_draw_parameters = window->draw_parameters;
//viewController->m_width = width;
//viewController->m_height = height;
MTKView* view = [[MTKView alloc] initWithFrame:rectangle];
view.device = g_metal_device;
@ -451,20 +476,32 @@ int mfb_update(void* window, void* buffer)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int mfb_update_with_buffer(void* window, void* buffer)
int mfb_update_with_buffer(void* window, void* buffer, uint32_t buf_width, uint32_t buf_height, uint32_t buf_stride)
{
OSXWindow* win = (OSXWindow*)window;
win->draw_parameters->buffer_width = buf_width;
win->draw_parameters->buffer_height = buf_height;
win->draw_parameters->buffer_stride = buf_stride;
win->draw_parameters->scale_mode = 0;
if (win->shared_data) {
SharedData* shared_data = (SharedData*)win->shared_data;
memcpy(win->draw_buffer, buffer, shared_data->width * shared_data->height * 4);
win->draw_parameters->buffer = buffer;
win->draw_parameters->bg_color = win->shared_data->bg_color;
win->draw_parameters->scale_mode = win->shared_data->scale_mode;
} else {
memcpy(win->draw_buffer, buffer, win->width * win->height * 4);
win->draw_parameters->scale_mode = 0;
}
//win->draw_parameters->pos_x = 0;
//win->draw_parameters->pos_y = 0;
//win->draw_parameters->width = buf_width;
//win->draw_parameters->height = buf_height;
int state = generic_update(win);
[[win contentView] setNeedsDisplay:YES];
return state;
}
@ -603,7 +640,7 @@ static CFStringRef create_string_for_key(CGKeyCode keyCode)
if (!layoutData)
return 0;
const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData);
const UCKeyboardLayout* keyboardLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(layoutData);
UInt32 keysDown = 0;
UniChar chars[4];

View file

@ -42,10 +42,10 @@ void build_submenu(NSMenu* menu, MenuDesc* desc);
NSView* childContentView;
@public void (*key_callback)(void* user_data, int key, int state);
@public void (*char_callback)(void* user_data, unsigned int key);
@public int width;
@public int height;
@public float width;
@public float height;
@public int scale;
@public void* draw_buffer;
@public DrawParameters* draw_parameters;
@public void* rust_data;
@public SharedData* shared_data;
@public bool should_close;

View file

@ -181,7 +181,7 @@
if (childContentView)
[childContentView removeFromSuperview];
printf("osxwindow: setContentFrameView %p\n", frameView);
//printf("osxwindow: setContentFrameView %p\n", frameView);
//printf("osxwindow: setting controller %p\n", view_controller);
//frameView->m_view_controller = view_controller;

View file

@ -1,20 +1,43 @@
#import <Cocoa/Cocoa.h>
#import <MetalKit/MetalKit.h>
#include "shared_data.h"
// Number of textures in flight (tripple buffered)
const int MaxBuffersInFlight = 3;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct DrawState {
int texture_width;
int texture_height;
id<MTLBuffer> vertex_buffer;
id<MTLTexture> texture;
} DrawState;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct Vertex {
float x,y;
float u,v;
} Vertex;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct DelayedTextureDelete {
id<MTLTexture> texture;
int frame_count;
} DelayedTextureDelete;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface WindowViewController : NSViewController<MTKViewDelegate>
{
@public id<MTLTexture> m_texture_buffers[MaxBuffersInFlight];
@public id<MTLTexture> m_delayed_delete_textures[MaxBuffersInFlight];
@public DrawState m_draw_state[MaxBuffersInFlight];
@public DelayedTextureDelete m_delayed_delete_textures[MaxBuffersInFlight];
@public int m_current_buffer;
@public void* m_draw_buffer;
@public int m_width;
@public int m_height;
@public int m_delayed_delete_count;
@public DrawParameters* m_draw_parameters;
@public float m_width;
@public float m_height;
// Used for syncing with CPU/GPU
@public dispatch_semaphore_t m_semaphore;
}

View file

@ -7,32 +7,273 @@ id<MTLCommandQueue> g_command_queue;
id<MTLLibrary> g_library;
id<MTLRenderPipelineState> g_pipeline_state;
enum ScaleMode {
ScaleMode_Stretch,
ScaleMode_AspectRatioStretch,
ScaleMode_Center,
ScaleMode_UpperLeft,
};
typedef struct Box {
int x, y, width, height;
} Box;
static void gen_normalized(Vertex* output, const Box* box, float x, float y, float u, float v) {
// data gets normalized in the shader
float pos_x = box->x / x;
float pos_y = box->y / y;
float width = box->width / x;
float height = box->height / y;
output[0].x = pos_x;
output[0].y = pos_y;
output[0].u = u;
output[0].v = 0.0f;
output[1].x = width;
output[1].y = pos_y;
output[1].u = u;
output[1].v = v;
output[2].x = width;
output[2].y = height;
output[2].u = 0.0f;
output[2].v = v;
output[3].x = pos_x;
output[3].y = pos_y;
output[3].u = u;
output[3].v = 0.0f;
output[4].x = width;
output[4].y = height;
output[4].u = 0.0f;
output[4].v = v;
output[5].x = pos_x;
output[5].y = height;
output[5].u = 0.0f;
output[5].v = 0.0f;
}
static void calculate_scaling(
Vertex* output,
int buf_width, int buf_height,
int texture_width, int texture_height,
float window_width, float window_height,
int scale_mode)
{
float x_ratio = (float)window_width;
float y_ratio = (float)window_height;
float v_ratio = (float)buf_width / (float)texture_width;
float u_ratio = (float)buf_height / (float)texture_height;
switch (scale_mode) {
case ScaleMode_Stretch:
{
Box box = { 0, 0, window_width, window_height };
gen_normalized(output, &box, x_ratio, y_ratio, u_ratio, v_ratio);
break;
}
case ScaleMode_AspectRatioStretch:
{
float buffer_aspect = (float)buf_width / (float)buf_height;
float win_aspect = (float)window_width / (float)(window_height);
if (buffer_aspect > win_aspect) {
int new_height = (int)(window_width / buffer_aspect);
int offset = (new_height - window_height) / -2;
Box box = { 0, offset, window_width, offset + new_height };
gen_normalized(output, &box, x_ratio, y_ratio, u_ratio, v_ratio);
} else {
int new_width = (int)(window_height * buffer_aspect);
int offset = (new_width - window_width) / -2;
Box box = { offset, 0, offset + new_width, window_height };
gen_normalized(output, &box, x_ratio, y_ratio, u_ratio, v_ratio);
}
break;
}
case ScaleMode_Center:
{
int pos_x;
int pos_y;
if (buf_height > window_height) {
pos_y = -(buf_height - window_height) / 2;
} else {
pos_y = (window_height - buf_height) / 2;
}
if (buf_width > window_width) {
pos_x = -(buf_width - window_width) / 2;
} else {
pos_x = (window_width - buf_width) / 2;
}
int height = buf_height + pos_y;
int width = buf_width + pos_x;
Box box = { pos_x, pos_y, width, height };
gen_normalized(output, &box, x_ratio, y_ratio, u_ratio, v_ratio);
break;
}
case ScaleMode_UpperLeft:
{
int pos_y;
if (buf_height > window_height) {
pos_y = -(buf_height - window_height);
} else {
pos_y = (window_height - buf_height);
}
Box box = { 0, pos_y, buf_width, buf_height + pos_y };
gen_normalized(output, &box, x_ratio, y_ratio, u_ratio, v_ratio);
break;
}
default:
break;
}
}
@implementation WindowViewController
-(void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
{
(void)view;
(void)size;
// resize
(void)view;
NSSize new_size = [view bounds].size;
m_width = new_size.width;
m_height = new_size.height;
}
uint32_t upper_power_of_two(uint32_t v) {
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
-(void)drawInMTKView:(nonnull MTKView *)view
{
//printf("draw view\n");
const int buffer_width = m_draw_parameters->buffer_width;
const int buffer_height = m_draw_parameters->buffer_height;
// Wait to ensure only MaxBuffersInFlight number of frames are getting proccessed
// by any stage in the Metal pipeline (App, Metal, Drivers, GPU, etc)
dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
if (!m_draw_parameters->buffer) {
return;
}
// Iterate through our Metal buffers, and cycle back to the first when we've written to MaxBuffersInFlight
m_current_buffer = (m_current_buffer + 1) % MaxBuffersInFlight;
for (int i = 0; i < MaxBuffersInFlight; ++i) {
DelayedTextureDelete* del_texture = &m_delayed_delete_textures[i];
int frame_count = del_texture->frame_count - 1;
if (frame_count == 0) {
//printf("freeing texture\n");
[del_texture->texture setPurgeableState:MTLPurgeableStateEmpty];
[del_texture->texture release];
del_texture->frame_count = -1;
} else if (frame_count > 0) {
del_texture->frame_count = frame_count;
}
}
DrawState* draw_state = &m_draw_state[m_current_buffer];
// make sure the current buffer fits in the texture, otherwise allocate a new one
if (buffer_width > draw_state->texture_width ||
buffer_height > draw_state->texture_height) {
//printf("buffer size %d %d\n", buffer_width, buffer_height);
//printf("old texture size %d %d\n", draw_state->texture_width, draw_state->texture_height);
int new_width = upper_power_of_two(buffer_width);
int new_height = upper_power_of_two(buffer_height);
//printf("allocating new texture at size %d %d\n", new_width, new_height);
MTLTextureDescriptor* texture_desc = [[MTLTextureDescriptor alloc] init];
// Indicate that each pixel has a blue, green, red, and alpha channel, where each channel is
// an 8-bit unsigned normalized value (i.e. 0 maps to 0.0 and 255 maps to 1.0)
texture_desc.pixelFormat = MTLPixelFormatBGRA8Unorm;
// Set the pixel dimensions of the texture
texture_desc.width = new_width;
texture_desc.height = new_width;
// Allocate the now texture
id<MTLTexture> texture = [view.device newTextureWithDescriptor:texture_desc];
if (texture == 0) {
printf("Failed to create texture of size %d - %d. Please report this issue. Skipping rendering\n",
new_width, new_height);
return;
}
bool found_del_slot = false;
// Put the old texture up for deleting and fail if there are no free slots around.
for (int i = 0; i < MaxBuffersInFlight; ++i) {
if (m_delayed_delete_textures[i].frame_count == -1) {
m_delayed_delete_textures[i].texture = draw_state->texture;
m_delayed_delete_textures[i].frame_count = MaxBuffersInFlight;
found_del_slot = true;
break;
}
}
if (!found_del_slot) {
printf("Unable to delete texture!\n");
}
draw_state->texture = texture;
draw_state->texture_width = new_width;
draw_state->texture_height = new_width;
}
// Calculate the number of bytes per row of our image.
NSUInteger bytesPerRow = 4 * m_width;
MTLRegion region = { { 0, 0, 0 }, { m_width, m_height, 1 } };
NSUInteger bytesPerRow = 4 * m_draw_parameters->buffer_stride;
MTLRegion region = { { 0, 0, 0 },
{ m_draw_parameters->buffer_width,
m_draw_parameters->buffer_height, 1 } };
//printf("updating texture with %p\n", m_draw_parameters->buffer);
// Copy the bytes from our data object into the texture
[m_texture_buffers[m_current_buffer] replaceRegion:region
mipmapLevel:0 withBytes:m_draw_buffer bytesPerRow:bytesPerRow];
[draw_state->texture replaceRegion:region
mipmapLevel:0 withBytes:m_draw_parameters->buffer bytesPerRow:bytesPerRow];
// Update the vertex buffer
calculate_scaling(
draw_state->vertex_buffer.contents,
m_draw_parameters->buffer_width, m_draw_parameters->buffer_height,
draw_state->texture_width, draw_state->texture_height,
m_width, m_height,
m_draw_parameters->scale_mode);
// Create a new command buffer for each render pass to the current drawable
id<MTLCommandBuffer> commandBuffer = [g_command_queue commandBuffer];
@ -54,7 +295,11 @@ id<MTLRenderPipelineState> g_pipeline_state;
if (renderPassDescriptor != nil)
{
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(1.0, 0.0, 0.0, 1.0);
float red = ((m_draw_parameters->bg_color >> 16) & 0xff) * 1.0f / 255.0f;
float green = ((m_draw_parameters->bg_color >> 8) & 0xff) * 1.0f / 255.0f;
float blue = ((m_draw_parameters->bg_color >> 0) & 0xff) * 1.0f / 255.0f;
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(red, green, blue, 1.0);
// Create a render command encoder so we can render into something
id<MTLRenderCommandEncoder> renderEncoder =
@ -64,12 +309,13 @@ id<MTLRenderPipelineState> g_pipeline_state;
// Set render command encoder state
[renderEncoder setRenderPipelineState:g_pipeline_state];
[renderEncoder setFragmentTexture:m_texture_buffers[m_current_buffer] atIndex:0];
[renderEncoder setFragmentTexture:draw_state->texture atIndex:0];
[renderEncoder setVertexBuffer:draw_state->vertex_buffer offset:0 atIndex:0];
// Draw the vertices of our quads
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:3];
vertexCount:6];
// We're done encoding commands
[renderEncoder endEncoding];
@ -191,35 +437,13 @@ id<MTLRenderPipelineState> g_pipeline_state;
int width = (int)size.width;
int height = (int)size.height;
//m_view_controller->m_width = (int)width;
//m_view_controller->m_height = (int)height;
// if windows is resized we need to create new textures so we do that here and put the old textures in a
// "to delete" queue and set a frame counter of 3 frames before the gets released
if (window->shared_data) {
if ((width != window->shared_data->width) ||
(height != window->shared_data->height)) {
MTLTextureDescriptor* textureDescriptor = [[MTLTextureDescriptor alloc] init];
// Indicate that each pixel has a blue, green, red, and alpha channel, where each channel is
// an 8-bit unsigned normalized value (i.e. 0 maps to 0.0 and 255 maps to 1.0)
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
// Set the pixel dimensions of the texture
textureDescriptor.width = width;
textureDescriptor.height = height;
// Create the texture from the device by using the descriptor
m_view_controller->m_width = width;
m_view_controller->m_height = height;
for (int i = 0; i < MaxBuffersInFlight; ++i) {
m_view_controller->m_delayed_delete_count = 3;
m_view_controller->m_delayed_delete_textures[i] = m_view_controller->m_texture_buffers[i];
m_view_controller->m_texture_buffers[i] = [g_metal_device newTextureWithDescriptor:textureDescriptor];
}
}
window->shared_data->width = width;
window->shared_data->height = height;
}
@ -237,6 +461,7 @@ id<MTLRenderPipelineState> g_pipeline_state;
- (void)windowResized:(NSNotification *)notification
{
(void)notification;
}
@end

View file

@ -1,6 +1,8 @@
#pragma once
typedef struct SharedData {
unsigned int bg_color;
unsigned int scale_mode;
unsigned int width;
unsigned int height;
float mouse_x;
@ -10,3 +12,14 @@ typedef struct SharedData {
unsigned char mouse_state[8];
} SharedData;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct DrawParameters {
void* buffer;
unsigned int bg_color;
int buffer_width;
int buffer_height;
int buffer_stride;
int scale_mode;
} DrawParameters;

208
src/native/unix/scalar.cpp Normal file
View file

@ -0,0 +1,208 @@
#include <stdint.h>
#include <stdio.h>
extern "C" void Image_resize_linear_c(uint32_t* target, const uint32_t* source, int w, int h, int s, int w2, int h2) {
if (w2 <= 0) { w2 = 1; }
if (h2 <= 0) { h2 = 1; }
float x_ratio = ((float)(w)) / w2;
float y_ratio = ((float)(h)) / h2;
int step_x = x_ratio * 1024.0f;
int step_y = y_ratio * 1024.0f;
int fixed_y = 0;
for (int i = 0; i < h2; i++) {
const int y = (fixed_y >> 10) * s;
int fixed_x = 0;
for (int j = 0; j < w2; j++) {
int x = fixed_x >> 10;
int index = (y + x);
*target++ = source[index];
fixed_x += step_x;
}
fixed_y += step_y;
}
}
static void resize_linear_c_stride(uint32_t* target, const uint32_t* source, int w, int h, int s, int w2, int h2, int stride) {
if (w2 <= 0) { w2 = 1; }
if (h2 <= 0) { h2 = 1; }
float x_ratio = ((float)(w)) / w2;
float y_ratio = ((float)(h)) / h2;
int step_x = x_ratio * 1024.0f;
int step_y = y_ratio * 1024.0f;
int fixed_y = 0;
int stride_step = stride - w2;
for (int i = 0; i < h2; i++) {
const int y = (fixed_y >> 10) * s;
int fixed_x = 0;
for (int j = 0; j < w2; j++) {
int x = fixed_x >> 10;
int index = (y + x);
*target++ = source[index];
fixed_x += step_x;
}
target += stride_step;
fixed_y += step_y;
}
}
extern "C" void Image_resize_linear_aspect_fill_c(
uint32_t* target,
const uint32_t* source,
int w, int h, int s,
int window_width, int window_height, uint32_t bg_clear)
{
// TODO: Optimize by only clearing the areas the image blit doesn't fill
for (int i = 0; i < window_width * window_height; ++i) {
target[i] = bg_clear;
}
float buffer_aspect = float(w) / float(h);
float win_aspect = float(window_width) / float(window_height);
if (buffer_aspect > win_aspect) {
int new_height = (int)(window_width / buffer_aspect);
int offset = (new_height - window_height) / -2;
Image_resize_linear_c(
target + (offset * window_width),
source, w, h, s,
window_width, new_height);
} else {
int new_width = (int)(window_height * buffer_aspect);
int offset = (new_width - window_width) / -2;
resize_linear_c_stride(
target + offset, source, w, h, s,
new_width, window_height, window_width);
}
}
extern "C" void Image_center(
uint32_t* target,
const uint32_t* source,
int w, int h, int s,
int window_width, int window_height, uint32_t bg_clear)
{
// TODO: Optimize by only clearing the areas the image blit doesn't fill
for (int i = 0; i < window_width * window_height; ++i) {
target[i] = bg_clear;
}
if (h > window_height) {
int y_offset = (h - window_height) / 2;
int new_height = h - y_offset;
source += y_offset * s;
if (w > window_width) {
int x_offset = (w - window_width) / 2;
source += x_offset;
for (int y = 0; y < window_height; ++y) {
for (int x = 0; x < window_width; ++x) {
*target++ = *source++;
}
source += (s - window_width);
}
} else {
int x_offset = (window_width - w) / 2;
for (int y = 0; y < new_height; ++y) {
target += x_offset;
for (int x = 0; x < w; ++x) {
*target++ = *source++;
}
target += (window_width - (w + x_offset));
source += s - w;
}
}
} else {
int y_offset = (window_height - h) / 2;
target += y_offset * window_width;
if (w > window_width) {
int x_offset = (w - window_width) / 2;
source += x_offset;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < window_width; ++x) {
*target++ = *source++;
}
source += (s - window_width);
}
} else {
int x_offset = (window_width - w) / 2;
target += x_offset;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
*target++ = *source++;
}
target += (window_width - w);
source += s - w;
}
}
}
}
extern "C" void Image_upper_left(
uint32_t* target,
const uint32_t* source,
int w, int h, int s,
int window_width, int window_height, uint32_t bg_clear)
{
// TODO: Optimize by only clearing the areas the image blit doesn't fill
for (int i = 0; i < window_width * window_height; ++i) {
target[i] = bg_clear;
}
if (h > window_height) {
int y_offset = (h - window_height) / 2;
int new_height = h - y_offset;
if (w > window_width) {
for (int y = 0; y < window_height; ++y) {
for (int x = 0; x < window_width; ++x) {
*target++ = *source++;
}
source += (s - window_width);
}
} else {
for (int y = 0; y < new_height; ++y) {
for (int x = 0; x < w; ++x) {
*target++ = *source++;
}
target += (window_width - w);
source += s - w;
}
}
} else {
if (w > window_width) {
for (int y = 0; y < h; ++y) {
for (int x = 0; x < window_width; ++x) {
*target++ = *source++;
}
source += (s - window_width);
}
} else {
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
*target++ = *source++;
}
target += (window_width - w);
source += s - w;
}
}
}
}

View file

@ -5,6 +5,7 @@ extern crate raw_window_handle;
use error::Error;
use key_handler::KeyHandler;
use Result;
use rate::UpdateRate;
use {Key, KeyRepeat, MouseButton, MouseMode, Scale, WindowOptions};
// use MenuItem;
use buffer_helper;
@ -166,7 +167,13 @@ extern "C" {
fn mfb_set_title(window: *mut c_void, title: *const c_char);
fn mfb_close(window: *mut c_void);
fn mfb_update(window: *mut c_void);
fn mfb_update_with_buffer(window: *mut c_void, buffer: *const c_uchar);
fn mfb_update_with_buffer(
window: *mut c_void,
buffer: *const c_uchar,
buf_width: u32,
buf_height: u32,
buf_stride: u32,
);
fn mfb_set_position(window: *mut c_void, x: i32, y: i32);
fn mfb_set_key_callback(
window: *mut c_void,
@ -200,6 +207,8 @@ extern "C" {
#[derive(Default)]
#[repr(C)]
pub struct SharedData {
pub bg_color: u32,
pub scale_mode: u32,
pub width: u32,
pub height: u32,
pub mouse_x: f32,
@ -214,6 +223,7 @@ pub struct Window {
scale_factor: usize,
pub shared_data: SharedData,
key_handler: KeyHandler,
update_rate: UpdateRate,
pub has_set_data: bool,
menus: Vec<MenuHandle>,
}
@ -248,7 +258,7 @@ unsafe extern "C" fn char_callback(window: *mut c_void, code_point: u32) {
unsafe impl raw_window_handle::HasRawWindowHandle for Window {
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
let handle = raw_window_handle::macos::MacOSHandle {
ns_window: self.window_handle,
ns_window: self.window_handle as *mut _,
ns_view: std::ptr::null_mut(),
..raw_window_handle::macos::MacOSHandle::empty()
};
@ -284,11 +294,14 @@ impl Window {
window_handle: handle,
scale_factor: scale_factor,
shared_data: SharedData {
bg_color: 0,
scale_mode: opts.scale_mode as u32,
width: width as u32 * scale_factor as u32,
height: height as u32 * scale_factor as u32,
..SharedData::default()
},
key_handler: KeyHandler::new(),
update_rate: UpdateRate::new(),
has_set_data: false,
menus: Vec::new(),
})
@ -303,6 +316,16 @@ impl Window {
}
}
#[inline]
pub fn set_rate(&mut self, rate: Option<std::time::Duration>) {
self.update_rate.set_rate(rate);
}
#[inline]
pub fn update_rate(&mut self) {
self.update_rate.update();
}
#[inline]
pub fn get_window_handle(&self) -> *mut raw::c_void {
self.window_handle as *mut raw::c_void
@ -313,21 +336,30 @@ impl Window {
mfb_set_mouse_data(self.window_handle, &mut self.shared_data);
}
pub fn update_with_buffer(&mut self, buffer: &[u32]) -> Result<()> {
#[inline]
pub fn set_background_color(&mut self, color: u32) {
self.shared_data.bg_color = color;
}
pub fn update_with_buffer_stride(
&mut self,
buffer: &[u32],
buf_width: usize,
buf_height: usize,
buf_stride: usize,
) -> Result<()> {
self.key_handler.update();
let check_res = buffer_helper::check_buffer_size(
self.shared_data.width as usize,
self.shared_data.height as usize,
self.scale_factor as usize,
buffer,
);
if check_res.is_err() {
return check_res;
}
buffer_helper::check_buffer_size(buf_width, buf_height, buf_stride, buffer)?;
unsafe {
mfb_update_with_buffer(self.window_handle, buffer.as_ptr() as *const u8);
mfb_update_with_buffer(
self.window_handle,
buffer.as_ptr() as *const u8,
buf_width as u32,
buf_height as u32,
buf_stride as u32,
);
Self::set_mouse_data(self);
mfb_set_key_callback(
self.window_handle,
@ -459,7 +491,7 @@ impl Window {
}
#[inline]
pub fn set_input_callback(&mut self, callback: Box<InputCallback>) {
pub fn set_input_callback(&mut self, callback: Box<dyn InputCallback>) {
self.key_handler.set_input_callback(callback)
}

View file

@ -9,14 +9,15 @@
#![allow(non_upper_case_globals)]
extern crate cast;
extern crate x11_dl;
extern crate raw_window_handle;
extern crate x11_dl;
use self::x11_dl::keysym::*;
use self::x11_dl::xcursor;
use self::x11_dl::xlib;
use key_handler::KeyHandler;
use {InputCallback, Key, KeyRepeat, MouseButton, MouseMode, Scale, WindowOptions};
use rate::UpdateRate;
use {InputCallback, Key, KeyRepeat, MouseButton, MouseMode, Scale, ScaleMode, WindowOptions};
use error::Error;
use Result;
@ -35,6 +36,54 @@ use mouse_handler;
const Button6: c_uint = xlib::Button5 + 1;
const Button7: c_uint = xlib::Button5 + 2;
// We have these in C so we can always have optimize on (-O3) so they
// run fast in debug build as well. These functions should be seen as
// "system" functions that just doesn't exist in X11
extern "C" {
fn Image_upper_left(
target: *mut u32,
source: *const u32,
source_w: u32,
source_h: u32,
source_stride: u32,
dest_width: u32,
dest_height: u32,
bg_color: u32,
);
fn Image_center(
target: *mut u32,
source: *const u32,
source_w: u32,
source_h: u32,
source_stride: u32,
dest_width: u32,
dest_height: u32,
bg_color: u32,
);
fn Image_resize_linear_aspect_fill_c(
target: *mut u32,
source: *const u32,
source_w: u32,
source_h: u32,
source_stride: u32,
dest_width: u32,
dest_height: u32,
bg_color: u32,
);
fn Image_resize_linear_c(
target: *mut u32,
source: *const u32,
source_w: u32,
source_h: u32,
source_stride: u32,
dest_width: u32,
dest_height: u32,
);
}
mod key_mapping;
struct DisplayInfo {
@ -228,6 +277,8 @@ pub struct Window {
height: u32, //
scale: i32,
bg_color: u32,
scale_mode: ScaleMode,
mouse_x: f32,
mouse_y: f32,
@ -239,6 +290,7 @@ pub struct Window {
should_close: bool, // received delete window message from X server
key_handler: KeyHandler,
update_rate: UpdateRate,
menu_counter: MenuHandle,
menus: Vec<UnixMenu>,
}
@ -372,10 +424,13 @@ impl Window {
mouse_y: 0.0,
scroll_x: 0.0,
scroll_y: 0.0,
bg_color: 0,
scale_mode: opts.scale_mode,
buttons: [0, 0, 0],
prev_cursor: CursorStyle::Arrow,
should_close: false,
key_handler: KeyHandler::new(),
update_rate: UpdateRate::new(),
menu_counter: MenuHandle(0),
menus: Vec::new(),
})
@ -431,15 +486,16 @@ impl Window {
};
}
pub fn update_with_buffer(&mut self, buffer: &[u32]) -> Result<()> {
buffer_helper::check_buffer_size(
self.width as usize,
self.height as usize,
self.scale as usize,
buffer,
)?;
pub fn update_with_buffer_stride(
&mut self,
buffer: &[u32],
buf_width: usize,
buf_height: usize,
buf_stride: usize,
) -> Result<()> {
buffer_helper::check_buffer_size(buf_width, buf_height, buf_stride, buffer)?;
unsafe { self.raw_blit_buffer(buffer) };
unsafe { self.raw_blit_buffer(buffer, buf_width, buf_height, buf_stride) };
self.update();
@ -464,6 +520,11 @@ impl Window {
self.handle as *mut raw::c_void
}
#[inline]
pub fn set_background_color(&mut self, bg_color: u32) {
self.bg_color = bg_color;
}
#[inline]
pub fn set_position(&mut self, x: isize, y: isize) {
unsafe {
@ -523,6 +584,16 @@ impl Window {
}
}
#[inline]
pub fn set_rate(&mut self, rate: Option<std::time::Duration>) {
self.update_rate.set_rate(rate);
}
#[inline]
pub fn update_rate(&mut self) {
self.update_rate.update();
}
#[inline]
pub fn get_keys(&self) -> Option<Vec<Key>> {
self.key_handler.get_keys()
@ -639,37 +710,63 @@ impl Window {
////////////////////////////////////
unsafe fn raw_blit_buffer(&mut self, buffer: &[u32]) {
match self.scale {
1 => {
// input buffer may be larger than necessary, so get a slice of correct size
let src_buf = &buffer[0..self.draw_buffer.len()];
self.draw_buffer[..].copy_from_slice(src_buf);
unsafe fn raw_blit_buffer(
&mut self,
buffer: &[u32],
buf_width: usize,
buf_height: usize,
buf_stride: usize,
) {
match self.scale_mode {
ScaleMode::Stretch => {
Image_resize_linear_c(
self.draw_buffer.as_mut_ptr(),
buffer.as_ptr(),
buf_width as u32,
buf_height as u32,
buf_stride as u32,
self.width as u32,
self.height as u32,
);
}
2 => {
self.scale_2x(buffer);
ScaleMode::AspectRatioStretch => {
Image_resize_linear_aspect_fill_c(
self.draw_buffer.as_mut_ptr(),
buffer.as_ptr(),
buf_width as u32,
buf_height as u32,
buf_stride as u32,
self.width as u32,
self.height as u32,
self.bg_color,
);
}
4 => {
self.scale_4x(buffer);
ScaleMode::Center => {
Image_center(
self.draw_buffer.as_mut_ptr(),
buffer.as_ptr(),
buf_width as u32,
buf_height as u32,
buf_stride as u32,
self.width as u32,
self.height as u32,
self.bg_color,
);
}
8 => {
self.scale_8x(buffer);
}
16 => {
self.scale_16x(buffer);
}
32 => {
self.scale_32x(buffer);
}
_ => {
panic!("bad scale for raw_blit_buffer()");
ScaleMode::UpperLeft => {
Image_upper_left(
self.draw_buffer.as_mut_ptr(),
buffer.as_ptr(),
buf_width as u32,
buf_height as u32,
buf_stride as u32,
self.width as u32,
self.height as u32,
self.bg_color,
);
}
}
@ -1002,35 +1099,6 @@ impl Window {
}
}
// macro_rules inside impl {} blocks currently not supported
// https://github.com/rust-lang/rust/issues/37205
macro_rules! gen_scale_x(
($($fn_name:ident, $x:expr),+$(,)?) => (
impl Window {
$(unsafe fn $fn_name(&mut self, buffer : &[u32]) {
let w = self.width as usize;
let bw = (self.width as usize) / $x;
let bh = (self.height as usize) / $x;
for y in 0..bh {
let src = &buffer[y * bw..y * bw + bw];
for dy in 0..$x {
let dest = &mut self.draw_buffer[(y * $x + dy) * w..(y * $x + dy) * w + w];
for x in 0..bw {
dest[x * $x .. x * $x + $x].copy_from_slice(&[src[x]; $x]);
}
}
}
})+
}
)
);
gen_scale_x!(scale_2x, 2, scale_4x, 4, scale_8x, 8, scale_16x, 16, scale_32x, 32,);
impl Drop for Window {
fn drop(&mut self) {
unsafe {

View file

@ -8,9 +8,10 @@ const INVALID_ACCEL: usize = 0xffffffff;
use error::Error;
use key_handler::KeyHandler;
use rate::UpdateRate;
use Result;
use {CursorStyle, MenuHandle, MenuItem, MenuItemHandle};
use {InputCallback, Key, KeyRepeat, MouseButton, MouseMode, Scale, WindowOptions};
use {InputCallback, Key, KeyRepeat, MouseButton, MouseMode, Scale, ScaleMode, WindowOptions};
use {MENU_KEY_ALT, MENU_KEY_CTRL, MENU_KEY_SHIFT, MENU_KEY_WIN};
use buffer_helper;
@ -174,8 +175,7 @@ unsafe extern "system" fn wnd_proc(
wparam: minwindef::WPARAM,
lparam: minwindef::LPARAM,
) -> minwindef::LRESULT {
// This make sure we actually don't do anything before the user data has been setup for the
// window
// This make sure we actually don't do anything before the user data has been setup for the window
let user_data = get_window_long(window);
@ -186,21 +186,18 @@ unsafe extern "system" fn wnd_proc(
let mut wnd: &mut Window = mem::transmute(user_data);
match msg {
/*
winuser::WM_MOUSEMOVE => {
let mouse_coords = lparam as u32;
let scale = user_data.scale as f32;
user_data.mouse.local_x = (((mouse_coords >> 16) & 0xffff) as f32) / scale;
user_data.mouse.local_y = ((mouse_coords & 0xffff) as f32) / scale;
return 0;
}
*/
winuser::WM_MOUSEWHEEL => {
let scroll = ((((wparam as u32) >> 16) & 0xffff) as i16) as f32 * 0.1;
wnd.mouse.scroll = scroll;
}
winuser::WM_SETCURSOR => {
if winapi::shared::minwindef::LOWORD(lparam as u32) == winuser::HTCLIENT as u16 {
winuser::SetCursor(wnd.cursors[wnd.cursor as usize]);
return 1;
}
}
winuser::WM_KEYDOWN => {
update_key_state(wnd, (lparam as u32) >> 16, true);
return 0;
@ -241,6 +238,13 @@ unsafe extern "system" fn wnd_proc(
}
}
/*
winuser::WM_ERASEBKGND => {
let dc = wnd.dc.unwrap();
wingdi::SelectObject(dc, wnd.clear_brush as *mut std::ffi::c_void);
wingdi::Rectangle(dc, 0, 0, wnd.width, wnd.height);
}
*/
winuser::WM_SIZE => {
let width = (lparam as u32) & 0xffff;
let height = ((lparam as u32) >> 16) & 0xffff;
@ -250,7 +254,7 @@ unsafe extern "system" fn wnd_proc(
winuser::WM_PAINT => {
// if we have nothing to draw here we return the default function
if wnd.buffer.len() == 0 {
if wnd.draw_params.buffer == std::ptr::null() {
return winuser::DefWindowProcW(window, msg, wparam, lparam);
}
@ -260,23 +264,127 @@ unsafe extern "system" fn wnd_proc(
bitmap_info.bmi_header.biPlanes = 1;
bitmap_info.bmi_header.biBitCount = 32;
bitmap_info.bmi_header.biCompression = wingdi::BI_BITFIELDS;
bitmap_info.bmi_header.biWidth = wnd.width;
bitmap_info.bmi_header.biHeight = -wnd.height;
bitmap_info.bmi_header.biWidth = wnd.draw_params.buffer_width as i32;
bitmap_info.bmi_header.biHeight = -(wnd.draw_params.buffer_height as i32);
bitmap_info.bmi_colors[0].rgbRed = 0xff;
bitmap_info.bmi_colors[1].rgbGreen = 0xff;
bitmap_info.bmi_colors[2].rgbBlue = 0xff;
let buffer_width = wnd.draw_params.buffer_width as i32;
let buffer_height = wnd.draw_params.buffer_height as i32;
let window_width = wnd.width as i32;
let window_height = wnd.height as i32;
let mut new_height = window_height;
let mut new_width = window_width;
let mut x_offset = 0;
let mut y_offset = 0;
let dc = wnd.dc.unwrap();
wingdi::SelectObject(dc, wnd.clear_brush as *mut std::ffi::c_void);
match wnd.draw_params.scale_mode {
ScaleMode::AspectRatioStretch => {
let buffer_aspect = buffer_width as f32 / buffer_height as f32;
let win_aspect = window_width as f32 / window_height as f32;
if buffer_aspect > win_aspect {
new_height = (window_width as f32 / buffer_aspect) as i32;
y_offset = (new_height - window_height) / -2;
if y_offset != 0 {
wingdi::Rectangle(dc, 0, 0, window_width, y_offset);
wingdi::Rectangle(
dc,
0,
y_offset + new_height,
window_width,
window_height,
);
}
} else {
new_width = (window_height as f32 * buffer_aspect) as i32;
x_offset = (new_width - window_width) / -2;
if x_offset != 0 {
wingdi::Rectangle(dc, 0, 0, x_offset, window_height);
wingdi::Rectangle(
dc,
x_offset + new_width,
0,
window_width,
window_height,
);
}
}
}
ScaleMode::Center => {
new_width = buffer_width;
new_height = buffer_height;
if buffer_height > window_height {
y_offset = -(buffer_height - window_height) / 2;
} else {
y_offset = (window_height - buffer_height) / 2;
}
if buffer_width > window_width {
x_offset = -(buffer_width - window_width) / 2;
} else {
x_offset = (window_width - buffer_width) / 2;
}
if y_offset > 0 {
wingdi::Rectangle(dc, 0, 0, window_width, y_offset);
wingdi::Rectangle(
dc,
0,
y_offset + new_height,
window_width,
window_height,
);
}
if x_offset > 0 {
wingdi::Rectangle(dc, 0, y_offset, x_offset, buffer_height + y_offset);
wingdi::Rectangle(
dc,
x_offset + buffer_width,
y_offset,
window_width,
buffer_height + y_offset,
);
}
}
ScaleMode::UpperLeft => {
new_width = buffer_width;
new_height = buffer_height;
if buffer_width < window_width {
wingdi::Rectangle(dc, buffer_width, 0, window_width, window_height);
}
if buffer_height < window_height {
wingdi::Rectangle(dc, 0, buffer_height, window_width, window_height);
}
}
_ => (),
}
wingdi::StretchDIBits(
wnd.dc.unwrap(),
dc,
x_offset,
y_offset,
new_width,
new_height,
0,
0,
wnd.width * wnd.scale_factor,
wnd.height * wnd.scale_factor,
0,
0,
wnd.width,
wnd.height,
mem::transmute(wnd.buffer.as_ptr()),
wnd.draw_params.buffer_width as i32,
wnd.draw_params.buffer_height as i32,
mem::transmute(wnd.draw_params.buffer),
mem::transmute(&bitmap_info),
wingdi::DIB_RGB_COLORS,
wingdi::SRCCOPY,
@ -293,10 +401,6 @@ unsafe extern "system" fn wnd_proc(
return winuser::DefWindowProcW(window, msg, wparam, lparam);
}
pub enum MinifbError {
UnableToCreateWindow,
}
fn to_wstring(str: &str) -> Vec<u16> {
let v: Vec<u16> = OsStr::new(str)
.encode_wide()
@ -313,29 +417,41 @@ struct MouseData {
pub scroll: f32,
}
/*
struct MenuStore {
name: String,
menu: HMENU,
accel_items: Vec<ACCEL>,
struct DrawParameters {
buffer: *const u32,
buffer_width: u32,
buffer_height: u32,
scale_mode: ScaleMode,
}
impl Default for DrawParameters {
fn default() -> Self {
DrawParameters {
buffer: std::ptr::null(),
buffer_width: 0,
buffer_height: 0,
scale_mode: ScaleMode::Stretch,
}
}
}
*/
pub struct Window {
mouse: MouseData,
dc: Option<windef::HDC>,
window: Option<windef::HWND>,
buffer: Vec<u32>,
clear_brush: windef::HBRUSH,
is_open: bool,
scale_factor: i32,
width: i32,
height: i32,
menus: Vec<Menu>,
key_handler: KeyHandler,
update_rate: UpdateRate,
accel_table: windef::HACCEL,
accel_key: usize,
prev_cursor: CursorStyle,
cursor: CursorStyle,
cursors: [windef::HCURSOR; 8],
draw_params: DrawParameters,
}
unsafe impl raw_window_handle::HasRawWindowHandle for Window {
@ -366,7 +482,7 @@ impl Window {
cbWndExtra: 0,
hInstance: libloaderapi::GetModuleHandleA(ptr::null()),
hIcon: ptr::null_mut(),
hCursor: ptr::null_mut(),
hCursor: winuser::LoadCursorW(ptr::null_mut(), winuser::IDC_ARROW),
hbrBackground: ptr::null_mut(),
lpszMenuName: ptr::null(),
lpszClassName: class_name.as_ptr(),
@ -463,16 +579,17 @@ impl Window {
mouse: MouseData::default(),
dc: Some(winuser::GetDC(handle.unwrap())),
window: Some(handle.unwrap()),
buffer: Vec::new(),
key_handler: KeyHandler::new(),
update_rate: UpdateRate::new(),
is_open: true,
scale_factor: scale_factor,
width: width as i32,
height: height as i32,
width: (width * scale_factor as usize) as i32,
height: (height * scale_factor as usize) as i32,
menus: Vec::new(),
accel_table: ptr::null_mut(),
accel_key: INVALID_ACCEL,
prev_cursor: CursorStyle::Arrow,
cursor: CursorStyle::Arrow,
clear_brush: wingdi::CreateSolidBrush(0),
cursors: [
winuser::LoadCursorW(ptr::null_mut(), winuser::IDC_ARROW),
winuser::LoadCursorW(ptr::null_mut(), winuser::IDC_IBEAM),
@ -483,11 +600,12 @@ impl Window {
winuser::LoadCursorW(ptr::null_mut(), winuser::IDC_SIZENS),
winuser::LoadCursorW(ptr::null_mut(), winuser::IDC_SIZEALL),
],
draw_params: DrawParameters {
scale_mode: opts.scale_mode,
..DrawParameters::default()
},
};
// Set arrow as default cursor
winuser::SetCursor(window.cursors[0]);
Ok(window)
}
}
@ -561,15 +679,17 @@ impl Window {
#[inline]
pub fn set_cursor_style(&mut self, cursor: CursorStyle) {
unsafe {
if self.prev_cursor == cursor {
return;
}
self.cursor = cursor;
}
winuser::SetCursor(self.cursors[cursor as usize]);
#[inline]
pub fn set_rate(&mut self, rate: Option<std::time::Duration>) {
self.update_rate.set_rate(rate);
}
self.prev_cursor = cursor;
}
#[inline]
pub fn update_rate(&mut self) {
self.update_rate.update();
}
#[inline]
@ -588,7 +708,7 @@ impl Window {
}
#[inline]
pub fn set_input_callback(&mut self, callback: Box<InputCallback>) {
pub fn set_input_callback(&mut self, callback: Box<dyn InputCallback>) {
self.key_handler.set_input_callback(callback)
}
@ -619,7 +739,8 @@ impl Window {
fn generic_update(&mut self, window: windef::HWND) {
unsafe {
let mut point: windef::POINT = mem::uninitialized();
let mut point: windef::POINT = mem::zeroed();
winuser::GetCursorPos(&mut point);
winuser::ScreenToClient(window, &mut point);
@ -635,7 +756,7 @@ impl Window {
fn message_loop(&self, window: windef::HWND) {
unsafe {
let mut msg = mem::uninitialized();
let mut msg = mem::zeroed();
while winuser::PeekMessageW(&mut msg, window, 0, 0, winuser::PM_REMOVE) != 0 {
// Make this code a bit nicer
@ -657,22 +778,35 @@ impl Window {
}
}
pub fn update_with_buffer(&mut self, buffer: &[u32]) -> Result<()> {
pub fn set_background_color(&mut self, color: u32) {
unsafe {
wingdi::DeleteObject(self.clear_brush as *mut std::ffi::c_void);
let r = (color >> 16) & 0xff;
let g = (color >> 8) & 0xff;
let b = (color >> 0) & 0xff;
self.clear_brush = wingdi::CreateSolidBrush((b << 16) | (g << 8) | r);
}
}
pub fn update_with_buffer_stride(
&mut self,
buffer: &[u32],
buf_width: usize,
buf_height: usize,
buf_stride: usize,
) -> Result<()> {
let window = self.window.unwrap();
Self::generic_update(self, window);
let check_res = buffer_helper::check_buffer_size(
self.width as usize,
self.height as usize,
self.scale_factor as usize,
buffer,
);
if check_res.is_err() {
return check_res;
}
buffer_helper::check_buffer_size(buf_width, buf_height, buf_stride, buffer)?;
self.draw_params.buffer = buffer.as_ptr();
self.draw_params.buffer_width = buf_width as u32;
self.draw_params.buffer_height = buf_height as u32;
// stride currently not supported
//self.draw_params.buffer_stride = buf_stride as u32;
self.buffer = buffer.to_vec();
unsafe {
winuser::InvalidateRect(window, ptr::null_mut(), minwindef::TRUE);
}
@ -732,7 +866,7 @@ impl Window {
// the current client size is preserved and still show all pixels
//
unsafe fn adjust_window_size_for_menu(handle: windef::HWND) {
let mut rect: windef::RECT = mem::uninitialized();
let mut rect: windef::RECT = mem::zeroed();
let menu_height = winuser::GetSystemMetrics(winuser::SM_CYMENU);

41
src/rate.rs Normal file
View file

@ -0,0 +1,41 @@
extern crate time;
use std::time::Duration;
pub struct UpdateRate {
target_rate: Option<Duration>,
prev_time: f64,
}
impl UpdateRate {
pub fn new() -> UpdateRate {
UpdateRate {
// Default limit to 4 ms
target_rate: Some(Duration::from_millis(4)),
prev_time: 0.0,
}
}
#[inline]
pub fn set_rate(&mut self, rate: Option<Duration>) {
self.target_rate = rate
}
pub fn update(&mut self) {
if let Some(rate) = self.target_rate {
let target_rate = rate.as_secs_f64();
let current_time = time::precise_time_s();
let delta = current_time - self.prev_time;
if delta < target_rate {
let sleep_time = target_rate - delta;
if sleep_time > 0.0 {
//println!("sleeping {} ms", sleep_time * 1000.0);
std::thread::sleep(Duration::from_secs_f64(sleep_time));
}
}
self.prev_time = time::precise_time_s();
}
}
}