ruffle/render/webgl/src/lib.rs

1618 lines
56 KiB
Rust

use bytemuck::{Pod, Zeroable};
use fnv::FnvHashMap;
use ruffle_render::backend::null::NullBitmapSource;
use ruffle_render::backend::{RenderBackend, ShapeHandle, ViewportDimensions};
use ruffle_render::bitmap::{Bitmap, BitmapFormat, BitmapHandle, BitmapSource};
use ruffle_render::commands::{CommandHandler, CommandList};
use ruffle_render::error::Error as BitmapError;
use ruffle_render::shape_utils::DistilledShape;
use ruffle_render::tessellator::{
Gradient as TessGradient, GradientType, ShapeTessellator, Vertex as TessVertex,
};
use ruffle_render::transform::Transform;
use ruffle_web_common::{JsError, JsResult};
use swf::{BlendMode, Color};
use thiserror::Error;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{
HtmlCanvasElement, OesVertexArrayObject, WebGl2RenderingContext as Gl2, WebGlBuffer,
WebGlFramebuffer, WebGlProgram, WebGlRenderbuffer, WebGlRenderingContext as Gl, WebGlShader,
WebGlTexture, WebGlUniformLocation, WebGlVertexArrayObject, WebglDebugRendererInfo,
};
#[derive(Error, Debug)]
pub enum Error {
#[error("Couldn't create GL context")]
CantCreateGLContext,
#[error("Couldn't create frame buffer")]
UnableToCreateFrameBuffer,
#[error("Couldn't create program")]
UnableToCreateProgram,
#[error("Couldn't create texture")]
UnableToCreateTexture,
#[error("Couldn't compile shader")]
UnableToCreateShader,
#[error("Couldn't create render buffer")]
UnableToCreateRenderBuffer,
#[error("Couldn't create vertex array object")]
UnableToCreateVAO,
#[error("Javascript error: {0}")]
JavascriptError(#[from] JsError),
#[error("OES_element_index_uint extension not available")]
OESExtensionNotFound,
#[error("VAO extension not found")]
VAOExtensionNotFound,
#[error("Couldn't link shader program: {0}")]
LinkingShaderProgram(String),
#[error("GL Error in {0}: {1}")]
GLError(&'static str, u32),
}
const COLOR_VERTEX_GLSL: &str = include_str!("../shaders/color.vert");
const COLOR_FRAGMENT_GLSL: &str = include_str!("../shaders/color.frag");
const TEXTURE_VERTEX_GLSL: &str = include_str!("../shaders/texture.vert");
const GRADIENT_FRAGMENT_GLSL: &str = include_str!("../shaders/gradient.frag");
const BITMAP_FRAGMENT_GLSL: &str = include_str!("../shaders/bitmap.frag");
const NUM_VERTEX_ATTRIBUTES: u32 = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MaskState {
NoMask,
DrawMaskStencil,
DrawMaskedContent,
ClearMaskStencil,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
struct Vertex {
position: [f32; 2],
color: u32,
}
impl From<TessVertex> for Vertex {
fn from(vertex: TessVertex) -> Self {
Self {
position: [vertex.x, vertex.y],
color: u32::from_le_bytes([
vertex.color.r,
vertex.color.g,
vertex.color.b,
vertex.color.a,
]),
}
}
}
pub struct WebGlRenderBackend {
/// WebGL1 context
gl: Gl,
// WebGL2 context, if available.
gl2: Option<Gl2>,
/// In WebGL1, VAOs are only available as an extension.
vao_ext: OesVertexArrayObject,
// The frame buffers used for resolving MSAA.
msaa_buffers: Option<MsaaBuffers>,
msaa_sample_count: u32,
color_program: ShaderProgram,
bitmap_program: ShaderProgram,
gradient_program: ShaderProgram,
shape_tessellator: ShapeTessellator,
meshes: Vec<Mesh>,
color_quad_shape: ShapeHandle,
bitmap_quad_shape: ShapeHandle,
mask_state: MaskState,
num_masks: u32,
mask_state_dirty: bool,
is_transparent: bool,
active_program: *const ShaderProgram,
blend_modes: Vec<BlendMode>,
mult_color: Option<[f32; 4]>,
add_color: Option<[f32; 4]>,
renderbuffer_width: i32,
renderbuffer_height: i32,
view_matrix: [[f32; 4]; 4],
bitmap_registry: FnvHashMap<BitmapHandle, RegistryData>,
next_bitmap_handle: BitmapHandle,
// This is currently unused - we just hold on to it
// to expose via `get_viewport_dimensions`
viewport_scale_factor: f64,
}
struct RegistryData {
bitmap: Bitmap,
texture: WebGlTexture,
}
const MAX_GRADIENT_COLORS: usize = 15;
impl WebGlRenderBackend {
pub fn new(canvas: &HtmlCanvasElement, is_transparent: bool) -> Result<Self, Error> {
// Create WebGL context.
let options = [
("stencil", JsValue::TRUE),
(
"alpha",
if is_transparent {
JsValue::TRUE
} else {
JsValue::FALSE
},
),
("antialias", JsValue::FALSE),
("depth", JsValue::FALSE),
("failIfMajorPerformanceCaveat", JsValue::TRUE), // fail if no GPU available
("premultipliedAlpha", JsValue::TRUE),
];
let context_options = js_sys::Object::new();
for (name, value) in options.iter() {
js_sys::Reflect::set(&context_options, &JsValue::from(*name), value).warn_on_error();
}
// Attempt to create a WebGL2 context, but fall back to WebGL1 if unavailable.
let (gl, gl2, vao_ext, msaa_sample_count) = if let Ok(Some(gl)) =
canvas.get_context_with_context_options("webgl2", &context_options)
{
log::info!("Creating WebGL2 context.");
let gl2 = gl
.dyn_into::<Gl2>()
.map_err(|_| Error::CantCreateGLContext)?;
// Determine MSAA sample count.
// Default to 4x MSAA on desktop, 2x on mobile/tablets.
let mut msaa_sample_count = if ruffle_web_common::is_mobile_or_tablet() {
log::info!("Running on a mobile device; defaulting to 2x MSAA");
2
} else {
4
};
// Ensure that we don't exceed the max MSAA of this device.
if let Ok(max_samples) = gl2.get_parameter(Gl2::MAX_SAMPLES) {
let max_samples = max_samples.as_f64().unwrap_or(0.0) as u32;
if max_samples > 0 && max_samples < msaa_sample_count {
log::info!("Device only supports {}xMSAA", max_samples);
msaa_sample_count = max_samples;
}
}
// WebGLRenderingContext inherits from WebGL2RenderingContext, so cast it down.
(
gl2.clone().unchecked_into::<Gl>(),
Some(gl2),
JsValue::UNDEFINED.unchecked_into(),
msaa_sample_count,
)
} else {
// Fall back to WebGL1.
// Request antialiasing on WebGL1, because there isn't general MSAA support.
js_sys::Reflect::set(
&context_options,
&JsValue::from("antialias"),
&JsValue::TRUE,
)
.warn_on_error();
if let Ok(Some(gl)) = canvas.get_context_with_context_options("webgl", &context_options)
{
log::info!("Falling back to WebGL1.");
let gl = gl
.dyn_into::<Gl>()
.map_err(|_| Error::CantCreateGLContext)?;
// `dyn_into` doesn't work here; why?
let vao = gl
.get_extension("OES_vertex_array_object")
.into_js_result()?
.ok_or(Error::VAOExtensionNotFound)?
.unchecked_into::<OesVertexArrayObject>();
// On WebGL1, we need to explicitly request support for u32 index buffers.
let _ext = gl
.get_extension("OES_element_index_uint")
.into_js_result()?
.ok_or(Error::OESExtensionNotFound)?;
(gl, None, vao, 1)
} else {
return Err(Error::CantCreateGLContext);
}
};
if log::log_enabled!(log::Level::Info) {
// Get WebGL driver info.
let driver_info = gl
.get_extension("WEBGL_debug_renderer_info")
.and_then(|_| gl.get_parameter(WebglDebugRendererInfo::UNMASKED_RENDERER_WEBGL))
.ok()
.and_then(|val| val.as_string())
.unwrap_or_else(|| "<unknown>".to_string());
log::info!("WebGL graphics driver: {}", driver_info);
}
let color_vertex = Self::compile_shader(&gl, Gl::VERTEX_SHADER, COLOR_VERTEX_GLSL)?;
let texture_vertex = Self::compile_shader(&gl, Gl::VERTEX_SHADER, TEXTURE_VERTEX_GLSL)?;
let color_fragment = Self::compile_shader(&gl, Gl::FRAGMENT_SHADER, COLOR_FRAGMENT_GLSL)?;
let bitmap_fragment = Self::compile_shader(&gl, Gl::FRAGMENT_SHADER, BITMAP_FRAGMENT_GLSL)?;
let gradient_fragment =
Self::compile_shader(&gl, Gl::FRAGMENT_SHADER, GRADIENT_FRAGMENT_GLSL)?;
let color_program = ShaderProgram::new(&gl, &color_vertex, &color_fragment)?;
let bitmap_program = ShaderProgram::new(&gl, &texture_vertex, &bitmap_fragment)?;
let gradient_program = ShaderProgram::new(&gl, &texture_vertex, &gradient_fragment)?;
gl.enable(Gl::BLEND);
// Necessary to load RGB textures (alignment defaults to 4).
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
let mut renderer = Self {
gl,
gl2,
vao_ext,
msaa_buffers: None,
msaa_sample_count,
color_program,
gradient_program,
bitmap_program,
shape_tessellator: ShapeTessellator::new(),
meshes: vec![],
color_quad_shape: ShapeHandle(0),
bitmap_quad_shape: ShapeHandle(1),
renderbuffer_width: 1,
renderbuffer_height: 1,
view_matrix: [[0.0; 4]; 4],
mask_state: MaskState::NoMask,
num_masks: 0,
mask_state_dirty: true,
is_transparent,
active_program: std::ptr::null(),
blend_modes: vec![],
mult_color: None,
add_color: None,
bitmap_registry: Default::default(),
next_bitmap_handle: BitmapHandle(0),
viewport_scale_factor: 1.0,
};
renderer.push_blend_mode(BlendMode::Normal);
let color_quad_mesh = renderer.build_quad_mesh(&renderer.color_program)?;
renderer.meshes.push(color_quad_mesh);
let bitmap_quad_mesh = renderer.build_quad_mesh(&renderer.bitmap_program)?;
renderer.meshes.push(bitmap_quad_mesh);
renderer.set_viewport_dimensions(ViewportDimensions {
width: 1,
height: 1,
scale_factor: 1.0,
});
Ok(renderer)
}
fn build_quad_mesh(&self, program: &ShaderProgram) -> Result<Mesh, Error> {
let vao = self.create_vertex_array()?;
let vertex_buffer = self.gl.create_buffer().unwrap();
self.gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&vertex_buffer));
self.gl.buffer_data_with_u8_array(
Gl::ARRAY_BUFFER,
bytemuck::cast_slice(&[
Vertex {
position: [0.0, 0.0],
color: 0xffff_ffff,
},
Vertex {
position: [1.0, 0.0],
color: 0xffff_ffff,
},
Vertex {
position: [1.0, 1.0],
color: 0xffff_ffff,
},
Vertex {
position: [0.0, 1.0],
color: 0xffff_ffff,
},
]),
Gl::STATIC_DRAW,
);
let index_buffer = self.gl.create_buffer().unwrap();
self.gl
.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&index_buffer));
self.gl.buffer_data_with_u8_array(
Gl::ELEMENT_ARRAY_BUFFER,
bytemuck::cast_slice(&[0u32, 1, 2, 0, 2, 3]),
Gl::STATIC_DRAW,
);
if program.vertex_position_location != 0xffff_ffff {
self.gl.vertex_attrib_pointer_with_i32(
program.vertex_position_location,
2,
Gl::FLOAT,
false,
12,
0,
);
self.gl
.enable_vertex_attrib_array(program.vertex_position_location as u32);
}
if program.vertex_color_location != 0xffff_ffff {
self.gl.vertex_attrib_pointer_with_i32(
program.vertex_color_location,
4,
Gl::UNSIGNED_BYTE,
true,
12,
8,
);
self.gl
.enable_vertex_attrib_array(program.vertex_color_location as u32);
}
self.bind_vertex_array(None);
for i in program.num_vertex_attributes..NUM_VERTEX_ATTRIBUTES {
self.gl.disable_vertex_attrib_array(i);
}
let quad_mesh = Mesh {
draws: vec![Draw {
draw_type: if program.program == self.bitmap_program.program {
DrawType::Bitmap(BitmapDraw {
matrix: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
handle: BitmapHandle(0),
is_smoothed: true,
is_repeating: false,
})
} else {
DrawType::Color
},
vao,
vertex_buffer,
index_buffer,
num_indices: 6,
num_mask_indices: 6,
}],
};
Ok(quad_mesh)
}
fn compile_shader(gl: &Gl, shader_type: u32, glsl_src: &str) -> Result<WebGlShader, Error> {
let shader = gl
.create_shader(shader_type)
.ok_or(Error::UnableToCreateShader)?;
gl.shader_source(&shader, glsl_src);
gl.compile_shader(&shader);
if log::log_enabled!(log::Level::Error) {
let log = gl.get_shader_info_log(&shader).unwrap_or_default();
if !log.is_empty() {
log::error!("{}", log);
}
}
Ok(shader)
}
fn build_msaa_buffers(&mut self) -> Result<(), Error> {
if self.gl2.is_none() || self.msaa_sample_count <= 1 {
self.gl.bind_framebuffer(Gl::FRAMEBUFFER, None);
self.gl.bind_renderbuffer(Gl::RENDERBUFFER, None);
return Ok(());
}
let gl = self.gl2.as_ref().unwrap();
// Delete previous buffers, if they exist.
if let Some(msaa_buffers) = self.msaa_buffers.take() {
gl.delete_renderbuffer(Some(&msaa_buffers.color_renderbuffer));
gl.delete_renderbuffer(Some(&msaa_buffers.stencil_renderbuffer));
gl.delete_framebuffer(Some(&msaa_buffers.render_framebuffer));
gl.delete_framebuffer(Some(&msaa_buffers.color_framebuffer));
gl.delete_texture(Some(&msaa_buffers.framebuffer_texture));
}
// Create frame and render buffers.
let render_framebuffer = gl
.create_framebuffer()
.ok_or(Error::UnableToCreateFrameBuffer)?;
let color_framebuffer = gl
.create_framebuffer()
.ok_or(Error::UnableToCreateFrameBuffer)?;
// Note for future self:
// Whenever we support playing transparent movies,
// switch this to RGBA and probably need to change shaders to all
// be premultiplied alpha.
let color_renderbuffer = gl
.create_renderbuffer()
.ok_or(Error::UnableToCreateRenderBuffer)?;
gl.bind_renderbuffer(Gl2::RENDERBUFFER, Some(&color_renderbuffer));
gl.renderbuffer_storage_multisample(
Gl2::RENDERBUFFER,
self.msaa_sample_count as i32,
Gl2::RGBA8,
self.renderbuffer_width,
self.renderbuffer_height,
);
gl.check_error("renderbuffer_storage_multisample (color)")?;
let stencil_renderbuffer = gl
.create_renderbuffer()
.ok_or(Error::UnableToCreateFrameBuffer)?;
gl.bind_renderbuffer(Gl2::RENDERBUFFER, Some(&stencil_renderbuffer));
gl.renderbuffer_storage_multisample(
Gl2::RENDERBUFFER,
self.msaa_sample_count as i32,
Gl2::STENCIL_INDEX8,
self.renderbuffer_width,
self.renderbuffer_height,
);
gl.check_error("renderbuffer_storage_multisample (stencil)")?;
gl.bind_framebuffer(Gl2::FRAMEBUFFER, Some(&render_framebuffer));
gl.framebuffer_renderbuffer(
Gl2::FRAMEBUFFER,
Gl2::COLOR_ATTACHMENT0,
Gl2::RENDERBUFFER,
Some(&color_renderbuffer),
);
gl.framebuffer_renderbuffer(
Gl2::FRAMEBUFFER,
Gl2::STENCIL_ATTACHMENT,
Gl2::RENDERBUFFER,
Some(&stencil_renderbuffer),
);
let framebuffer_texture = gl.create_texture().ok_or(Error::UnableToCreateTexture)?;
gl.bind_texture(Gl2::TEXTURE_2D, Some(&framebuffer_texture));
gl.tex_parameteri(Gl2::TEXTURE_2D, Gl2::TEXTURE_MAG_FILTER, Gl::NEAREST as i32);
gl.tex_parameteri(Gl2::TEXTURE_2D, Gl2::TEXTURE_MIN_FILTER, Gl::NEAREST as i32);
gl.tex_parameteri(
Gl2::TEXTURE_2D,
Gl2::TEXTURE_WRAP_S,
Gl::CLAMP_TO_EDGE as i32,
);
gl.tex_parameteri(
Gl2::TEXTURE_2D,
Gl2::TEXTURE_WRAP_T,
Gl2::CLAMP_TO_EDGE as i32,
);
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
Gl2::TEXTURE_2D,
0,
Gl2::RGBA as i32,
self.renderbuffer_width,
self.renderbuffer_height,
0,
Gl2::RGBA,
Gl2::UNSIGNED_BYTE,
None,
)
.into_js_result()?;
gl.bind_texture(Gl2::TEXTURE_2D, None);
gl.bind_framebuffer(Gl2::FRAMEBUFFER, Some(&color_framebuffer));
gl.framebuffer_texture_2d(
Gl2::FRAMEBUFFER,
Gl2::COLOR_ATTACHMENT0,
Gl2::TEXTURE_2D,
Some(&framebuffer_texture),
0,
);
gl.bind_framebuffer(Gl2::FRAMEBUFFER, None);
self.msaa_buffers = Some(MsaaBuffers {
color_renderbuffer,
stencil_renderbuffer,
render_framebuffer,
color_framebuffer,
framebuffer_texture,
});
Ok(())
}
fn register_shape_internal(
&mut self,
shape: DistilledShape,
bitmap_source: &dyn BitmapSource,
) -> Mesh {
use ruffle_render::tessellator::DrawType as TessDrawType;
let lyon_mesh = self
.shape_tessellator
.tessellate_shape(shape, bitmap_source);
let mut draws = Vec::with_capacity(lyon_mesh.len());
for draw in lyon_mesh {
let num_indices = draw.indices.len() as i32;
let num_mask_indices = draw.mask_index_count as i32;
let vao = self.create_vertex_array().unwrap();
let vertex_buffer = self.gl.create_buffer().unwrap();
self.gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&vertex_buffer));
let vertices: Vec<_> = draw.vertices.into_iter().map(Vertex::from).collect();
self.gl.buffer_data_with_u8_array(
Gl::ARRAY_BUFFER,
bytemuck::cast_slice(&vertices),
Gl::STATIC_DRAW,
);
let index_buffer = self.gl.create_buffer().unwrap();
self.gl
.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&index_buffer));
self.gl.buffer_data_with_u8_array(
Gl::ELEMENT_ARRAY_BUFFER,
bytemuck::cast_slice(&draw.indices),
Gl::STATIC_DRAW,
);
let program = match draw.draw_type {
TessDrawType::Color => &self.color_program,
TessDrawType::Gradient(_) => &self.gradient_program,
TessDrawType::Bitmap(_) => &self.bitmap_program,
};
// Unfortunately it doesn't seem to be possible to ensure that vertex attributes will be in
// a guaranteed position between shaders in WebGL1 (no layout qualifiers in GLSL in OpenGL ES 1.0).
// Attributes can change between shaders, even if the vertex layout is otherwise "the same".
// This varies between platforms based on what the GLSL compiler decides to do.
if program.vertex_position_location != 0xffff_ffff {
self.gl.vertex_attrib_pointer_with_i32(
program.vertex_position_location,
2,
Gl::FLOAT,
false,
12,
0,
);
self.gl
.enable_vertex_attrib_array(program.vertex_position_location as u32);
}
if program.vertex_color_location != 0xffff_ffff {
self.gl.vertex_attrib_pointer_with_i32(
program.vertex_color_location,
4,
Gl::UNSIGNED_BYTE,
true,
12,
8,
);
self.gl
.enable_vertex_attrib_array(program.vertex_color_location as u32);
}
draws.push(match draw.draw_type {
TessDrawType::Color => Draw {
draw_type: DrawType::Color,
vao,
vertex_buffer,
index_buffer,
num_indices,
num_mask_indices,
},
TessDrawType::Gradient(gradient) => Draw {
draw_type: DrawType::Gradient(Box::new(Gradient::from(gradient))),
vao,
vertex_buffer,
index_buffer,
num_indices,
num_mask_indices,
},
TessDrawType::Bitmap(bitmap) => Draw {
draw_type: DrawType::Bitmap(BitmapDraw {
matrix: bitmap.matrix,
handle: bitmap.bitmap,
is_smoothed: bitmap.is_smoothed,
is_repeating: bitmap.is_repeating,
}),
vao,
vertex_buffer,
index_buffer,
num_indices,
num_mask_indices,
},
});
self.bind_vertex_array(None);
for i in program.num_vertex_attributes..NUM_VERTEX_ATTRIBUTES {
self.gl.disable_vertex_attrib_array(i);
}
}
Mesh { draws }
}
/// Creates and binds a new VAO.
fn create_vertex_array(&self) -> Result<WebGlVertexArrayObject, Error> {
let vao = if let Some(gl2) = &self.gl2 {
let vao = gl2.create_vertex_array().ok_or(Error::UnableToCreateVAO)?;
gl2.bind_vertex_array(Some(&vao));
vao
} else {
let vao = self
.vao_ext
.create_vertex_array_oes()
.ok_or(Error::UnableToCreateVAO)?;
self.vao_ext.bind_vertex_array_oes(Some(&vao));
vao
};
Ok(vao)
}
/// Binds a VAO.
fn bind_vertex_array(&self, vao: Option<&WebGlVertexArrayObject>) {
if let Some(gl2) = &self.gl2 {
gl2.bind_vertex_array(vao);
} else {
self.vao_ext.bind_vertex_array_oes(vao);
};
}
fn set_stencil_state(&mut self) {
// Set stencil state for masking, if necessary.
if self.mask_state_dirty {
match self.mask_state {
MaskState::NoMask => {
self.gl.disable(Gl::STENCIL_TEST);
self.gl.color_mask(true, true, true, true);
}
MaskState::DrawMaskStencil => {
self.gl.enable(Gl::STENCIL_TEST);
self.gl
.stencil_func(Gl::EQUAL, (self.num_masks - 1) as i32, 0xff);
self.gl.stencil_op(Gl::KEEP, Gl::KEEP, Gl::INCR);
self.gl.color_mask(false, false, false, false);
}
MaskState::DrawMaskedContent => {
self.gl.enable(Gl::STENCIL_TEST);
self.gl.stencil_func(Gl::EQUAL, self.num_masks as i32, 0xff);
self.gl.stencil_op(Gl::KEEP, Gl::KEEP, Gl::KEEP);
self.gl.color_mask(true, true, true, true);
}
MaskState::ClearMaskStencil => {
self.gl.enable(Gl::STENCIL_TEST);
self.gl.stencil_func(Gl::EQUAL, self.num_masks as i32, 0xff);
self.gl.stencil_op(Gl::KEEP, Gl::KEEP, Gl::DECR);
self.gl.color_mask(false, false, false, false);
}
}
}
}
fn apply_blend_mode(&mut self, mode: BlendMode) {
let (blend_op, src_rgb, dst_rgb) = match mode {
BlendMode::Normal => {
// src + (1-a)
(Gl::FUNC_ADD, Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA)
}
BlendMode::Add => {
// src + dst
(Gl::FUNC_ADD, Gl::ONE, Gl::ONE)
}
BlendMode::Subtract => {
// dst - src
(Gl::FUNC_REVERSE_SUBTRACT, Gl::ONE, Gl::ONE)
}
_ => {
// TODO: Unsupported blend mode. Default to normal for now.
(Gl::FUNC_ADD, Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA)
}
};
self.gl.blend_equation_separate(blend_op, Gl::FUNC_ADD);
self.gl
.blend_func_separate(src_rgb, dst_rgb, Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA);
}
fn begin_frame(&mut self, clear: Color) {
self.active_program = std::ptr::null();
self.mask_state = MaskState::NoMask;
self.num_masks = 0;
self.mask_state_dirty = true;
self.mult_color = None;
self.add_color = None;
// Bind to MSAA render buffer if using MSAA.
if let Some(msaa_buffers) = &self.msaa_buffers {
let gl = &self.gl;
gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&msaa_buffers.render_framebuffer));
}
self.gl
.viewport(0, 0, self.renderbuffer_width, self.renderbuffer_height);
self.set_stencil_state();
if self.is_transparent {
self.gl.clear_color(0.0, 0.0, 0.0, 0.0);
} else {
self.gl.clear_color(
clear.r as f32 / 255.0,
clear.g as f32 / 255.0,
clear.b as f32 / 255.0,
clear.a as f32 / 255.0,
);
}
self.gl.stencil_mask(0xff);
self.gl.clear(Gl::COLOR_BUFFER_BIT | Gl::STENCIL_BUFFER_BIT);
}
fn end_frame(&mut self) {
// Resolve MSAA, if we're using it (WebGL2).
if let (Some(ref gl), Some(ref msaa_buffers)) = (&self.gl2, &self.msaa_buffers) {
// Disable any remaining masking state.
self.gl.disable(Gl::STENCIL_TEST);
self.gl.color_mask(true, true, true, true);
// Resolve the MSAA in the render buffer.
gl.bind_framebuffer(
Gl2::READ_FRAMEBUFFER,
Some(&msaa_buffers.render_framebuffer),
);
gl.bind_framebuffer(Gl2::DRAW_FRAMEBUFFER, Some(&msaa_buffers.color_framebuffer));
gl.blit_framebuffer(
0,
0,
self.renderbuffer_width,
self.renderbuffer_height,
0,
0,
self.renderbuffer_width,
self.renderbuffer_height,
Gl2::COLOR_BUFFER_BIT,
Gl2::NEAREST,
);
// Render the resolved framebuffer texture to a quad on the screen.
gl.bind_framebuffer(Gl2::FRAMEBUFFER, None);
self.gl.viewport(
0,
0,
self.gl.drawing_buffer_width(),
self.gl.drawing_buffer_height(),
);
let program = &self.bitmap_program;
self.gl.use_program(Some(&program.program));
// Scale to fill screen.
program.uniform_matrix4fv(
&self.gl,
ShaderUniform::WorldMatrix,
&[
[2.0, 0.0, 0.0, 0.0],
[0.0, 2.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[-1.0, -1.0, 0.0, 1.0],
],
);
program.uniform_matrix4fv(
&self.gl,
ShaderUniform::ViewMatrix,
&[
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
);
program.uniform4fv(&self.gl, ShaderUniform::MultColor, &[1.0, 1.0, 1.0, 1.0]);
program.uniform4fv(&self.gl, ShaderUniform::AddColor, &[0.0, 0.0, 0.0, 0.0]);
program.uniform_matrix3fv(
&self.gl,
ShaderUniform::TextureMatrix,
&[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
);
// Bind the framebuffer texture.
self.gl.active_texture(Gl2::TEXTURE0);
self.gl
.bind_texture(Gl2::TEXTURE_2D, Some(&msaa_buffers.framebuffer_texture));
program.uniform1i(&self.gl, ShaderUniform::BitmapTexture, 0);
// Render the quad.
let quad = &self.meshes[self.bitmap_quad_shape.0];
self.bind_vertex_array(Some(&quad.draws[0].vao));
self.gl.draw_elements_with_i32(
Gl::TRIANGLES,
quad.draws[0].num_indices,
Gl::UNSIGNED_INT,
0,
);
}
}
}
impl RenderBackend for WebGlRenderBackend {
fn render_offscreen(
&mut self,
_handle: BitmapHandle,
_width: u32,
_height: u32,
_commands: CommandList,
_clear_color: Color,
) -> Result<Bitmap, ruffle_render::error::Error> {
Err(ruffle_render::error::Error::Unimplemented)
}
fn viewport_dimensions(&self) -> ViewportDimensions {
ViewportDimensions {
width: self.renderbuffer_width as u32,
height: self.renderbuffer_height as u32,
scale_factor: self.viewport_scale_factor,
}
}
fn set_viewport_dimensions(&mut self, dimensions: ViewportDimensions) {
// Build view matrix based on canvas size.
self.view_matrix = [
[1.0 / (dimensions.width as f32 / 2.0), 0.0, 0.0, 0.0],
[0.0, -1.0 / (dimensions.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],
];
// Setup GL viewport and renderbuffers clamped to reasonable sizes.
// We don't use `.clamp()` here because `self.gl.drawing_buffer_width()` and
// `self.gl.drawing_buffer_height()` return zero when the WebGL context is lost,
// then an assertion error would be triggered.
self.renderbuffer_width = (dimensions.width as i32)
.max(1)
.min(self.gl.drawing_buffer_width());
self.renderbuffer_height = (dimensions.height as i32)
.max(1)
.min(self.gl.drawing_buffer_height());
// Recreate framebuffers with the new size.
let _ = self.build_msaa_buffers();
self.gl
.viewport(0, 0, self.renderbuffer_width, self.renderbuffer_height);
}
fn register_shape(
&mut self,
shape: DistilledShape,
bitmap_source: &dyn BitmapSource,
) -> ShapeHandle {
let handle = ShapeHandle(self.meshes.len());
let mesh = self.register_shape_internal(shape, bitmap_source);
self.meshes.push(mesh);
handle
}
fn replace_shape(
&mut self,
shape: DistilledShape,
bitmap_source: &dyn BitmapSource,
handle: ShapeHandle,
) {
let mesh = self.register_shape_internal(shape, bitmap_source);
self.meshes[handle.0] = mesh;
}
fn register_glyph_shape(&mut self, glyph: &swf::Glyph) -> ShapeHandle {
let shape = ruffle_render::shape_utils::swf_glyph_to_shape(glyph);
let handle = ShapeHandle(self.meshes.len());
let mesh = self.register_shape_internal((&shape).into(), &NullBitmapSource);
self.meshes.push(mesh);
handle
}
fn submit_frame(&mut self, clear: Color, commands: CommandList) {
self.begin_frame(clear);
commands.execute(self);
self.end_frame();
}
fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option<Bitmap> {
self.bitmap_registry.get(&bitmap).map(|e| e.bitmap.clone())
}
fn register_bitmap(&mut self, bitmap: Bitmap) -> Result<BitmapHandle, BitmapError> {
let format = match bitmap.format() {
BitmapFormat::Rgb => Gl::RGB,
BitmapFormat::Rgba => Gl::RGBA,
};
let texture = self.gl.create_texture().unwrap();
self.gl.bind_texture(Gl::TEXTURE_2D, Some(&texture));
self.gl
.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
Gl::TEXTURE_2D,
0,
format as i32,
bitmap.width() as i32,
bitmap.height() as i32,
0,
format,
Gl::UNSIGNED_BYTE,
Some(bitmap.data()),
)
.into_js_result()
.map_err(|e| BitmapError::JavascriptError(e.into()))?;
// You must set the texture parameters for non-power-of-2 textures to function in WebGL1.
self.gl
.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
self.gl
.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
self.gl
.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
self.gl
.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
let handle = self.next_bitmap_handle;
self.next_bitmap_handle = BitmapHandle(self.next_bitmap_handle.0 + 1);
self.bitmap_registry
.insert(handle, RegistryData { bitmap, texture });
Ok(handle)
}
fn unregister_bitmap(&mut self, bitmap: BitmapHandle) {
self.bitmap_registry.remove(&bitmap);
}
fn update_texture(
&mut self,
handle: BitmapHandle,
width: u32,
height: u32,
rgba: Vec<u8>,
) -> Result<BitmapHandle, BitmapError> {
let texture = if let Some(entry) = self.bitmap_registry.get(&handle) {
&entry.texture
} else {
log::warn!("Tried to replace nonexistent texture");
return Ok(handle);
};
self.gl.bind_texture(Gl::TEXTURE_2D, Some(&texture));
self.gl
.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
Gl::TEXTURE_2D,
0,
Gl::RGBA as i32,
width as i32,
height as i32,
0,
Gl::RGBA,
Gl::UNSIGNED_BYTE,
Some(&rgba),
)
.into_js_result()
.map_err(|e| BitmapError::JavascriptError(e.into()))?;
Ok(handle)
}
}
impl CommandHandler for WebGlRenderBackend {
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool) {
self.set_stencil_state();
if let Some(entry) = self.bitmap_registry.get(&bitmap) {
// Adjust the quad draw to use the target bitmap.
let mesh = &self.meshes[self.bitmap_quad_shape.0];
let draw = &mesh.draws[0];
let bitmap_matrix = if let DrawType::Bitmap(BitmapDraw { matrix, .. }) = &draw.draw_type
{
matrix
} else {
unreachable!()
};
// Scale the quad to the bitmap's dimensions.
let matrix = transform.matrix
* ruffle_render::matrix::Matrix::scale(
entry.bitmap.width() as f32,
entry.bitmap.height() as f32,
);
let world_matrix = [
[matrix.a, matrix.b, 0.0, 0.0],
[matrix.c, matrix.d, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[
matrix.tx.to_pixels() as f32,
matrix.ty.to_pixels() as f32,
0.0,
1.0,
],
];
let mult_color = transform.color_transform.mult_rgba_normalized();
let add_color = transform.color_transform.add_rgba_normalized();
self.bind_vertex_array(Some(&draw.vao));
let program = &self.bitmap_program;
// Set common render state, while minimizing unnecessary state changes.
// TODO: Using designated layout specifiers in WebGL2/OpenGL ES 3, we could guarantee that uniforms
// are in the same location between shaders, and avoid changing them unless necessary.
if program as *const ShaderProgram != self.active_program {
self.gl.use_program(Some(&program.program));
self.active_program = program as *const ShaderProgram;
program.uniform_matrix4fv(&self.gl, ShaderUniform::ViewMatrix, &self.view_matrix);
self.mult_color = None;
self.add_color = None;
}
program.uniform_matrix4fv(&self.gl, ShaderUniform::WorldMatrix, &world_matrix);
if Some(mult_color) != self.mult_color {
program.uniform4fv(&self.gl, ShaderUniform::MultColor, &mult_color);
self.mult_color = Some(mult_color);
}
if Some(add_color) != self.add_color {
program.uniform4fv(&self.gl, ShaderUniform::AddColor, &add_color);
self.add_color = Some(add_color);
}
program.uniform_matrix3fv(&self.gl, ShaderUniform::TextureMatrix, bitmap_matrix);
// Bind texture.
self.gl.active_texture(Gl::TEXTURE0);
self.gl.bind_texture(Gl::TEXTURE_2D, Some(&entry.texture));
program.uniform1i(&self.gl, ShaderUniform::BitmapTexture, 0);
// Set texture parameters.
let filter = if smoothing {
Gl::LINEAR as i32
} else {
Gl::NEAREST as i32
};
self.gl
.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, filter);
self.gl
.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, filter);
let wrap = Gl::CLAMP_TO_EDGE as i32;
self.gl
.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, wrap);
self.gl
.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, wrap);
// Draw the triangles.
self.gl
.draw_elements_with_i32(Gl::TRIANGLES, draw.num_indices, Gl::UNSIGNED_INT, 0);
}
}
fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform) {
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.to_pixels() as f32,
transform.matrix.ty.to_pixels() as f32,
0.0,
1.0,
],
];
let mult_color = transform.color_transform.mult_rgba_normalized();
let add_color = transform.color_transform.add_rgba_normalized();
self.set_stencil_state();
let mesh = &self.meshes[shape.0];
for draw in &mesh.draws {
// Ignore strokes when drawing a mask stencil.
let num_indices = if self.mask_state != MaskState::DrawMaskStencil
&& self.mask_state != MaskState::ClearMaskStencil
{
draw.num_indices
} else {
draw.num_mask_indices
};
if num_indices == 0 {
continue;
}
self.bind_vertex_array(Some(&draw.vao));
let program = match &draw.draw_type {
DrawType::Color => &self.color_program,
DrawType::Gradient(_) => &self.gradient_program,
DrawType::Bitmap { .. } => &self.bitmap_program,
};
// Set common render state, while minimizing unnecessary state changes.
// TODO: Using designated layout specifiers in WebGL2/OpenGL ES 3, we could guarantee that uniforms
// are in the same location between shaders, and avoid changing them unless necessary.
if program as *const ShaderProgram != self.active_program {
self.gl.use_program(Some(&program.program));
self.active_program = program as *const ShaderProgram;
program.uniform_matrix4fv(&self.gl, ShaderUniform::ViewMatrix, &self.view_matrix);
self.mult_color = None;
self.add_color = None;
}
program.uniform_matrix4fv(&self.gl, ShaderUniform::WorldMatrix, &world_matrix);
if Some(mult_color) != self.mult_color {
program.uniform4fv(&self.gl, ShaderUniform::MultColor, &mult_color);
self.mult_color = Some(mult_color);
}
if Some(add_color) != self.add_color {
program.uniform4fv(&self.gl, ShaderUniform::AddColor, &add_color);
self.add_color = Some(add_color);
}
// Set shader specific uniforms.
match &draw.draw_type {
DrawType::Color => (),
DrawType::Gradient(gradient) => {
program.uniform_matrix3fv(
&self.gl,
ShaderUniform::TextureMatrix,
&gradient.matrix,
);
program.uniform1i(
&self.gl,
ShaderUniform::GradientType,
gradient.gradient_type,
);
program.uniform1fv(&self.gl, ShaderUniform::GradientRatios, &gradient.ratios);
program.uniform4fv(
&self.gl,
ShaderUniform::GradientColors,
bytemuck::cast_slice(&gradient.colors),
);
program.uniform1i(
&self.gl,
ShaderUniform::GradientNumColors,
gradient.num_colors as i32,
);
program.uniform1i(
&self.gl,
ShaderUniform::GradientRepeatMode,
gradient.repeat_mode,
);
program.uniform1f(
&self.gl,
ShaderUniform::GradientFocalPoint,
gradient.focal_point,
);
program.uniform1i(
&self.gl,
ShaderUniform::GradientInterpolation,
(gradient.interpolation == swf::GradientInterpolation::LinearRgb) as i32,
);
}
DrawType::Bitmap(bitmap) => {
let texture = if let Some(entry) = self.bitmap_registry.get(&bitmap.handle) {
&entry.texture
} else {
// Bitmap not registered
continue;
};
program.uniform_matrix3fv(
&self.gl,
ShaderUniform::TextureMatrix,
&bitmap.matrix,
);
// Bind texture.
self.gl.active_texture(Gl::TEXTURE0);
self.gl.bind_texture(Gl::TEXTURE_2D, Some(&texture));
program.uniform1i(&self.gl, ShaderUniform::BitmapTexture, 0);
// Set texture parameters.
let filter = if bitmap.is_smoothed {
Gl::LINEAR as i32
} else {
Gl::NEAREST as i32
};
self.gl
.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, filter);
self.gl
.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, filter);
// On WebGL1, you are unable to change the wrapping parameter of non-power-of-2 textures.
let wrap = if self.gl2.is_some() && bitmap.is_repeating {
Gl::REPEAT as i32
} else {
Gl::CLAMP_TO_EDGE as i32
};
self.gl
.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, wrap);
self.gl
.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, wrap);
}
}
// Draw the triangles.
self.gl
.draw_elements_with_i32(Gl::TRIANGLES, num_indices, Gl::UNSIGNED_INT, 0);
}
}
fn draw_rect(&mut self, color: Color, matrix: &ruffle_render::matrix::Matrix) {
let world_matrix = [
[matrix.a, matrix.b, 0.0, 0.0],
[matrix.c, matrix.d, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[
matrix.tx.to_pixels() as f32,
matrix.ty.to_pixels() as f32,
0.0,
1.0,
],
];
let mult_color = [
color.r as f32 * 255.0,
color.g as f32 * 255.0,
color.b as f32 * 255.0,
color.a as f32 * 255.0,
];
let add_color = [0.0; 4];
self.set_stencil_state();
let program = &self.color_program;
// Set common render state, while minimizing unnecessary state changes.
// TODO: Using designated layout specifiers in WebGL2/OpenGL ES 3, we could guarantee that uniforms
// are in the same location between shaders, and avoid changing them unless necessary.
if program as *const ShaderProgram != self.active_program {
self.gl.use_program(Some(&program.program));
self.active_program = program as *const ShaderProgram;
program.uniform_matrix4fv(&self.gl, ShaderUniform::ViewMatrix, &self.view_matrix);
self.mult_color = None;
self.add_color = None;
};
self.color_program
.uniform_matrix4fv(&self.gl, ShaderUniform::WorldMatrix, &world_matrix);
if Some(mult_color) != self.mult_color {
self.color_program
.uniform4fv(&self.gl, ShaderUniform::MultColor, &mult_color);
self.mult_color = Some(mult_color);
}
if Some(add_color) != self.add_color {
self.color_program
.uniform4fv(&self.gl, ShaderUniform::AddColor, &add_color);
self.add_color = Some(add_color);
}
let quad = &self.meshes[self.color_quad_shape.0];
self.bind_vertex_array(Some(&quad.draws[0].vao));
self.gl.draw_elements_with_i32(
Gl::TRIANGLES,
quad.draws[0].num_indices,
Gl::UNSIGNED_INT,
0,
);
}
fn push_mask(&mut self) {
debug_assert!(
self.mask_state == MaskState::NoMask || self.mask_state == MaskState::DrawMaskedContent
);
self.num_masks += 1;
self.mask_state = MaskState::DrawMaskStencil;
self.mask_state_dirty = true;
}
fn activate_mask(&mut self) {
debug_assert!(self.num_masks > 0 && self.mask_state == MaskState::DrawMaskStencil);
self.mask_state = MaskState::DrawMaskedContent;
self.mask_state_dirty = true;
}
fn deactivate_mask(&mut self) {
debug_assert!(self.num_masks > 0 && self.mask_state == MaskState::DrawMaskedContent);
self.mask_state = MaskState::ClearMaskStencil;
self.mask_state_dirty = true;
}
fn pop_mask(&mut self) {
debug_assert!(self.num_masks > 0 && self.mask_state == MaskState::ClearMaskStencil);
self.num_masks -= 1;
self.mask_state = if self.num_masks == 0 {
MaskState::NoMask
} else {
MaskState::DrawMaskedContent
};
self.mask_state_dirty = true;
}
fn push_blend_mode(&mut self, blend: BlendMode) {
if self.blend_modes.last() != Some(&blend) {
self.apply_blend_mode(blend);
}
self.blend_modes.push(blend);
}
fn pop_blend_mode(&mut self) {
let old = self.blend_modes.pop();
// We never pop our base 'BlendMode::Normal'
let current = *self.blend_modes.last().unwrap();
if old != Some(current) {
self.apply_blend_mode(current);
}
}
}
#[derive(Clone, Debug)]
struct Gradient {
matrix: [[f32; 3]; 3],
gradient_type: i32,
ratios: [f32; MAX_GRADIENT_COLORS],
colors: [[f32; 4]; MAX_GRADIENT_COLORS],
num_colors: u32,
repeat_mode: i32,
focal_point: f32,
interpolation: swf::GradientInterpolation,
}
impl From<TessGradient> for Gradient {
fn from(gradient: TessGradient) -> Self {
let mut ratios = [0.0; MAX_GRADIENT_COLORS];
let mut colors = [[0.0; 4]; MAX_GRADIENT_COLORS];
ratios[..gradient.num_colors].copy_from_slice(&gradient.ratios[..gradient.num_colors]);
colors[..gradient.num_colors].copy_from_slice(&gradient.colors[..gradient.num_colors]);
for i in gradient.num_colors..MAX_GRADIENT_COLORS {
ratios[i] = ratios[i - 1];
colors[i] = colors[i - 1];
}
Self {
matrix: gradient.matrix,
gradient_type: match gradient.gradient_type {
GradientType::Linear => 0,
GradientType::Radial => 1,
GradientType::Focal => 2,
},
ratios,
colors,
num_colors: gradient.num_colors as u32,
repeat_mode: match gradient.repeat_mode {
swf::GradientSpread::Pad => 0,
swf::GradientSpread::Repeat => 1,
swf::GradientSpread::Reflect => 2,
},
focal_point: gradient.focal_point.to_f32(),
interpolation: gradient.interpolation,
}
}
}
#[derive(Clone, Debug)]
struct BitmapDraw {
matrix: [[f32; 3]; 3],
handle: BitmapHandle,
is_repeating: bool,
is_smoothed: bool,
}
struct Mesh {
draws: Vec<Draw>,
}
#[allow(dead_code)]
struct Draw {
draw_type: DrawType,
vertex_buffer: WebGlBuffer,
index_buffer: WebGlBuffer,
vao: WebGlVertexArrayObject,
num_indices: i32,
num_mask_indices: i32,
}
enum DrawType {
Color,
Gradient(Box<Gradient>),
Bitmap(BitmapDraw),
}
struct MsaaBuffers {
color_renderbuffer: WebGlRenderbuffer,
stencil_renderbuffer: WebGlRenderbuffer,
render_framebuffer: WebGlFramebuffer,
color_framebuffer: WebGlFramebuffer,
framebuffer_texture: WebGlTexture,
}
// Because the shaders are currently simple and few in number, we are using a
// straightforward shader model. We maintain an enum of every possible uniform,
// and each shader tries to grab the location of each uniform.
struct ShaderProgram {
program: WebGlProgram,
uniforms: [Option<WebGlUniformLocation>; NUM_UNIFORMS],
vertex_position_location: u32,
vertex_color_location: u32,
num_vertex_attributes: u32,
}
// These should match the uniform names in the shaders.
const NUM_UNIFORMS: usize = 13;
const UNIFORM_NAMES: [&str; NUM_UNIFORMS] = [
"world_matrix",
"view_matrix",
"mult_color",
"add_color",
"u_matrix",
"u_gradient_type",
"u_ratios",
"u_colors",
"u_num_colors",
"u_repeat_mode",
"u_focal_point",
"u_interpolation",
"u_texture",
];
enum ShaderUniform {
WorldMatrix = 0,
ViewMatrix,
MultColor,
AddColor,
TextureMatrix,
GradientType,
GradientRatios,
GradientColors,
GradientNumColors,
GradientRepeatMode,
GradientFocalPoint,
GradientInterpolation,
BitmapTexture,
}
impl ShaderProgram {
fn new(
gl: &Gl,
vertex_shader: &WebGlShader,
fragment_shader: &WebGlShader,
) -> Result<Self, Error> {
let program = gl.create_program().ok_or(Error::UnableToCreateProgram)?;
gl.attach_shader(&program, vertex_shader);
gl.attach_shader(&program, fragment_shader);
gl.link_program(&program);
if !gl
.get_program_parameter(&program, Gl::LINK_STATUS)
.as_bool()
.unwrap_or(false)
{
let msg = format!(
"Error linking shader program: {:?}",
gl.get_program_info_log(&program)
);
log::error!("{}", msg);
return Err(Error::LinkingShaderProgram(msg));
}
// Find uniforms.
let mut uniforms: [Option<WebGlUniformLocation>; NUM_UNIFORMS] = Default::default();
for i in 0..NUM_UNIFORMS {
uniforms[i] = gl.get_uniform_location(&program, UNIFORM_NAMES[i]);
}
let vertex_position_location = gl.get_attrib_location(&program, "position") as u32;
let vertex_color_location = gl.get_attrib_location(&program, "color") as u32;
let num_vertex_attributes = if vertex_position_location != 0xffff_ffff {
1
} else {
0
} + if vertex_color_location != 0xffff_ffff {
1
} else {
0
};
Ok(ShaderProgram {
program,
uniforms,
vertex_position_location,
vertex_color_location,
num_vertex_attributes,
})
}
fn uniform1f(&self, gl: &Gl, uniform: ShaderUniform, value: f32) {
gl.uniform1f(self.uniforms[uniform as usize].as_ref(), value);
}
fn uniform1fv(&self, gl: &Gl, uniform: ShaderUniform, values: &[f32]) {
gl.uniform1fv_with_f32_array(self.uniforms[uniform as usize].as_ref(), values);
}
fn uniform1i(&self, gl: &Gl, uniform: ShaderUniform, value: i32) {
gl.uniform1i(self.uniforms[uniform as usize].as_ref(), value);
}
fn uniform4fv(&self, gl: &Gl, uniform: ShaderUniform, values: &[f32]) {
gl.uniform4fv_with_f32_array(self.uniforms[uniform as usize].as_ref(), values);
}
fn uniform_matrix3fv(&self, gl: &Gl, uniform: ShaderUniform, values: &[[f32; 3]; 3]) {
gl.uniform_matrix3fv_with_f32_array(
self.uniforms[uniform as usize].as_ref(),
false,
bytemuck::cast_slice(values),
);
}
fn uniform_matrix4fv(&self, gl: &Gl, uniform: ShaderUniform, values: &[[f32; 4]; 4]) {
gl.uniform_matrix4fv_with_f32_array(
self.uniforms[uniform as usize].as_ref(),
false,
bytemuck::cast_slice(values),
);
}
}
impl WebGlRenderBackend {}
trait GlExt {
fn check_error(&self, error_msg: &'static str) -> Result<(), Error>;
}
impl GlExt for Gl {
/// Check if GL returned an error for the previous operation.
fn check_error(&self, error_msg: &'static str) -> Result<(), Error> {
match self.get_error() {
Self::NO_ERROR => Ok(()),
error => Err(Error::GLError(error_msg, error)),
}
}
}
impl GlExt for Gl2 {
/// Check if GL returned an error for the previous operation.
fn check_error(&self, error_msg: &'static str) -> Result<(), Error> {
match self.get_error() {
Self::NO_ERROR => Ok(()),
error => Err(Error::GLError(error_msg, error)),
}
}
}