1369 lines
47 KiB
Rust
1369 lines
47 KiB
Rust
use glium::uniforms::{Sampler, UniformValue, Uniforms};
|
|
use glium::{draw_parameters::DrawParameters, implement_vertex, uniform, Display, Frame, Surface};
|
|
use glutin::WindowedContext;
|
|
use lyon::tessellation::geometry_builder::{BuffersBuilder, VertexBuffers};
|
|
use lyon::{
|
|
path::PathEvent, tessellation, tessellation::FillTessellator, tessellation::StrokeTessellator,
|
|
};
|
|
use ruffle_core::backend::render::swf::{self, FillStyle};
|
|
use ruffle_core::backend::render::{
|
|
BitmapHandle, BitmapInfo, Color, Letterbox, RenderBackend, ShapeHandle, Transform,
|
|
};
|
|
use ruffle_core::shape_utils::{DrawCommand, DrawPath};
|
|
use std::convert::TryInto;
|
|
use swf::Twips;
|
|
|
|
type Error = Box<dyn std::error::Error>;
|
|
|
|
pub struct GliumRenderBackend {
|
|
display: Display,
|
|
target: Option<Frame>,
|
|
shader_program: glium::Program,
|
|
gradient_shader_program: glium::Program,
|
|
bitmap_shader_program: glium::Program,
|
|
meshes: Vec<Mesh>,
|
|
quad_shape: ShapeHandle,
|
|
textures: Vec<(swf::CharacterId, Texture)>,
|
|
num_masks: u32,
|
|
num_masks_active: u32,
|
|
write_stencil_mask: u32,
|
|
test_stencil_mask: u32,
|
|
next_stencil_mask: u32,
|
|
mask_stack: Vec<(u32, u32)>,
|
|
viewport_width: f32,
|
|
viewport_height: f32,
|
|
view_matrix: [[f32; 4]; 4],
|
|
}
|
|
|
|
impl GliumRenderBackend {
|
|
pub fn new<T: glutin::ContextCurrentState>(
|
|
windowed_context: WindowedContext<T>,
|
|
) -> Result<GliumRenderBackend, Error> {
|
|
let display = Display::from_gl_window(windowed_context)?;
|
|
|
|
use glium::program::ProgramCreationInput;
|
|
let shader_program = glium::Program::new(
|
|
&display,
|
|
ProgramCreationInput::SourceCode {
|
|
vertex_shader: VERTEX_SHADER,
|
|
fragment_shader: FRAGMENT_SHADER,
|
|
geometry_shader: None,
|
|
tessellation_control_shader: None,
|
|
tessellation_evaluation_shader: None,
|
|
transform_feedback_varyings: None,
|
|
outputs_srgb: true,
|
|
uses_point_size: false,
|
|
},
|
|
)?;
|
|
|
|
let gradient_shader_program = glium::Program::new(
|
|
&display,
|
|
ProgramCreationInput::SourceCode {
|
|
vertex_shader: TEXTURE_VERTEX_SHADER,
|
|
fragment_shader: GRADIENT_FRAGMENT_SHADER,
|
|
geometry_shader: None,
|
|
tessellation_control_shader: None,
|
|
tessellation_evaluation_shader: None,
|
|
transform_feedback_varyings: None,
|
|
outputs_srgb: true,
|
|
uses_point_size: false,
|
|
},
|
|
)?;
|
|
|
|
let bitmap_shader_program = glium::Program::new(
|
|
&display,
|
|
ProgramCreationInput::SourceCode {
|
|
vertex_shader: TEXTURE_VERTEX_SHADER,
|
|
fragment_shader: BITMAP_FRAGMENT_SHADER,
|
|
geometry_shader: None,
|
|
tessellation_control_shader: None,
|
|
tessellation_evaluation_shader: None,
|
|
transform_feedback_varyings: None,
|
|
outputs_srgb: true,
|
|
uses_point_size: false,
|
|
},
|
|
)?;
|
|
|
|
let quad_mesh = Self::build_quad_mesh(&display)?;
|
|
let quad_shape = ShapeHandle(0);
|
|
|
|
let mut renderer = GliumRenderBackend {
|
|
display,
|
|
shader_program,
|
|
gradient_shader_program,
|
|
bitmap_shader_program,
|
|
target: None,
|
|
meshes: vec![quad_mesh],
|
|
quad_shape,
|
|
textures: vec![],
|
|
viewport_width: 500.0,
|
|
viewport_height: 500.0,
|
|
view_matrix: [[0.0; 4]; 4],
|
|
num_masks: 0,
|
|
num_masks_active: 0,
|
|
write_stencil_mask: 0,
|
|
test_stencil_mask: 0,
|
|
next_stencil_mask: 1,
|
|
mask_stack: vec![],
|
|
};
|
|
renderer.build_matrices();
|
|
Ok(renderer)
|
|
}
|
|
|
|
// Builds the quad mesh that is used for rendering bitmap display objects.
|
|
fn build_quad_mesh(display: &Display) -> Result<Mesh, Error> {
|
|
let vertex_buffer = glium::VertexBuffer::new(
|
|
display,
|
|
&[
|
|
Vertex {
|
|
position: [0.0, 0.0],
|
|
color: [1.0, 1.0, 1.0, 1.0],
|
|
},
|
|
Vertex {
|
|
position: [1.0, 0.0],
|
|
color: [1.0, 1.0, 1.0, 1.0],
|
|
},
|
|
Vertex {
|
|
position: [1.0, 1.0],
|
|
color: [1.0, 1.0, 1.0, 1.0],
|
|
},
|
|
Vertex {
|
|
position: [0.0, 1.0],
|
|
color: [1.0, 1.0, 1.0, 1.0],
|
|
},
|
|
],
|
|
)?;
|
|
|
|
let index_buffer = glium::IndexBuffer::new(
|
|
display,
|
|
glium::index::PrimitiveType::TrianglesList,
|
|
&[0, 1, 2, 0, 2, 3],
|
|
)?;
|
|
|
|
let quad_mesh = Mesh {
|
|
draws: vec![Draw {
|
|
draw_type: DrawType::Bitmap {
|
|
uniforms: BitmapUniforms {
|
|
matrix: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
|
|
id: 0,
|
|
},
|
|
is_smoothed: true,
|
|
is_repeating: false,
|
|
},
|
|
vertex_buffer,
|
|
index_buffer,
|
|
}],
|
|
};
|
|
Ok(quad_mesh)
|
|
}
|
|
|
|
pub fn display(&self) -> &Display {
|
|
&self.display
|
|
}
|
|
|
|
fn register_shape_internal(&mut self, shape: &swf::Shape) -> ShapeHandle {
|
|
let handle = ShapeHandle(self.meshes.len());
|
|
let paths = ruffle_core::shape_utils::swf_shape_to_paths(shape);
|
|
|
|
use lyon::tessellation::{FillOptions, StrokeOptions};
|
|
|
|
let mut mesh = Mesh { draws: vec![] };
|
|
|
|
let mut fill_tess = FillTessellator::new();
|
|
let mut stroke_tess = StrokeTessellator::new();
|
|
let mut lyon_mesh: VertexBuffers<_, u32> = VertexBuffers::new();
|
|
|
|
fn flush_draw(
|
|
draw: DrawType,
|
|
mesh: &mut Mesh,
|
|
lyon_mesh: &mut VertexBuffers<Vertex, u32>,
|
|
display: &Display,
|
|
) {
|
|
if lyon_mesh.vertices.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let vertex_buffer = glium::VertexBuffer::new(display, &lyon_mesh.vertices[..]).unwrap();
|
|
|
|
let index_buffer = glium::IndexBuffer::new(
|
|
display,
|
|
glium::index::PrimitiveType::TrianglesList,
|
|
&lyon_mesh.indices[..],
|
|
)
|
|
.unwrap();
|
|
|
|
mesh.draws.push(Draw {
|
|
draw_type: draw,
|
|
vertex_buffer,
|
|
index_buffer,
|
|
});
|
|
|
|
*lyon_mesh = VertexBuffers::new();
|
|
}
|
|
|
|
for path in paths {
|
|
match path {
|
|
DrawPath::Fill { style, commands } => match style {
|
|
FillStyle::Color(color) => {
|
|
let 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,
|
|
];
|
|
|
|
let vertex_ctor = move |vertex: tessellation::FillVertex| Vertex {
|
|
position: [vertex.position.x, vertex.position.y],
|
|
color,
|
|
};
|
|
let mut buffers_builder = BuffersBuilder::new(&mut lyon_mesh, vertex_ctor);
|
|
|
|
if let Err(e) = fill_tess.tessellate_path(
|
|
ruffle_path_to_lyon_path(commands, true),
|
|
&FillOptions::even_odd(),
|
|
&mut buffers_builder,
|
|
) {
|
|
// This may just be a degenerate path; skip it.
|
|
log::error!("Tessellation failure: {:?}", e);
|
|
continue;
|
|
}
|
|
}
|
|
FillStyle::LinearGradient(gradient) => {
|
|
flush_draw(DrawType::Color, &mut mesh, &mut lyon_mesh, &self.display);
|
|
|
|
let vertex_ctor = move |vertex: tessellation::FillVertex| Vertex {
|
|
position: [vertex.position.x, vertex.position.y],
|
|
color: [1.0, 1.0, 1.0, 1.0],
|
|
};
|
|
let mut buffers_builder = BuffersBuilder::new(&mut lyon_mesh, vertex_ctor);
|
|
|
|
if let Err(e) = fill_tess.tessellate_path(
|
|
ruffle_path_to_lyon_path(commands, true),
|
|
&FillOptions::even_odd(),
|
|
&mut buffers_builder,
|
|
) {
|
|
// This may just be a degenerate path; skip it.
|
|
log::error!("Tessellation failure: {:?}", e);
|
|
continue;
|
|
}
|
|
|
|
let mut colors: Vec<[f32; 4]> = Vec::with_capacity(8);
|
|
let mut ratios: Vec<f32> = Vec::with_capacity(8);
|
|
for record in &gradient.records {
|
|
colors.push([
|
|
f32::from(record.color.r) / 255.0,
|
|
f32::from(record.color.g) / 255.0,
|
|
f32::from(record.color.b) / 255.0,
|
|
f32::from(record.color.a) / 255.0,
|
|
]);
|
|
ratios.push(f32::from(record.ratio) / 255.0);
|
|
}
|
|
|
|
let uniforms = GradientUniforms {
|
|
gradient_type: 0,
|
|
ratios,
|
|
colors,
|
|
num_colors: gradient.records.len() as u32,
|
|
matrix: swf_to_gl_matrix(gradient.matrix.clone()),
|
|
repeat_mode: 0,
|
|
focal_point: 0.0,
|
|
};
|
|
|
|
flush_draw(
|
|
DrawType::Gradient(uniforms),
|
|
&mut mesh,
|
|
&mut lyon_mesh,
|
|
&self.display,
|
|
);
|
|
}
|
|
FillStyle::RadialGradient(gradient) => {
|
|
flush_draw(DrawType::Color, &mut mesh, &mut lyon_mesh, &self.display);
|
|
|
|
let vertex_ctor = move |vertex: tessellation::FillVertex| Vertex {
|
|
position: [vertex.position.x, vertex.position.y],
|
|
color: [1.0, 1.0, 1.0, 1.0],
|
|
};
|
|
let mut buffers_builder = BuffersBuilder::new(&mut lyon_mesh, vertex_ctor);
|
|
|
|
if let Err(e) = fill_tess.tessellate_path(
|
|
ruffle_path_to_lyon_path(commands, true),
|
|
&FillOptions::even_odd(),
|
|
&mut buffers_builder,
|
|
) {
|
|
// This may just be a degenerate path; skip it.
|
|
log::error!("Tessellation failure: {:?}", e);
|
|
continue;
|
|
}
|
|
|
|
let mut colors: Vec<[f32; 4]> = Vec::with_capacity(8);
|
|
let mut ratios: Vec<f32> = Vec::with_capacity(8);
|
|
for record in &gradient.records {
|
|
colors.push([
|
|
f32::from(record.color.r) / 255.0,
|
|
f32::from(record.color.g) / 255.0,
|
|
f32::from(record.color.b) / 255.0,
|
|
f32::from(record.color.a) / 255.0,
|
|
]);
|
|
ratios.push(f32::from(record.ratio) / 255.0);
|
|
}
|
|
|
|
let uniforms = GradientUniforms {
|
|
gradient_type: 1,
|
|
ratios,
|
|
colors,
|
|
num_colors: gradient.records.len() as u32,
|
|
matrix: swf_to_gl_matrix(gradient.matrix.clone()),
|
|
repeat_mode: 0,
|
|
focal_point: 0.0,
|
|
};
|
|
|
|
flush_draw(
|
|
DrawType::Gradient(uniforms),
|
|
&mut mesh,
|
|
&mut lyon_mesh,
|
|
&self.display,
|
|
);
|
|
}
|
|
FillStyle::FocalGradient {
|
|
gradient,
|
|
focal_point,
|
|
} => {
|
|
flush_draw(DrawType::Color, &mut mesh, &mut lyon_mesh, &self.display);
|
|
|
|
let vertex_ctor = move |vertex: tessellation::FillVertex| Vertex {
|
|
position: [vertex.position.x, vertex.position.y],
|
|
color: [1.0, 1.0, 1.0, 1.0],
|
|
};
|
|
let mut buffers_builder = BuffersBuilder::new(&mut lyon_mesh, vertex_ctor);
|
|
|
|
if let Err(e) = fill_tess.tessellate_path(
|
|
ruffle_path_to_lyon_path(commands, true),
|
|
&FillOptions::even_odd(),
|
|
&mut buffers_builder,
|
|
) {
|
|
// This may just be a degenerate path; skip it.
|
|
log::error!("Tessellation failure: {:?}", e);
|
|
continue;
|
|
}
|
|
|
|
let mut colors: Vec<[f32; 4]> = Vec::with_capacity(8);
|
|
let mut ratios: Vec<f32> = Vec::with_capacity(8);
|
|
for record in &gradient.records {
|
|
colors.push([
|
|
f32::from(record.color.r) / 255.0,
|
|
f32::from(record.color.g) / 255.0,
|
|
f32::from(record.color.b) / 255.0,
|
|
f32::from(record.color.a) / 255.0,
|
|
]);
|
|
ratios.push(f32::from(record.ratio) / 255.0);
|
|
}
|
|
|
|
let uniforms = GradientUniforms {
|
|
gradient_type: 1,
|
|
ratios,
|
|
colors,
|
|
num_colors: gradient.records.len() as u32,
|
|
matrix: swf_to_gl_matrix(gradient.matrix.clone()),
|
|
repeat_mode: 0,
|
|
focal_point: *focal_point,
|
|
};
|
|
|
|
flush_draw(
|
|
DrawType::Gradient(uniforms),
|
|
&mut mesh,
|
|
&mut lyon_mesh,
|
|
&self.display,
|
|
);
|
|
}
|
|
FillStyle::Bitmap {
|
|
id,
|
|
matrix,
|
|
is_smoothed,
|
|
is_repeating,
|
|
} => {
|
|
flush_draw(DrawType::Color, &mut mesh, &mut lyon_mesh, &self.display);
|
|
|
|
let vertex_ctor = move |vertex: tessellation::FillVertex| Vertex {
|
|
position: [vertex.position.x, vertex.position.y],
|
|
color: [1.0, 1.0, 1.0, 1.0],
|
|
};
|
|
let mut buffers_builder = BuffersBuilder::new(&mut lyon_mesh, vertex_ctor);
|
|
|
|
if let Err(e) = fill_tess.tessellate_path(
|
|
ruffle_path_to_lyon_path(commands, true),
|
|
&FillOptions::even_odd(),
|
|
&mut buffers_builder,
|
|
) {
|
|
// This may just be a degenerate path; skip it.
|
|
log::error!("Tessellation failure: {:?}", e);
|
|
continue;
|
|
}
|
|
|
|
let texture = &self
|
|
.textures
|
|
.iter()
|
|
.find(|(other_id, _tex)| *other_id == *id)
|
|
.unwrap()
|
|
.1;
|
|
|
|
let uniforms = BitmapUniforms {
|
|
matrix: swf_bitmap_to_gl_matrix(
|
|
matrix.clone(),
|
|
texture.width,
|
|
texture.height,
|
|
),
|
|
id: *id,
|
|
};
|
|
|
|
flush_draw(
|
|
DrawType::Bitmap {
|
|
uniforms,
|
|
is_smoothed: *is_smoothed,
|
|
is_repeating: *is_repeating,
|
|
},
|
|
&mut mesh,
|
|
&mut lyon_mesh,
|
|
&self.display,
|
|
);
|
|
}
|
|
},
|
|
DrawPath::Stroke {
|
|
style,
|
|
commands,
|
|
is_closed,
|
|
} => {
|
|
let color = [
|
|
f32::from(style.color.r) / 255.0,
|
|
f32::from(style.color.g) / 255.0,
|
|
f32::from(style.color.b) / 255.0,
|
|
f32::from(style.color.a) / 255.0,
|
|
];
|
|
|
|
let vertex_ctor = move |vertex: tessellation::StrokeVertex| Vertex {
|
|
position: [vertex.position.x, vertex.position.y],
|
|
color,
|
|
};
|
|
let mut buffers_builder = BuffersBuilder::new(&mut lyon_mesh, vertex_ctor);
|
|
|
|
// TODO(Herschel): 0 width indicates "hairline".
|
|
let width = if style.width.to_pixels() >= 1.0 {
|
|
style.width.to_pixels() as f32
|
|
} else {
|
|
1.0
|
|
};
|
|
|
|
let mut options = StrokeOptions::default()
|
|
.with_line_width(width)
|
|
.with_line_join(match style.join_style {
|
|
swf::LineJoinStyle::Round => tessellation::LineJoin::Round,
|
|
swf::LineJoinStyle::Bevel => tessellation::LineJoin::Bevel,
|
|
swf::LineJoinStyle::Miter(_) => tessellation::LineJoin::MiterClip,
|
|
})
|
|
.with_start_cap(match style.start_cap {
|
|
swf::LineCapStyle::None => tessellation::LineCap::Butt,
|
|
swf::LineCapStyle::Round => tessellation::LineCap::Round,
|
|
swf::LineCapStyle::Square => tessellation::LineCap::Square,
|
|
})
|
|
.with_end_cap(match style.end_cap {
|
|
swf::LineCapStyle::None => tessellation::LineCap::Butt,
|
|
swf::LineCapStyle::Round => tessellation::LineCap::Round,
|
|
swf::LineCapStyle::Square => tessellation::LineCap::Square,
|
|
});
|
|
|
|
if let swf::LineJoinStyle::Miter(limit) = style.join_style {
|
|
options = options.with_miter_limit(limit);
|
|
}
|
|
|
|
if let Err(e) = stroke_tess.tessellate_path(
|
|
ruffle_path_to_lyon_path(commands, is_closed),
|
|
&options,
|
|
&mut buffers_builder,
|
|
) {
|
|
// This may just be a degenerate path; skip it.
|
|
log::error!("Tessellation failure: {:?}", e);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
flush_draw(DrawType::Color, &mut mesh, &mut lyon_mesh, &self.display);
|
|
|
|
self.meshes.push(mesh);
|
|
|
|
handle
|
|
}
|
|
|
|
fn build_matrices(&mut self) {
|
|
self.view_matrix = [
|
|
[1.0 / (self.viewport_width as f32 / 2.0), 0.0, 0.0, 0.0],
|
|
[0.0, -1.0 / (self.viewport_height as f32 / 2.0), 0.0, 0.0],
|
|
[0.0, 0.0, 1.0, 0.0],
|
|
[-1.0, 1.0, 0.0, 1.0],
|
|
];
|
|
}
|
|
}
|
|
|
|
impl RenderBackend for GliumRenderBackend {
|
|
fn set_viewport_dimensions(&mut self, width: u32, height: u32) {
|
|
self.viewport_width = width as f32;
|
|
self.viewport_height = height as f32;
|
|
self.build_matrices();
|
|
}
|
|
|
|
fn register_shape(&mut self, shape: &swf::Shape) -> ShapeHandle {
|
|
self.register_shape_internal(shape)
|
|
}
|
|
|
|
fn register_glyph_shape(&mut self, glyph: &swf::Glyph) -> ShapeHandle {
|
|
let shape = swf::Shape {
|
|
version: 2,
|
|
id: 0,
|
|
shape_bounds: Default::default(),
|
|
edge_bounds: Default::default(),
|
|
has_fill_winding_rule: false,
|
|
has_non_scaling_strokes: false,
|
|
has_scaling_strokes: true,
|
|
styles: swf::ShapeStyles {
|
|
fill_styles: vec![FillStyle::Color(Color {
|
|
r: 255,
|
|
g: 255,
|
|
b: 255,
|
|
a: 255,
|
|
})],
|
|
line_styles: vec![],
|
|
},
|
|
shape: glyph.shape_records.clone(),
|
|
};
|
|
self.register_shape_internal(&shape)
|
|
}
|
|
|
|
fn register_bitmap_jpeg(
|
|
&mut self,
|
|
id: swf::CharacterId,
|
|
data: &[u8],
|
|
jpeg_tables: Option<&[u8]>,
|
|
) -> BitmapInfo {
|
|
let data = ruffle_core::backend::render::glue_tables_to_jpeg(data, jpeg_tables);
|
|
self.register_bitmap_jpeg_2(id, &data[..])
|
|
}
|
|
|
|
fn register_bitmap_jpeg_2(&mut self, id: swf::CharacterId, data: &[u8]) -> BitmapInfo {
|
|
let data = ruffle_core::backend::render::remove_invalid_jpeg_data(data);
|
|
|
|
let mut decoder = jpeg_decoder::Decoder::new(&data[..]);
|
|
decoder.read_info().unwrap();
|
|
let metadata = decoder.info().unwrap();
|
|
let decoded_data = decoder.decode().expect("failed to decode image");
|
|
let image = glium::texture::RawImage2d::from_raw_rgb(
|
|
decoded_data,
|
|
(metadata.width.into(), metadata.height.into()),
|
|
);
|
|
|
|
let texture = glium::texture::Texture2d::new(&self.display, image).unwrap();
|
|
|
|
let handle = BitmapHandle(self.textures.len());
|
|
self.textures.push((
|
|
id,
|
|
Texture {
|
|
texture,
|
|
width: metadata.width.into(),
|
|
height: metadata.height.into(),
|
|
},
|
|
));
|
|
|
|
BitmapInfo {
|
|
handle,
|
|
width: metadata.width,
|
|
height: metadata.height,
|
|
}
|
|
}
|
|
|
|
fn register_bitmap_jpeg_3(
|
|
&mut self,
|
|
id: swf::CharacterId,
|
|
jpeg_data: &[u8],
|
|
alpha_data: &[u8],
|
|
) -> BitmapInfo {
|
|
let (width, height, rgba) =
|
|
ruffle_core::backend::render::define_bits_jpeg_to_rgba(jpeg_data, alpha_data)
|
|
.expect("Error decoding DefineBitsJPEG3");
|
|
|
|
let image = glium::texture::RawImage2d::from_raw_rgba(rgba, (width, height));
|
|
let texture = glium::texture::Texture2d::new(&self.display, image).unwrap();
|
|
let handle = BitmapHandle(self.textures.len());
|
|
self.textures.push((
|
|
id,
|
|
Texture {
|
|
texture,
|
|
width,
|
|
height,
|
|
},
|
|
));
|
|
|
|
BitmapInfo {
|
|
handle,
|
|
width: width.try_into().unwrap(),
|
|
height: height.try_into().unwrap(),
|
|
}
|
|
}
|
|
|
|
fn register_bitmap_png(&mut self, swf_tag: &swf::DefineBitsLossless) -> BitmapInfo {
|
|
let decoded_data = ruffle_core::backend::render::define_bits_lossless_to_rgba(swf_tag)
|
|
.expect("Error decoding DefineBitsLossless");
|
|
|
|
let image = glium::texture::RawImage2d::from_raw_rgba(
|
|
decoded_data,
|
|
(swf_tag.width.into(), swf_tag.height.into()),
|
|
);
|
|
|
|
let texture = glium::texture::Texture2d::new(&self.display, image).unwrap();
|
|
|
|
let handle = BitmapHandle(self.textures.len());
|
|
self.textures.push((
|
|
swf_tag.id,
|
|
Texture {
|
|
texture,
|
|
width: swf_tag.width.into(),
|
|
height: swf_tag.height.into(),
|
|
},
|
|
));
|
|
|
|
BitmapInfo {
|
|
handle,
|
|
width: swf_tag.width,
|
|
height: swf_tag.height,
|
|
}
|
|
}
|
|
|
|
fn begin_frame(&mut self) {
|
|
assert!(self.target.is_none());
|
|
self.target = Some(self.display.draw());
|
|
self.num_masks = 0;
|
|
self.num_masks_active = 0;
|
|
self.write_stencil_mask = 0;
|
|
self.test_stencil_mask = 0;
|
|
self.next_stencil_mask = 1;
|
|
}
|
|
|
|
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_srgb_and_stencil(
|
|
(
|
|
f32::from(color.r) / 255.0,
|
|
f32::from(color.g) / 255.0,
|
|
f32::from(color.b) / 255.0,
|
|
f32::from(color.a) / 255.0,
|
|
),
|
|
0,
|
|
);
|
|
}
|
|
|
|
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform) {
|
|
// TODO: Might be better to make this separate code to render the bitmap
|
|
// instead of going through render_shape. But render_shape already handles
|
|
// masking etc.
|
|
if let Some((id, bitmap)) = self.textures.get(bitmap.0) {
|
|
// Adjust the quad draw to use the target bitmap.
|
|
let mesh = &mut self.meshes[self.quad_shape.0];
|
|
let draw = &mut mesh.draws[0];
|
|
let width = bitmap.width as f32;
|
|
let height = bitmap.height as f32;
|
|
if let DrawType::Bitmap {
|
|
uniforms: BitmapUniforms { id: draw_id, .. },
|
|
..
|
|
} = &mut draw.draw_type
|
|
{
|
|
*draw_id = *id;
|
|
}
|
|
|
|
// Scale the quad to the bitmap's dimensions.
|
|
use ruffle_core::matrix::Matrix;
|
|
let scale_transform = Transform {
|
|
matrix: transform.matrix
|
|
* Matrix {
|
|
a: width,
|
|
d: height,
|
|
..Default::default()
|
|
},
|
|
..*transform
|
|
};
|
|
|
|
// Render the quad.
|
|
self.render_shape(self.quad_shape, &scale_transform);
|
|
}
|
|
}
|
|
|
|
fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform) {
|
|
let target = self.target.as_mut().unwrap();
|
|
|
|
let mesh = &self.meshes[shape.0];
|
|
|
|
let world_matrix = [
|
|
[transform.matrix.a, transform.matrix.b, 0.0, 0.0],
|
|
[transform.matrix.c, transform.matrix.d, 0.0, 0.0],
|
|
[0.0, 0.0, 1.0, 0.0],
|
|
[
|
|
transform.matrix.tx / 20.0,
|
|
transform.matrix.ty / 20.0,
|
|
0.0,
|
|
1.0,
|
|
],
|
|
];
|
|
|
|
let mult_color = [
|
|
transform.color_transform.r_mult,
|
|
transform.color_transform.g_mult,
|
|
transform.color_transform.b_mult,
|
|
transform.color_transform.a_mult,
|
|
];
|
|
|
|
let add_color = [
|
|
transform.color_transform.r_add,
|
|
transform.color_transform.g_add,
|
|
transform.color_transform.b_add,
|
|
transform.color_transform.a_add,
|
|
];
|
|
|
|
let mut draw_parameters = DrawParameters::default();
|
|
mask_draw_parameters(
|
|
&mut draw_parameters,
|
|
self.num_masks,
|
|
self.num_masks_active,
|
|
self.write_stencil_mask,
|
|
self.test_stencil_mask,
|
|
);
|
|
|
|
for draw in &mesh.draws {
|
|
match &draw.draw_type {
|
|
DrawType::Color => {
|
|
draw_parameters.blend = color_blend();
|
|
|
|
target
|
|
.draw(
|
|
&draw.vertex_buffer,
|
|
&draw.index_buffer,
|
|
&self.shader_program,
|
|
&uniform! { view_matrix: self.view_matrix, world_matrix: world_matrix, mult_color: mult_color, add_color: add_color },
|
|
&draw_parameters
|
|
)
|
|
.unwrap();
|
|
}
|
|
DrawType::Gradient(gradient_uniforms) => {
|
|
let uniforms = GradientUniformsFull {
|
|
view_matrix: self.view_matrix,
|
|
world_matrix,
|
|
mult_color,
|
|
add_color,
|
|
gradient: gradient_uniforms.clone(),
|
|
};
|
|
|
|
draw_parameters.blend = color_blend();
|
|
|
|
target
|
|
.draw(
|
|
&draw.vertex_buffer,
|
|
&draw.index_buffer,
|
|
&self.gradient_shader_program,
|
|
&uniforms,
|
|
&draw_parameters,
|
|
)
|
|
.unwrap();
|
|
}
|
|
DrawType::Bitmap {
|
|
uniforms,
|
|
is_smoothed,
|
|
is_repeating,
|
|
} => {
|
|
let texture = &self
|
|
.textures
|
|
.iter()
|
|
.find(|(id, _tex)| *id == uniforms.id)
|
|
.unwrap()
|
|
.1;
|
|
|
|
// Set texture sampler smooth/repeat parameters.
|
|
use glium::uniforms::{
|
|
MagnifySamplerFilter, MinifySamplerFilter, SamplerWrapFunction,
|
|
};
|
|
let texture = &texture
|
|
.texture
|
|
.sampled()
|
|
.magnify_filter(if *is_smoothed {
|
|
MagnifySamplerFilter::Linear
|
|
} else {
|
|
MagnifySamplerFilter::Nearest
|
|
})
|
|
.minify_filter(if *is_smoothed {
|
|
MinifySamplerFilter::LinearMipmapLinear
|
|
} else {
|
|
MinifySamplerFilter::Nearest
|
|
})
|
|
.wrap_function(if *is_repeating {
|
|
SamplerWrapFunction::Repeat
|
|
} else {
|
|
SamplerWrapFunction::Clamp
|
|
});
|
|
|
|
let uniforms = BitmapUniformsFull {
|
|
view_matrix: self.view_matrix,
|
|
world_matrix,
|
|
mult_color,
|
|
add_color,
|
|
matrix: uniforms.matrix,
|
|
texture,
|
|
};
|
|
|
|
draw_parameters.blend = bitmap_blend();
|
|
|
|
target
|
|
.draw(
|
|
&draw.vertex_buffer,
|
|
&draw.index_buffer,
|
|
&self.bitmap_shader_program,
|
|
&uniforms,
|
|
&draw_parameters,
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn draw_pause_overlay(&mut self) {}
|
|
|
|
fn draw_letterbox(&mut self, letterbox: Letterbox) {
|
|
let target = self.target.as_mut().unwrap();
|
|
let black = Some((0.0, 0.0, 0.0, 1.0));
|
|
match letterbox {
|
|
Letterbox::None => (),
|
|
Letterbox::Letterbox(margin_height) => {
|
|
target.clear(
|
|
Some(&glium::Rect {
|
|
left: 0,
|
|
bottom: 0,
|
|
width: self.viewport_width as u32,
|
|
height: margin_height as u32,
|
|
}),
|
|
black,
|
|
true,
|
|
None,
|
|
None,
|
|
);
|
|
target.clear(
|
|
Some(&glium::Rect {
|
|
left: 0,
|
|
bottom: (self.viewport_height - margin_height) as u32,
|
|
width: self.viewport_width as u32,
|
|
height: margin_height as u32,
|
|
}),
|
|
black,
|
|
true,
|
|
None,
|
|
None,
|
|
);
|
|
}
|
|
Letterbox::Pillarbox(margin_width) => {
|
|
target.clear(
|
|
Some(&glium::Rect {
|
|
left: 0,
|
|
bottom: 0,
|
|
width: margin_width as u32,
|
|
height: self.viewport_height as u32,
|
|
}),
|
|
black,
|
|
true,
|
|
None,
|
|
None,
|
|
);
|
|
target.clear(
|
|
Some(&glium::Rect {
|
|
left: (self.viewport_width - margin_width) as u32,
|
|
bottom: 0,
|
|
width: margin_width as u32,
|
|
height: self.viewport_height as u32,
|
|
}),
|
|
black,
|
|
true,
|
|
None,
|
|
None,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn push_mask(&mut self) {
|
|
// Desktop draws the masker to the stencil buffer, one bit per mask.
|
|
// Masks-within-masks are handled as a bitmask.
|
|
// This does unfortunately mean we are limited in the number of masks at once (usually 8 bits).
|
|
if self.next_stencil_mask >= 0x100 {
|
|
// If we've reached the limit of masks, clear the stencil buffer and start over.
|
|
// But this may not be correct if there is still a mask active (mask-within-mask).
|
|
let target = self.target.as_mut().unwrap();
|
|
if self.test_stencil_mask != 0 {
|
|
log::warn!(
|
|
"Too many masks active for stencil buffer; possibly incorrect rendering"
|
|
);
|
|
}
|
|
self.next_stencil_mask = 1;
|
|
target.clear_stencil(self.test_stencil_mask as i32);
|
|
}
|
|
self.num_masks += 1;
|
|
self.mask_stack
|
|
.push((self.write_stencil_mask, self.test_stencil_mask));
|
|
self.write_stencil_mask = self.next_stencil_mask;
|
|
self.test_stencil_mask |= self.next_stencil_mask;
|
|
self.next_stencil_mask <<= 1;
|
|
}
|
|
fn activate_mask(&mut self) {
|
|
self.num_masks_active += 1;
|
|
}
|
|
fn pop_mask(&mut self) {
|
|
if !self.mask_stack.is_empty() {
|
|
self.num_masks -= 1;
|
|
self.num_masks_active -= 1;
|
|
let (write, test) = self.mask_stack.pop().unwrap();
|
|
self.write_stencil_mask = write;
|
|
self.test_stencil_mask = test;
|
|
} else {
|
|
log::warn!("Mask stack underflow\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Texture {
|
|
width: u32,
|
|
height: u32,
|
|
texture: glium::Texture2d,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
struct Vertex {
|
|
position: [f32; 2],
|
|
color: [f32; 4],
|
|
}
|
|
|
|
implement_vertex!(Vertex, position, color);
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct GradientUniforms {
|
|
matrix: [[f32; 3]; 3],
|
|
gradient_type: i32,
|
|
ratios: Vec<f32>,
|
|
colors: Vec<[f32; 4]>,
|
|
num_colors: u32,
|
|
repeat_mode: i32,
|
|
focal_point: f32,
|
|
}
|
|
|
|
impl Uniforms for GradientUniforms {
|
|
fn visit_values<'a, F: FnMut(&str, UniformValue<'a>)>(&'a self, mut visit: F) {
|
|
visit("u_matrix", UniformValue::Mat3(self.matrix));
|
|
visit(
|
|
"u_gradient_type",
|
|
UniformValue::SignedInt(self.gradient_type),
|
|
);
|
|
for i in 0..self.num_colors as usize {
|
|
visit(
|
|
&format!("u_ratios[{}]", i)[..],
|
|
UniformValue::Float(self.ratios[i]),
|
|
);
|
|
visit(
|
|
&format!("u_colors[{}]", i)[..],
|
|
UniformValue::Vec4(self.colors[i]),
|
|
);
|
|
}
|
|
visit("u_num_colors", UniformValue::UnsignedInt(self.num_colors));
|
|
visit("u_repeat_mode", UniformValue::SignedInt(self.repeat_mode));
|
|
visit("u_focal_point", UniformValue::Float(self.focal_point));
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct GradientUniformsFull {
|
|
world_matrix: [[f32; 4]; 4],
|
|
view_matrix: [[f32; 4]; 4],
|
|
mult_color: [f32; 4],
|
|
add_color: [f32; 4],
|
|
gradient: GradientUniforms,
|
|
}
|
|
|
|
impl Uniforms for GradientUniformsFull {
|
|
fn visit_values<'a, F: FnMut(&str, UniformValue<'a>)>(&'a self, mut visit: F) {
|
|
visit("world_matrix", UniformValue::Mat4(self.world_matrix));
|
|
visit("view_matrix", UniformValue::Mat4(self.view_matrix));
|
|
visit("mult_color", UniformValue::Vec4(self.mult_color));
|
|
visit("add_color", UniformValue::Vec4(self.add_color));
|
|
self.gradient.visit_values(visit);
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct BitmapUniforms {
|
|
matrix: [[f32; 3]; 3],
|
|
id: swf::CharacterId,
|
|
}
|
|
|
|
impl Uniforms for BitmapUniforms {
|
|
fn visit_values<'a, F: FnMut(&str, UniformValue<'a>)>(&'a self, mut visit: F) {
|
|
visit("u_matrix", UniformValue::Mat3(self.matrix));
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct BitmapUniformsFull<'a> {
|
|
world_matrix: [[f32; 4]; 4],
|
|
view_matrix: [[f32; 4]; 4],
|
|
mult_color: [f32; 4],
|
|
add_color: [f32; 4],
|
|
matrix: [[f32; 3]; 3],
|
|
texture: &'a Sampler<'a, glium::Texture2d>,
|
|
}
|
|
|
|
impl<'a> Uniforms for BitmapUniformsFull<'a> {
|
|
fn visit_values<'v, F: FnMut(&str, UniformValue<'v>)>(&'v self, mut visit: F) {
|
|
use glium::uniforms::AsUniformValue;
|
|
visit("world_matrix", UniformValue::Mat4(self.world_matrix));
|
|
visit("view_matrix", UniformValue::Mat4(self.view_matrix));
|
|
visit("mult_color", UniformValue::Vec4(self.mult_color));
|
|
visit("add_color", UniformValue::Vec4(self.add_color));
|
|
visit("u_matrix", UniformValue::Mat3(self.matrix));
|
|
visit("u_texture", self.texture.as_uniform_value());
|
|
}
|
|
}
|
|
|
|
const VERTEX_SHADER: &str = r#"
|
|
#version 140
|
|
|
|
uniform mat4 view_matrix;
|
|
uniform mat4 world_matrix;
|
|
uniform vec4 mult_color;
|
|
uniform vec4 add_color;
|
|
|
|
in vec2 position;
|
|
in vec4 color;
|
|
out vec4 frag_color;
|
|
|
|
void main() {
|
|
frag_color = color * mult_color + add_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;
|
|
}
|
|
"#;
|
|
|
|
const TEXTURE_VERTEX_SHADER: &str = r#"
|
|
#version 140
|
|
|
|
uniform mat4 view_matrix;
|
|
uniform mat4 world_matrix;
|
|
|
|
uniform mat3 u_matrix;
|
|
|
|
in vec2 position;
|
|
in vec4 color;
|
|
out vec2 frag_uv;
|
|
|
|
void main() {
|
|
frag_uv = vec2(u_matrix * vec3(position, 1.0));
|
|
gl_Position = view_matrix * world_matrix * vec4(position, 0.0, 1.0);
|
|
}
|
|
"#;
|
|
|
|
const GRADIENT_FRAGMENT_SHADER: &str = r#"
|
|
#version 140
|
|
uniform vec4 mult_color;
|
|
uniform vec4 add_color;
|
|
|
|
uniform int u_gradient_type;
|
|
uniform float u_ratios[8];
|
|
uniform vec4 u_colors[8];
|
|
uniform uint u_num_colors;
|
|
uniform int u_repeat_mode;
|
|
uniform float u_focal_point;
|
|
|
|
in vec2 frag_uv;
|
|
out vec4 out_color;
|
|
|
|
void main() {
|
|
vec4 color;
|
|
int last = int(int(u_num_colors) - 1);
|
|
float t;
|
|
if( u_gradient_type == 0 )
|
|
{
|
|
t = frag_uv.x;
|
|
}
|
|
else if( u_gradient_type == 1 )
|
|
{
|
|
t = length(frag_uv * 2.0 - 1.0);
|
|
}
|
|
else if( u_gradient_type == 2 )
|
|
{
|
|
vec2 uv = frag_uv * 2.0 - 1.0;
|
|
vec2 d = vec2(u_focal_point, 0.0) - uv;
|
|
float l = length(d);
|
|
d /= l;
|
|
t = l / (sqrt(1.0 - u_focal_point*u_focal_point*d.y*d.y) + u_focal_point*d.x);
|
|
}
|
|
if( u_repeat_mode == 0 )
|
|
{
|
|
// Clamp
|
|
t = clamp(t, 0.0, 1.0);
|
|
}
|
|
else if( u_repeat_mode == 1 )
|
|
{
|
|
// Repeat
|
|
t = fract(t);
|
|
}
|
|
else
|
|
{
|
|
// Mirror
|
|
if( t < 0.0 )
|
|
{
|
|
t = -t;
|
|
}
|
|
if( (int(t)&1) == 0 ) {
|
|
t = fract(t);
|
|
} else {
|
|
t = 1.0 - fract(t);
|
|
}
|
|
}
|
|
int i = 0;
|
|
int j = 1;
|
|
t = clamp(t, u_ratios[0], u_ratios[last]);
|
|
while( t > u_ratios[j] )
|
|
{
|
|
i = j;
|
|
j++;
|
|
}
|
|
float a = (t - u_ratios[i]) / (u_ratios[j] - u_ratios[i]);
|
|
color = mix(u_colors[i], u_colors[j], a);
|
|
out_color = mult_color * color + add_color;
|
|
}
|
|
"#;
|
|
|
|
const BITMAP_FRAGMENT_SHADER: &str = r#"
|
|
#version 140
|
|
uniform vec4 mult_color;
|
|
uniform vec4 add_color;
|
|
|
|
in vec2 frag_uv;
|
|
out vec4 out_color;
|
|
|
|
uniform sampler2D u_texture;
|
|
|
|
void main() {
|
|
|
|
vec4 color = texture(u_texture, frag_uv);
|
|
// Unmultiply alpha before apply color transform.
|
|
if( color.a > 0 ) {
|
|
color.rgb /= color.a;
|
|
color = mult_color * color + add_color;
|
|
color.rgb *= color.a;
|
|
}
|
|
|
|
out_color = color;
|
|
}
|
|
"#;
|
|
|
|
struct Mesh {
|
|
draws: Vec<Draw>,
|
|
}
|
|
|
|
struct Draw {
|
|
draw_type: DrawType,
|
|
vertex_buffer: glium::VertexBuffer<Vertex>,
|
|
index_buffer: glium::IndexBuffer<u32>,
|
|
}
|
|
|
|
enum DrawType {
|
|
Color,
|
|
Gradient(GradientUniforms),
|
|
Bitmap {
|
|
uniforms: BitmapUniforms,
|
|
is_smoothed: bool,
|
|
is_repeating: bool,
|
|
},
|
|
}
|
|
|
|
fn point(x: Twips, y: Twips) -> lyon::math::Point {
|
|
lyon::math::Point::new(x.to_pixels() as f32, y.to_pixels() as f32)
|
|
}
|
|
|
|
fn ruffle_path_to_lyon_path(
|
|
commands: Vec<DrawCommand>,
|
|
mut is_closed: bool,
|
|
) -> impl Iterator<Item = PathEvent> {
|
|
use lyon::geom::{LineSegment, QuadraticBezierSegment};
|
|
|
|
let mut cur = lyon::math::Point::new(0.0, 0.0);
|
|
let mut i = commands.into_iter();
|
|
std::iter::from_fn(move || match i.next() {
|
|
Some(DrawCommand::MoveTo { x, y }) => {
|
|
cur = point(x, y);
|
|
Some(PathEvent::MoveTo(cur))
|
|
}
|
|
Some(DrawCommand::LineTo { x, y }) => {
|
|
let next = point(x, y);
|
|
let cmd = PathEvent::Line(LineSegment {
|
|
from: cur,
|
|
to: next,
|
|
});
|
|
cur = next;
|
|
Some(cmd)
|
|
}
|
|
Some(DrawCommand::CurveTo { x1, y1, x2, y2 }) => {
|
|
let next = point(x2, y2);
|
|
let cmd = PathEvent::Quadratic(QuadraticBezierSegment {
|
|
from: cur,
|
|
ctrl: point(x1, y1),
|
|
to: next,
|
|
});
|
|
cur = next;
|
|
Some(cmd)
|
|
}
|
|
None => {
|
|
if is_closed {
|
|
is_closed = false;
|
|
Some(PathEvent::Close(LineSegment { from: cur, to: cur }))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
#[allow(clippy::many_single_char_names)]
|
|
fn swf_to_gl_matrix(m: swf::Matrix) -> [[f32; 3]; 3] {
|
|
let tx = m.translate_x.get() as f32;
|
|
let ty = m.translate_y.get() as f32;
|
|
let det = m.scale_x * m.scale_y - m.rotate_skew_1 * m.rotate_skew_0;
|
|
let mut a = m.scale_y / det;
|
|
let mut b = -m.rotate_skew_1 / det;
|
|
let mut c = -(tx * m.scale_y - m.rotate_skew_1 * ty) / det;
|
|
let mut d = -m.rotate_skew_0 / det;
|
|
let mut e = m.scale_x / det;
|
|
let mut f = (tx * m.rotate_skew_0 - m.scale_x * ty) / det;
|
|
|
|
a *= 20.0 / 32768.0;
|
|
b *= 20.0 / 32768.0;
|
|
d *= 20.0 / 32768.0;
|
|
e *= 20.0 / 32768.0;
|
|
|
|
c /= 32768.0;
|
|
f /= 32768.0;
|
|
c += 0.5;
|
|
f += 0.5;
|
|
[[a, d, 0.0], [b, e, 0.0], [c, f, 1.0]]
|
|
}
|
|
|
|
#[allow(clippy::many_single_char_names)]
|
|
fn swf_bitmap_to_gl_matrix(m: swf::Matrix, bitmap_width: u32, bitmap_height: u32) -> [[f32; 3]; 3] {
|
|
let bitmap_width = bitmap_width as f32;
|
|
let bitmap_height = bitmap_height as f32;
|
|
|
|
let tx = m.translate_x.get() as f32;
|
|
let ty = m.translate_y.get() as f32;
|
|
let det = m.scale_x * m.scale_y - m.rotate_skew_1 * m.rotate_skew_0;
|
|
let mut a = m.scale_y / det;
|
|
let mut b = -m.rotate_skew_1 / det;
|
|
let mut c = -(tx * m.scale_y - m.rotate_skew_1 * ty) / det;
|
|
let mut d = -m.rotate_skew_0 / det;
|
|
let mut e = m.scale_x / det;
|
|
let mut f = (tx * m.rotate_skew_0 - m.scale_x * ty) / det;
|
|
|
|
a *= 20.0 / bitmap_width;
|
|
b *= 20.0 / bitmap_width;
|
|
d *= 20.0 / bitmap_height;
|
|
e *= 20.0 / bitmap_height;
|
|
|
|
c /= bitmap_width;
|
|
f /= bitmap_height;
|
|
|
|
[[a, d, 0.0], [b, e, 0.0], [c, f, 1.0]]
|
|
}
|
|
|
|
/// Returns the drawing parameters for masking.
|
|
#[inline]
|
|
fn mask_draw_parameters(
|
|
params: &mut DrawParameters,
|
|
num_masks: u32,
|
|
num_masks_active: u32,
|
|
write_stencil_mask: u32,
|
|
test_stencil_mask: u32,
|
|
) {
|
|
use glium::draw_parameters::{Stencil, StencilOperation, StencilTest};
|
|
if num_masks > 0 {
|
|
let (value, test, pass_op, color_mask, write_mask) = if num_masks_active < num_masks {
|
|
(
|
|
write_stencil_mask as i32,
|
|
StencilTest::AlwaysPass,
|
|
StencilOperation::Replace,
|
|
(false, false, false, false),
|
|
write_stencil_mask,
|
|
)
|
|
} else {
|
|
(
|
|
test_stencil_mask as i32,
|
|
StencilTest::IfEqual {
|
|
mask: test_stencil_mask,
|
|
},
|
|
StencilOperation::Keep,
|
|
(true, true, true, true),
|
|
test_stencil_mask,
|
|
)
|
|
};
|
|
params.color_mask = color_mask;
|
|
params.stencil = Stencil {
|
|
test_clockwise: test,
|
|
reference_value_clockwise: value,
|
|
write_mask_clockwise: write_mask,
|
|
fail_operation_clockwise: StencilOperation::Keep,
|
|
pass_depth_fail_operation_clockwise: StencilOperation::Keep,
|
|
depth_pass_operation_clockwise: pass_op,
|
|
test_counter_clockwise: test,
|
|
reference_value_counter_clockwise: value,
|
|
write_mask_counter_clockwise: write_mask,
|
|
fail_operation_counter_clockwise: StencilOperation::Keep,
|
|
pass_depth_fail_operation_counter_clockwise: StencilOperation::Keep,
|
|
depth_pass_operation_counter_clockwise: pass_op,
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Returns the drawing parameters for standard color/gradient fills.
|
|
#[inline]
|
|
fn color_blend() -> glium::Blend {
|
|
glium::Blend::alpha_blending()
|
|
}
|
|
|
|
/// Returns the drawing parameters for bitmaps with pre-multipled alpha.
|
|
#[inline]
|
|
fn bitmap_blend() -> glium::Blend {
|
|
use glium::{BlendingFunction, LinearBlendingFactor};
|
|
glium::Blend {
|
|
color: BlendingFunction::Addition {
|
|
source: LinearBlendingFactor::One,
|
|
destination: LinearBlendingFactor::OneMinusSourceAlpha,
|
|
},
|
|
alpha: BlendingFunction::Addition {
|
|
source: LinearBlendingFactor::SourceAlpha,
|
|
destination: LinearBlendingFactor::OneMinusSourceAlpha,
|
|
},
|
|
..Default::default()
|
|
}
|
|
}
|