perf(terminal): draw webgl backdrops with quads
This commit is contained in:
@@ -146,7 +146,7 @@
|
|||||||
- WebGL2 初始化 shader/program/vertex buffer/atlas texture/fallback texture。
|
- WebGL2 初始化 shader/program/vertex buffer/atlas texture/fallback texture。
|
||||||
- `GlyphAtlas` 按 grapheme + bold/italic + cell width 缓存字形;ASCII、CJK wide 字符、组合字符按 `unicode-segmentation` / `unicode-width` 拆成 cell glyph。
|
- `GlyphAtlas` 按 grapheme + bold/italic + cell width 缓存字形;ASCII、CJK wide 字符、组合字符按 `unicode-segmentation` / `unicode-width` 拆成 cell glyph。
|
||||||
- 文本通过 per-cell quad 批量绘制,颜色作为 vertex attribute;小字号不再走整屏纹理二次采样。
|
- 文本通过 per-cell quad 批量绘制,颜色作为 vertex attribute;小字号不再走整屏纹理二次采样。
|
||||||
- 背景、underline、cursor 背景由 Canvas backdrop 绘制后作为底层 texture 合成;cursor 前景文字仍进 atlas。
|
- 背景、underline、cursor 背景在 WebGL 正常路径中直接绘制为纯色 quad;cursor 前景文字仍进 atlas。
|
||||||
- atlas 溢出或遇到不可拆分复杂段时自动回退整屏 Canvas texture,避免真实终端内容白屏。
|
- atlas 溢出或遇到不可拆分复杂段时自动回退整屏 Canvas texture,避免真实终端内容白屏。
|
||||||
- WebGL2 不可用时自动落回 Canvas 2D。
|
- WebGL2 不可用时自动落回 Canvas 2D。
|
||||||
2. **Canvas 2D 渲染器** ✅
|
2. **Canvas 2D 渲染器** ✅
|
||||||
@@ -255,6 +255,10 @@
|
|||||||
- WebGL render path 记录阶段耗时:DOM/canvas 同步、backdrop 绘制、glyph collect、backdrop texture pass、glyph atlas、atlas upload、vertex build/upload、draw call。
|
- WebGL render path 记录阶段耗时:DOM/canvas 同步、backdrop 绘制、glyph collect、backdrop texture pass、glyph atlas、atlas upload、vertex build/upload、draw call。
|
||||||
- 记录计数:cells、atlas entries、atlas insertions/resets、full-canvas fallback 次数、glyph draw failed 次数。
|
- 记录计数:cells、atlas entries、atlas insertions/resets、full-canvas fallback 次数、glyph draw failed 次数。
|
||||||
- `window.__terminalBench.webglFrames` 暴露每帧采样,`scripts/terminal-bench.mjs` 汇总为 `rendererStats`,用于判断慢点是整屏 backdrop texture、atlas、vertex buffer,还是 fallback。
|
- `window.__terminalBench.webglFrames` 暴露每帧采样,`scripts/terminal-bench.mjs` 汇总为 `rendererStats`,用于判断慢点是整屏 backdrop texture、atlas、vertex buffer,还是 fallback。
|
||||||
|
6. **WebGL backdrop texture pass 拆除** ✅
|
||||||
|
- 正常 WebGL 路径不再每帧把 Canvas backdrop 上传为整屏 texture;背景、underline、cursor 背景改为 1×1 白色纹理 + 纯色 quad 批量绘制。
|
||||||
|
- `backdropTextureMs` 只在复杂 glyph / atlas 失败等 full-canvas fallback 路径产生,用于继续观察 fallback 是否被真实内容触发。
|
||||||
|
- 页面切换、`?renderer=webgl` 和 `--renderer webgl` 保留,下一轮基准可直接对比这次优化后 WebGL 是否追近 Canvas。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -460,6 +460,7 @@ pub(super) struct WebGlRendererState {
|
|||||||
program: WebGlProgram,
|
program: WebGlProgram,
|
||||||
atlas_texture: WebGlTexture,
|
atlas_texture: WebGlTexture,
|
||||||
fallback_texture: WebGlTexture,
|
fallback_texture: WebGlTexture,
|
||||||
|
solid_texture: WebGlTexture,
|
||||||
buffer: WebGlBuffer,
|
buffer: WebGlBuffer,
|
||||||
position_location: u32,
|
position_location: u32,
|
||||||
tex_coord_location: u32,
|
tex_coord_location: u32,
|
||||||
@@ -573,6 +574,15 @@ struct GlyphQuad {
|
|||||||
color: [f32; 4],
|
color: [f32; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
struct SolidQuad {
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
width: f64,
|
||||||
|
height: f64,
|
||||||
|
color: [f32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
fn split_segment_glyphs(
|
fn split_segment_glyphs(
|
||||||
segment: &TerminalSegment,
|
segment: &TerminalSegment,
|
||||||
row: usize,
|
row: usize,
|
||||||
@@ -631,6 +641,75 @@ fn collect_glyph_cells(
|
|||||||
Some(cells)
|
Some(cells)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn collect_backdrop_quads(frame: CanvasSurfaceFrame<'_>) -> Option<Vec<SolidQuad>> {
|
||||||
|
let (cell_w, cell_h) = frame.cell_size;
|
||||||
|
let mut quads = Vec::new();
|
||||||
|
|
||||||
|
for (row_idx, row) in frame.visible.iter().enumerate() {
|
||||||
|
let y = frame.content_top_px + row_idx as f64 * cell_h;
|
||||||
|
let mut col = 0usize;
|
||||||
|
for segment in &row.segments {
|
||||||
|
let x = col as f64 * cell_w;
|
||||||
|
let width = segment.cols as f64 * cell_w;
|
||||||
|
if segment.is_cursor {
|
||||||
|
let rect =
|
||||||
|
cursor_shape_rect(frame.options.cursor.style, x, y, width.max(cell_w), cell_h);
|
||||||
|
push_color_rect(
|
||||||
|
&mut quads,
|
||||||
|
rect.x,
|
||||||
|
rect.y,
|
||||||
|
rect.width,
|
||||||
|
rect.height,
|
||||||
|
&frame.options.theme.cursor_background,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
push_color_rect(
|
||||||
|
&mut quads,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
cell_h,
|
||||||
|
themed_color(&segment.style.background, frame.options),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if segment.style.underline {
|
||||||
|
let color = if segment.is_cursor {
|
||||||
|
&frame.options.theme.cursor_foreground
|
||||||
|
} else {
|
||||||
|
themed_color(&segment.style.color, frame.options)
|
||||||
|
};
|
||||||
|
push_color_rect(&mut quads, x, y + cell_h - 1.0, width, 1.0, color)?;
|
||||||
|
}
|
||||||
|
col += segment.cols;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(quads)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_color_rect(
|
||||||
|
quads: &mut Vec<SolidQuad>,
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
width: f64,
|
||||||
|
height: f64,
|
||||||
|
color: &str,
|
||||||
|
) -> Option<()> {
|
||||||
|
let color = parse_css_rgb(color)?;
|
||||||
|
if color[3] <= 0.0 || width <= 0.0 || height <= 0.0 {
|
||||||
|
return Some(());
|
||||||
|
}
|
||||||
|
quads.push(SolidQuad {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
color,
|
||||||
|
});
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_css_rgb(color: &str) -> Option<[f32; 4]> {
|
fn parse_css_rgb(color: &str) -> Option<[f32; 4]> {
|
||||||
let color = color.trim();
|
let color = color.trim();
|
||||||
if color == "transparent" {
|
if color == "transparent" {
|
||||||
@@ -678,6 +757,21 @@ fn push_quad(vertices: &mut Vec<f32>, quad: GlyphQuad, dom_state: CanvasDomState
|
|||||||
vertices.extend_from_slice(&data);
|
vertices.extend_from_slice(&data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_solid_quad(vertices: &mut Vec<f32>, quad: SolidQuad, dom_state: CanvasDomState) {
|
||||||
|
push_quad(
|
||||||
|
vertices,
|
||||||
|
GlyphQuad {
|
||||||
|
x: quad.x,
|
||||||
|
y: quad.y,
|
||||||
|
width: quad.width,
|
||||||
|
height: quad.height,
|
||||||
|
uv: [0.0, 0.0, 1.0, 1.0],
|
||||||
|
color: quad.color,
|
||||||
|
},
|
||||||
|
dom_state,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn glyph_uv(entry: GlyphAtlasEntry, atlas_size: u32) -> [f64; 4] {
|
fn glyph_uv(entry: GlyphAtlasEntry, atlas_size: u32) -> [f64; 4] {
|
||||||
let size = f64::from(atlas_size);
|
let size = f64::from(atlas_size);
|
||||||
[
|
[
|
||||||
@@ -716,45 +810,51 @@ pub(super) fn render_webgl(
|
|||||||
return Err("WebGL context is lost".to_owned());
|
return Err("WebGL context is lost".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
let backdrop_start = perf_now();
|
|
||||||
sync_canvas_bitmap(&state.source_canvas, frame.dom_state);
|
|
||||||
let _ = state
|
|
||||||
.source_ctx
|
|
||||||
.set_transform(frame.dpr, 0.0, 0.0, frame.dpr, 0.0, 0.0);
|
|
||||||
render_canvas_backdrop(&state.source_ctx, frame);
|
|
||||||
stats.backdrop_ms = perf_now() - backdrop_start;
|
|
||||||
|
|
||||||
let collect_start = perf_now();
|
let collect_start = perf_now();
|
||||||
let Some(cells) = collect_glyph_cells(frame.visible, frame.options) else {
|
let Some(cells) = collect_glyph_cells(frame.visible, frame.options) else {
|
||||||
stats.collect_ms = perf_now() - collect_start;
|
stats.collect_ms = perf_now() - collect_start;
|
||||||
stats.fallback_full_canvas = true;
|
render_webgl_canvas_fallback(state, frame, &mut stats)?;
|
||||||
let fallback_canvas_start = perf_now();
|
stats.total_ms = perf_now() - total_start;
|
||||||
render_canvas(&state.source_ctx, frame);
|
return Ok(stats);
|
||||||
stats.fallback_canvas_ms += perf_now() - fallback_canvas_start;
|
};
|
||||||
let texture_start = perf_now();
|
let Some(backdrop_quads) = collect_backdrop_quads(frame) else {
|
||||||
draw_webgl_texture_frame(state, frame.dom_state)?;
|
stats.collect_ms = perf_now() - collect_start;
|
||||||
stats.backdrop_texture_ms += perf_now() - texture_start;
|
render_webgl_canvas_fallback(state, frame, &mut stats)?;
|
||||||
stats.total_ms = perf_now() - total_start;
|
stats.total_ms = perf_now() - total_start;
|
||||||
return Ok(stats);
|
return Ok(stats);
|
||||||
};
|
};
|
||||||
stats.collect_ms = perf_now() - collect_start;
|
stats.collect_ms = perf_now() - collect_start;
|
||||||
stats.cells = cells.len();
|
stats.cells = cells.len();
|
||||||
|
|
||||||
let texture_start = perf_now();
|
let backdrop_start = perf_now();
|
||||||
draw_webgl_texture_frame(state, frame.dom_state)?;
|
draw_webgl_backdrop_quads(state, frame.dom_state, &backdrop_quads)?;
|
||||||
stats.backdrop_texture_ms += perf_now() - texture_start;
|
stats.backdrop_ms = perf_now() - backdrop_start;
|
||||||
if draw_webgl_glyphs(state, frame, &cells, &mut stats).is_err() {
|
if draw_webgl_glyphs(state, frame, &cells, &mut stats).is_err() {
|
||||||
stats.glyph_draw_failed = true;
|
stats.glyph_draw_failed = true;
|
||||||
|
render_webgl_canvas_fallback(state, frame, &mut stats)?;
|
||||||
|
}
|
||||||
|
stats.atlas_entries = state.atlas.entries.len();
|
||||||
|
stats.total_ms = perf_now() - total_start;
|
||||||
|
Ok(stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_webgl_canvas_fallback(
|
||||||
|
state: &WebGlRendererState,
|
||||||
|
frame: CanvasSurfaceFrame<'_>,
|
||||||
|
stats: &mut WebGlRenderStats,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
stats.fallback_full_canvas = true;
|
||||||
let fallback_canvas_start = perf_now();
|
let fallback_canvas_start = perf_now();
|
||||||
|
sync_canvas_bitmap(&state.source_canvas, frame.dom_state);
|
||||||
|
let _ = state
|
||||||
|
.source_ctx
|
||||||
|
.set_transform(frame.dpr, 0.0, 0.0, frame.dpr, 0.0, 0.0);
|
||||||
render_canvas(&state.source_ctx, frame);
|
render_canvas(&state.source_ctx, frame);
|
||||||
stats.fallback_canvas_ms += perf_now() - fallback_canvas_start;
|
stats.fallback_canvas_ms += perf_now() - fallback_canvas_start;
|
||||||
let texture_start = perf_now();
|
let texture_start = perf_now();
|
||||||
draw_webgl_texture_frame(state, frame.dom_state)?;
|
draw_webgl_texture_frame(state, frame.dom_state)?;
|
||||||
stats.backdrop_texture_ms += perf_now() - texture_start;
|
stats.backdrop_texture_ms += perf_now() - texture_start;
|
||||||
}
|
Ok(())
|
||||||
stats.atlas_entries = state.atlas.entries.len();
|
|
||||||
stats.total_ms = perf_now() - total_start;
|
|
||||||
Ok(stats)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_webgl_renderer(canvas: &HtmlCanvasElement) -> Result<WebGlRendererState, String> {
|
fn create_webgl_renderer(canvas: &HtmlCanvasElement) -> Result<WebGlRendererState, String> {
|
||||||
@@ -805,6 +905,7 @@ fn create_webgl_renderer(canvas: &HtmlCanvasElement) -> Result<WebGlRendererStat
|
|||||||
.create_texture()
|
.create_texture()
|
||||||
.ok_or_else(|| "failed to create WebGL fallback texture".to_owned())?;
|
.ok_or_else(|| "failed to create WebGL fallback texture".to_owned())?;
|
||||||
configure_webgl_texture(&gl, &fallback_texture);
|
configure_webgl_texture(&gl, &fallback_texture);
|
||||||
|
let solid_texture = create_webgl_solid_texture(&gl)?;
|
||||||
gl.pixel_storei(WebGl2RenderingContext::UNPACK_FLIP_Y_WEBGL, 1);
|
gl.pixel_storei(WebGl2RenderingContext::UNPACK_FLIP_Y_WEBGL, 1);
|
||||||
gl.pixel_storei(WebGl2RenderingContext::UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
|
gl.pixel_storei(WebGl2RenderingContext::UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
|
||||||
gl.enable(WebGl2RenderingContext::BLEND);
|
gl.enable(WebGl2RenderingContext::BLEND);
|
||||||
@@ -851,6 +952,7 @@ fn create_webgl_renderer(canvas: &HtmlCanvasElement) -> Result<WebGlRendererStat
|
|||||||
program,
|
program,
|
||||||
atlas_texture,
|
atlas_texture,
|
||||||
fallback_texture,
|
fallback_texture,
|
||||||
|
solid_texture,
|
||||||
buffer,
|
buffer,
|
||||||
position_location: position_location as u32,
|
position_location: position_location as u32,
|
||||||
tex_coord_location: tex_coord_location as u32,
|
tex_coord_location: tex_coord_location as u32,
|
||||||
@@ -883,6 +985,27 @@ fn configure_webgl_texture(gl: &WebGl2RenderingContext, texture: &WebGlTexture)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_webgl_solid_texture(gl: &WebGl2RenderingContext) -> Result<WebGlTexture, String> {
|
||||||
|
let texture = gl
|
||||||
|
.create_texture()
|
||||||
|
.ok_or_else(|| "failed to create WebGL solid texture".to_owned())?;
|
||||||
|
configure_webgl_texture(gl, &texture);
|
||||||
|
let white = [255, 255, 255, 255];
|
||||||
|
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||||
|
WebGl2RenderingContext::TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
WebGl2RenderingContext::RGBA as i32,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
WebGl2RenderingContext::RGBA,
|
||||||
|
WebGl2RenderingContext::UNSIGNED_BYTE,
|
||||||
|
Some(&white),
|
||||||
|
)
|
||||||
|
.map_err(|_| "failed to upload WebGL solid texture".to_owned())?;
|
||||||
|
Ok(texture)
|
||||||
|
}
|
||||||
|
|
||||||
fn compile_webgl_shader(
|
fn compile_webgl_shader(
|
||||||
gl: &WebGl2RenderingContext,
|
gl: &WebGl2RenderingContext,
|
||||||
shader_type: u32,
|
shader_type: u32,
|
||||||
@@ -1013,6 +1136,46 @@ fn draw_webgl_texture_frame(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_webgl_backdrop_quads(
|
||||||
|
state: &WebGlRendererState,
|
||||||
|
dom_state: CanvasDomState,
|
||||||
|
quads: &[SolidQuad],
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let gl = &state.gl;
|
||||||
|
gl.viewport(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
dom_state.backing_width as i32,
|
||||||
|
dom_state.backing_height as i32,
|
||||||
|
);
|
||||||
|
gl.clear_color(0.0, 0.0, 0.0, 0.0);
|
||||||
|
gl.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);
|
||||||
|
if quads.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut vertices = Vec::with_capacity(quads.len() * WEBGL_VERTEX_FLOATS * 6);
|
||||||
|
for quad in quads {
|
||||||
|
push_solid_quad(&mut vertices, *quad, dom_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.use_program(Some(&state.program));
|
||||||
|
bind_webgl_vertex_layout(gl, state);
|
||||||
|
upload_webgl_vertices(gl, &vertices);
|
||||||
|
gl.active_texture(WebGl2RenderingContext::TEXTURE0);
|
||||||
|
gl.bind_texture(
|
||||||
|
WebGl2RenderingContext::TEXTURE_2D,
|
||||||
|
Some(&state.solid_texture),
|
||||||
|
);
|
||||||
|
gl.uniform1i(state.sampler_location.as_ref(), 0);
|
||||||
|
gl.draw_arrays(
|
||||||
|
WebGl2RenderingContext::TRIANGLES,
|
||||||
|
0,
|
||||||
|
(vertices.len() / WEBGL_VERTEX_FLOATS) as i32,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_webgl_glyphs(
|
fn draw_webgl_glyphs(
|
||||||
state: &mut WebGlRendererState,
|
state: &mut WebGlRendererState,
|
||||||
frame: CanvasSurfaceFrame<'_>,
|
frame: CanvasSurfaceFrame<'_>,
|
||||||
@@ -1174,39 +1337,6 @@ fn draw_glyph_into_atlas(
|
|||||||
Ok(Some(entry))
|
Ok(Some(entry))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_canvas_backdrop(ctx: &CanvasRenderingContext2d, frame: CanvasSurfaceFrame<'_>) {
|
|
||||||
let (cell_w, cell_h) = frame.cell_size;
|
|
||||||
let canvas_width = ctx.canvas().map(|c| c.width() as f64).unwrap_or(0.0);
|
|
||||||
let canvas_height = ctx.canvas().map(|c| c.height() as f64).unwrap_or(0.0);
|
|
||||||
ctx.clear_rect(0.0, 0.0, canvas_width, canvas_height);
|
|
||||||
|
|
||||||
for (row_idx, row) in frame.visible.iter().enumerate() {
|
|
||||||
let y = frame.content_top_px + row_idx as f64 * cell_h;
|
|
||||||
let mut col = 0usize;
|
|
||||||
for segment in &row.segments {
|
|
||||||
let x = col as f64 * cell_w;
|
|
||||||
let width = segment.cols as f64 * cell_w;
|
|
||||||
if segment.is_cursor {
|
|
||||||
ctx.set_fill_style_str(&frame.options.theme.cursor_background);
|
|
||||||
let rect =
|
|
||||||
cursor_shape_rect(frame.options.cursor.style, x, y, width.max(cell_w), cell_h);
|
|
||||||
ctx.fill_rect(rect.x, rect.y, rect.width, rect.height);
|
|
||||||
} else if set_fill_color(ctx, themed_color(&segment.style.background, frame.options)) {
|
|
||||||
ctx.fill_rect(x, y, width, cell_h);
|
|
||||||
}
|
|
||||||
if segment.style.underline {
|
|
||||||
if segment.is_cursor {
|
|
||||||
ctx.set_fill_style_str(&frame.options.theme.cursor_foreground);
|
|
||||||
} else {
|
|
||||||
set_fill_color(ctx, themed_color(&segment.style.color, frame.options));
|
|
||||||
}
|
|
||||||
ctx.fill_rect(x, y + cell_h - 1.0, width, 1.0);
|
|
||||||
}
|
|
||||||
col += segment.cols;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_cell_text(row: &TerminalRow, cursor_col: usize) -> Option<String> {
|
fn cursor_cell_text(row: &TerminalRow, cursor_col: usize) -> Option<String> {
|
||||||
let mut col = 0usize;
|
let mut col = 0usize;
|
||||||
for segment in &row.segments {
|
for segment in &row.segments {
|
||||||
@@ -1523,6 +1653,103 @@ mod tests {
|
|||||||
assert_eq!(&vertices[0..8], &[-1.0, 0.0, 0.0, 0.5, 1.0, 0.5, 0.0, 1.0]);
|
assert_eq!(&vertices[0..8], &[-1.0, 0.0, 0.0, 0.5, 1.0, 0.5, 0.0, 1.0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn push_solid_quad_uses_full_white_texture_uvs() {
|
||||||
|
let dom = CanvasDomState {
|
||||||
|
backing_width: 200,
|
||||||
|
backing_height: 100,
|
||||||
|
css_width: 100,
|
||||||
|
css_height: 50,
|
||||||
|
top_px: 0.0,
|
||||||
|
};
|
||||||
|
let mut vertices = Vec::new();
|
||||||
|
push_solid_quad(
|
||||||
|
&mut vertices,
|
||||||
|
SolidQuad {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
width: 50.0,
|
||||||
|
height: 25.0,
|
||||||
|
color: [0.25, 0.5, 0.75, 1.0],
|
||||||
|
},
|
||||||
|
dom,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(vertices.len(), WEBGL_VERTEX_FLOATS * 6);
|
||||||
|
assert_eq!(
|
||||||
|
&vertices[0..8],
|
||||||
|
&[-1.0, 0.0, 0.0, 1.0, 0.25, 0.5, 0.75, 1.0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn collect_backdrop_quads_skips_transparent_and_tracks_cursor_overlay() {
|
||||||
|
let mut options = default_options();
|
||||||
|
options.cursor.style = TerminalCursorStyle::Underline;
|
||||||
|
options.theme.cursor_background = "#ffffff".to_owned();
|
||||||
|
options.theme.cursor_foreground = "#000000".to_owned();
|
||||||
|
let rows = vec![TerminalRow {
|
||||||
|
segments: vec![
|
||||||
|
TerminalSegment {
|
||||||
|
text: "ab".to_owned(),
|
||||||
|
cols: 2,
|
||||||
|
style: TerminalStyle {
|
||||||
|
background: "transparent".to_owned(),
|
||||||
|
underline: true,
|
||||||
|
color: "#80ff00".to_owned(),
|
||||||
|
..TerminalStyle::default()
|
||||||
|
},
|
||||||
|
is_cursor: false,
|
||||||
|
},
|
||||||
|
TerminalSegment {
|
||||||
|
text: "c".to_owned(),
|
||||||
|
cols: 1,
|
||||||
|
style: TerminalStyle::default(),
|
||||||
|
is_cursor: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}];
|
||||||
|
let frame = CanvasSurfaceFrame {
|
||||||
|
dom_state: CanvasDomState {
|
||||||
|
backing_width: 300,
|
||||||
|
backing_height: 90,
|
||||||
|
css_width: 100,
|
||||||
|
css_height: 30,
|
||||||
|
top_px: 0.0,
|
||||||
|
},
|
||||||
|
dpr: 1.0,
|
||||||
|
visible: &rows,
|
||||||
|
cell_size: (10.0, 18.0),
|
||||||
|
content_top_px: 4.0,
|
||||||
|
cursor_pos: Some((0, 2)),
|
||||||
|
options: &options,
|
||||||
|
};
|
||||||
|
|
||||||
|
let quads = collect_backdrop_quads(frame).expect("backdrop quads");
|
||||||
|
|
||||||
|
assert_eq!(quads.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
quads[0],
|
||||||
|
SolidQuad {
|
||||||
|
x: 0.0,
|
||||||
|
y: 21.0,
|
||||||
|
width: 20.0,
|
||||||
|
height: 1.0,
|
||||||
|
color: [128.0 / 255.0, 1.0, 0.0, 1.0],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
quads[1],
|
||||||
|
SolidQuad {
|
||||||
|
x: 20.0,
|
||||||
|
y: 19.0,
|
||||||
|
width: 10.0,
|
||||||
|
height: 3.0,
|
||||||
|
color: [1.0, 1.0, 1.0, 1.0],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn canvas_viewport_top_pins_live_mode_to_bottom() {
|
fn canvas_viewport_top_pins_live_mode_to_bottom() {
|
||||||
let total_px = 1000.0 * DEFAULT_LINE_HEIGHT_PX;
|
let total_px = 1000.0 * DEFAULT_LINE_HEIGHT_PX;
|
||||||
|
|||||||
Reference in New Issue
Block a user