render: Introduced render commands, moved to a command list model instead of direct rendering

This commit is contained in:
Nathan Adams 2022-09-03 19:22:57 +02:00
parent c7f420dde5
commit 267ea0fd13
20 changed files with 797 additions and 659 deletions

View File

@ -11,6 +11,7 @@ use crate::display_object::TDisplayObject;
use bitflags::bitflags;
use ruffle_render::backend::RenderBackend;
use ruffle_render::bitmap::{Bitmap, BitmapFormat, BitmapHandle};
use ruffle_render::commands::{CommandHandler, CommandList};
use ruffle_render::transform::Transform;
use std::ops::Range;
@ -971,54 +972,50 @@ impl<'gc> BitmapData<'gc> {
let mut transform_stack = ruffle_render::transform::TransformStack::new();
transform_stack.push(&transform);
let handle = self.bitmap_handle(context.renderer).unwrap();
let mut commands = CommandList::new();
let mut render_context = RenderContext {
renderer: context.renderer,
commands: &mut commands,
gc_context: context.gc_context,
ui: context.ui,
library: &context.library,
transform_stack: &mut transform_stack,
is_offscreen: true,
stage: context.stage,
clip_depth_stack: vec![],
allow_mask: true,
};
// Make the screen opacity match the opacity of this bitmap
let initial_alpha = if self.transparency { 0 } else { 0xFF };
render_context.commands.push_blend_mode(blend_mode);
match &mut source {
IBitmapDrawable::BitmapData(data) => {
let source_handle = data
.write(context.gc_context)
.bitmap_handle(render_context.renderer)
.unwrap();
render_context.commands.render_bitmap(
source_handle,
render_context.transform_stack.transform(),
smoothing,
);
}
IBitmapDrawable::DisplayObject(object) => {
// Note that we do *not* use `render_base`,
// as we want to ignore the object's mask and normal transform
object.render_self(&mut render_context);
}
}
render_context.commands.pop_blend_mode();
let image = context.renderer.render_offscreen(
handle,
bitmapdata_width,
bitmapdata_height,
&mut |renderer| {
let mut render_context = RenderContext {
renderer,
gc_context: context.gc_context,
ui: context.ui,
library: &context.library,
transform_stack: &mut transform_stack,
is_offscreen: true,
stage: context.stage,
clip_depth_stack: vec![],
allow_mask: true,
};
// Make the screen opacity match the opacity of this bitmap
let initial_alpha = if self.transparency { 0 } else { 0xFF };
render_context
.renderer
.begin_frame(swf::Color::from_rgb(0x000000, initial_alpha));
render_context.renderer.push_blend_mode(blend_mode);
match &mut source {
IBitmapDrawable::BitmapData(data) => {
let source_handle = data
.write(context.gc_context)
.bitmap_handle(render_context.renderer)
.unwrap();
render_context.renderer.render_bitmap(
source_handle,
render_context.transform_stack.transform(),
smoothing,
);
}
IBitmapDrawable::DisplayObject(object) => {
// Note that we do *not* use `render_base`,
// as we want to ignore the object's mask and normal transform
object.render_self(&mut render_context);
}
}
render_context.renderer.pop_blend_mode();
render_context.renderer.end_frame();
Ok(())
},
commands,
swf::Color::from_rgb(0x000000, initial_alpha),
);
match image {

View File

@ -27,6 +27,7 @@ use gc_arena::{Collect, MutationContext};
use instant::Instant;
use rand::rngs::SmallRng;
use ruffle_render::backend::RenderBackend;
use ruffle_render::commands::CommandList;
use ruffle_render::transform::TransformStack;
use ruffle_video::backend::VideoBackend;
use std::collections::{HashMap, VecDeque};
@ -428,9 +429,12 @@ impl<'gc> Default for ActionQueue<'gc> {
/// Shared data used during rendering.
/// `Player` creates this when it renders a frame and passes it down to display objects.
pub struct RenderContext<'a, 'gc, 'gc_context> {
/// The renderer, used by the display objects to draw themselves.
/// The renderer, used by the display objects to register themselves.
pub renderer: &'a mut dyn RenderBackend,
/// The command list, used by the display objects to draw themselves.
pub commands: &'a mut CommandList,
/// The GC MutationContext, used to perform any GcCell writes
/// that must occur during rendering.
pub gc_context: MutationContext<'gc, 'gc_context>,

View File

@ -47,6 +47,7 @@ pub use interactive::{InteractiveObject, TInteractiveObject};
pub use loader_display::LoaderDisplay;
pub use morph_shape::{MorphShape, MorphShapeStatic};
pub use movie_clip::{MovieClip, Scene};
use ruffle_render::commands::CommandHandler;
pub use stage::{Stage, StageAlign, StageDisplayState, StageQuality, StageScaleMode, WindowMode};
pub use text::Text;
pub use video::Video;
@ -483,7 +484,7 @@ pub fn render_base<'gc>(this: DisplayObject<'gc>, context: &mut RenderContext<'_
context.transform_stack.push(this.base().transform());
let blend_mode = this.blend_mode();
if blend_mode != BlendMode::Normal {
context.renderer.push_blend_mode(this.blend_mode());
context.commands.push_blend_mode(this.blend_mode());
}
let scroll_rect_matrix = if let Some(rect) = this.scroll_rect() {
@ -518,13 +519,13 @@ pub fn render_base<'gc>(this: DisplayObject<'gc>, context: &mut RenderContext<'_
if let Some(m) = mask {
mask_transform.matrix = this.global_to_local_matrix();
mask_transform.matrix *= m.local_to_global_matrix();
context.renderer.push_mask();
context.commands.push_mask();
context.allow_mask = false;
context.transform_stack.push(&mask_transform);
m.render_self(context);
context.transform_stack.pop();
context.allow_mask = true;
context.renderer.activate_mask();
context.commands.activate_mask();
}
// There are two parts to 'DisplayObject.scrollRect':
@ -539,10 +540,10 @@ pub fn render_base<'gc>(this: DisplayObject<'gc>, context: &mut RenderContext<'_
// lies in the intersection of the scroll rect and DisplayObject.mask,
// which is exactly the behavior that we want.
if let Some(rect_mat) = scroll_rect_matrix {
context.renderer.push_mask();
context.commands.push_mask();
// The color doesn't matter, as this is a mask.
context.renderer.draw_rect(Color::BLACK, &rect_mat);
context.renderer.activate_mask();
context.commands.draw_rect(Color::BLACK, &rect_mat);
context.commands.activate_mask();
}
this.render_self(context);
@ -550,22 +551,22 @@ pub fn render_base<'gc>(this: DisplayObject<'gc>, context: &mut RenderContext<'_
if let Some(rect_mat) = scroll_rect_matrix {
// Draw the rectangle again after deactivating the mask,
// to reset the stencil buffer.
context.renderer.deactivate_mask();
context.renderer.draw_rect(Color::BLACK, &rect_mat);
context.renderer.pop_mask();
context.commands.deactivate_mask();
context.commands.draw_rect(Color::BLACK, &rect_mat);
context.commands.pop_mask();
}
if let Some(m) = mask {
context.renderer.deactivate_mask();
context.commands.deactivate_mask();
context.allow_mask = false;
context.transform_stack.push(&mask_transform);
m.render_self(context);
context.transform_stack.pop();
context.allow_mask = true;
context.renderer.pop_mask();
context.commands.pop_mask();
}
if blend_mode != BlendMode::Normal {
context.renderer.pop_blend_mode();
context.commands.pop_blend_mode();
}
if scroll_rect_matrix.is_some() {

View File

@ -11,6 +11,7 @@ use crate::prelude::*;
use crate::vminterface::Instantiator;
use gc_arena::{Collect, Gc, GcCell, MutationContext};
use ruffle_render::bitmap::BitmapHandle;
use ruffle_render::commands::CommandHandler;
use std::cell::{Ref, RefMut};
/// A Bitmap display object is a raw bitamp on the stage.
@ -272,7 +273,7 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> {
}
}
context.renderer.render_bitmap(
context.commands.render_bitmap(
bitmap_handle,
context.transform_stack.transform(),
bitmap_data.smoothing,

View File

@ -11,6 +11,7 @@ use crate::string::WStr;
use bitflags::bitflags;
use gc_arena::{Collect, MutationContext};
use ruffle_macros::enum_trait_object;
use ruffle_render::commands::CommandHandler;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fmt::Debug;
@ -293,21 +294,21 @@ pub trait TDisplayObjectContainer<'gc>:
// Clear the mask stencil and pop the mask.
let (prev_clip_depth, clip_child) = clip_depth_stack.pop().unwrap();
clip_depth = prev_clip_depth;
context.renderer.deactivate_mask();
context.commands.deactivate_mask();
context.allow_mask = false;
clip_child.render(context);
context.allow_mask = true;
context.renderer.pop_mask();
context.commands.pop_mask();
}
if context.allow_mask && child.clip_depth() > 0 && child.allow_as_mask() {
// Push and render the mask.
clip_depth_stack.push((clip_depth, child));
clip_depth = child.clip_depth();
context.renderer.push_mask();
context.commands.push_mask();
context.allow_mask = false;
child.render(context);
context.allow_mask = true;
context.renderer.activate_mask();
context.commands.activate_mask();
} else if child.visible() {
// Normal child.
child.render(context);
@ -316,11 +317,11 @@ pub trait TDisplayObjectContainer<'gc>:
// Pop any remaining masks.
for (_, clip_child) in clip_depth_stack.into_iter().rev() {
context.renderer.deactivate_mask();
context.commands.deactivate_mask();
context.allow_mask = false;
clip_child.render(context);
context.allow_mask = true;
context.renderer.pop_mask();
context.commands.pop_mask();
}
}
}

View File

@ -26,6 +26,7 @@ use crate::tag_utils::SwfMovie;
use crate::vminterface::{AvmObject, Instantiator};
use chrono::Utc;
use gc_arena::{Collect, Gc, GcCell, MutationContext};
use ruffle_render::commands::CommandHandler;
use ruffle_render::shape_utils::DrawCommand;
use ruffle_render::transform::Transform;
use std::{cell::Ref, cell::RefMut, sync::Arc};
@ -882,7 +883,7 @@ impl<'gc> EditText<'gc> {
x + Twips::from_pixels(-1.0),
Twips::from_pixels(2.0),
);
context.renderer.draw_rect(Color::BLACK, &selection_box);
context.commands.draw_rect(Color::BLACK, &selection_box);
// Set text color to white
context.transform_stack.push(&Transform {
@ -898,7 +899,7 @@ impl<'gc> EditText<'gc> {
// Render glyph.
let glyph_shape_handle = glyph.shape_handle(context.renderer);
context
.renderer
.commands
.render_shape(glyph_shape_handle, context.transform_stack.transform());
context.transform_stack.pop();
@ -912,7 +913,7 @@ impl<'gc> EditText<'gc> {
x + Twips::from_pixels(-1.0),
Twips::from_pixels(2.0),
);
context.renderer.draw_rect(color.clone(), &caret);
context.commands.draw_rect(color.clone(), &caret);
} else if pos == length - 1 && caret_pos == length {
let caret = context.transform_stack.transform().matrix
* Matrix::create_box(
@ -922,7 +923,7 @@ impl<'gc> EditText<'gc> {
x + advance,
Twips::from_pixels(2.0),
);
context.renderer.draw_rect(color.clone(), &caret);
context.commands.draw_rect(color.clone(), &caret);
}
}
},
@ -1558,7 +1559,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
edit_text.drawing.render(context);
context.renderer.push_mask();
context.commands.push_mask();
let mask = Matrix::create_box(
edit_text.bounds.width().to_pixels() as f32,
edit_text.bounds.height().to_pixels() as f32,
@ -1566,11 +1567,11 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
Twips::ZERO,
Twips::ZERO,
);
context.renderer.draw_rect(
context.commands.draw_rect(
Color::BLACK,
&(context.transform_stack.transform().matrix * mask),
);
context.renderer.activate_mask();
context.commands.activate_mask();
let scroll_offset = if edit_text.scroll > 1 {
let line_data = &edit_text.line_data;
@ -1612,7 +1613,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
Twips::from_pixels(-1.0),
Twips::from_pixels(2.0),
);
context.renderer.draw_rect(Color::BLACK, &caret);
context.commands.draw_rect(Color::BLACK, &caret);
}
}
} else {
@ -1623,12 +1624,12 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
context.transform_stack.pop();
context.renderer.deactivate_mask();
context.renderer.draw_rect(
context.commands.deactivate_mask();
context.commands.draw_rect(
Color::BLACK,
&(context.transform_stack.transform().matrix * mask),
);
context.renderer.pop_mask();
context.commands.pop_mask();
context.transform_stack.pop();
}

View File

@ -10,6 +10,7 @@ use crate::tag_utils::SwfMovie;
use crate::vminterface::Instantiator;
use gc_arena::{Collect, GcCell, MutationContext};
use ruffle_render::backend::ShapeHandle;
use ruffle_render::commands::CommandHandler;
use std::cell::{Ref, RefMut};
use std::sync::Arc;
@ -169,7 +170,7 @@ impl<'gc> TDisplayObject<'gc> for Graphic<'gc> {
drawing.render(context);
} else if let Some(render_handle) = self.0.read().static_data.render_handle {
context
.renderer
.commands
.render_shape(render_handle, context.transform_stack.transform())
}
}

View File

@ -5,6 +5,7 @@ use crate::prelude::*;
use crate::tag_utils::SwfMovie;
use gc_arena::{Collect, Gc, GcCell, MutationContext};
use ruffle_render::backend::{RenderBackend, ShapeHandle};
use ruffle_render::commands::CommandHandler;
use std::cell::{Ref, RefCell, RefMut};
use std::sync::Arc;
use swf::{Fixed16, Fixed8, Twips};
@ -94,7 +95,7 @@ impl<'gc> TDisplayObject<'gc> for MorphShape<'gc> {
let static_data = this.static_data;
let shape_handle = static_data.get_shape(context.renderer, context.library, ratio);
context
.renderer
.commands
.render_shape(shape_handle, context.transform_stack.transform());
}

View File

@ -24,6 +24,7 @@ use crate::vminterface::Instantiator;
use bitflags::bitflags;
use gc_arena::{Collect, GcCell, MutationContext};
use ruffle_render::backend::ViewportDimensions;
use ruffle_render::commands::CommandHandler;
use std::cell::{Ref, RefMut};
use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
@ -512,7 +513,7 @@ impl<'gc> Stage<'gc> {
if margin_top + margin_bottom > margin_left + margin_right {
// Top + bottom
if margin_top > 0.0 {
context.renderer.draw_rect(
context.commands.draw_rect(
Color::BLACK,
&Matrix::create_box(
viewport_width,
@ -524,7 +525,7 @@ impl<'gc> Stage<'gc> {
);
}
if margin_bottom > 0.0 {
context.renderer.draw_rect(
context.commands.draw_rect(
Color::BLACK,
&Matrix::create_box(
viewport_width,
@ -538,7 +539,7 @@ impl<'gc> Stage<'gc> {
} else {
// Left + right
if margin_left > 0.0 {
context.renderer.draw_rect(
context.commands.draw_rect(
Color::BLACK,
&Matrix::create_box(
margin_left,
@ -550,7 +551,7 @@ impl<'gc> Stage<'gc> {
);
}
if margin_right > 0.0 {
context.renderer.draw_rect(
context.commands.draw_rect(
Color::BLACK,
&Matrix::create_box(
margin_right,
@ -700,22 +701,11 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
}
fn render(&self, context: &mut RenderContext<'_, 'gc, '_>) {
let background_color =
if self.window_mode() != WindowMode::Transparent || self.is_fullscreen() {
self.background_color().unwrap_or(Color::WHITE)
} else {
Color::from_rgba(0)
};
context.renderer.begin_frame(background_color);
render_base((*self).into(), context);
if self.should_letterbox() {
self.draw_letterbox(context);
}
context.renderer.end_frame();
}
fn enter_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) {

View File

@ -4,6 +4,7 @@ use crate::font::TextRenderSettings;
use crate::prelude::*;
use crate::tag_utils::SwfMovie;
use gc_arena::{Collect, GcCell, MutationContext};
use ruffle_render::commands::CommandHandler;
use ruffle_render::transform::Transform;
use std::cell::{Ref, RefMut};
use std::sync::Arc;
@ -136,7 +137,7 @@ impl<'gc> TDisplayObject<'gc> for Text<'gc> {
context.transform_stack.push(&transform);
let glyph_shape_handle = glyph.shape_handle(context.renderer);
context
.renderer
.commands
.render_shape(glyph_shape_handle, context.transform_stack.transform());
context.transform_stack.pop();
transform.matrix.tx += Twips::new(c.advance);

View File

@ -12,6 +12,7 @@ use crate::vminterface::{AvmObject, Instantiator};
use gc_arena::{Collect, GcCell, MutationContext};
use ruffle_render::bitmap::BitmapInfo;
use ruffle_render::bounding_box::BoundingBox;
use ruffle_render::commands::CommandHandler;
use ruffle_video::error::Error;
use ruffle_video::frame::EncodedFrame;
use ruffle_video::VideoStreamHandle;
@ -465,7 +466,7 @@ impl<'gc> TDisplayObject<'gc> for Video<'gc> {
};
context
.renderer
.commands
.render_bitmap(bitmap.handle, &transform, smoothing);
} else {
log::warn!("Video has no decoded frame to render.");

View File

@ -3,6 +3,7 @@ use gc_arena::Collect;
use ruffle_render::backend::ShapeHandle;
use ruffle_render::bitmap::{BitmapInfo, BitmapSource};
use ruffle_render::bounding_box::BoundingBox;
use ruffle_render::commands::CommandHandler;
use ruffle_render::shape_utils::{DistilledShape, DrawCommand, DrawPath};
use std::cell::Cell;
use swf::{FillStyle, LineStyle, Twips};
@ -290,7 +291,7 @@ impl Drawing {
if let Some(handle) = self.render_handle.get() {
context
.renderer
.commands
.render_shape(handle, context.transform_stack.transform());
}
}

View File

@ -42,6 +42,7 @@ use instant::Instant;
use log::info;
use rand::{rngs::SmallRng, SeedableRng};
use ruffle_render::backend::{null::NullRenderer, RenderBackend, ViewportDimensions};
use ruffle_render::commands::CommandList;
use ruffle_render::transform::TransformStack;
use ruffle_video::backend::VideoBackend;
use std::cell::RefCell;
@ -1350,24 +1351,38 @@ impl Player {
pub fn render(&mut self) {
let (renderer, ui, transform_stack) =
(&mut self.renderer, &mut self.ui, &mut self.transform_stack);
let mut commands = CommandList::new();
let mut background_color = Color::WHITE;
self.gc_arena.borrow().mutate(|gc_context, gc_root| {
let root_data = gc_root.data.read();
let stage = root_data.stage;
let mut render_context = RenderContext {
renderer: renderer.deref_mut(),
commands: &mut commands,
gc_context,
ui: ui.deref_mut(),
library: &root_data.library,
transform_stack,
is_offscreen: false,
stage: root_data.stage,
stage,
clip_depth_stack: vec![],
allow_mask: true,
};
root_data.stage.render(&mut render_context);
stage.render(&mut render_context);
background_color =
if stage.window_mode() != WindowMode::Transparent || stage.is_fullscreen() {
stage.background_color().unwrap_or(Color::WHITE)
} else {
Color::from_rgba(0)
};
});
renderer.submit_frame(background_color, commands);
self.needs_render = false;
}

View File

@ -3,6 +3,7 @@ use ruffle_render::backend::null::NullBitmapSource;
use ruffle_render::backend::{RenderBackend, ShapeHandle, ViewportDimensions};
use ruffle_render::bitmap::{Bitmap, BitmapFormat, BitmapHandle, BitmapSource};
use ruffle_render::color_transform::ColorTransform;
use ruffle_render::commands::{CommandHandler, CommandList};
use ruffle_render::error::Error;
use ruffle_render::matrix::Matrix;
use ruffle_render::shape_utils::{DistilledShape, DrawCommand, LineScaleMode, LineScales};
@ -377,6 +378,28 @@ impl WebCanvasRenderBackend {
.set_global_composite_operation(mode)
.expect("Failed to update BlendMode");
}
fn begin_frame(&mut self, clear: Color) {
// Reset canvas transform in case it was left in a dirty state.
self.context.reset_transform().unwrap();
let width = self.canvas.width();
let height = self.canvas.height();
if clear.a > 0 {
let color = format!("rgba({}, {}, {}, {})", clear.r, clear.g, clear.b, clear.a);
self.context.set_fill_style(&color.into());
let _ = self.context.set_global_composite_operation("copy");
self.context
.fill_rect(0.0, 0.0, width.into(), height.into());
let _ = self.context.set_global_composite_operation("source-over");
} else {
self.context
.clear_rect(0.0, 0.0, width.into(), height.into());
}
self.mask_state = MaskState::DrawContent;
}
}
impl RenderBackend for WebCanvasRenderBackend {
@ -427,37 +450,53 @@ impl RenderBackend for WebCanvasRenderBackend {
_handle: BitmapHandle,
_width: u32,
_height: u32,
_f: &mut dyn FnMut(&mut dyn RenderBackend) -> Result<(), ruffle_render::error::Error>,
_commands: CommandList,
_clear_color: Color,
) -> Result<Bitmap, ruffle_render::error::Error> {
Err(ruffle_render::error::Error::Unimplemented)
Err(Error::Unimplemented)
}
fn begin_frame(&mut self, clear: Color) {
// Reset canvas transform in case it was left in a dirty state.
self.context.reset_transform().unwrap();
let width = self.canvas.width();
let height = self.canvas.height();
if clear.a > 0 {
let color = format!("rgba({}, {}, {}, {})", clear.r, clear.g, clear.b, clear.a);
self.context.set_fill_style(&color.into());
let _ = self.context.set_global_composite_operation("copy");
self.context
.fill_rect(0.0, 0.0, width.into(), height.into());
let _ = self.context.set_global_composite_operation("source-over");
} else {
self.context
.clear_rect(0.0, 0.0, width.into(), height.into());
}
self.mask_state = MaskState::DrawContent;
fn submit_frame(&mut self, clear: Color, commands: CommandList) {
self.begin_frame(clear);
commands.execute(self);
}
fn end_frame(&mut self) {
// Noop
fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option<Bitmap> {
let bitmap = &self.bitmaps[&bitmap];
bitmap.get_pixels()
}
fn register_bitmap(&mut self, bitmap: Bitmap) -> Result<BitmapHandle, Error> {
let handle = self.next_bitmap_handle;
self.next_bitmap_handle = BitmapHandle(self.next_bitmap_handle.0 + 1);
let bitmap_data = BitmapData::new(bitmap).map_err(Error::JavascriptError)?;
self.bitmaps.insert(handle, bitmap_data);
Ok(handle)
}
fn unregister_bitmap(&mut self, bitmap: BitmapHandle) {
self.bitmaps.remove(&bitmap);
}
fn update_texture(
&mut self,
handle: BitmapHandle,
width: u32,
height: u32,
rgba: Vec<u8>,
) -> Result<BitmapHandle, Error> {
// TODO: Could be optimized to a single put_image_data call
// in case it is already stored as a canvas+context.
self.bitmaps.insert(
handle,
BitmapData::new(Bitmap::new(width, height, BitmapFormat::Rgba, rgba))
.map_err(Error::JavascriptError)?,
);
Ok(handle)
}
}
impl CommandHandler for WebCanvasRenderBackend {
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool) {
if self.mask_state == MaskState::ClearMask {
return;
@ -739,40 +778,6 @@ impl RenderBackend for WebCanvasRenderBackend {
self.apply_blend_mode(current);
}
}
fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option<Bitmap> {
let bitmap = &self.bitmaps[&bitmap];
bitmap.get_pixels()
}
fn register_bitmap(&mut self, bitmap: Bitmap) -> Result<BitmapHandle, Error> {
let handle = self.next_bitmap_handle;
self.next_bitmap_handle = BitmapHandle(self.next_bitmap_handle.0 + 1);
let bitmap_data = BitmapData::new(bitmap).map_err(Error::JavascriptError)?;
self.bitmaps.insert(handle, bitmap_data);
Ok(handle)
}
fn unregister_bitmap(&mut self, bitmap: BitmapHandle) {
self.bitmaps.remove(&bitmap);
}
fn update_texture(
&mut self,
handle: BitmapHandle,
width: u32,
height: u32,
rgba: Vec<u8>,
) -> Result<BitmapHandle, Error> {
// TODO: Could be optimized to a single put_image_data call
// in case it is already stored as a canvas+context.
self.bitmaps.insert(
handle,
BitmapData::new(Bitmap::new(width, height, BitmapFormat::Rgba, rgba))
.map_err(Error::JavascriptError)?,
);
Ok(handle)
}
}
/// Convert a series of `DrawCommands` to a `Path2d` shape.

View File

@ -1,13 +1,13 @@
pub mod null;
use crate::bitmap::{Bitmap, BitmapHandle, BitmapInfo, BitmapSource};
use crate::commands::CommandList;
use crate::error::Error;
use crate::matrix::Matrix;
use crate::shape_utils::DistilledShape;
use crate::transform::Transform;
use crate::utils;
use downcast_rs::{impl_downcast, Downcast};
use swf;
use swf::Color;
pub trait RenderBackend: Downcast {
fn viewport_dimensions(&self) -> ViewportDimensions;
@ -50,7 +50,8 @@ pub trait RenderBackend: Downcast {
handle: BitmapHandle,
width: u32,
height: u32,
f: &mut dyn FnMut(&mut dyn RenderBackend) -> Result<(), Error>,
commands: CommandList,
clear_color: Color,
) -> Result<Bitmap, Error>;
fn register_bitmap_jpeg_2(&mut self, data: &[u8]) -> Result<BitmapInfo, Error> {
@ -96,18 +97,7 @@ pub trait RenderBackend: Downcast {
})
}
fn begin_frame(&mut self, clear: swf::Color);
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool);
fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform);
fn draw_rect(&mut self, color: swf::Color, matrix: &Matrix);
fn end_frame(&mut self);
fn push_mask(&mut self);
fn activate_mask(&mut self);
fn deactivate_mask(&mut self);
fn pop_mask(&mut self);
fn push_blend_mode(&mut self, blend: swf::BlendMode);
fn pop_blend_mode(&mut self);
fn submit_frame(&mut self, clear: swf::Color, commands: CommandList);
fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option<Bitmap>;
fn register_bitmap(&mut self, bitmap: Bitmap) -> Result<BitmapHandle, Error>;

View File

@ -1,9 +1,8 @@
use crate::backend::{RenderBackend, ShapeHandle, ViewportDimensions};
use crate::bitmap::{Bitmap, BitmapHandle, BitmapInfo, BitmapSource};
use crate::commands::CommandList;
use crate::error::Error;
use crate::matrix::Matrix;
use crate::shape_utils::DistilledShape;
use crate::transform::Transform;
use swf::Color;
pub struct NullBitmapSource;
@ -54,23 +53,13 @@ impl RenderBackend for NullRenderer {
_handle: BitmapHandle,
_width: u32,
_height: u32,
_f: &mut dyn FnMut(&mut dyn RenderBackend) -> Result<(), Error>,
_commands: CommandList,
_clear_color: Color,
) -> Result<Bitmap, Error> {
Err(Error::Unimplemented)
}
fn begin_frame(&mut self, _clear: Color) {}
fn render_bitmap(&mut self, _bitmap: BitmapHandle, _transform: &Transform, _smoothing: bool) {}
fn render_shape(&mut self, _shape: ShapeHandle, _transform: &Transform) {}
fn draw_rect(&mut self, _color: Color, _matrix: &Matrix) {}
fn end_frame(&mut self) {}
fn push_mask(&mut self) {}
fn activate_mask(&mut self) {}
fn deactivate_mask(&mut self) {}
fn pop_mask(&mut self) {}
fn push_blend_mode(&mut self, _blend_mode: swf::BlendMode) {}
fn pop_blend_mode(&mut self) {}
fn submit_frame(&mut self, _clear: Color, _commands: CommandList) {}
fn get_bitmap_pixels(&mut self, _bitmap: BitmapHandle) -> Option<Bitmap> {
None

120
render/src/commands.rs Normal file
View File

@ -0,0 +1,120 @@
use crate::backend::ShapeHandle;
use crate::bitmap::BitmapHandle;
use crate::matrix::Matrix;
use crate::transform::Transform;
use swf::{BlendMode, Color};
pub trait CommandHandler {
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool);
fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform);
fn draw_rect(&mut self, color: Color, matrix: &Matrix);
fn push_mask(&mut self);
fn activate_mask(&mut self);
fn deactivate_mask(&mut self);
fn pop_mask(&mut self);
fn push_blend_mode(&mut self, blend: BlendMode);
fn pop_blend_mode(&mut self);
}
#[derive(Debug, Default)]
pub struct CommandList(Vec<Command>);
impl CommandList {
pub fn new() -> Self {
Self::default()
}
pub fn execute(self, handler: &mut impl CommandHandler) {
for command in self.0 {
match command {
Command::RenderBitmap {
bitmap,
transform,
smoothing,
} => handler.render_bitmap(bitmap, &transform, smoothing),
Command::RenderShape { shape, transform } => {
handler.render_shape(shape, &transform)
}
Command::DrawRect { color, matrix } => handler.draw_rect(color, &matrix),
Command::PushMask => handler.push_mask(),
Command::ActivateMask => handler.activate_mask(),
Command::DeactivateMask => handler.deactivate_mask(),
Command::PopMask => handler.pop_mask(),
Command::PushBlendMode(blend) => handler.push_blend_mode(blend),
Command::PopBlendMode => handler.pop_blend_mode(),
}
}
}
}
impl CommandHandler for CommandList {
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool) {
self.0.push(Command::RenderBitmap {
bitmap,
transform: transform.clone(),
smoothing,
});
}
fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform) {
self.0.push(Command::RenderShape {
shape,
transform: transform.clone(),
});
}
fn draw_rect(&mut self, color: Color, matrix: &Matrix) {
self.0.push(Command::DrawRect {
color,
matrix: *matrix,
});
}
fn push_mask(&mut self) {
self.0.push(Command::PushMask);
}
fn activate_mask(&mut self) {
self.0.push(Command::ActivateMask);
}
fn deactivate_mask(&mut self) {
self.0.push(Command::DeactivateMask);
}
fn pop_mask(&mut self) {
self.0.push(Command::PopMask);
}
fn push_blend_mode(&mut self, blend: BlendMode) {
self.0.push(Command::PushBlendMode(blend));
}
fn pop_blend_mode(&mut self) {
self.0.push(Command::PopBlendMode);
}
}
#[derive(Debug)]
pub enum Command {
RenderBitmap {
bitmap: BitmapHandle,
transform: Transform,
smoothing: bool,
},
RenderShape {
shape: ShapeHandle,
transform: Transform,
},
DrawRect {
color: Color,
matrix: Matrix,
},
PushMask,
ActivateMask,
DeactivateMask,
PopMask,
PushBlendMode(BlendMode),
PopBlendMode,
}

View File

@ -8,5 +8,6 @@ pub mod shape_utils;
pub mod transform;
pub mod utils;
pub mod commands;
#[cfg(feature = "tessellator")]
pub mod tessellator;

View File

@ -3,6 +3,7 @@ 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::{
@ -736,81 +737,6 @@ impl WebGlRenderBackend {
self.gl
.blend_func_separate(src_rgb, dst_rgb, Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA);
}
}
impl RenderBackend for WebGlRenderBackend {
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 render_offscreen(
&mut self,
_handle: BitmapHandle,
_width: u32,
_height: u32,
_f: &mut dyn FnMut(&mut dyn RenderBackend) -> Result<(), ruffle_render::error::Error>,
) -> Result<Bitmap, ruffle_render::error::Error> {
Err(ruffle_render::error::Error::Unimplemented)
}
fn begin_frame(&mut self, clear: Color) {
self.active_program = std::ptr::null();
@ -931,7 +857,174 @@ impl RenderBackend for WebGlRenderBackend {
);
}
}
}
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) {
@ -1285,89 +1378,6 @@ impl RenderBackend for WebGlRenderBackend {
self.apply_blend_mode(current);
}
}
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)
}
}
#[derive(Clone, Debug)]

View File

@ -11,6 +11,7 @@ use crate::{
use fnv::FnvHashMap;
use ruffle_render::backend::{RenderBackend, ShapeHandle, ViewportDimensions};
use ruffle_render::bitmap::{Bitmap, 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::{DrawType as TessDrawType, ShapeTessellator};
@ -550,6 +551,173 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
&self.descriptors
}
fn begin_frame(&mut self, clear: Color) {
self.mask_state = MaskState::NoMask;
self.num_masks = 0;
self.uniform_buffers.reset();
let frame_output = match self.target.get_next_texture() {
Ok(frame) => frame,
Err(e) => {
log::warn!("Couldn't begin new render frame: {}", e);
// Attemp to recreate the swap chain in this case.
self.target.resize(
&self.descriptors.device,
self.target.width(),
self.target.height(),
);
return;
}
};
let label = create_debug_label!("Draw encoder");
let draw_encoder =
self.descriptors
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: label.as_deref(),
});
let uniform_encoder_label = create_debug_label!("Uniform upload command encoder");
let uniform_encoder =
self.descriptors
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: uniform_encoder_label.as_deref(),
});
let mut frame_data = Box::new((draw_encoder, frame_output, uniform_encoder));
self.globals
.update_uniform(&self.descriptors.device, &mut frame_data.0);
// Use intermediate render targets when resolving MSAA or copying from linear-to-sRGB texture.
let (color_view, resolve_target) = match (&self.frame_buffer_view, &self.copy_srgb_view) {
(None, None) => (frame_data.1.view(), None),
(None, Some(copy)) => (copy, None),
(Some(frame_buffer), None) => (frame_buffer, Some(frame_data.1.view())),
(Some(frame_buffer), Some(copy)) => (frame_buffer, Some(copy)),
};
let render_pass = frame_data.0.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: color_view,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: f64::from(clear.r) / 255.0,
g: f64::from(clear.g) / 255.0,
b: f64::from(clear.b) / 255.0,
a: f64::from(clear.a) / 255.0,
}),
store: true,
},
resolve_target,
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_texture_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(0.0),
store: false,
}),
stencil_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(0),
store: true,
}),
}),
label: None,
});
// Since RenderPass holds a reference to the CommandEncoder, we cast the lifetime
// away to allow for the self-referencing struct. draw_encoder is boxed so its
// address should remain stable.
self.current_frame = Some(Frame {
render_pass: unsafe {
std::mem::transmute::<_, wgpu::RenderPass<'static>>(render_pass)
},
frame_data,
});
}
fn end_frame(&mut self) {
if let Some(frame) = self.current_frame.take() {
let draw_encoder = frame.frame_data.0;
let mut uniform_encoder = frame.frame_data.2;
let render_pass = frame.render_pass;
// Finalize render pass.
drop(render_pass);
// If we have an sRGB surface, copy from our linear intermediate buffer to the sRGB surface.
let command_buffers = if let Some(copy_srgb_bind_group) = &self.copy_srgb_bind_group {
debug_assert!(self.copy_srgb_view.is_some());
let mut copy_encoder = self.descriptors.device.create_command_encoder(
&wgpu::CommandEncoderDescriptor {
label: create_debug_label!("Frame copy command encoder").as_deref(),
},
);
let mut render_pass = copy_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: frame.frame_data.1.view(),
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: true,
},
resolve_target: None,
})],
depth_stencil_attachment: None,
label: None,
});
render_pass.set_pipeline(&self.descriptors.pipelines.copy_srgb_pipeline);
render_pass.set_bind_group(0, self.globals.bind_group(), &[]);
self.uniform_buffers.write_uniforms(
&self.descriptors.device,
&self.descriptors.uniform_buffers_layout,
&mut uniform_encoder,
&mut render_pass,
1,
&Transforms {
world_matrix: [
[self.target.width() as f32, 0.0, 0.0, 0.0],
[0.0, self.target.height() as f32, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
color_adjustments: ColorAdjustments {
mult_color: [1.0, 1.0, 1.0, 1.0],
add_color: [0.0, 0.0, 0.0, 0.0],
},
},
);
render_pass.set_bind_group(2, copy_srgb_bind_group, &[]);
render_pass.set_bind_group(
3,
self.descriptors
.bitmap_samplers
.get_bind_group(false, false),
&[],
);
render_pass.set_vertex_buffer(0, self.quad_vbo.slice(..));
render_pass.set_index_buffer(self.quad_ibo.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..6, 0, 0..1);
drop(render_pass);
vec![
uniform_encoder.finish(),
draw_encoder.finish(),
copy_encoder.finish(),
]
} else {
vec![uniform_encoder.finish(), draw_encoder.finish()]
};
self.uniform_buffers.finish();
self.target.submit(
&self.descriptors.device,
&self.descriptors.queue,
command_buffers,
frame.frame_data.1,
);
}
}
pub fn target(&self) -> &T {
&self.target
}
@ -563,8 +731,9 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
handle: BitmapHandle,
width: u32,
height: u32,
f: &mut dyn FnMut(&mut dyn RenderBackend) -> Result<(), ruffle_render::error::Error>,
) -> Result<(Self, ruffle_render::bitmap::Bitmap), ruffle_render::error::Error> {
commands: CommandList,
clear_color: Color,
) -> Result<(Self, Bitmap), ruffle_render::error::Error> {
// We need ownership of `Texture` to access the non-`Clone`
// `wgpu` fields. At the end of this method, we re-insert
// `texture` into the map.
@ -659,7 +828,7 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
blend_modes: vec![BlendMode::Normal],
};
let f_res = f(&mut texture_backend);
texture_backend.submit_frame(clear_color, commands);
// Capture with premultiplied alpha, which is what we use for all textures
let image = texture_backend
@ -667,7 +836,7 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
.capture(&texture_backend.descriptors.device, true);
let image = image.map(|image| {
ruffle_render::bitmap::Bitmap::new(
Bitmap::new(
image.dimensions().0,
image.dimensions().1,
ruffle_render::bitmap::BitmapFormat::Rgba,
@ -694,9 +863,6 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
texture.texture_wrapper.texture = texture_backend.target.texture;
self.bitmap_registry.insert(handle, texture);
// Check result after restoring the backend fields
f_res?;
Ok((self, image.unwrap()))
}
}
@ -848,89 +1014,158 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
handle
}
fn begin_frame(&mut self, clear: Color) {
self.mask_state = MaskState::NoMask;
self.num_masks = 0;
self.uniform_buffers.reset();
fn submit_frame(&mut self, clear: Color, commands: CommandList) {
self.begin_frame(clear);
commands.execute(self);
self.end_frame();
}
let frame_output = match self.target.get_next_texture() {
Ok(frame) => frame,
Err(e) => {
log::warn!("Couldn't begin new render frame: {}", e);
// Attemp to recreate the swap chain in this case.
self.target.resize(
&self.descriptors.device,
self.target.width(),
self.target.height(),
);
return;
}
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> {
if bitmap.width() > self.descriptors.limits.max_texture_dimension_2d
|| bitmap.height() > self.descriptors.limits.max_texture_dimension_2d
{
return Err(BitmapError::TooLarge);
}
let bitmap = bitmap.to_rgba();
let extent = wgpu::Extent3d {
width: bitmap.width(),
height: bitmap.height(),
depth_or_array_layers: 1,
};
let label = create_debug_label!("Draw encoder");
let draw_encoder =
self.descriptors
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: label.as_deref(),
});
let uniform_encoder_label = create_debug_label!("Uniform upload command encoder");
let uniform_encoder =
self.descriptors
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: uniform_encoder_label.as_deref(),
});
let mut frame_data = Box::new((draw_encoder, frame_output, uniform_encoder));
let texture_label = create_debug_label!("Bitmap");
let texture = self
.descriptors
.device
.create_texture(&wgpu::TextureDescriptor {
label: texture_label.as_deref(),
size: extent,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC,
});
self.globals
.update_uniform(&self.descriptors.device, &mut frame_data.0);
// Use intermediate render targets when resolving MSAA or copying from linear-to-sRGB texture.
let (color_view, resolve_target) = match (&self.frame_buffer_view, &self.copy_srgb_view) {
(None, None) => (frame_data.1.view(), None),
(None, Some(copy)) => (copy, None),
(Some(frame_buffer), None) => (frame_buffer, Some(frame_data.1.view())),
(Some(frame_buffer), Some(copy)) => (frame_buffer, Some(copy)),
};
let render_pass = frame_data.0.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: color_view,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: f64::from(clear.r) / 255.0,
g: f64::from(clear.g) / 255.0,
b: f64::from(clear.b) / 255.0,
a: f64::from(clear.a) / 255.0,
}),
store: true,
},
resolve_target,
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_texture_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(0.0),
store: false,
}),
stencil_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(0),
store: true,
}),
}),
label: None,
});
// Since RenderPass holds a reference to the CommandEncoder, we cast the lifetime
// away to allow for the self-referencing struct. draw_encoder is boxed so its
// address should remain stable.
self.current_frame = Some(Frame {
render_pass: unsafe {
std::mem::transmute::<_, wgpu::RenderPass<'static>>(render_pass)
self.descriptors.queue.write_texture(
wgpu::ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: Default::default(),
aspect: wgpu::TextureAspect::All,
},
frame_data,
});
bitmap.data(),
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: NonZeroU32::new(4 * extent.width),
rows_per_image: None,
},
extent,
);
let handle = self.next_bitmap_handle;
self.next_bitmap_handle = BitmapHandle(self.next_bitmap_handle.0 + 1);
let width = bitmap.width();
let height = bitmap.height();
// Make bind group for bitmap quad.
let texture_view = texture.create_view(&Default::default());
let bind_group = self
.descriptors
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &target_data!(self).pipelines.bitmap_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &self.quad_tex_transforms,
offset: 0,
size: wgpu::BufferSize::new(
std::mem::size_of::<TextureTransforms>() as u64
),
}),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&texture_view),
},
],
label: create_debug_label!("Bitmap {} bind group", handle.0).as_deref(),
});
if self
.bitmap_registry
.insert(
handle,
RegistryData {
bitmap,
texture_wrapper: Texture {
width,
height,
texture,
bind_group,
texture_offscreen: None,
},
},
)
.is_some()
{
panic!("Overwrote existing bitmap {:?}", handle);
}
Ok(handle)
}
fn unregister_bitmap(&mut self, handle: BitmapHandle) {
self.bitmap_registry.remove(&handle);
}
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_wrapper.texture
} else {
log::warn!("Tried to replace nonexistent texture");
return Ok(handle);
};
let extent = wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
};
self.descriptors.queue.write_texture(
wgpu::ImageCopyTexture {
texture,
mip_level: 0,
origin: Default::default(),
aspect: wgpu::TextureAspect::All,
},
&rgba,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: NonZeroU32::new(4 * extent.width),
rows_per_image: None,
},
extent,
);
Ok(handle)
}
fn render_offscreen(
@ -938,7 +1173,8 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
handle: BitmapHandle,
width: u32,
height: u32,
f: &mut dyn FnMut(&mut dyn RenderBackend) -> Result<(), ruffle_render::error::Error>,
commands: CommandList,
clear_color: Color,
) -> Result<Bitmap, ruffle_render::error::Error> {
// Rendering to a texture backend requires us to use non-`Clone`
// wgpu resources (e.g. `wgpu::Device`, `wgpu::Queue`.
@ -965,11 +1201,13 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
// printed, and there's not really much point in attempting
// to recover from a partially failed render operation, anyway.
Ok(take_mut(self, |this| {
this.render_offscreen_internal(handle, width, height, f)
this.render_offscreen_internal(handle, width, height, commands, clear_color)
.expect("Failed to render to offscreen backend")
}))
}
}
impl<T: RenderTarget> CommandHandler for WgpuRenderBackend<T> {
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool) {
let target_data = target_data!(self);
if let Some(entry) = self.bitmap_registry.get(&bitmap) {
@ -1249,88 +1487,6 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
frame.render_pass.draw_indexed(0..6, 0, 0..1);
}
fn end_frame(&mut self) {
if let Some(frame) = self.current_frame.take() {
let draw_encoder = frame.frame_data.0;
let mut uniform_encoder = frame.frame_data.2;
let render_pass = frame.render_pass;
// Finalize render pass.
drop(render_pass);
// If we have an sRGB surface, copy from our linear intermediate buffer to the sRGB surface.
let command_buffers = if let Some(copy_srgb_bind_group) = &self.copy_srgb_bind_group {
debug_assert!(self.copy_srgb_view.is_some());
let mut copy_encoder = self.descriptors.device.create_command_encoder(
&wgpu::CommandEncoderDescriptor {
label: create_debug_label!("Frame copy command encoder").as_deref(),
},
);
let mut render_pass = copy_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: frame.frame_data.1.view(),
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: true,
},
resolve_target: None,
})],
depth_stencil_attachment: None,
label: None,
});
render_pass.set_pipeline(&target_data!(self).pipelines.copy_srgb_pipeline);
render_pass.set_bind_group(0, self.globals.bind_group(), &[]);
self.uniform_buffers.write_uniforms(
&self.descriptors.device,
&self.descriptors.uniform_buffers_layout,
&mut uniform_encoder,
&mut render_pass,
1,
&Transforms {
world_matrix: [
[self.target.width() as f32, 0.0, 0.0, 0.0],
[0.0, self.target.height() as f32, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
color_adjustments: ColorAdjustments {
mult_color: [1.0, 1.0, 1.0, 1.0],
add_color: [0.0, 0.0, 0.0, 0.0],
},
},
);
render_pass.set_bind_group(2, copy_srgb_bind_group, &[]);
render_pass.set_bind_group(
3,
self.descriptors
.bitmap_samplers
.get_bind_group(false, false),
&[],
);
render_pass.set_vertex_buffer(0, self.quad_vbo.slice(..));
render_pass.set_index_buffer(self.quad_ibo.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..6, 0, 0..1);
drop(render_pass);
vec![
uniform_encoder.finish(),
draw_encoder.finish(),
copy_encoder.finish(),
]
} else {
vec![uniform_encoder.finish(), draw_encoder.finish()]
};
self.uniform_buffers.finish();
self.target.submit(
&self.descriptors.device,
&self.descriptors.queue,
command_buffers,
frame.frame_data.1,
);
}
}
fn push_mask(&mut self) {
debug_assert!(
self.mask_state == MaskState::NoMask || self.mask_state == MaskState::DrawMaskedContent
@ -1366,154 +1522,6 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
fn pop_blend_mode(&mut self) {
self.blend_modes.pop();
}
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> {
if bitmap.width() > self.descriptors.limits.max_texture_dimension_2d
|| bitmap.height() > self.descriptors.limits.max_texture_dimension_2d
{
return Err(BitmapError::TooLarge);
}
let bitmap = bitmap.to_rgba();
let extent = wgpu::Extent3d {
width: bitmap.width(),
height: bitmap.height(),
depth_or_array_layers: 1,
};
let texture_label = create_debug_label!("Bitmap");
let texture = self
.descriptors
.device
.create_texture(&wgpu::TextureDescriptor {
label: texture_label.as_deref(),
size: extent,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC,
});
self.descriptors.queue.write_texture(
wgpu::ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: Default::default(),
aspect: wgpu::TextureAspect::All,
},
bitmap.data(),
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: NonZeroU32::new(4 * extent.width),
rows_per_image: None,
},
extent,
);
let handle = self.next_bitmap_handle;
self.next_bitmap_handle = BitmapHandle(self.next_bitmap_handle.0 + 1);
let width = bitmap.width();
let height = bitmap.height();
// Make bind group for bitmap quad.
let texture_view = texture.create_view(&Default::default());
let bind_group = self
.descriptors
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &target_data!(self).pipelines.bitmap_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &self.quad_tex_transforms,
offset: 0,
size: wgpu::BufferSize::new(
std::mem::size_of::<TextureTransforms>() as u64
),
}),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&texture_view),
},
],
label: create_debug_label!("Bitmap {} bind group", handle.0).as_deref(),
});
if self
.bitmap_registry
.insert(
handle,
RegistryData {
bitmap,
texture_wrapper: Texture {
width,
height,
texture,
bind_group,
texture_offscreen: None,
},
},
)
.is_some()
{
panic!("Overwrote existing bitmap {:?}", handle);
}
Ok(handle)
}
fn unregister_bitmap(&mut self, handle: BitmapHandle) {
self.bitmap_registry.remove(&handle);
}
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_wrapper.texture
} else {
log::warn!("Tried to replace nonexistent texture");
return Ok(handle);
};
let extent = wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
};
self.descriptors.queue.write_texture(
wgpu::ImageCopyTexture {
texture,
mip_level: 0,
origin: Default::default(),
aspect: wgpu::TextureAspect::All,
},
&rgba,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: NonZeroU32::new(4 * extent.width),
rows_per_image: None,
},
extent,
);
Ok(handle)
}
}
fn create_depth_texture_view(