1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-24 13:52:55 +03:00

sixel: improve sixel parsing performance.

Test scenario is:

```
wezterm -n --config enable_kitty_graphics=false
./notcurses-demo -p ../data iv
```

In `20220326-231415-4cd89333`

```
             runtime│ frames│output(B)│    FPS│%r│%a│%w│TheoFPS║
══╤════════╤════════╪═══════╪═════════╪═══════╪══╪══╪══╪═══════╣
 1│   intro│  12.38s│    238│  10.50Mi│   19.2│ 1│ 0│82│  22.82║
 2│    view│  37.87s│    982│  89.35Mi│   25.9│ 0│ 0│46│  54.31║
══╧════════╧════════╪═══════╪═════════╪═══════╧══╧══╧══╧═══════╝
              50.25s│   1220│  99.86Mi│
```

With this commit:

```
             runtime│ frames│output(B)│    FPS│%r│%a│%w│TheoFPS║
══╤════════╤════════╪═══════╪═════════╪═══════╪══╪══╪══╪═══════╣
 1│   intro│   3.39s│    458│  23.13Mi│  135.2│ 8│ 1│17│ 490.79║
 2│    view│  18.59s│    978│  89.35Mi│   52.6│ 1│ 0│ 5│ 645.10║
══╧════════╧════════╪═══════╪═════════╪═══════╧══╧══╧══╧═══════╝
              21.98s│   1436│ 112.48Mi│
```

as a point of comparison, here's the kitty protocol numbers on
the same build:

```
             runtime│ frames│output(B)│    FPS│%r│%a│%w│TheoFPS║
══╤════════╤════════╪═══════╪═════════╪═══════╪══╪══╪══╪═══════╣
 1│   intro│   3.68s│    485│  39.49Mi│  131.7│ 5│ 1│23│ 421.85║
 2│    view│  20.66s│    979│ 423.90Mi│   47.4│ 1│ 0│18│ 225.83║
══╧════════╧════════╪═══════╪═════════╪═══════╧══╧══╧══╧═══════╝
              24.34s│   1464│ 463.39Mi│
```

so we're now in the same ballpark.

cc: @autumnmeowmeow in case you have some other sixel benchmarks
to help verify this :)

refs: https://github.com/wez/wezterm/issues/217
This commit is contained in:
Wez Furlong 2022-03-27 15:20:21 -07:00
parent 15cd990e33
commit 77e5cdd00d

View File

@ -1,14 +1,16 @@
use crate::color::RgbColor;
use crate::escape::{Sixel, SixelData};
use regex::bytes::Regex;
use std::time::Instant;
const MAX_PARAMS: usize = 5;
const MAX_SIXEL_SIZE: usize = 100_000_000;
pub struct SixelBuilder {
pub sixel: Sixel,
buf: Vec<u8>,
repeat_re: Regex,
raster_re: Regex,
colordef_re: Regex,
coloruse_re: Regex,
params: [i64; MAX_PARAMS],
param_no: usize,
current_command: u8,
start: Instant,
}
impl SixelBuilder {
@ -26,11 +28,6 @@ impl SixelBuilder {
};
let horizontal_grid_size = params.get(2).map(|&x| x);
let repeat_re = Regex::new("^!(\\d+)([\x3f-\x7e])").unwrap();
let raster_re = Regex::new("^\"(\\d+);(\\d+)(;(\\d+))?(;(\\d+))?").unwrap();
let colordef_re = Regex::new("^#(\\d+);(\\d+);(\\d+);(\\d+);(\\d+);?").unwrap();
let coloruse_re = Regex::new("^#(\\d+)([^;\\d]|$)").unwrap();
Self {
sixel: Sixel {
pan,
@ -41,114 +38,73 @@ impl SixelBuilder {
horizontal_grid_size,
data: vec![],
},
buf: vec![],
repeat_re,
raster_re,
colordef_re,
coloruse_re,
param_no: 0,
params: [-1; MAX_PARAMS],
current_command: 0,
start: Instant::now(),
}
}
pub fn push(&mut self, data: u8) {
self.buf.push(data);
}
pub fn finish(&mut self) {
fn cap_int<T: std::str::FromStr>(m: regex::bytes::Match) -> Option<T> {
let bytes = m.as_bytes();
// Safe because we matched digits from the regex
let s = unsafe { std::str::from_utf8_unchecked(bytes) };
s.parse::<T>().ok()
}
let mut remainder = &self.buf[..];
while !remainder.is_empty() {
let data = remainder[0];
if data == b'$' {
match data {
b'$' => {
self.finish_command();
self.sixel.data.push(SixelData::CarriageReturn);
remainder = &remainder[1..];
continue;
}
if data == b'-' {
b'-' => {
self.finish_command();
self.sixel.data.push(SixelData::NewLine);
remainder = &remainder[1..];
continue;
}
if data >= 0x3f && data <= 0x7e {
0x3f..=0x7e if self.current_command == b'!' => {
self.sixel.data.push(SixelData::Repeat {
repeat_count: self.params[0] as u32,
data: data - 0x3f,
});
self.finish_command();
}
0x3f..=0x7e => {
self.finish_command();
self.sixel.data.push(SixelData::Data(data - 0x3f));
remainder = &remainder[1..];
continue;
}
if let Some(c) = self.raster_re.captures(remainder) {
let all = c.get(0).unwrap();
let matched_len = all.as_bytes().len();
let pan = cap_int(c.get(1).unwrap()).unwrap_or(2);
let pad = cap_int(c.get(2).unwrap()).unwrap_or(1);
let pixel_width = c.get(4).and_then(cap_int);
let pixel_height = c.get(6).and_then(cap_int);
self.sixel.pan = pan;
self.sixel.pad = pad;
self.sixel.pixel_width = pixel_width;
self.sixel.pixel_height = pixel_height;
if let (Some(w), Some(h)) = (pixel_width, pixel_height) {
let size = w as usize * h as usize;
// Ideally we'd just use `try_reserve` here, but that is
// nightly Rust only at the time of writing this comment:
// <https://github.com/rust-lang/rust/issues/48043>
const MAX_SIXEL_SIZE: usize = 100_000_000;
if size > MAX_SIXEL_SIZE {
log::error!(
"Ignoring sixel data {}x{} because {} bytes > max allowed {}",
w,
h,
size,
MAX_SIXEL_SIZE
);
self.sixel.pixel_width = None;
self.sixel.pixel_height = None;
self.sixel.data.clear();
b'#' | b'!' | b'"' => {
self.finish_command();
self.current_command = data;
}
b'0'..=b'9' if self.current_command != 0 => {
let pos = self.param_no;
if pos >= MAX_PARAMS {
return;
}
self.sixel.data.reserve(size);
if self.params[pos] == -1 {
self.params[pos] = 0;
}
self.params[pos] = self.params[pos]
.saturating_mul(10)
.saturating_add((data - b'0') as i64);
}
b';' if self.current_command != 0 => {
let pos = self.param_no;
if pos >= MAX_PARAMS {
return;
}
self.param_no += 1;
}
_ => {
// Invalid; break out of current command
self.finish_command();
}
}
}
remainder = &remainder[matched_len..];
continue;
}
if let Some(c) = self.coloruse_re.captures(remainder) {
let all = c.get(0).unwrap();
let matched_len = all.as_bytes().len();
let color_number = cap_int(c.get(1).unwrap()).unwrap_or(0);
self.sixel
.data
.push(SixelData::SelectColorMapEntry(color_number));
let pop_len = matched_len - c.get(2).unwrap().as_bytes().len();
remainder = &remainder[pop_len..];
continue;
}
if let Some(c) = self.colordef_re.captures(remainder) {
let all = c.get(0).unwrap();
let matched_len = all.as_bytes().len();
let color_number = cap_int(c.get(1).unwrap()).unwrap_or(0);
let system = cap_int(c.get(2).unwrap()).unwrap_or(1);
let a = cap_int(c.get(3).unwrap()).unwrap_or(0);
let b = cap_int(c.get(4).unwrap()).unwrap_or(0);
let c = cap_int(c.get(5).unwrap()).unwrap_or(0);
fn finish_command(&mut self) {
match self.current_command {
b'#' if self.param_no >= 4 => {
// Define a color
let color_number = self.params[0] as u16;
let system = self.params[1] as u16;
let a = self.params[2] as u16;
let b = self.params[3] as u8;
let c = self.params[4] as u8;
if system == 1 {
self.sixel.data.push(SixelData::DefineColorMapHSL {
@ -166,32 +122,66 @@ impl SixelBuilder {
.data
.push(SixelData::DefineColorMapRGB { color_number, rgb });
}
remainder = &remainder[matched_len..];
continue;
}
b'#' => {
// Use a color
let color_number = self.params[0] as u16;
if let Some(c) = self.repeat_re.captures(remainder) {
let all = c.get(0).unwrap();
let matched_len = all.as_bytes().len();
let repeat_count = cap_int(c.get(1).unwrap()).unwrap_or(1);
let data = c.get(2).unwrap().as_bytes()[0] - 0x3f;
self.sixel
.data
.push(SixelData::Repeat { repeat_count, data });
remainder = &remainder[matched_len..];
continue;
.push(SixelData::SelectColorMapEntry(color_number));
}
b'"' => {
// Set raster attributes
let pan = if self.params[0] == -1 {
2
} else {
self.params[0]
};
let pad = if self.params[1] == -1 {
1
} else {
self.params[1]
};
let pixel_width = self.params[2];
let pixel_height = self.params[3];
self.sixel.pan = pan;
self.sixel.pad = pad;
if self.param_no >= 3 {
self.sixel.pixel_width.replace(pixel_width as u32);
self.sixel.pixel_height.replace(pixel_height as u32);
let size = pixel_width as usize * pixel_height as usize;
// Ideally we'd just use `try_reserve` here, but that is
// nightly Rust only at the time of writing this comment:
// <https://github.com/rust-lang/rust/issues/48043>
if size > MAX_SIXEL_SIZE {
log::error!(
"finished sixel parse with {} bytes pending {:?}",
remainder.len(),
std::str::from_utf8(&remainder[0..24.min(remainder.len())])
"Ignoring sixel data {}x{} because {} bytes > max allowed {}",
pixel_width,
pixel_height,
size,
MAX_SIXEL_SIZE
);
break;
self.sixel.pixel_width = None;
self.sixel.pixel_height = None;
self.sixel.data.clear();
return;
}
self.sixel.data.reserve(size);
}
}
_ => {}
}
self.param_no = 0;
self.params = [-1; MAX_PARAMS];
self.current_command = 0;
}
pub fn finish(&mut self) {
self.finish_command();
}
}