This commit is contained in:
Mike Welsh 2019-04-27 10:54:37 -07:00
parent ba6843cf55
commit f69381cdda
12 changed files with 590 additions and 40 deletions

View File

@ -25,6 +25,7 @@ swf = { git = "https://github.com/Herschel/swf-rs", version = "*" }
# Desktop dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
lyon = "0.13.1"
structopt = "0.2.15"
glium = "0.24"
glutin = "0.20"

View File

@ -1,7 +1,18 @@
pub mod common;
pub mod glium;
pub mod shape_utils;
use swf::Shape;
#[cfg(target_arch = "wasm32")]
pub mod web_canvas;
use self::common::ShapeHandle;
use crate::{matrix::Matrix, Color};
pub trait RenderBackend {
//fn register_shape(shape: &Shape) {}
fn register_shape(&mut self, shape: &swf::Shape) -> common::ShapeHandle;
fn begin_frame(&mut self);
fn clear(&mut self, color: crate::Color);
fn render_shape(&mut self, shape: ShapeHandle, matrix: &Matrix);
fn end_frame(&mut self);
}

View File

@ -0,0 +1,2 @@
#[derive(Copy, Clone, Debug)]
pub struct ShapeHandle(pub usize);

View File

@ -1,17 +1,484 @@
use super::RenderBackend;
use super::{common::ShapeHandle, shape_utils, RenderBackend};
use crate::backend::ui::glutin::GlutinBackend;
use glium::Display;
use glutin::Context;
use crate::{matrix::Matrix, Color};
use glium::{implement_vertex, uniform, Display, Frame, Surface};
use lyon::tessellation::geometry_builder::{BuffersBuilder, VertexBuffers, VertexConstructor};
use lyon::tessellation::FillVertex;
use lyon::{path::PathEvent, tessellation, tessellation::FillTessellator};
use std::collections::{HashMap, VecDeque};
use swf::{FillStyle, LineStyle};
pub struct GliumRenderBackend {
display: Display,
target: Option<Frame>,
shader_program: glium::Program,
meshes: Vec<Mesh>,
}
impl GliumRenderBackend {
pub fn new(ui: &mut GlutinBackend) -> Result<GliumRenderBackend, Box<std::error::Error>> {
let display = Display::from_gl_window(ui.take_context())?;
Ok(GliumRenderBackend { display })
let shader_program =
glium::Program::from_source(&display, VERTEX_SHADER, FRAGMENT_SHADER, None)?;
Ok(GliumRenderBackend {
display,
shader_program,
target: None,
meshes: vec![],
})
}
}
impl RenderBackend for GliumRenderBackend {}
impl RenderBackend for GliumRenderBackend {
fn register_shape(&mut self, shape: &swf::Shape) -> ShapeHandle {
let handle = ShapeHandle(self.meshes.len());
use lyon::tessellation::FillOptions;
// let mut mesh: VertexBuffers<_, u16> = VertexBuffers::new();
// let paths = swf_shape_to_lyon_paths(shape);
// let fill_tess = FillTessellator::new();
// for path in paths {
// let mut buffers_builder = BuffersBuilder::new(&mut mesh, ());
// fill_tess.tessellate_path(
// path.into_iter(),
// &FillOptions::even_odd(),
// &mut buffers_builder,
// );
// }
let vertices = vec![
Vertex {
position: [0.0, 0.0],
color: [1.0, 0.0, 0.0, 1.0],
},
Vertex {
position: [100.0, 0.0],
color: [1.0, 0.0, 0.0, 1.0],
},
Vertex {
position: [100.0, 100.0],
color: [1.0, 0.0, 0.0, 1.0],
},
Vertex {
position: [0.0, 100.0],
color: [1.0, 0.0, 0.0, 1.0],
},
];
let indices = vec![0, 1, 2, 0, 2, 3];
let vertex_buffer = glium::VertexBuffer::new(&self.display, &vertices).unwrap();
let index_buffer = glium::IndexBuffer::new(
&self.display,
glium::index::PrimitiveType::TrianglesList,
&indices,
)
.unwrap();
let mesh = Mesh {
vertex_buffer,
index_buffer,
};
self.meshes.push(mesh);
handle
}
fn begin_frame(&mut self) {
assert!(self.target.is_none());
self.target = Some(self.display.draw());
}
fn end_frame(&mut self) {
assert!(self.target.is_some());
let target = self.target.take().unwrap();
target.finish().unwrap();
}
fn clear(&mut self, color: Color) {
let target = self.target.as_mut().unwrap();
target.clear_color(
f32::from(color.r) / 255.0,
f32::from(color.g) / 255.0,
f32::from(color.b) / 255.0,
f32::from(color.a) / 255.0,
);
}
fn render_shape(&mut self, shape: ShapeHandle, matrix: &Matrix) {
let mesh = &self.meshes[shape.0];
let view_matrix = [
[1.0 / 250.0f32, 0.0, 0.0, 0.0],
[0.0, -1.0 / 250.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[-1.0, 1.0, 0.0, 1.0],
];
let world_matrix = [
[matrix.a, matrix.b, 0.0, 0.0],
[matrix.b, matrix.d, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[matrix.tx, matrix.ty, 0.0, 1.0],
];
let target = self.target.as_mut().unwrap();
target
.draw(
&mesh.vertex_buffer,
&mesh.index_buffer,
&self.shader_program,
&uniform! { view_matrix: view_matrix, world_matrix: world_matrix },
&Default::default(),
)
.unwrap();
}
}
#[derive(Copy, Clone, Debug)]
struct Vertex {
position: [f32; 2],
color: [f32; 4],
}
implement_vertex!(Vertex, position, color);
const VERTEX_SHADER: &str = r#"
#version 140
uniform mat4 view_matrix;
uniform mat4 world_matrix;
in vec2 position;
in vec4 color;
out vec4 frag_color;
void main() {
frag_color = color;
gl_Position = view_matrix * world_matrix * vec4(position, 0.0, 1.0);
}
"#;
const FRAGMENT_SHADER: &str = r#"
#version 140
in vec4 frag_color;
out vec4 out_color;
void main() {
out_color = frag_color;
}
"#;
struct Mesh {
vertex_buffer: glium::VertexBuffer<Vertex>,
index_buffer: glium::IndexBuffer<u32>,
}
fn point(x: f32, y: f32) -> lyon::math::Point {
lyon::math::Point::new(x, y)
}
fn swf_shape_to_lyon_paths(shape: &swf::Shape) -> Vec<Vec<lyon::path::PathEvent>> {
let cmds = get_paths(shape);
let mut out_paths = vec![];
let mut prev;
use lyon::geom::{LineSegment, QuadraticBezierSegment};
for cmd in cmds {
if let PathCommandType::Fill(fill_style) = cmd.command_type {
let mut out_path = vec![PathEvent::MoveTo(point(cmd.path.start.0, cmd.path.start.1))];
prev = point(cmd.path.start.0, cmd.path.start.1);
for edge in cmd.path.edges {
let out_cmd = match edge {
PathEdge::Straight(x, y) => {
let cmd = PathEvent::Line(LineSegment {
from: prev,
to: point(x, y),
});
prev = point(x, y);
cmd
}
PathEdge::Bezier(x1, y1, x2, y2) => {
let cmd = PathEvent::Quadratic(QuadraticBezierSegment {
from: prev,
ctrl: point(x1, y1),
to: point(x2, y2),
});
prev = point(x2, y2);
cmd
}
};
out_path.push(out_cmd);
}
out_path.push(PathEvent::Close(LineSegment {
from: prev,
to: prev,
}));
out_paths.push(out_path);
}
}
out_paths
}
fn get_paths(shape: &swf::Shape) -> impl Iterator<Item = PathCommand> {
let mut x = 0.0;
let mut y = 0.0;
let mut fill_styles = &shape.styles.fill_styles;
let mut line_styles = &shape.styles.line_styles;
let mut fill_style_0 = 0;
let mut fill_style_1 = 0;
let mut line_style = 0;
let mut paths: HashMap<u32, PendingPaths> = HashMap::new();
let mut strokes: HashMap<u32, PendingPaths> = HashMap::new();
let mut out = vec![];
for record in &shape.shape {
use swf::ShapeRecord::*;
match record {
StyleChange(style_change) => {
if let Some((move_x, move_y)) = style_change.move_to {
x = move_x;
y = move_y;
}
if let Some(i) = style_change.fill_style_0 {
fill_style_0 = i;
}
if let Some(i) = style_change.fill_style_1 {
fill_style_1 = i;
}
if let Some(i) = style_change.line_style {
line_style = i;
}
if let Some(ref new_styles) = style_change.new_styles {
for (id, paths) in paths {
for path in paths.open_paths {
out.push(PathCommand {
command_type: paths.command_type.clone(),
path,
})
}
}
for (id, paths) in strokes {
for path in paths.open_paths {
out.push(PathCommand {
command_type: paths.command_type.clone(),
path,
})
}
}
paths = HashMap::new();
strokes = HashMap::new();
fill_styles = &new_styles.fill_styles;
line_styles = &new_styles.line_styles;
}
}
StraightEdge { delta_x, delta_y } => {
if fill_style_0 != 0 {
let path = paths.entry(fill_style_0).or_insert_with(|| {
PendingPaths::new(PathCommandType::Fill(
fill_styles[fill_style_0 as usize - 1].clone(),
))
});
path.add_edge((x + delta_x, y + delta_y), PathEdge::Straight(x, y));
}
if fill_style_1 != 0 {
let path = paths.entry(fill_style_1).or_insert_with(|| {
PendingPaths::new(PathCommandType::Fill(
fill_styles[fill_style_1 as usize - 1].clone(),
))
});
path.add_edge((x, y), PathEdge::Straight(x + delta_x, y + delta_y));
}
if line_style != 0 {
let path = strokes.entry(line_style).or_insert_with(|| {
PendingPaths::new(PathCommandType::Stroke(
line_styles[line_style as usize - 1].clone(),
))
});
path.add_edge((x, y), PathEdge::Straight(x + delta_x, y + delta_y));
}
x += delta_x;
y += delta_y;
}
CurvedEdge {
control_delta_x,
control_delta_y,
anchor_delta_x,
anchor_delta_y,
} => {
if fill_style_0 != 0 {
let path = paths.entry(fill_style_0).or_insert_with(|| {
PendingPaths::new(PathCommandType::Fill(
fill_styles[fill_style_0 as usize - 1].clone(),
))
});
path.add_edge(
(
x + control_delta_x + anchor_delta_x,
y + control_delta_y + anchor_delta_y,
),
PathEdge::Bezier(x + control_delta_x, y + control_delta_y, x, y),
);
}
if fill_style_1 != 0 {
let path = paths.entry(fill_style_1).or_insert_with(|| {
PendingPaths::new(PathCommandType::Fill(
fill_styles[fill_style_1 as usize - 1].clone(),
))
});
path.add_edge(
(x, y),
PathEdge::Bezier(
x + control_delta_x,
y + control_delta_y,
x + control_delta_x + anchor_delta_x,
y + control_delta_y + anchor_delta_y,
),
);
}
if line_style != 0 {
let path = strokes.entry(line_style).or_insert_with(|| {
PendingPaths::new(PathCommandType::Stroke(
line_styles[line_style as usize - 1].clone(),
))
});
path.add_edge(
(x, y),
PathEdge::Bezier(
x + control_delta_x,
y + control_delta_y,
x + control_delta_x + anchor_delta_x,
y + control_delta_y + anchor_delta_y,
),
);
}
x += control_delta_x + anchor_delta_x;
y += control_delta_y + anchor_delta_y;
}
}
}
for (id, paths) in paths {
for path in paths.open_paths {
out.push(PathCommand {
command_type: paths.command_type.clone(),
path,
})
}
}
for (id, paths) in strokes {
for path in paths.open_paths {
out.push(PathCommand {
command_type: paths.command_type.clone(),
path,
})
}
}
out.into_iter()
}
pub struct PathCommand {
command_type: PathCommandType,
path: Path,
}
#[derive(Clone, Debug)]
enum PathCommandType {
Fill(FillStyle),
Stroke(LineStyle),
}
struct PendingPaths {
command_type: PathCommandType,
open_paths: Vec<Path>,
}
impl PendingPaths {
fn new(command_type: PathCommandType) -> PendingPaths {
Self {
command_type,
open_paths: vec![],
}
}
fn add_edge(&mut self, start: (f32, f32), edge: PathEdge) {
let new_path = Path {
start,
end: match edge {
PathEdge::Straight(x, y) => (x, y),
PathEdge::Bezier(_cx, _cy, ax, ay) => (ax, ay),
},
edges: {
let mut edges = VecDeque::new();
edges.push_back(edge);
edges
},
};
self.merge_subpath(new_path);
}
fn merge_subpath(&mut self, mut path: Path) {
fn approx_eq(a: (f32, f32), b: (f32, f32)) -> bool {
let dx = a.0 - b.0;
let dy = a.1 - b.1;
const EPSILON: f32 = 0.0001;
dx.abs() < EPSILON && dy.abs() < EPSILON
}
let mut path_index = None;
for (i, other) in self.open_paths.iter_mut().enumerate() {
if approx_eq(path.end, other.start) {
other.start = path.start;
for edge in path.edges.iter().rev() {
other.edges.push_front(*edge);
}
path_index = Some(i);
break;
}
if approx_eq(other.end, path.start) {
other.end = path.end;
other.edges.append(&mut path.edges);
path_index = Some(i);
break;
}
}
if let Some(i) = path_index {
let path = self.open_paths.swap_remove(i);
self.merge_subpath(path);
} else {
self.open_paths.push(path);
}
}
}
struct Path {
start: (f32, f32),
end: (f32, f32),
edges: VecDeque<PathEdge>,
}
#[derive(Copy, Clone)]
enum PathEdge {
Straight(f32, f32),
Bezier(f32, f32, f32, f32),
}

View File

@ -266,7 +266,7 @@ struct Stroke {
}
// TODO(Herschel): Iterater-ize this.
fn swf_shape_to_paths(shape: &Shape) -> (Vec<Path>, Vec<Path>) {
pub fn swf_shape_to_paths(shape: &Shape) -> (Vec<Path>, Vec<Path>) {
let mut layers = vec![];
let mut paths = HashMap::<u32, Path>::new();
let mut stroke_paths = HashMap::<u32, Path>::new();

View File

@ -0,0 +1,62 @@
use super::RenderBackend;
use web_sys::{CanvasRenderingContext2d, HtmlImageElement>;
pub struct WebCanvasRenderBackend {
context: CanvasRenderingContext2d,
width: f64,
height: f64,
shapes: Vec<ShapeData>,
}
struct ShapeData {
image: HtmlImageElement,
x_min: f64,
y_min: f64,
}
impl WebCanvas {
fn new(context: CanvasRenderingContext2d, width: f64, height: f64) -> WebCanvas {
context: CanvasRenderingContext2d,
width: f64,
height: f64,
}
}
impl RenderBackend for WebCanvas {
fn register_shape(&mut self, shape: &swf::Shape) -> ShapeHandle {
let handle = ShapeHandle(self.meshes.len());
let image = HtmlImageElement::new().unwrap();
use url::percent_encoding::{percent_encode, DEFAULT_ENCODE_SET};
let svg = super::shape_utils::swf_shape_to_paths(&shape);
let svg_encoded = format!("data:image/xvg+xml;{}", percent_encode(svg, DEFAULT_ENCODE_SET));
image.set_src(&svg_encoded);
self.shapes.push(ShapeData{
image, x_min: shape.shape_bounds.x_min.into(), y_min: shape.shape_bounds.y_min.into()
});
handle
}
fn begin_frame(&mut self) {
context.reset_transform();
}
fn end_frame(&mut self) {
// Noop
}
fn clear(&mut self, color: Color) {
let color = format!("rgb({}, {}, {}", color.r, color.g, color.b);
context.fill_rect(0, 0, self.width, self.height, &color);
}
fn render_shape(&mut self, shape: ShapeHandle, matrix: &Matrix) {
let shape = &self.shapes[shape.0];
context.set_transform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty).unwrap();
context.draw_image_with_html_image_element(&shape.image, shape.x_min, shape.y_min).unwrap();
}
}

View File

@ -1,8 +1,8 @@
pub enum Character {
Graphic {
//image: HtmlImageElement,
x_min: f32,
y_min: f32,
shape_handle: crate::backend::render::common::ShapeHandle,
},
MovieClip {
num_frames: u16,

View File

@ -1,3 +1,4 @@
use crate::backend::render::common::ShapeHandle;
use crate::color_transform::ColorTransform;
use crate::display_object::DisplayObject;
use crate::matrix::Matrix;
@ -7,6 +8,7 @@ use bacon_rajan_cc::{Trace, Tracer};
use web_sys::HtmlImageElement;
pub struct Graphic {
shape_handle: ShapeHandle,
matrix: Matrix,
color_transform: ColorTransform,
x_min: f32,
@ -14,8 +16,9 @@ pub struct Graphic {
}
impl Graphic {
pub fn new(x_min: f32, y_min: f32) -> Graphic {
pub fn new(shape_handle: ShapeHandle, x_min: f32, y_min: f32) -> Graphic {
Graphic {
shape_handle,
color_transform: Default::default(),
x_min,
y_min,
@ -63,7 +66,10 @@ impl DisplayObject for Graphic {
// .draw_image_with_html_image_element(&self.image, self.x_min.into(), self.y_min.into())
// .expect("Couldn't render image");
context.matrix_stack.pop();
context
.renderer
.render_shape(self.shape_handle, &world_matrix);
context.color_transform_stack.push(&self.color_transform);
}

View File

@ -7,10 +7,10 @@ mod library;
mod matrix;
mod movie_clip;
mod player;
mod shape_utils;
mod stage;
pub use player::Player;
use swf::Color;
#[cfg(not(target_arch = "wasm32"))]
#[allow(dead_code)]

View File

@ -31,12 +31,13 @@ impl Library {
) -> Result<DisplayObjectNode, Box<std::error::Error>> {
match self.characters.get(&id) {
Some(Character::Graphic {
//image,
x_min,
y_min,
shape_handle,
}) => Ok(DisplayObjectNode::Graphic(Graphic::new(
//image.clone(),
*x_min, *y_min,
*shape_handle,
*x_min,
*y_min,
))),
Some(Character::MovieClip {
tag_stream_start,

View File

@ -27,14 +27,12 @@ pub struct Player {
render_context: RenderContext,
ui: Box<UiBackend>,
renderer: Box<RenderBackend>,
library: Library,
stage: Cc<RefCell<Stage>>,
frame_rate: f64,
frame_accumulator: f64,
//cur_timestamp: f64,
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
@ -49,9 +47,9 @@ impl Player {
tag_stream,
ui,
renderer,
render_context: RenderContext {
renderer,
matrix_stack: MatrixStack::new(),
color_transform_stack: ColorTransformStack::new(),
},
@ -61,7 +59,6 @@ impl Player {
frame_rate: swf.frame_rate.into(),
frame_accumulator: 0.0,
//cur_timestamp: std::time::Instant::now(),
})
}
@ -81,21 +78,18 @@ impl Player {
}
}
pub fn tick(&mut self, timestamp: f64) {
// let dt = timestamp - self.cur_timestamp;
// self.cur_timestamp = timestamp;
pub fn tick(&mut self, dt: f64) {
self.frame_accumulator += dt;
let frame_time = 1000.0 / self.frame_rate;
let needs_render = self.frame_accumulator >= frame_time;
while self.frame_accumulator >= frame_time {
self.frame_accumulator -= frame_time;
self.run_frame();
}
// self.frame_accumulator += dt;
// let frame_time = 1000.0 / self.frame_rate;
// let needs_render = self.frame_accumulator >= frame_time;
// while self.frame_accumulator >= frame_time {
// self.frame_accumulator -= frame_time;
// self.run_frame();
// }
// if needs_render {
// self.render();
// }
if needs_render {
self.render();
}
}
}
@ -105,6 +99,7 @@ impl Player {
tag_stream: &mut self.tag_stream,
position_stack: vec![],
library: &mut self.library,
renderer: &mut *self.render_context.renderer,
};
let mut stage = self.stage.borrow_mut();
@ -113,10 +108,11 @@ impl Player {
}
fn render(&mut self) {
self.render_context.renderer.begin_frame();
let stage = self.stage.borrow_mut();
/*
let background_color = stage.background_color();
/*let background_color = stage.background_color();
let css_color = format!(
"rgb({}, {}, {})",
background_color.r, background_color.g, background_color.b
@ -132,9 +128,11 @@ impl Player {
self.render_context
.context_2d
.fill_rect(0.0, 0.0, width, height);
*/
*/
stage.render(&mut self.render_context);
self.render_context.renderer.end_frame();
}
}
@ -142,10 +140,11 @@ pub struct UpdateContext<'a> {
pub tag_stream: &'a mut swf::read::Reader<Cursor<Vec<u8>>>,
pub position_stack: Vec<u64>,
pub library: &'a mut Library,
pub renderer: &'a mut RenderBackend,
}
pub struct RenderContext {
//pub context_2d: CanvasRenderingContext2d,
pub renderer: Box<RenderBackend>,
pub matrix_stack: MatrixStack,
pub color_transform_stack: ColorTransformStack,
}

View File

@ -79,12 +79,11 @@ impl DisplayObject for Stage {
Tag::DefineShape(shape) => {
if !context.library.contains_character(shape.id) {
//let mut image = HtmlImageElement::new().unwrap();
//image.set_src(&url_encoded_svg);
let shape_handle = context.renderer.register_shape(&shape);
context.library.register_character(
shape.id,
Character::Graphic {
//image,
shape_handle,
x_min: shape.shape_bounds.x_min,
y_min: shape.shape_bounds.y_min,
},
@ -155,6 +154,8 @@ impl DisplayObject for Stage {
}
fn render(&self, context: &mut RenderContext) {
context.renderer.clear(self.background_color.clone());
context.matrix_stack.push(&self.matrix);
context.color_transform_stack.push(&self.color_transform);