Skip to content

Commit

Permalink
webgl: Lazily clear the canvas right before the first webgl command o…
Browse files Browse the repository at this point in the history
…f the next frame.
  • Loading branch information
jdm committed Oct 10, 2019
1 parent 4d7110a commit c53680b
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 124 deletions.
11 changes: 11 additions & 0 deletions components/canvas/gl_context.rs
Expand Up @@ -179,6 +179,17 @@ impl GLContextWrapper {
}
}

pub fn framebuffer(&self) -> gl::GLuint {
match *self {
GLContextWrapper::Native(ref ctx) => {
ctx.borrow_draw_buffer().unwrap().get_framebuffer()
},
GLContextWrapper::OSMesa(ref ctx) => {
ctx.borrow_draw_buffer().unwrap().get_framebuffer()
},
}
}

pub fn get_info(&self) -> (Size2D<i32>, u32, GLLimits, GLFormats) {
match *self {
GLContextWrapper::Native(ref ctx) => {
Expand Down
157 changes: 77 additions & 80 deletions components/canvas/webgl_thread.rs
Expand Up @@ -239,6 +239,12 @@ impl WebGLThread {
let result = self.create_webgl_context(version, size, attributes);
result_sender
.send(result.map(|(id, limits, share_mode, framebuffer_format)| {
let image_key = self
.cached_context_info
.get_mut(&id)
.expect("Where's the cached context info?")
.image_key;

let data = Self::make_current_if_needed(
id,
&self.contexts,
Expand Down Expand Up @@ -277,6 +283,7 @@ impl WebGLThread {
glsl_version,
api_type,
framebuffer_format,
image_key,
}
}))
.unwrap();
Expand All @@ -299,12 +306,12 @@ impl WebGLThread {
WebGLMsg::Unlock(ctx_id) => {
self.handle_unlock(ctx_id);
},
WebGLMsg::UpdateWebRenderImage(ctx_id, sender) => {
self.handle_update_wr_image(ctx_id, sender);
},
WebGLMsg::DOMToTextureCommand(command) => {
self.handle_dom_to_texture(command);
},
WebGLMsg::SwapBuffers(context_ids, sender) => {
self.handle_swap_buffers(context_ids, sender);
},
WebGLMsg::Exit => {
return true;
},
Expand All @@ -326,6 +333,11 @@ impl WebGLThread {
&mut self.bound_context_id,
);
if let Some(data) = data {
let info = self.cached_context_info.get_mut(&context_id).unwrap();
if info.clear_required {
info.clear_required = false;
Self::clear_drawing_buffer(data);
}
data.ctx.apply_command(
command,
data.use_apple_vertex_arrays,
Expand Down Expand Up @@ -400,32 +412,36 @@ impl WebGLThread {
data.ctx.gl().delete_sync(gl_sync);
debug_assert!(data.ctx.gl().get_error() == gl::NO_ERROR);
}

self.clear_drawing_buffer(context_id);
}

fn clear_drawing_buffer(&mut self, context_id: WebGLContextId) {
let info = self.cached_context_info.get_mut(&context_id).unwrap();
if info.preserve_drawing_buffer {
return;
#[allow(unsafe_code)]
fn clear_drawing_buffer(data: &mut GLContextData) {
trace!("clearing GL framebuffer");

// Ensure we're clearing the default framebuffer.
let mut fb = [0];
unsafe {
data.ctx
.gl()
.get_integer_v(gl::FRAMEBUFFER_BINDING, &mut fb);
}
data.ctx
.gl()
.bind_framebuffer(gl::FRAMEBUFFER, data.ctx.framebuffer());

let data = Self::make_current_if_needed(
context_id,
&self.contexts,
&mut self.bound_context_id,
)
.expect("WebGLContext not found when clearing drawing buffer");
trace!("clearing GL framebuffer");
data.ctx.gl().clear_color(0., 0., 0., 0.);
data.ctx.gl().clear_depth(1.0);
data.ctx.gl().clear_stencil(0);
data.ctx.gl().clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT);
data.ctx
.gl()
.clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT);

let (r, g, b, a) = data.state.clear_color;
data.ctx.gl().clear_color(r, g, b, a);
data.ctx.gl().clear_depth(data.state.depth_clear_value);
data.ctx.gl().clear_stencil(data.state.stencil_clear_value);

data.ctx.gl().bind_framebuffer(gl::FRAMEBUFFER, fb[0] as _);
}

/// Creates a new WebGLContext
Expand Down Expand Up @@ -475,17 +491,33 @@ impl WebGLThread {
},
);

let image_key = match share_mode {
WebGLContextShareMode::Readback => Self::create_wr_readback_image(
&self.webrender_api,
size,
attributes.alpha,
vec![0; 4 * size.width as usize * size.height as usize],
),
WebGLContextShareMode::SharedTexture => Self::create_wr_external_image(
&self.webrender_api,
size.to_i32(),
attributes.alpha,
id,
),
};

self.cached_context_info.insert(
id,
WebGLContextInfo {
texture_id,
size,
alpha: attributes.alpha,
image_key: None,
image_key: image_key,
share_mode,
gl_sync: None,
render_state: ContextRenderState::Unlocked,
preserve_drawing_buffer,
clear_required: false,
},
);

Expand Down Expand Up @@ -527,7 +559,7 @@ impl WebGLThread {
// Readback mode already updates the image every frame to send the raw pixels.
// See `handle_update_wr_image`.
match (info.image_key, info.share_mode) {
(Some(image_key), WebGLContextShareMode::SharedTexture) => {
(image_key, WebGLContextShareMode::SharedTexture) => {
Self::update_wr_external_image(
&self.webrender_api,
info.size,
Expand All @@ -553,9 +585,7 @@ impl WebGLThread {
if let Some(info) = self.cached_context_info.remove(&context_id) {
let mut txn = webrender_api::Transaction::new();

if let Some(image_key) = info.image_key {
txn.delete_image(image_key);
}
txn.delete_image(info.image_key);

self.webrender_api.update_resources(txn.resource_updates)
}
Expand All @@ -571,68 +601,33 @@ impl WebGLThread {
self.bound_context_id = None;
}

/// Handles the creation/update of webrender_api::ImageKeys for a specific WebGLContext.
/// This method is invoked from a UpdateWebRenderImage message sent by the layout thread.
/// If SharedTexture is used the UpdateWebRenderImage message is sent only after a WebGLContext creation.
/// If Readback is used UpdateWebRenderImage message is sent always on each layout iteration in order to
/// submit the updated raw pixels.
fn handle_update_wr_image(
fn handle_swap_buffers(
&mut self,
context_id: WebGLContextId,
sender: WebGLSender<webrender_api::ImageKey>,
context_ids: Vec<WebGLContextId>,
completed_sender: WebGLSender<()>,
) {
let info = self.cached_context_info.get_mut(&context_id).unwrap();
let webrender_api = &self.webrender_api;

let image_key = match info.share_mode {
WebGLContextShareMode::SharedTexture => {
let size = info.size;
let alpha = info.alpha;
// Reuse existing ImageKey or generate a new one.
// When using a shared texture ImageKeys are only generated after a WebGLContext creation.
*info.image_key.get_or_insert_with(|| {
Self::create_wr_external_image(webrender_api, size, alpha, context_id)
})
},
WebGLContextShareMode::Readback => {
let pixels = Self::raw_pixels(&self.contexts[&context_id].ctx, info.size);
match info.image_key.clone() {
Some(image_key) => {
// ImageKey was already created, but WR Images must
// be updated every frame in readback mode to send the new raw pixels.
Self::update_wr_readback_image(
webrender_api,
info.size,
info.alpha,
image_key,
pixels,
);

image_key
},
None => {
// Generate a new ImageKey for Readback mode.
let image_key = Self::create_wr_readback_image(
webrender_api,
info.size,
info.alpha,
pixels,
);
info.image_key = Some(image_key);
image_key
},
}
},
};
for context_id in context_ids {
let info = self.cached_context_info.get_mut(&context_id).unwrap();
let webrender_api = &self.webrender_api;

// Send the ImageKey to the Layout thread.
sender.send(image_key).unwrap();
if let WebGLContextShareMode::Readback = info.share_mode {
let pixels = Self::raw_pixels(&self.contexts[&context_id].ctx, info.size);
// WR Images must be updated every frame in readback mode to send the new raw pixels.
Self::update_wr_readback_image(
webrender_api,
info.size,
info.alpha,
info.image_key,
pixels,
);
}

if let WebGLContextShareMode::Readback = info.share_mode {
// Ensure that the drawing buffer is cleared when webrender isn't involved
// in drawing the GL texture.
self.clear_drawing_buffer(context_id);
if !info.preserve_drawing_buffer {
info.clear_required = true;
}
}

let _ = completed_sender.send(());
}

fn handle_dom_to_texture(&mut self, command: DOMToTextureCommand) {
Expand Down Expand Up @@ -911,7 +906,7 @@ struct WebGLContextInfo {
/// True if the WebGLContext uses an alpha channel.
alpha: bool,
/// Currently used WebRender image key.
image_key: Option<webrender_api::ImageKey>,
image_key: webrender_api::ImageKey,
/// The sharing mode used to send the image to WebRender.
share_mode: WebGLContextShareMode,
/// GLSync Object used for a correct synchronization with Webrender external image callbacks.
Expand All @@ -920,6 +915,8 @@ struct WebGLContextInfo {
render_state: ContextRenderState,
/// Should the drawing buffer be preserved between frames?
preserve_drawing_buffer: bool,
/// Does the canvas need to be cleared before executing further WebGL commands?
clear_required: bool,
}

/// Data about the linked DOM<->WebGLTexture elements.
Expand Down
12 changes: 4 additions & 8 deletions components/canvas_traits/webgl.rs
Expand Up @@ -64,10 +64,10 @@ pub enum WebGLMsg {
/// The WR unlocks a context when it finished reading the shared texture contents.
/// Unlock messages are always sent after a Lock message.
Unlock(WebGLContextId),
/// Creates or updates the image keys required for WebRender.
UpdateWebRenderImage(WebGLContextId, WebGLSender<ImageKey>),
/// Commands used for the DOMToTexture feature.
DOMToTextureCommand(DOMToTextureCommand),
/// Performs a buffer swap.
SwapBuffers(Vec<WebGLContextId>, WebGLSender<()>),
/// Frees all resources and closes the thread.
Exit,
}
Expand All @@ -93,6 +93,8 @@ pub struct WebGLCreateContextResult {
pub api_type: GlType,
/// The format for creating new offscreen framebuffers for this context.
pub framebuffer_format: GLFormats,
/// The WebRender image key.
pub image_key: ImageKey,
}

#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)]
Expand Down Expand Up @@ -174,12 +176,6 @@ impl WebGLMsgSender {
self.sender.send(WebGLMsg::RemoveContext(self.ctx_id))
}

#[inline]
pub fn send_update_wr_image(&self, sender: WebGLSender<ImageKey>) -> WebGLSendResult {
self.sender
.send(WebGLMsg::UpdateWebRenderImage(self.ctx_id, sender))
}

pub fn send_dom_to_texture(&self, command: DOMToTextureCommand) -> WebGLSendResult {
self.sender.send(WebGLMsg::DOMToTextureCommand(command))
}
Expand Down
5 changes: 4 additions & 1 deletion components/script/dom/bindings/trace.rs
Expand Up @@ -48,7 +48,9 @@ use canvas_traits::canvas::{CompositionOrBlending, LineCapStyle, LineJoinStyle,
use canvas_traits::webgl::WebGLVertexArrayId;
use canvas_traits::webgl::{ActiveAttribInfo, ActiveUniformInfo, GlType, TexDataType, TexFormat};
use canvas_traits::webgl::{GLFormats, GLLimits, WebGLQueryId, WebGLSamplerId};
use canvas_traits::webgl::{WebGLBufferId, WebGLChan, WebGLContextShareMode, WebGLError};
use canvas_traits::webgl::{
WebGLBufferId, WebGLChan, WebGLContextId, WebGLContextShareMode, WebGLError,
};
use canvas_traits::webgl::{WebGLFramebufferId, WebGLMsgSender, WebGLPipeline, WebGLProgramId};
use canvas_traits::webgl::{WebGLReceiver, WebGLRenderbufferId, WebGLSLVersion, WebGLSender};
use canvas_traits::webgl::{WebGLShaderId, WebGLSyncId, WebGLTextureId, WebGLVersion};
Expand Down Expand Up @@ -519,6 +521,7 @@ unsafe_no_jsmanaged_fields!(Rect<f32>);
unsafe_no_jsmanaged_fields!(CascadeData);
unsafe_no_jsmanaged_fields!(WindowGLContext);
unsafe_no_jsmanaged_fields!(Frame);
unsafe_no_jsmanaged_fields!(WebGLContextId);

unsafe impl<'a> JSTraceable for &'a str {
#[inline]
Expand Down
22 changes: 22 additions & 0 deletions components/script/dom/document.rs
Expand Up @@ -109,6 +109,7 @@ use crate::stylesheet_set::StylesheetSetRef;
use crate::task::TaskBox;
use crate::task_source::{TaskSource, TaskSourceName};
use crate::timers::OneshotTimerCallback;
use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg};
use cookie::Cookie;
use devtools_traits::ScriptToDevtoolsControlMsg;
use dom_struct::dom_struct;
Expand Down Expand Up @@ -395,6 +396,8 @@ pub struct Document {
/// where `id` needs to match any of the registered ShadowRoots
/// hosting the media controls UI.
media_controls: DomRefCell<HashMap<String, Dom<ShadowRoot>>>,
/// List of all WebGL context IDs that need flushing.
dirty_webgl_contexts: DomRefCell<HashSet<WebGLContextId>>,
}

#[derive(JSTraceable, MallocSizeOf)]
Expand Down Expand Up @@ -2490,6 +2493,24 @@ impl Document {
debug_assert!(false, "Trying to unregister unknown media controls");
}
}

pub fn add_dirty_canvas(&self, context_id: WebGLContextId) {
self.dirty_webgl_contexts.borrow_mut().insert(context_id);
}

pub fn flush_dirty_canvases(&self) {
let dirty_context_ids: Vec<_> = self.dirty_webgl_contexts.borrow_mut().drain().collect();
if dirty_context_ids.is_empty() {
return;
}
let (sender, receiver) = webgl::webgl_channel().unwrap();
self.window
.webgl_chan()
.expect("Where's the WebGL channel?")
.send(WebGLMsg::SwapBuffers(dirty_context_ids, sender))
.unwrap();
receiver.recv().unwrap();
}
}

#[derive(MallocSizeOf, PartialEq)]
Expand Down Expand Up @@ -2784,6 +2805,7 @@ impl Document {
shadow_roots: DomRefCell::new(HashSet::new()),
shadow_roots_styles_changed: Cell::new(false),
media_controls: DomRefCell::new(HashMap::new()),
dirty_webgl_contexts: DomRefCell::new(HashSet::new()),
}
}

Expand Down

0 comments on commit c53680b

Please sign in to comment.