Add glyphon renderer
This commit is contained in:
parent
11384d0c33
commit
65b613e6e4
7 changed files with 1511 additions and 919 deletions
1431
Cargo.lock
generated
1431
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
30
Cargo.toml
30
Cargo.toml
|
|
@ -7,10 +7,36 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
alacritty_terminal = "0.19"
|
alacritty_terminal = "0.19"
|
||||||
softbuffer = "0.4"
|
bytemuck = "1.14"
|
||||||
|
env_logger = "0.10"
|
||||||
|
log = "0.4"
|
||||||
tiny-skia = "0.11"
|
tiny-skia = "0.11"
|
||||||
winit = "0.29"
|
|
||||||
|
|
||||||
[dependencies.cosmic-text]
|
[dependencies.cosmic-text]
|
||||||
git = "https://github.com/pop-os/cosmic-text.git"
|
git = "https://github.com/pop-os/cosmic-text.git"
|
||||||
branch = "refactor"
|
branch = "refactor"
|
||||||
|
|
||||||
|
[dependencies.glyphon]
|
||||||
|
git = "https://github.com/jackpot51/glyphon.git"
|
||||||
|
branch = "refactor"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.pollster]
|
||||||
|
version = "0.3"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.softbuffer]
|
||||||
|
git = "https://github.com/pop-os/softbuffer.git"
|
||||||
|
branch = "cosmic"
|
||||||
|
|
||||||
|
[dependencies.wgpu]
|
||||||
|
version = "0.18"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.winit]
|
||||||
|
git = "https://github.com/pop-os/winit.git"
|
||||||
|
branch = "master"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
wgpu = ["dep:glyphon", "dep:pollster", "dep:wgpu"]
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,6 @@
|
||||||
# cosmic-term
|
# cosmic-term
|
||||||
WIP COSMIC terminal emulator, built using [alacritty\_terminal](https://docs.rs/alacritty_terminal) that is provided by the [alacritty](https://github.com/alacritty/alacritty) project. `cosmic-term` provides bidirectional rendering and ligatures with a custom renderer based on [cosmic-text](https://github.com/pop-os/cosmic-text).
|
WIP COSMIC terminal emulator, built using [alacritty\_terminal](https://docs.rs/alacritty_terminal) that is provided by the [alacritty](https://github.com/alacritty/alacritty) project. `cosmic-term` provides bidirectional rendering and ligatures with a custom renderer based on [cosmic-text](https://github.com/pop-os/cosmic-text).
|
||||||
|
|
||||||
|
The `wgpu` feature, enabled by default, supports GPU rendering using `glyphon`
|
||||||
|
and `wgpu`. If `wgpu` is not enabled or fails to initialize, then rendering falls
|
||||||
|
back to using `softbuffer` and `tiny-skia`.
|
||||||
|
|
|
||||||
619
src/main.rs
619
src/main.rs
|
|
@ -14,17 +14,21 @@ use alacritty_terminal::{
|
||||||
};
|
};
|
||||||
use cosmic_text::{
|
use cosmic_text::{
|
||||||
Attrs, AttrsList, Buffer, BufferLine, Family, FontSystem, Metrics, Shaping, Style, SwashCache,
|
Attrs, AttrsList, Buffer, BufferLine, Family, FontSystem, Metrics, Shaping, Style, SwashCache,
|
||||||
SwashContent, Weight, Wrap,
|
Weight, Wrap,
|
||||||
};
|
};
|
||||||
use std::{mem, num::NonZeroU32, rc::Rc, slice, sync::Arc, time::Instant};
|
use std::{mem, rc::Rc, sync::Arc, time::Instant};
|
||||||
use tiny_skia::{ColorU8, Paint, PixmapMut, PixmapPaint, PixmapRef, Rect, Transform};
|
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{ElementState, Event as WinitEvent, KeyEvent, Modifiers, WindowEvent},
|
event::{
|
||||||
|
ElementState, Event as WinitEvent, KeyboardInput, ModifiersState, VirtualKeyCode,
|
||||||
|
WindowEvent,
|
||||||
|
},
|
||||||
event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy},
|
event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy},
|
||||||
keyboard::{Key, ModifiersState, NamedKey},
|
|
||||||
window::WindowBuilder,
|
window::WindowBuilder,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use self::renderer::Renderer;
|
||||||
|
mod renderer;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct Size {
|
struct Size {
|
||||||
width: f32,
|
width: f32,
|
||||||
|
|
@ -132,6 +136,8 @@ fn colors() -> Colors {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
let mut font_system = FontSystem::new();
|
let mut font_system = FontSystem::new();
|
||||||
let mut swash_cache = SwashCache::new();
|
let mut swash_cache = SwashCache::new();
|
||||||
let metrics = Metrics::new(14.0, 20.0);
|
let metrics = Metrics::new(14.0, 20.0);
|
||||||
|
|
@ -147,9 +153,7 @@ fn main() {
|
||||||
};
|
};
|
||||||
println!("{}, {}", cell_width, cell_height);
|
println!("{}, {}", cell_width, cell_height);
|
||||||
|
|
||||||
let event_loop = EventLoopBuilder::<TermEvent>::with_user_event()
|
let event_loop = EventLoopBuilder::<TermEvent>::with_user_event().build();
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
let event_loop_proxy = event_loop.create_proxy();
|
let event_loop_proxy = event_loop.create_proxy();
|
||||||
let window = Rc::new(WindowBuilder::new().build(&event_loop).unwrap());
|
let window = Rc::new(WindowBuilder::new().build(&event_loop).unwrap());
|
||||||
let (width, height) = {
|
let (width, height) = {
|
||||||
|
|
@ -180,380 +184,253 @@ fn main() {
|
||||||
let mut notifier = Notifier(pty_event_loop.channel());
|
let mut notifier = Notifier(pty_event_loop.channel());
|
||||||
let pty_join_handle = pty_event_loop.spawn();
|
let pty_join_handle = pty_event_loop.spawn();
|
||||||
|
|
||||||
let context = softbuffer::Context::new(window.clone()).unwrap();
|
let mut renderer = Renderer::new(window.clone()).unwrap();
|
||||||
let mut surface = softbuffer::Surface::new(&context, window.clone()).unwrap();
|
let mut modifiers = ModifiersState::default();
|
||||||
let mut modifiers = Modifiers::default();
|
event_loop.run(move |event, _elwt, control_flow| {
|
||||||
event_loop
|
*control_flow = ControlFlow::Wait;
|
||||||
.run(move |event, elwt| {
|
|
||||||
elwt.set_control_flow(ControlFlow::Wait);
|
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
WinitEvent::WindowEvent {
|
WinitEvent::RedrawRequested(window_id) if window_id == window.id() => {
|
||||||
window_id,
|
let instant = Instant::now();
|
||||||
event: WindowEvent::RedrawRequested,
|
|
||||||
} if window_id == window.id() => {
|
|
||||||
let instant = Instant::now();
|
|
||||||
|
|
||||||
let (width, height) = {
|
renderer.render(&mut buffer, &mut font_system, &mut swash_cache);
|
||||||
let size = window.inner_size();
|
|
||||||
(size.width, size.height)
|
|
||||||
};
|
|
||||||
surface
|
|
||||||
.resize(
|
|
||||||
NonZeroU32::new(width).unwrap(),
|
|
||||||
NonZeroU32::new(height).unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut surface_buffer = surface.buffer_mut().unwrap();
|
println!("draw {:?}", instant.elapsed());
|
||||||
let surface_buffer_u8 = unsafe {
|
}
|
||||||
slice::from_raw_parts_mut(
|
WinitEvent::WindowEvent {
|
||||||
surface_buffer.as_mut_ptr() as *mut u8,
|
event: WindowEvent::ReceivedCharacter(c),
|
||||||
surface_buffer.len() * 4,
|
window_id,
|
||||||
)
|
} if window_id == window.id() => {
|
||||||
};
|
match (modifiers.logo(), modifiers.ctrl(), modifiers.alt()) {
|
||||||
let mut pixmap =
|
(true, _, _) => {} // Ignore super
|
||||||
PixmapMut::from_bytes(surface_buffer_u8, width, height).unwrap();
|
(false, true, _) => {
|
||||||
pixmap.fill(tiny_skia::Color::from_rgba8(0, 0, 0, 0xFF));
|
// Control keys
|
||||||
|
if c >= 'a' && c <= 'z' {
|
||||||
// Set scroll to view scroll
|
notifier.notify(vec![(c as u8) - b'a' + 1]);
|
||||||
//TODO buffer.set_scroll(*scroll);
|
|
||||||
// Set size, will relayout and shape until scroll if changed
|
|
||||||
buffer.set_size(&mut font_system, width as f32, height as f32);
|
|
||||||
// Shape until scroll, ensures scroll is clamped
|
|
||||||
buffer.shape_until_scroll(&mut font_system, true);
|
|
||||||
// Update scroll after buffer clamps it
|
|
||||||
//TODO *scroll = buffer.scroll();
|
|
||||||
|
|
||||||
let mut paint = Paint::default();
|
|
||||||
paint.anti_alias = false;
|
|
||||||
let pixmap_paint = PixmapPaint::default();
|
|
||||||
let transform = Transform::identity();
|
|
||||||
for run in buffer.layout_runs() {
|
|
||||||
for glyph in run.glyphs.iter() {
|
|
||||||
let physical_glyph = glyph.physical((0., 0.), 1.0);
|
|
||||||
|
|
||||||
let glyph_color = match glyph.color_opt {
|
|
||||||
Some(some) => some,
|
|
||||||
None => cosmic_text::Color::rgb(0xFF, 0xFF, 0xFF),
|
|
||||||
};
|
|
||||||
|
|
||||||
let background_color = cosmic_text::Color(glyph.metadata as u32);
|
|
||||||
if background_color.0 != 0xFF000000 {
|
|
||||||
//TODO: Have to swap RGB for BGR
|
|
||||||
paint.set_color_rgba8(
|
|
||||||
background_color.b(),
|
|
||||||
background_color.g(),
|
|
||||||
background_color.r(),
|
|
||||||
background_color.a(),
|
|
||||||
);
|
|
||||||
pixmap.fill_rect(
|
|
||||||
Rect::from_xywh(
|
|
||||||
glyph.x,
|
|
||||||
run.line_top,
|
|
||||||
glyph.w,
|
|
||||||
metrics.line_height,
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
&paint,
|
|
||||||
transform,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
match swash_cache.get_image(&mut font_system, physical_glyph.cache_key)
|
|
||||||
{
|
|
||||||
Some(image) if !image.data.is_empty() => {
|
|
||||||
let mut data = Vec::with_capacity(
|
|
||||||
(image.placement.width * image.placement.height) as usize,
|
|
||||||
);
|
|
||||||
match image.content {
|
|
||||||
SwashContent::Mask => {
|
|
||||||
let mut i = 0;
|
|
||||||
while i < image.data.len() {
|
|
||||||
//TODO: Have to swap RGB for BGR
|
|
||||||
data.push(
|
|
||||||
ColorU8::from_rgba(
|
|
||||||
glyph_color.b(),
|
|
||||||
glyph_color.g(),
|
|
||||||
glyph_color.r(),
|
|
||||||
image.data[i],
|
|
||||||
)
|
|
||||||
.premultiply(),
|
|
||||||
);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SwashContent::Color => {
|
|
||||||
let mut i = 0;
|
|
||||||
while i < image.data.len() {
|
|
||||||
//TODO: Have to swap RGB for BGR
|
|
||||||
data.push(
|
|
||||||
ColorU8::from_rgba(
|
|
||||||
image.data[i + 2],
|
|
||||||
image.data[i + 1],
|
|
||||||
image.data[i],
|
|
||||||
image.data[i + 3],
|
|
||||||
)
|
|
||||||
.premultiply(),
|
|
||||||
);
|
|
||||||
i += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SwashContent::SubpixelMask => {
|
|
||||||
todo!("TODO: SubpixelMask");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let glyph_pixmap = PixmapRef::from_bytes(
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
data.as_ptr() as *const u8,
|
|
||||||
data.len() * 4,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
image.placement.width,
|
|
||||||
image.placement.height,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
pixmap.draw_pixmap(
|
|
||||||
physical_glyph.x + image.placement.left,
|
|
||||||
run.line_y as i32 + physical_glyph.y - image.placement.top,
|
|
||||||
glyph_pixmap,
|
|
||||||
&pixmap_paint,
|
|
||||||
transform,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buffer.set_redraw(false);
|
(false, false, true) => {
|
||||||
|
// Alt keys
|
||||||
surface_buffer.present().unwrap();
|
let mut buf = [0x1B, 0, 0, 0, 0];
|
||||||
|
let str = c.encode_utf8(&mut buf[1..]);
|
||||||
println!("draw {:?}", instant.elapsed());
|
notifier.notify(str.as_bytes().to_vec());
|
||||||
}
|
}
|
||||||
WinitEvent::WindowEvent {
|
(false, false, false) => {
|
||||||
event:
|
let mut buf = [0, 0, 0, 0];
|
||||||
WindowEvent::KeyboardInput {
|
let str = c.encode_utf8(&mut buf);
|
||||||
event:
|
notifier.notify(str.as_bytes().to_vec());
|
||||||
KeyEvent {
|
|
||||||
logical_key,
|
|
||||||
text: text_opt,
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
},
|
|
||||||
window_id,
|
|
||||||
} if window_id == window.id() => {
|
|
||||||
println!("{:?} {:?}", modifiers, text_opt);
|
|
||||||
match (
|
|
||||||
modifiers.state().contains(ModifiersState::SUPER),
|
|
||||||
modifiers.state().contains(ModifiersState::CONTROL),
|
|
||||||
modifiers.state().contains(ModifiersState::ALT),
|
|
||||||
) {
|
|
||||||
(true, _, _) => {} // Ignore super
|
|
||||||
(false, true, _) => match text_opt {
|
|
||||||
Some(text) if text.len() == 1 => {
|
|
||||||
// Control keys
|
|
||||||
let c = text.chars().next().unwrap_or_default();
|
|
||||||
if c >= 'a' && c <= 'z' {
|
|
||||||
notifier.notify(vec![(c as u8) - b'a' + 1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
(false, false, true) => match text_opt {
|
|
||||||
Some(text) => {
|
|
||||||
// Alt keys
|
|
||||||
//TODO: handle alt keys with no text
|
|
||||||
let mut bytes = text.as_bytes().to_vec();
|
|
||||||
bytes.insert(0, b'\x1B');
|
|
||||||
notifier.notify(bytes);
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
},
|
|
||||||
(false, false, false) => match text_opt {
|
|
||||||
Some(text) => {
|
|
||||||
notifier.notify(text.as_bytes().to_vec());
|
|
||||||
}
|
|
||||||
None => match logical_key {
|
|
||||||
Key::Named(NamedKey::ArrowUp) => {
|
|
||||||
notifier.notify(b"\x1B[A".as_slice());
|
|
||||||
}
|
|
||||||
Key::Named(NamedKey::ArrowDown) => {
|
|
||||||
notifier.notify(b"\x1B[B".as_slice());
|
|
||||||
}
|
|
||||||
Key::Named(NamedKey::ArrowRight) => {
|
|
||||||
notifier.notify(b"\x1B[C".as_slice());
|
|
||||||
}
|
|
||||||
Key::Named(NamedKey::ArrowLeft) => {
|
|
||||||
notifier.notify(b"\x1B[D".as_slice());
|
|
||||||
}
|
|
||||||
Key::Named(NamedKey::End) => {
|
|
||||||
notifier.notify(b"\x1B[F".as_slice());
|
|
||||||
}
|
|
||||||
Key::Named(NamedKey::Home) => {
|
|
||||||
notifier.notify(b"\x1B[H".as_slice());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WinitEvent::WindowEvent {
|
}
|
||||||
event: WindowEvent::ModifiersChanged(new_modifiers),
|
WinitEvent::WindowEvent {
|
||||||
window_id,
|
event:
|
||||||
} if window_id == window.id() => {
|
WindowEvent::KeyboardInput {
|
||||||
modifiers = new_modifiers;
|
input:
|
||||||
}
|
KeyboardInput {
|
||||||
WinitEvent::WindowEvent {
|
state: ElementState::Pressed,
|
||||||
event: WindowEvent::Resized(physical_size),
|
virtual_keycode: Some(virtual_keycode),
|
||||||
window_id,
|
..
|
||||||
} if window_id == window.id() => {
|
},
|
||||||
dimensions.width = physical_size.width as f32;
|
..
|
||||||
dimensions.height = physical_size.height as f32;
|
},
|
||||||
notifier.on_resize(dimensions.into());
|
window_id,
|
||||||
term.lock().resize(dimensions);
|
} if window_id == window.id() => {
|
||||||
window.request_redraw();
|
match (modifiers.logo(), modifiers.ctrl(), modifiers.alt()) {
|
||||||
}
|
(true, _, _) => {} // Ignore super
|
||||||
WinitEvent::WindowEvent {
|
(false, true, _) => {
|
||||||
event: WindowEvent::CloseRequested,
|
// Control keys will use ReceivedCharacter instead
|
||||||
window_id,
|
}
|
||||||
} if window_id == window.id() => {
|
(false, false, true) => {
|
||||||
term.lock().exit();
|
//TODO: support Alt keys without character
|
||||||
}
|
}
|
||||||
WinitEvent::UserEvent(user_event) => {
|
(false, false, false) => match virtual_keycode {
|
||||||
println!("{:?}", user_event);
|
VirtualKeyCode::Up => {
|
||||||
match user_event {
|
notifier.notify(b"\x1B[A".as_slice());
|
||||||
TermEvent::Exit => elwt.exit(),
|
}
|
||||||
TermEvent::PtyWrite(text) => notifier.notify(text.into_bytes()),
|
VirtualKeyCode::Down => {
|
||||||
TermEvent::Title(title) => {
|
notifier.notify(b"\x1B[B".as_slice());
|
||||||
window.set_title(&title);
|
}
|
||||||
|
VirtualKeyCode::Right => {
|
||||||
|
notifier.notify(b"\x1B[C".as_slice());
|
||||||
|
}
|
||||||
|
VirtualKeyCode::Left => {
|
||||||
|
notifier.notify(b"\x1B[D".as_slice());
|
||||||
|
}
|
||||||
|
VirtualKeyCode::End => {
|
||||||
|
notifier.notify(b"\x1B[F".as_slice());
|
||||||
|
}
|
||||||
|
VirtualKeyCode::Home => {
|
||||||
|
notifier.notify(b"\x1B[H".as_slice());
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
},
|
||||||
|
|
||||||
let instant = Instant::now();
|
|
||||||
|
|
||||||
//TODO: is redraw needed after all events?
|
|
||||||
//TODO: use LineDamageBounds
|
|
||||||
{
|
|
||||||
let mut last_point = Point::new(Line(0), Column(0));
|
|
||||||
let mut text = String::new();
|
|
||||||
let mut attrs_list = AttrsList::new(default_attrs);
|
|
||||||
let term_guard = term.lock();
|
|
||||||
let grid = term_guard.grid();
|
|
||||||
for indexed in grid.display_iter() {
|
|
||||||
if indexed.point.line != last_point.line {
|
|
||||||
let line_i = last_point.line.0 as usize;
|
|
||||||
while line_i >= buffer.lines.len() {
|
|
||||||
buffer.lines.push(BufferLine::new(
|
|
||||||
"",
|
|
||||||
AttrsList::new(default_attrs),
|
|
||||||
Shaping::Advanced,
|
|
||||||
));
|
|
||||||
buffer.set_redraw(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if buffer.lines[line_i].set_text(text.clone(), attrs_list.clone()) {
|
|
||||||
buffer.set_redraw(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
text.clear();
|
|
||||||
attrs_list.clear_spans();
|
|
||||||
}
|
|
||||||
//TODO: use indexed.point.column?
|
|
||||||
|
|
||||||
//TODO: skip leading spacer?
|
|
||||||
if indexed.cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
|
|
||||||
// Skip wide spacers (cells after wide characters)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let start = text.len();
|
|
||||||
text.push(indexed.cell.c);
|
|
||||||
if let Some(zerowidth) = indexed.cell.zerowidth() {
|
|
||||||
for &c in zerowidth {
|
|
||||||
text.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let end = text.len();
|
|
||||||
|
|
||||||
let convert_color = |color| {
|
|
||||||
let rgb = match color {
|
|
||||||
Color::Named(named_color) => match colors[named_color] {
|
|
||||||
Some(rgb) => rgb,
|
|
||||||
None => {
|
|
||||||
eprintln!("missing named color {:?}", named_color);
|
|
||||||
Rgb::default()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Color::Spec(rgb) => rgb,
|
|
||||||
Color::Indexed(index) => match colors[index as usize] {
|
|
||||||
Some(rgb) => rgb,
|
|
||||||
None => {
|
|
||||||
eprintln!("missing indexed color {}", index);
|
|
||||||
Rgb::default()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
cosmic_text::Color::rgb(rgb.r, rgb.g, rgb.b)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut attrs = default_attrs;
|
|
||||||
let mut fg = convert_color(indexed.cell.fg);
|
|
||||||
let mut bg = convert_color(indexed.cell.bg);
|
|
||||||
//TODO: better handling of cursor
|
|
||||||
if indexed.point == grid.cursor.point {
|
|
||||||
mem::swap(&mut fg, &mut bg);
|
|
||||||
}
|
|
||||||
attrs = attrs.color(fg);
|
|
||||||
// Use metadata as background color
|
|
||||||
attrs = attrs.metadata(bg.0 as usize);
|
|
||||||
//TODO: more flags
|
|
||||||
if indexed.cell.flags.contains(Flags::BOLD) {
|
|
||||||
attrs = attrs.weight(Weight::BOLD);
|
|
||||||
}
|
|
||||||
if indexed.cell.flags.contains(Flags::ITALIC) {
|
|
||||||
attrs = attrs.style(Style::Italic);
|
|
||||||
}
|
|
||||||
if attrs != attrs_list.defaults() {
|
|
||||||
attrs_list.add_span(start..end, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
last_point = indexed.point;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: do not repeat!
|
|
||||||
let line_i = last_point.line.0 as usize;
|
|
||||||
while line_i >= buffer.lines.len() {
|
|
||||||
buffer.lines.push(BufferLine::new(
|
|
||||||
"",
|
|
||||||
AttrsList::new(default_attrs),
|
|
||||||
Shaping::Advanced,
|
|
||||||
));
|
|
||||||
buffer.set_redraw(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if buffer.lines[line_i].set_text(text, attrs_list) {
|
|
||||||
buffer.set_redraw(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if buffer.redraw() {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("buffer update {:?}", instant.elapsed());
|
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
})
|
WinitEvent::WindowEvent {
|
||||||
.unwrap();
|
event: WindowEvent::ModifiersChanged(new_modifiers),
|
||||||
|
window_id,
|
||||||
|
} if window_id == window.id() => {
|
||||||
|
modifiers = new_modifiers;
|
||||||
|
}
|
||||||
|
WinitEvent::WindowEvent {
|
||||||
|
event: WindowEvent::Resized(physical_size),
|
||||||
|
window_id,
|
||||||
|
} if window_id == window.id() => {
|
||||||
|
let instant = Instant::now();
|
||||||
|
|
||||||
|
dimensions.width = physical_size.width as f32;
|
||||||
|
dimensions.height = physical_size.height as f32;
|
||||||
|
|
||||||
|
notifier.on_resize(dimensions.into());
|
||||||
|
|
||||||
|
term.lock().resize(dimensions);
|
||||||
|
|
||||||
|
buffer.set_size(
|
||||||
|
&mut font_system,
|
||||||
|
dimensions.width as f32,
|
||||||
|
dimensions.height as f32,
|
||||||
|
);
|
||||||
|
|
||||||
|
renderer.resize(physical_size.width, physical_size.height);
|
||||||
|
|
||||||
|
println!("resize {:?}", instant.elapsed());
|
||||||
|
}
|
||||||
|
WinitEvent::WindowEvent {
|
||||||
|
event: WindowEvent::CloseRequested,
|
||||||
|
window_id,
|
||||||
|
} if window_id == window.id() => {
|
||||||
|
term.lock().exit();
|
||||||
|
}
|
||||||
|
WinitEvent::UserEvent(user_event) => {
|
||||||
|
println!("{:?}", user_event);
|
||||||
|
match user_event {
|
||||||
|
//TODO: other error codes?
|
||||||
|
TermEvent::Exit => *control_flow = ControlFlow::ExitWithCode(0),
|
||||||
|
TermEvent::PtyWrite(text) => notifier.notify(text.into_bytes()),
|
||||||
|
TermEvent::Title(title) => {
|
||||||
|
window.set_title(&title);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let instant = Instant::now();
|
||||||
|
|
||||||
|
//TODO: is redraw needed after all events?
|
||||||
|
//TODO: use LineDamageBounds
|
||||||
|
{
|
||||||
|
let mut last_point = Point::new(Line(0), Column(0));
|
||||||
|
let mut text = String::new();
|
||||||
|
let mut attrs_list = AttrsList::new(default_attrs);
|
||||||
|
let term_guard = term.lock();
|
||||||
|
let grid = term_guard.grid();
|
||||||
|
for indexed in grid.display_iter() {
|
||||||
|
if indexed.point.line != last_point.line {
|
||||||
|
let line_i = last_point.line.0 as usize;
|
||||||
|
while line_i >= buffer.lines.len() {
|
||||||
|
buffer.lines.push(BufferLine::new(
|
||||||
|
"",
|
||||||
|
AttrsList::new(default_attrs),
|
||||||
|
Shaping::Advanced,
|
||||||
|
));
|
||||||
|
buffer.set_redraw(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if buffer.lines[line_i].set_text(text.clone(), attrs_list.clone()) {
|
||||||
|
buffer.set_redraw(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
text.clear();
|
||||||
|
attrs_list.clear_spans();
|
||||||
|
}
|
||||||
|
//TODO: use indexed.point.column?
|
||||||
|
|
||||||
|
//TODO: skip leading spacer?
|
||||||
|
if indexed.cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
|
||||||
|
// Skip wide spacers (cells after wide characters)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = text.len();
|
||||||
|
text.push(indexed.cell.c);
|
||||||
|
if let Some(zerowidth) = indexed.cell.zerowidth() {
|
||||||
|
for &c in zerowidth {
|
||||||
|
text.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let end = text.len();
|
||||||
|
|
||||||
|
let convert_color = |color| {
|
||||||
|
let rgb = match color {
|
||||||
|
Color::Named(named_color) => match colors[named_color] {
|
||||||
|
Some(rgb) => rgb,
|
||||||
|
None => {
|
||||||
|
eprintln!("missing named color {:?}", named_color);
|
||||||
|
Rgb::default()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Color::Spec(rgb) => rgb,
|
||||||
|
Color::Indexed(index) => match colors[index as usize] {
|
||||||
|
Some(rgb) => rgb,
|
||||||
|
None => {
|
||||||
|
eprintln!("missing indexed color {}", index);
|
||||||
|
Rgb::default()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
cosmic_text::Color::rgb(rgb.r, rgb.g, rgb.b)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut attrs = default_attrs;
|
||||||
|
let mut fg = convert_color(indexed.cell.fg);
|
||||||
|
let mut bg = convert_color(indexed.cell.bg);
|
||||||
|
//TODO: better handling of cursor
|
||||||
|
if indexed.point == grid.cursor.point {
|
||||||
|
mem::swap(&mut fg, &mut bg);
|
||||||
|
}
|
||||||
|
attrs = attrs.color(fg);
|
||||||
|
// Use metadata as background color
|
||||||
|
attrs = attrs.metadata(bg.0 as usize);
|
||||||
|
//TODO: more flags
|
||||||
|
if indexed.cell.flags.contains(Flags::BOLD) {
|
||||||
|
attrs = attrs.weight(Weight::BOLD);
|
||||||
|
}
|
||||||
|
if indexed.cell.flags.contains(Flags::ITALIC) {
|
||||||
|
attrs = attrs.style(Style::Italic);
|
||||||
|
}
|
||||||
|
if attrs != attrs_list.defaults() {
|
||||||
|
attrs_list.add_span(start..end, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
last_point = indexed.point;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: do not repeat!
|
||||||
|
let line_i = last_point.line.0 as usize;
|
||||||
|
while line_i >= buffer.lines.len() {
|
||||||
|
buffer.lines.push(BufferLine::new(
|
||||||
|
"",
|
||||||
|
AttrsList::new(default_attrs),
|
||||||
|
Shaping::Advanced,
|
||||||
|
));
|
||||||
|
buffer.set_redraw(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if buffer.lines[line_i].set_text(text, attrs_list) {
|
||||||
|
buffer.set_redraw(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.shape_until_scroll(&mut font_system, true);
|
||||||
|
|
||||||
|
if buffer.redraw() {
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("buffer update {:?}", instant.elapsed());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//TODO: hangs after event loop exit pty_join_handle.join().unwrap();
|
//TODO: hangs after event loop exit pty_join_handle.join().unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
60
src/renderer/mod.rs
Normal file
60
src/renderer/mod.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
use cosmic_text::{Buffer, FontSystem, SwashCache};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use winit::window::Window;
|
||||||
|
|
||||||
|
pub use self::software::SoftwareRenderer;
|
||||||
|
pub mod software;
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
pub use self::wgpu::WgpuRenderer;
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
pub mod wgpu;
|
||||||
|
|
||||||
|
pub enum Renderer {
|
||||||
|
Software(SoftwareRenderer),
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
Wgpu(WgpuRenderer),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderer {
|
||||||
|
pub fn new(window: Rc<Window>) -> Result<Self, String> {
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
match WgpuRenderer::new(window.clone()) {
|
||||||
|
Ok(renderer) => return Ok(Self::Wgpu(renderer)),
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("failed to use hardware rendering: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SoftwareRenderer::new(window).map(Renderer::Software)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(
|
||||||
|
&mut self,
|
||||||
|
buffer: &mut Buffer,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
swash_cache: &mut SwashCache,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
Self::Software(renderer) => {
|
||||||
|
renderer.render(buffer, font_system, swash_cache);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
Self::Wgpu(renderer) => {
|
||||||
|
renderer.render(buffer, font_system, swash_cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(&mut self, width: u32, height: u32) {
|
||||||
|
match self {
|
||||||
|
Self::Software(renderer) => {
|
||||||
|
renderer.resize(width, height);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
Self::Wgpu(renderer) => {
|
||||||
|
renderer.resize(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
141
src/renderer/software.rs
Normal file
141
src/renderer/software.rs
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
use cosmic_text::{Buffer, FontSystem, SwashCache, SwashContent};
|
||||||
|
use std::{rc::Rc, slice};
|
||||||
|
use tiny_skia::{ColorU8, Paint, Pixmap, PixmapPaint, PixmapRef, Rect, Transform};
|
||||||
|
use winit::window::Window;
|
||||||
|
|
||||||
|
pub struct SoftwareRenderer {
|
||||||
|
pub window: Rc<Window>,
|
||||||
|
pub context: softbuffer::GraphicsContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SoftwareRenderer {
|
||||||
|
pub fn new(window: Rc<Window>) -> Result<Self, String> {
|
||||||
|
let context = unsafe { softbuffer::GraphicsContext::new(&*window, &*window) }
|
||||||
|
.map_err(|err| format!("failed to create context: {}", err))?;
|
||||||
|
Ok(Self { window, context })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(
|
||||||
|
&mut self,
|
||||||
|
buffer: &mut Buffer,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
swash_cache: &mut SwashCache,
|
||||||
|
) {
|
||||||
|
let (width, height) = {
|
||||||
|
let size = self.window.inner_size();
|
||||||
|
(size.width, size.height)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut pixmap = Pixmap::new(width, height).unwrap();
|
||||||
|
//TODO: configurable background
|
||||||
|
pixmap.fill(tiny_skia::Color::from_rgba8(0, 0, 0, 0xFF));
|
||||||
|
|
||||||
|
let line_height = buffer.metrics().line_height;
|
||||||
|
let mut paint = Paint::default();
|
||||||
|
paint.anti_alias = false;
|
||||||
|
let pixmap_paint = PixmapPaint::default();
|
||||||
|
let transform = Transform::identity();
|
||||||
|
for run in buffer.layout_runs() {
|
||||||
|
for glyph in run.glyphs.iter() {
|
||||||
|
let physical_glyph = glyph.physical((0., 0.), 1.0);
|
||||||
|
|
||||||
|
let glyph_color = match glyph.color_opt {
|
||||||
|
Some(some) => some,
|
||||||
|
None => cosmic_text::Color::rgb(0xFF, 0xFF, 0xFF),
|
||||||
|
};
|
||||||
|
|
||||||
|
let background_color = cosmic_text::Color(glyph.metadata as u32);
|
||||||
|
if background_color.0 != 0xFF000000 {
|
||||||
|
//TODO: Have to swap RGB for BGR
|
||||||
|
paint.set_color_rgba8(
|
||||||
|
background_color.b(),
|
||||||
|
background_color.g(),
|
||||||
|
background_color.r(),
|
||||||
|
background_color.a(),
|
||||||
|
);
|
||||||
|
pixmap.fill_rect(
|
||||||
|
Rect::from_xywh(glyph.x, run.line_top, glyph.w, line_height).unwrap(),
|
||||||
|
&paint,
|
||||||
|
transform,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
match swash_cache.get_image(font_system, physical_glyph.cache_key) {
|
||||||
|
Some(image) if !image.data.is_empty() => {
|
||||||
|
let mut data = Vec::with_capacity(
|
||||||
|
(image.placement.width * image.placement.height) as usize,
|
||||||
|
);
|
||||||
|
match image.content {
|
||||||
|
SwashContent::Mask => {
|
||||||
|
let mut i = 0;
|
||||||
|
while i < image.data.len() {
|
||||||
|
//TODO: Have to swap RGB for BGR
|
||||||
|
data.push(
|
||||||
|
ColorU8::from_rgba(
|
||||||
|
glyph_color.b(),
|
||||||
|
glyph_color.g(),
|
||||||
|
glyph_color.r(),
|
||||||
|
image.data[i],
|
||||||
|
)
|
||||||
|
.premultiply(),
|
||||||
|
);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SwashContent::Color => {
|
||||||
|
let mut i = 0;
|
||||||
|
while i < image.data.len() {
|
||||||
|
//TODO: Have to swap RGB for BGR
|
||||||
|
data.push(
|
||||||
|
ColorU8::from_rgba(
|
||||||
|
image.data[i + 2],
|
||||||
|
image.data[i + 1],
|
||||||
|
image.data[i],
|
||||||
|
image.data[i + 3],
|
||||||
|
)
|
||||||
|
.premultiply(),
|
||||||
|
);
|
||||||
|
i += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SwashContent::SubpixelMask => {
|
||||||
|
todo!("TODO: SubpixelMask");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let glyph_pixmap = PixmapRef::from_bytes(
|
||||||
|
unsafe {
|
||||||
|
slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4)
|
||||||
|
},
|
||||||
|
image.placement.width,
|
||||||
|
image.placement.height,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
pixmap.draw_pixmap(
|
||||||
|
physical_glyph.x + image.placement.left,
|
||||||
|
run.line_y as i32 + physical_glyph.y - image.placement.top,
|
||||||
|
glyph_pixmap,
|
||||||
|
&pixmap_paint,
|
||||||
|
transform,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.set_redraw(false);
|
||||||
|
|
||||||
|
self.context.set_buffer(
|
||||||
|
bytemuck::cast_slice(pixmap.data()),
|
||||||
|
width as u16,
|
||||||
|
height as u16,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(&mut self, width: u32, height: u32) {
|
||||||
|
//TODO: resize image here for better performance
|
||||||
|
self.window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
145
src/renderer/wgpu.rs
Normal file
145
src/renderer/wgpu.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
use glyphon::{
|
||||||
|
Attrs, Buffer, Color, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache, TextArea,
|
||||||
|
TextAtlas, TextBounds, TextRenderer,
|
||||||
|
};
|
||||||
|
use std::{error::Error, rc::Rc};
|
||||||
|
use wgpu::{
|
||||||
|
CommandEncoderDescriptor, CompositeAlphaMode, Device, DeviceDescriptor, Features, Instance,
|
||||||
|
InstanceDescriptor, Limits, LoadOp, MultisampleState, Operations, PresentMode, Queue,
|
||||||
|
RenderPassColorAttachment, RenderPassDescriptor, RequestAdapterOptions, Surface,
|
||||||
|
SurfaceConfiguration, TextureFormat, TextureUsages, TextureViewDescriptor,
|
||||||
|
};
|
||||||
|
use winit::window::Window;
|
||||||
|
|
||||||
|
pub struct WgpuRenderer {
|
||||||
|
pub window: Rc<Window>,
|
||||||
|
pub device: Device,
|
||||||
|
pub queue: Queue,
|
||||||
|
pub surface: Surface,
|
||||||
|
pub config: SurfaceConfiguration,
|
||||||
|
pub atlas: TextAtlas,
|
||||||
|
pub text_renderer: TextRenderer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WgpuRenderer {
|
||||||
|
async fn new_async(window: Rc<Window>) -> Result<Self, String> {
|
||||||
|
let size = window.inner_size();
|
||||||
|
|
||||||
|
let instance = Instance::new(InstanceDescriptor::default());
|
||||||
|
let adapter = instance
|
||||||
|
.request_adapter(&RequestAdapterOptions::default())
|
||||||
|
.await
|
||||||
|
.ok_or(format!("failed to request adapter"))?;
|
||||||
|
let (device, queue) = adapter
|
||||||
|
.request_device(
|
||||||
|
&DeviceDescriptor {
|
||||||
|
label: None,
|
||||||
|
features: Features::empty(),
|
||||||
|
limits: Limits::downlevel_defaults(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("failed to request device: {}", err))?;
|
||||||
|
let surface = unsafe { instance.create_surface(&*window) }
|
||||||
|
.map_err(|err| format!("failed to create surface: {}", err))?;
|
||||||
|
let swapchain_format = TextureFormat::Bgra8UnormSrgb;
|
||||||
|
let mut config = SurfaceConfiguration {
|
||||||
|
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
format: swapchain_format,
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
present_mode: PresentMode::Fifo,
|
||||||
|
alpha_mode: CompositeAlphaMode::Opaque,
|
||||||
|
view_formats: vec![],
|
||||||
|
};
|
||||||
|
surface.configure(&device, &config);
|
||||||
|
|
||||||
|
let mut atlas = TextAtlas::new(&device, &queue, swapchain_format);
|
||||||
|
let mut text_renderer =
|
||||||
|
TextRenderer::new(&mut atlas, &device, MultisampleState::default(), None);
|
||||||
|
Ok(Self {
|
||||||
|
window,
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
surface,
|
||||||
|
config,
|
||||||
|
atlas,
|
||||||
|
text_renderer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(window: Rc<Window>) -> Result<Self, String> {
|
||||||
|
pollster::block_on(Self::new_async(window))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(
|
||||||
|
&mut self,
|
||||||
|
buffer: &mut Buffer,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
swash_cache: &mut SwashCache,
|
||||||
|
) {
|
||||||
|
self.text_renderer
|
||||||
|
.prepare(
|
||||||
|
&self.device,
|
||||||
|
&self.queue,
|
||||||
|
font_system,
|
||||||
|
&mut self.atlas,
|
||||||
|
Resolution {
|
||||||
|
width: self.config.width,
|
||||||
|
height: self.config.height,
|
||||||
|
},
|
||||||
|
[TextArea {
|
||||||
|
buffer: &buffer,
|
||||||
|
left: 0.0,
|
||||||
|
top: 0.0,
|
||||||
|
scale: 1.0,
|
||||||
|
bounds: TextBounds {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: self.config.width as i32,
|
||||||
|
bottom: self.config.height as i32,
|
||||||
|
},
|
||||||
|
default_color: Color::rgb(255, 255, 255),
|
||||||
|
}],
|
||||||
|
swash_cache,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let frame = self.surface.get_current_texture().unwrap();
|
||||||
|
let view = frame.texture.create_view(&TextureViewDescriptor::default());
|
||||||
|
let mut encoder = self
|
||||||
|
.device
|
||||||
|
.create_command_encoder(&CommandEncoderDescriptor { label: None });
|
||||||
|
{
|
||||||
|
let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[Some(RenderPassColorAttachment {
|
||||||
|
view: &view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: Operations {
|
||||||
|
load: LoadOp::Clear(wgpu::Color::BLACK),
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.text_renderer.render(&self.atlas, &mut pass).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.queue.submit(Some(encoder.finish()));
|
||||||
|
frame.present();
|
||||||
|
|
||||||
|
self.atlas.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(&mut self, width: u32, height: u32) {
|
||||||
|
self.config.width = width;
|
||||||
|
self.config.height = height;
|
||||||
|
self.surface.configure(&self.device, &self.config);
|
||||||
|
self.window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue