Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
use api::{ColorU, ImageFormat, ImageBufferKind};
use api::units::*;
use crate::debug_font_data;
use crate::device::{Device, Program, Texture, TextureSlot, VertexDescriptor, ShaderError, VAO};
use crate::device::{TextureFilter, VertexAttribute, VertexAttributeKind, VertexUsageHint};
use euclid::{Point2D, Rect, Size2D, Transform3D, default};
use crate::internal_types::Swizzle;
use std::f32;
#[derive(Debug, Copy, Clone)]
enum DebugSampler {
Font,
}
impl Into<TextureSlot> for DebugSampler {
fn into(self) -> TextureSlot {
match self {
DebugSampler::Font => TextureSlot(0),
}
}
}
const DESC_FONT: VertexDescriptor = VertexDescriptor {
vertex_attributes: &[
VertexAttribute {
name: "aPosition",
count: 2,
kind: VertexAttributeKind::F32,
},
VertexAttribute {
name: "aColor",
count: 4,
kind: VertexAttributeKind::U8Norm,
},
VertexAttribute {
name: "aColorTexCoord",
count: 2,
kind: VertexAttributeKind::F32,
},
],
instance_attributes: &[],
};
const DESC_COLOR: VertexDescriptor = VertexDescriptor {
vertex_attributes: &[
VertexAttribute {
name: "aPosition",
count: 2,
kind: VertexAttributeKind::F32,
},
VertexAttribute {
name: "aColor",
count: 4,
kind: VertexAttributeKind::U8Norm,
},
],
instance_attributes: &[],
};
#[repr(C)]
pub struct DebugFontVertex {
pub x: f32,
pub y: f32,
pub color: ColorU,
pub u: f32,
pub v: f32,
}
impl DebugFontVertex {
pub fn new(x: f32, y: f32, u: f32, v: f32, color: ColorU) -> DebugFontVertex {
DebugFontVertex { x, y, color, u, v }
}
}
#[repr(C)]
pub struct DebugColorVertex {
pub x: f32,
pub y: f32,
pub color: ColorU,
}
impl DebugColorVertex {
pub fn new(x: f32, y: f32, color: ColorU) -> DebugColorVertex {
DebugColorVertex { x, y, color }
}
}
pub struct DebugRenderer {
font_vertices: Vec<DebugFontVertex>,
font_indices: Vec<u32>,
font_program: Program,
font_vao: VAO,
font_texture: Texture,
tri_vertices: Vec<DebugColorVertex>,
tri_indices: Vec<u32>,
tri_vao: VAO,
line_vertices: Vec<DebugColorVertex>,
line_vao: VAO,
color_program: Program,
}
impl DebugRenderer {
pub fn new(device: &mut Device) -> Result<Self, ShaderError> {
let font_program = device.create_program_linked(
"debug_font",
&[],
&DESC_FONT,
)?;
device.bind_program(&font_program);
device.bind_shader_samplers(&font_program, &[("sColor0", DebugSampler::Font)]);
let color_program = device.create_program_linked(
"debug_color",
&[],
&DESC_COLOR,
)?;
let font_vao = device.create_vao(&DESC_FONT, 1);
let line_vao = device.create_vao(&DESC_COLOR, 1);
let tri_vao = device.create_vao(&DESC_COLOR, 1);
let font_texture = device.create_texture(
ImageBufferKind::Texture2D,
ImageFormat::R8,
debug_font_data::BMP_WIDTH,
debug_font_data::BMP_HEIGHT,
TextureFilter::Linear,
None,
);
device.upload_texture_immediate(
&font_texture,
&debug_font_data::FONT_BITMAP
);
Ok(DebugRenderer {
font_vertices: Vec::new(),
font_indices: Vec::new(),
line_vertices: Vec::new(),
tri_vao,
tri_vertices: Vec::new(),
tri_indices: Vec::new(),
font_program,
color_program,
font_vao,
line_vao,
font_texture,
})
}
pub fn deinit(self, device: &mut Device) {
device.delete_texture(self.font_texture);
device.delete_program(self.font_program);
device.delete_program(self.color_program);
device.delete_vao(self.tri_vao);
device.delete_vao(self.line_vao);
device.delete_vao(self.font_vao);
}
pub fn line_height(&self) -> f32 {
debug_font_data::FONT_SIZE as f32 * 1.1
}
/// Draws a line of text at the provided starting coordinates.
///
/// If |bounds| is specified, glyphs outside the bounds are discarded.
///
/// Y-coordinates is relative to screen top, along with everything else in
/// this file.
pub fn add_text(
&mut self,
x: f32,
y: f32,
text: &str,
color: ColorU,
bounds: Option<DeviceRect>,
) -> default::Rect<f32> {
let mut x_start = x;
let ipw = 1.0 / debug_font_data::BMP_WIDTH as f32;
let iph = 1.0 / debug_font_data::BMP_HEIGHT as f32;
let mut min_x = f32::MAX;
let mut max_x = -f32::MAX;
let mut min_y = f32::MAX;
let mut max_y = -f32::MAX;
for c in text.chars() {
let c = c as usize - debug_font_data::FIRST_GLYPH_INDEX as usize;
if c < debug_font_data::GLYPHS.len() {
let glyph = &debug_font_data::GLYPHS[c];
let x0 = (x_start + glyph.xo + 0.5).floor();
let y0 = (y + glyph.yo + 0.5).floor();
let x1 = x0 + glyph.x1 as f32 - glyph.x0 as f32;
let y1 = y0 + glyph.y1 as f32 - glyph.y0 as f32;
// If either corner of the glyph will end up out of bounds, drop it.
if let Some(b) = bounds {
let rect = DeviceRect {
min: DevicePoint::new(x0, y0),
max: DevicePoint::new(x1, y1),
};
if !b.contains_box(&rect) {
continue;
}
}
let s0 = glyph.x0 as f32 * ipw;
let t0 = glyph.y0 as f32 * iph;
let s1 = glyph.x1 as f32 * ipw;
let t1 = glyph.y1 as f32 * iph;
x_start += glyph.xa;
let vertex_count = self.font_vertices.len() as u32;
self.font_vertices
.push(DebugFontVertex::new(x0, y0, s0, t0, color));
self.font_vertices
.push(DebugFontVertex::new(x1, y0, s1, t0, color));
self.font_vertices
.push(DebugFontVertex::new(x0, y1, s0, t1, color));
self.font_vertices
.push(DebugFontVertex::new(x1, y1, s1, t1, color));
self.font_indices.push(vertex_count + 0);
self.font_indices.push(vertex_count + 1);
self.font_indices.push(vertex_count + 2);
self.font_indices.push(vertex_count + 2);
self.font_indices.push(vertex_count + 1);
self.font_indices.push(vertex_count + 3);
min_x = min_x.min(x0);
max_x = max_x.max(x1);
min_y = min_y.min(y0);
max_y = max_y.max(y1);
}
}
Rect::new(
Point2D::new(min_x, min_y),
Size2D::new(max_x - min_x, max_y - min_y),
)
}
pub fn add_quad(
&mut self,
x0: f32,
y0: f32,
x1: f32,
y1: f32,
color_top: ColorU,
color_bottom: ColorU,
) {
let vertex_count = self.tri_vertices.len() as u32;
self.tri_vertices
.push(DebugColorVertex::new(x0, y0, color_top));
self.tri_vertices
.push(DebugColorVertex::new(x1, y0, color_top));
self.tri_vertices
.push(DebugColorVertex::new(x0, y1, color_bottom));
self.tri_vertices
.push(DebugColorVertex::new(x1, y1, color_bottom));
self.tri_indices.push(vertex_count + 0);
self.tri_indices.push(vertex_count + 1);
self.tri_indices.push(vertex_count + 2);
self.tri_indices.push(vertex_count + 2);
self.tri_indices.push(vertex_count + 1);
self.tri_indices.push(vertex_count + 3);
}
#[allow(dead_code)]
pub fn add_line(&mut self, x0: i32, y0: i32, color0: ColorU, x1: i32, y1: i32, color1: ColorU) {
self.line_vertices
.push(DebugColorVertex::new(x0 as f32, y0 as f32, color0));
self.line_vertices
.push(DebugColorVertex::new(x1 as f32, y1 as f32, color1));
}
pub fn add_rect(&mut self, rect: &DeviceIntRect, thickness: i32, color: ColorU) {
let p0 = rect.min;
let p1 = rect.max;
if thickness > 1 && rect.width() > thickness * 2 && rect.height() > thickness * 2 {
let w = thickness as f32;
let p0 = p0.to_f32();
let p1 = p1.to_f32();
self.add_quad(p0.x, p0.y, p1.x, p0.y + w, color, color);
self.add_quad(p1.x - w, p0.y + w, p1.x, p1.y - w, color, color);
self.add_quad(p0.x, p1.y - w, p1.x, p1.y, color, color);
self.add_quad(p0.x, p0.y + w, p0.x + w, p1.y - w, color, color);
} else {
self.add_line(p0.x, p0.y, color, p1.x, p0.y, color);
self.add_line(p1.x, p0.y, color, p1.x, p1.y, color);
self.add_line(p1.x, p1.y, color, p0.x, p1.y, color);
self.add_line(p0.x, p1.y, color, p0.x, p0.y, color);
}
}
pub fn render(
&mut self,
device: &mut Device,
viewport_size: Option<DeviceIntSize>,
scale: f32,
surface_origin_is_top_left: bool,
) {
if let Some(viewport_size) = viewport_size {
device.disable_depth();
device.set_blend(true);
device.set_blend_mode_premultiplied_alpha();
let (bottom, top) = if surface_origin_is_top_left {
(0.0, viewport_size.height as f32 * scale)
} else {
(viewport_size.height as f32 * scale, 0.0)
};
let projection = Transform3D::ortho(
0.0,
viewport_size.width as f32 * scale,
bottom,
top,
device.ortho_near_plane(),
device.ortho_far_plane(),
);
// Triangles
if !self.tri_vertices.is_empty() {
device.bind_program(&self.color_program);
device.set_uniforms(&self.color_program, &projection);
device.bind_vao(&self.tri_vao);
device.update_vao_indices(&self.tri_vao, &self.tri_indices, VertexUsageHint::Dynamic);
device.update_vao_main_vertices(
&self.tri_vao,
&self.tri_vertices,
VertexUsageHint::Dynamic,
);
device.draw_triangles_u32(0, self.tri_indices.len() as i32);
}
// Lines
if !self.line_vertices.is_empty() {
device.bind_program(&self.color_program);
device.set_uniforms(&self.color_program, &projection);
device.bind_vao(&self.line_vao);
device.update_vao_main_vertices(
&self.line_vao,
&self.line_vertices,
VertexUsageHint::Dynamic,
);
device.draw_nonindexed_lines(0, self.line_vertices.len() as i32);
}
// Glyph
if !self.font_indices.is_empty() {
device.bind_program(&self.font_program);
device.set_uniforms(&self.font_program, &projection);
device.bind_texture(DebugSampler::Font, &self.font_texture, Swizzle::default());
device.bind_vao(&self.font_vao);
device.update_vao_indices(&self.font_vao, &self.font_indices, VertexUsageHint::Dynamic);
device.update_vao_main_vertices(
&self.font_vao,
&self.font_vertices,
VertexUsageHint::Dynamic,
);
device.draw_triangles_u32(0, self.font_indices.len() as i32);
}
}
self.font_indices.clear();
self.font_vertices.clear();
self.line_vertices.clear();
self.tri_vertices.clear();
self.tri_indices.clear();
}
}
pub struct LazyInitializedDebugRenderer {
debug_renderer: Option<DebugRenderer>,
failed: bool,
}
impl LazyInitializedDebugRenderer {
pub fn new() -> Self {
Self {
debug_renderer: None,
failed: false,
}
}
pub fn get_mut<'a>(&'a mut self, device: &mut Device) -> Option<&'a mut DebugRenderer> {
if self.failed {
return None;
}
if self.debug_renderer.is_none() {
match DebugRenderer::new(device) {
Ok(renderer) => { self.debug_renderer = Some(renderer); }
Err(_) => {
// The shader compilation code already logs errors.
self.failed = true;
}
}
}
self.debug_renderer.as_mut()
}
/// Returns mut ref to `debug::DebugRenderer` if one already exists, otherwise returns `None`.
pub fn try_get_mut<'a>(&'a mut self) -> Option<&'a mut DebugRenderer> {
self.debug_renderer.as_mut()
}
pub fn deinit(self, device: &mut Device) {
if let Some(debug_renderer) = self.debug_renderer {
debug_renderer.deinit(device);
}
}
}