1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-27 15:37:29 +03:00
wezterm/window/examples/wgpu.rs
Wez Furlong 79165617b1 window: add WindowState concept
WindowState is a bitfield that can represent maximized, full screen
and hidden window states.

WindowState is passed along with resize events, improving on the
prior basic is_full_screen boolean by representing the other states.

Notably, WindowState::MAXIMIZED is used to represent a state where
the window's size is constrained by some window environment function;
it could be due to the window being maximized in either or both the
vertical or horizontal directions, or by the window being in a tiled
state on any edge.

When the window is MAXIMIZED, wezterm will behave as though
`adjust_window_size_when_changing_font_size = false` because it knows
that it cannot adjust the window size in that state.

This potentially helps with #695, depending on whether the window
manager propagates this state information to wezterm.  Gnome/mutter
does a good job at this with both X11 and Wayland, but I couldn't get
sway to report these states and I don't know of any other tiling wm
that I can easily install and use on fedora, so there's a question
mark around that.
2021-08-06 18:56:37 -07:00

316 lines
10 KiB
Rust

use ::window::*;
use anyhow::Context;
use promise::spawn::spawn;
#[cfg(target_os = "macos")]
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
use std::cell::RefCell;
use std::rc::Rc;
use wezterm_font::FontConfiguration;
pub struct GpuContext {
pub swap_chain: wgpu::SwapChain,
pub sc_desc: wgpu::SwapChainDescriptor,
pub adapter: wgpu::Adapter,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub surface: wgpu::Surface,
}
struct MyWindow {
allow_close: bool,
cursor_pos: Point,
dims: Dimensions,
gpu: Option<GpuContext>,
render_pipeline: Option<wgpu::RenderPipeline>,
}
impl Drop for MyWindow {
fn drop(&mut self) {
eprintln!("MyWindow dropped");
}
}
impl MyWindow {
async fn enable_wgpu(&mut self, win: &Window) -> anyhow::Result<()> {
let instance = wgpu::Instance::new(if cfg!(target_os = "macos") {
wgpu::BackendBit::METAL
} else if cfg!(windows) {
// Vulkan supports window opacity, but DX12 doesn't
wgpu::BackendBit::PRIMARY
// wgpu::BackendBit::DX12
} else {
wgpu::BackendBit::PRIMARY
});
let surface = unsafe { instance.create_surface(win) };
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
compatible_surface: Some(&surface),
})
.await
.ok_or_else(|| anyhow::anyhow!("No suitable GPU adapters found on the system!"))?;
let adapter_info = adapter.get_info();
log::info!("wgpu adapter: {:?}", adapter_info);
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
},
None,
)
.await
.context("Unable to find a suitable GPU adapter!")?;
log::info!("wgpu device features: {:?}", device.features());
log::info!("wgpu device limits: {:?}", device.limits());
let format = adapter
.get_swap_chain_preferred_format(&surface)
.ok_or_else(|| anyhow::anyhow!("adapter is not compatible with surface"))?;
let sc_desc = wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
format,
width: self.dims.pixel_width as u32,
height: self.dims.pixel_height as u32,
present_mode: wgpu::PresentMode::Mailbox,
};
let swap_chain = device.create_swap_chain(&surface, &sc_desc);
let gpu = GpuContext {
swap_chain,
sc_desc,
adapter,
device,
queue,
surface,
};
let shader = gpu
.device
.create_shader_module(&wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
"shader.wgsl"
))),
flags: wgpu::ShaderFlags::all(),
});
let pipeline_layout = gpu
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[],
push_constant_ranges: &[],
});
self.render_pipeline
.replace(
gpu.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[format.into()],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
}),
);
self.gpu.replace(gpu);
self.paint(win)?;
Ok(())
}
fn paint(&mut self, win: &Window) -> anyhow::Result<()> {
self.fix_transparency(win);
if let Some(gpu) = self.gpu.as_mut() {
let frame = match gpu.swap_chain.get_current_frame() {
Ok(frame) => frame,
Err(err) => {
log::info!("get_current_frame: {:#}", err);
gpu.swap_chain = gpu.device.create_swap_chain(&gpu.surface, &gpu.sc_desc);
gpu.swap_chain
.get_current_frame()
.expect("Failed to acquire next swap chain texture!")
}
};
let mut encoder = gpu
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[wgpu::RenderPassColorAttachment {
view: &frame.output.view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.,
g: 0.,
b: 0.5,
a: 0.5,
}),
store: true,
},
}],
depth_stencil_attachment: None,
});
rpass.set_pipeline(self.render_pipeline.as_ref().unwrap());
rpass.draw(0..3, 0..1);
}
gpu.queue.submit(Some(encoder.finish()));
}
Ok(())
}
#[cfg(not(target_os = "macos"))]
fn fix_transparency(&mut self, _: &Window) {}
#[cfg(target_os = "macos")]
fn fix_transparency(&mut self, win: &Window) {
if let RawWindowHandle::MacOS(h) = win.raw_window_handle() {
use cocoa::base::{id, NO};
use objc::*;
unsafe {
// Allow transparency, as the default for Metal is opaque
let layer: id = msg_send![h.ns_view as id, layer];
let () = msg_send![layer, setOpaque: NO];
}
}
}
fn resize(&mut self, dims: Dimensions) {
if self.dims == dims {
// May just be a move event
return;
}
self.dims = dims;
if let Some(gpu) = self.gpu.as_mut() {
gpu.sc_desc.width = dims.pixel_width as u32;
gpu.sc_desc.height = dims.pixel_height as u32;
gpu.swap_chain = gpu.device.create_swap_chain(&gpu.surface, &gpu.sc_desc);
}
}
fn dispatch(&mut self, event: WindowEvent, win: &Window) {
match event {
WindowEvent::CloseRequested => {
eprintln!("can I close?");
if self.allow_close {
win.close();
} else {
self.allow_close = true;
}
}
WindowEvent::Destroyed => {
eprintln!("destroy was called!");
Connection::get().unwrap().terminate_message_loop();
}
WindowEvent::Resized {
dimensions,
window_state: _,
} => {
self.resize(dimensions);
#[cfg(target_os = "macos")]
self.paint(win).unwrap();
}
WindowEvent::MouseEvent(event) => {
self.cursor_pos = event.coords;
win.invalidate();
win.set_cursor(Some(MouseCursor::Arrow));
if event.kind == MouseEventKind::Press(MousePress::Left) {
eprintln!("{:?}", event);
}
}
WindowEvent::KeyEvent(key) => {
eprintln!("{:?}", key);
win.set_cursor(Some(MouseCursor::Text));
win.default_key_processing(key);
}
WindowEvent::NeedRepaint => {
self.paint(win).unwrap();
}
WindowEvent::AppearanceChanged(_)
| WindowEvent::Notification(_)
| WindowEvent::FocusChanged(_) => {}
}
}
}
async fn spawn_window() -> anyhow::Result<()> {
let fontconfig = Rc::new(FontConfiguration::new(
None,
::window::default_dpi() as usize,
)?);
let state = Rc::new(RefCell::new(MyWindow {
allow_close: false,
cursor_pos: Point::new(100, 200),
dims: Dimensions {
pixel_width: 800,
pixel_height: 600,
dpi: 0,
},
gpu: None,
render_pipeline: None,
}));
let cb_state = Rc::clone(&state);
let win = Window::new_window(
"myclass",
"the title",
800,
600,
None,
fontconfig,
move |event, window| {
let mut state = cb_state.borrow_mut();
state.dispatch(event, window)
},
)
.await?;
eprintln!("before show");
win.show();
{
let mut state = state.borrow_mut();
state.enable_wgpu(&win).await?;
}
eprintln!("window is visible, do loop");
Ok(())
}
fn main() -> anyhow::Result<()> {
let _ = pretty_env_logger::formatted_builder()
.filter(Some("wgpu_core"), log::LevelFilter::Error)
.filter(Some("gfx_backend_metal"), log::LevelFilter::Error)
.filter_level(log::LevelFilter::Info)
.try_init();
let conn = Connection::init()?;
spawn(async {
eprintln!("running this async block");
dbg!(spawn_window().await).ok();
eprintln!("end of async block");
})
.detach();
conn.run_message_loop()
}