2023-02-02 16:46:41 +00:00
|
|
|
use crate::buffer_builder::BufferBuilder;
|
2023-03-22 13:38:49 +00:00
|
|
|
use crate::buffer_pool::{BufferPool, TexturePool};
|
avm2: Partially implement Stage3D for wgpu backend
This PR implements core 'stage3D' APIs. We are now able
to render at least two demos from the Context3D docs - a simple
triangle render, and a rotating cube.
Implemented in this PR:
* Stage3D access and Context3D creation
* IndexBuffer3D and VertexBuffer3D creation, uploading, and usage
* Program3D uploading and usage (via `naga-agal`)
* Context3D: configureBackBuffer, clear, drawTriangles, and present
Not yet implemented:
* Any 'dispose()' methods
* Depth and stencil buffers
* Context3D texture apis
* Scissor rectangle
General implementation strategy:
A new `Object` variant is added for each of the Stage3D objects
(VertexBuffer3D, Program3D, etc). This stores a handle to the
parent `Context3D`, and (depending on the object) a handle
to the underlying native resource, via `Rc<dyn
SomeRenderBackendTrait>`).
Calling methods on Context3D does not usually result in an immediate
call to a `wgpu` method. Instead, we queue up commands in our
`Context3D` instance, and execute them all on a call to `present`.
This avoids some nasty wgpu lifetime issues, and is very similar
to the approah we use for normal rendering.
The actual rendering happens on a `Texture`, with dimensions
determined by `createBackBuffer`. During 'Stage' rendering,
we render all of these Stage3D textures *behind* the normal
stage (but in front of the overall stage background color).
2022-09-18 20:50:21 +00:00
|
|
|
use crate::context3d::WgpuContext3D;
|
2023-02-02 16:46:41 +00:00
|
|
|
use crate::mesh::{Mesh, PendingDraw};
|
2022-09-07 14:54:05 +00:00
|
|
|
use crate::surface::Surface;
|
2023-03-22 13:38:49 +00:00
|
|
|
use crate::target::{MaybeOwnedBuffer, TextureTarget};
|
2023-03-22 11:10:48 +00:00
|
|
|
use crate::target::{RenderTargetFrame, TextureBufferInfo};
|
2022-09-04 19:19:16 +00:00
|
|
|
use crate::uniform_buffer::BufferStorage;
|
2023-03-22 13:38:49 +00:00
|
|
|
use crate::utils::BufferDimensions;
|
2022-09-03 00:18:57 +00:00
|
|
|
use crate::{
|
2023-01-07 16:08:23 +00:00
|
|
|
as_texture, format_list, get_backend_names, ColorAdjustments, Descriptors, Error,
|
|
|
|
QueueSyncHandle, RenderTarget, SwapChainTarget, Texture, Transforms,
|
2022-09-03 00:18:57 +00:00
|
|
|
};
|
2023-05-01 22:29:50 +00:00
|
|
|
use image::imageops::FilterType;
|
2023-04-01 05:05:10 +00:00
|
|
|
use ruffle_render::backend::Context3D;
|
2022-09-03 00:18:57 +00:00
|
|
|
use ruffle_render::backend::{RenderBackend, ShapeHandle, ViewportDimensions};
|
2023-05-01 22:29:50 +00:00
|
|
|
use ruffle_render::bitmap::{
|
|
|
|
Bitmap, BitmapFormat, BitmapHandle, BitmapSource, PixelRegion, SyncHandle,
|
|
|
|
};
|
2022-09-04 19:19:16 +00:00
|
|
|
use ruffle_render::commands::CommandList;
|
2022-09-03 00:18:57 +00:00
|
|
|
use ruffle_render::error::Error as BitmapError;
|
2023-01-29 23:44:56 +00:00
|
|
|
use ruffle_render::filters::Filter;
|
2023-02-03 14:16:30 +00:00
|
|
|
use ruffle_render::quality::StageQuality;
|
2022-09-03 00:18:57 +00:00
|
|
|
use ruffle_render::shape_utils::DistilledShape;
|
2022-09-07 20:27:57 +00:00
|
|
|
use ruffle_render::tessellator::ShapeTessellator;
|
2023-01-03 14:53:22 +00:00
|
|
|
use std::borrow::Cow;
|
2023-03-22 21:33:08 +00:00
|
|
|
use std::cell::Cell;
|
2023-01-06 17:41:24 +00:00
|
|
|
use std::mem;
|
2022-09-03 00:18:57 +00:00
|
|
|
use std::path::Path;
|
|
|
|
use std::sync::Arc;
|
2023-01-29 23:44:56 +00:00
|
|
|
use swf::Color;
|
2023-01-06 12:17:12 +00:00
|
|
|
use tracing::instrument;
|
2022-09-03 00:18:57 +00:00
|
|
|
|
2023-03-22 21:33:08 +00:00
|
|
|
/// How many times a texture must be written to & read back from,
|
|
|
|
/// before it's automatically allocated a buffer on each write.
|
|
|
|
const TEXTURE_READS_BEFORE_PROMOTION: u8 = 5;
|
|
|
|
|
2022-09-03 00:18:57 +00:00
|
|
|
pub struct WgpuRenderBackend<T: RenderTarget> {
|
|
|
|
descriptors: Arc<Descriptors>,
|
2022-09-04 19:19:16 +00:00
|
|
|
uniform_buffers_storage: BufferStorage<Transforms>,
|
2022-12-27 22:18:03 +00:00
|
|
|
color_buffers_storage: BufferStorage<ColorAdjustments>,
|
2022-09-03 00:18:57 +00:00
|
|
|
target: T,
|
2022-09-07 14:54:05 +00:00
|
|
|
surface: Surface,
|
2022-09-03 00:18:57 +00:00
|
|
|
meshes: Vec<Mesh>,
|
|
|
|
shape_tessellator: ShapeTessellator,
|
|
|
|
// This is currently unused - we just store it to report in
|
|
|
|
// `get_viewport_dimensions`
|
|
|
|
viewport_scale_factor: f64,
|
2022-12-23 23:13:26 +00:00
|
|
|
texture_pool: TexturePool,
|
2023-01-02 23:00:50 +00:00
|
|
|
offscreen_texture_pool: TexturePool,
|
2023-03-22 21:33:08 +00:00
|
|
|
offscreen_buffer_pool: Arc<BufferPool<wgpu::Buffer, BufferDimensions>>,
|
2022-09-03 00:18:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl WgpuRenderBackend<SwapChainTarget> {
|
|
|
|
#[cfg(target_family = "wasm")]
|
2023-04-24 20:06:16 +00:00
|
|
|
pub async fn for_canvas(canvas: web_sys::HtmlCanvasElement) -> Result<Self, Error> {
|
2023-01-19 03:35:09 +00:00
|
|
|
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
|
|
|
backends: wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL,
|
|
|
|
dx12_shader_compiler: wgpu::Dx12Compiler::default(),
|
|
|
|
});
|
2023-01-05 04:59:40 +00:00
|
|
|
let surface = instance.create_surface_from_canvas(canvas)?;
|
2023-01-28 10:12:48 +00:00
|
|
|
let (adapter, device, queue) = Self::request_device(
|
2022-09-03 00:18:57 +00:00
|
|
|
wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL,
|
|
|
|
instance,
|
|
|
|
Some(&surface),
|
|
|
|
wgpu::PowerPreference::HighPerformance,
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
.await?;
|
2023-01-28 10:12:48 +00:00
|
|
|
let descriptors = Descriptors::new(adapter, device, queue);
|
2022-10-07 10:17:41 +00:00
|
|
|
let target =
|
|
|
|
SwapChainTarget::new(surface, &descriptors.adapter, (1, 1), &descriptors.device);
|
2023-02-03 15:44:21 +00:00
|
|
|
Self::new(Arc::new(descriptors), target)
|
2022-09-03 00:18:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(target_family = "wasm"))]
|
2023-01-05 04:59:20 +00:00
|
|
|
pub fn for_window<
|
|
|
|
W: raw_window_handle::HasRawWindowHandle + raw_window_handle::HasRawDisplayHandle,
|
|
|
|
>(
|
2022-09-03 00:18:57 +00:00
|
|
|
window: &W,
|
|
|
|
size: (u32, u32),
|
|
|
|
backend: wgpu::Backends,
|
|
|
|
power_preference: wgpu::PowerPreference,
|
|
|
|
trace_path: Option<&Path>,
|
|
|
|
) -> Result<Self, Error> {
|
|
|
|
if wgpu::Backends::SECONDARY.contains(backend) {
|
2023-01-06 12:17:12 +00:00
|
|
|
tracing::warn!(
|
2022-09-03 00:18:57 +00:00
|
|
|
"{} graphics backend support may not be fully supported.",
|
|
|
|
format_list(&get_backend_names(backend), "and")
|
|
|
|
);
|
|
|
|
}
|
2023-01-19 02:32:23 +00:00
|
|
|
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
|
|
|
backends: backend,
|
|
|
|
dx12_shader_compiler: wgpu::Dx12Compiler::default(),
|
|
|
|
});
|
2023-01-05 04:59:20 +00:00
|
|
|
let surface = unsafe { instance.create_surface(window) }?;
|
2023-01-28 10:12:48 +00:00
|
|
|
let (adapter, device, queue) = futures::executor::block_on(Self::request_device(
|
2022-09-03 00:18:57 +00:00
|
|
|
backend,
|
|
|
|
instance,
|
|
|
|
Some(&surface),
|
|
|
|
power_preference,
|
|
|
|
trace_path,
|
|
|
|
))?;
|
2023-01-28 10:12:48 +00:00
|
|
|
let descriptors = Descriptors::new(adapter, device, queue);
|
2022-10-07 10:17:41 +00:00
|
|
|
let target = SwapChainTarget::new(surface, &descriptors.adapter, size, &descriptors.device);
|
2023-02-03 15:44:21 +00:00
|
|
|
Self::new(Arc::new(descriptors), target)
|
2022-09-03 00:18:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(target_family = "wasm"))]
|
2022-09-07 01:30:52 +00:00
|
|
|
impl WgpuRenderBackend<crate::target::TextureTarget> {
|
2022-09-03 00:18:57 +00:00
|
|
|
pub fn for_offscreen(
|
|
|
|
size: (u32, u32),
|
|
|
|
backend: wgpu::Backends,
|
|
|
|
power_preference: wgpu::PowerPreference,
|
|
|
|
trace_path: Option<&Path>,
|
|
|
|
) -> Result<Self, Error> {
|
|
|
|
if wgpu::Backends::SECONDARY.contains(backend) {
|
2023-01-06 12:17:12 +00:00
|
|
|
tracing::warn!(
|
2022-09-03 00:18:57 +00:00
|
|
|
"{} graphics backend support may not be fully supported.",
|
|
|
|
format_list(&get_backend_names(backend), "and")
|
|
|
|
);
|
|
|
|
}
|
2023-01-19 02:32:23 +00:00
|
|
|
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
|
|
|
backends: backend,
|
|
|
|
dx12_shader_compiler: wgpu::Dx12Compiler::default(),
|
|
|
|
});
|
2023-01-28 10:12:48 +00:00
|
|
|
let (adapter, device, queue) = futures::executor::block_on(Self::request_device(
|
2022-09-03 00:18:57 +00:00
|
|
|
backend,
|
|
|
|
instance,
|
|
|
|
None,
|
|
|
|
power_preference,
|
|
|
|
trace_path,
|
|
|
|
))?;
|
2023-01-28 10:12:48 +00:00
|
|
|
let descriptors = Descriptors::new(adapter, device, queue);
|
2022-09-07 01:30:52 +00:00
|
|
|
let target = crate::target::TextureTarget::new(&descriptors.device, size)?;
|
2023-02-03 15:44:21 +00:00
|
|
|
Self::new(Arc::new(descriptors), target)
|
2022-09-03 00:18:57 +00:00
|
|
|
}
|
|
|
|
|
2023-03-24 21:09:57 +00:00
|
|
|
pub fn capture_frame(&self) -> Option<image::RgbaImage> {
|
2023-01-07 16:08:23 +00:00
|
|
|
use crate::utils::buffer_to_image;
|
2023-03-22 11:10:48 +00:00
|
|
|
if let Some(buffer) = &self.target.buffer {
|
2023-03-22 13:38:49 +00:00
|
|
|
let (buffer, dimensions) = buffer.buffer.inner();
|
2023-01-07 16:08:23 +00:00
|
|
|
Some(buffer_to_image(
|
|
|
|
&self.descriptors.device,
|
2023-03-22 13:38:49 +00:00
|
|
|
buffer,
|
|
|
|
dimensions,
|
2023-01-07 16:08:23 +00:00
|
|
|
None,
|
|
|
|
self.target.size,
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2022-09-03 00:18:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: RenderTarget> WgpuRenderBackend<T> {
|
2023-02-03 15:44:21 +00:00
|
|
|
pub fn new(descriptors: Arc<Descriptors>, target: T) -> Result<Self, Error> {
|
2022-09-03 00:18:57 +00:00
|
|
|
if target.width() > descriptors.limits.max_texture_dimension_2d
|
|
|
|
|| target.height() > descriptors.limits.max_texture_dimension_2d
|
|
|
|
{
|
|
|
|
return Err(format!(
|
|
|
|
"Render target texture cannot be larger than {}px on either dimension (requested {} x {})",
|
|
|
|
descriptors.limits.max_texture_dimension_2d,
|
|
|
|
target.width(),
|
|
|
|
target.height()
|
|
|
|
)
|
|
|
|
.into());
|
|
|
|
}
|
|
|
|
|
2022-09-07 14:54:05 +00:00
|
|
|
let surface = Surface::new(
|
|
|
|
&descriptors,
|
2023-02-03 15:44:21 +00:00
|
|
|
StageQuality::Low,
|
2022-09-07 14:54:05 +00:00
|
|
|
target.width(),
|
|
|
|
target.height(),
|
2022-09-21 00:44:56 +00:00
|
|
|
target.format(),
|
2022-09-07 14:54:05 +00:00
|
|
|
);
|
2022-09-03 00:18:57 +00:00
|
|
|
|
2022-09-04 19:19:16 +00:00
|
|
|
let uniform_buffers_storage =
|
2022-09-07 04:32:15 +00:00
|
|
|
BufferStorage::from_alignment(descriptors.limits.min_uniform_buffer_offset_alignment);
|
2022-09-03 00:18:57 +00:00
|
|
|
|
2022-12-27 22:18:03 +00:00
|
|
|
let color_buffers_storage =
|
|
|
|
BufferStorage::from_alignment(descriptors.limits.min_uniform_buffer_offset_alignment);
|
|
|
|
|
2023-03-22 13:38:49 +00:00
|
|
|
let offscreen_buffer_pool = BufferPool::new(Box::new(
|
|
|
|
|descriptors: &Descriptors, dimensions: &BufferDimensions| {
|
|
|
|
descriptors.device.create_buffer(&wgpu::BufferDescriptor {
|
|
|
|
label: None,
|
|
|
|
size: dimensions.size(),
|
|
|
|
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
|
|
|
|
mapped_at_creation: false,
|
|
|
|
})
|
|
|
|
},
|
|
|
|
));
|
|
|
|
|
2022-09-03 00:18:57 +00:00
|
|
|
Ok(Self {
|
|
|
|
descriptors,
|
2022-09-04 19:19:16 +00:00
|
|
|
uniform_buffers_storage,
|
2022-12-27 22:18:03 +00:00
|
|
|
color_buffers_storage,
|
2022-09-03 00:18:57 +00:00
|
|
|
target,
|
2022-09-07 14:54:05 +00:00
|
|
|
surface,
|
2022-09-03 00:18:57 +00:00
|
|
|
meshes: Vec::new(),
|
|
|
|
shape_tessellator: ShapeTessellator::new(),
|
|
|
|
viewport_scale_factor: 1.0,
|
2022-12-23 23:13:26 +00:00
|
|
|
texture_pool: TexturePool::new(),
|
2023-01-02 23:00:50 +00:00
|
|
|
offscreen_texture_pool: TexturePool::new(),
|
2023-03-22 21:33:08 +00:00
|
|
|
offscreen_buffer_pool: Arc::new(offscreen_buffer_pool),
|
2022-09-03 00:18:57 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-01-28 10:12:48 +00:00
|
|
|
pub async fn request_device(
|
2022-09-03 00:18:57 +00:00
|
|
|
backend: wgpu::Backends,
|
|
|
|
instance: wgpu::Instance,
|
|
|
|
surface: Option<&wgpu::Surface>,
|
|
|
|
power_preference: wgpu::PowerPreference,
|
|
|
|
trace_path: Option<&Path>,
|
2023-01-28 10:12:48 +00:00
|
|
|
) -> Result<(wgpu::Adapter, wgpu::Device, wgpu::Queue), Error> {
|
2022-09-03 00:18:57 +00:00
|
|
|
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
|
|
|
|
power_preference,
|
|
|
|
compatible_surface: surface,
|
|
|
|
force_fallback_adapter: false,
|
|
|
|
}).await
|
|
|
|
.ok_or_else(|| {
|
|
|
|
let names = get_backend_names(backend);
|
|
|
|
if names.is_empty() {
|
|
|
|
"Ruffle requires hardware acceleration, but no compatible graphics device was found (no backend provided?)".to_string()
|
|
|
|
} else if cfg!(any(windows, target_os = "macos")) {
|
|
|
|
format!("Ruffle does not support OpenGL on {}.", if cfg!(windows) { "Windows" } else { "macOS" })
|
|
|
|
} else {
|
|
|
|
format!("Ruffle requires hardware acceleration, but no compatible graphics device was found supporting {}", format_list(&names, "or"))
|
|
|
|
}
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let (device, queue) = request_device(&adapter, trace_path).await?;
|
2023-01-28 10:12:48 +00:00
|
|
|
Ok((adapter, device, queue))
|
2022-09-03 00:18:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn register_shape_internal(
|
|
|
|
&mut self,
|
|
|
|
shape: DistilledShape,
|
|
|
|
bitmap_source: &dyn BitmapSource,
|
|
|
|
) -> Mesh {
|
2022-09-07 20:27:57 +00:00
|
|
|
let shape_id = shape.id;
|
2022-09-03 00:18:57 +00:00
|
|
|
let lyon_mesh = self
|
|
|
|
.shape_tessellator
|
|
|
|
.tessellate_shape(shape, bitmap_source);
|
|
|
|
|
|
|
|
let mut draws = Vec::with_capacity(lyon_mesh.len());
|
2023-02-02 16:57:23 +00:00
|
|
|
let mut uniform_buffer = BufferBuilder::new(
|
|
|
|
self.descriptors.limits.min_uniform_buffer_offset_alignment as usize,
|
|
|
|
);
|
2023-02-02 17:21:15 +00:00
|
|
|
let mut vertex_buffer = BufferBuilder::new(0);
|
|
|
|
let mut index_buffer = BufferBuilder::new(0);
|
2022-09-03 00:18:57 +00:00
|
|
|
for draw in lyon_mesh {
|
|
|
|
let draw_id = draws.len();
|
2023-02-02 16:46:41 +00:00
|
|
|
if let Some(draw) = PendingDraw::new(
|
|
|
|
self,
|
|
|
|
bitmap_source,
|
|
|
|
draw,
|
|
|
|
shape_id,
|
|
|
|
draw_id,
|
|
|
|
&mut uniform_buffer,
|
2023-02-02 17:21:15 +00:00
|
|
|
&mut vertex_buffer,
|
|
|
|
&mut index_buffer,
|
2023-02-02 16:46:41 +00:00
|
|
|
) {
|
2023-01-17 01:52:42 +00:00
|
|
|
draws.push(draw);
|
|
|
|
}
|
2022-09-03 00:18:57 +00:00
|
|
|
}
|
|
|
|
|
2023-02-02 16:46:41 +00:00
|
|
|
let uniform_buffer = uniform_buffer.finish(
|
|
|
|
&self.descriptors.device,
|
|
|
|
create_debug_label!("Shape {} uniforms", shape_id),
|
2023-02-02 16:57:23 +00:00
|
|
|
wgpu::BufferUsages::UNIFORM,
|
2023-02-02 16:46:41 +00:00
|
|
|
);
|
2023-02-02 17:21:15 +00:00
|
|
|
let vertex_buffer = vertex_buffer.finish(
|
|
|
|
&self.descriptors.device,
|
|
|
|
create_debug_label!("Shape {} vertices", shape_id),
|
|
|
|
wgpu::BufferUsages::VERTEX,
|
|
|
|
);
|
|
|
|
let index_buffer = index_buffer.finish(
|
|
|
|
&self.descriptors.device,
|
|
|
|
create_debug_label!("Shape {} indices", shape_id),
|
|
|
|
wgpu::BufferUsages::INDEX,
|
|
|
|
);
|
2023-02-02 16:46:41 +00:00
|
|
|
|
|
|
|
let draws = draws
|
|
|
|
.into_iter()
|
|
|
|
.map(|d| d.finish(&self.descriptors, &uniform_buffer))
|
|
|
|
.collect();
|
|
|
|
|
2023-02-02 17:21:15 +00:00
|
|
|
Mesh {
|
|
|
|
draws,
|
|
|
|
vertex_buffer,
|
|
|
|
index_buffer,
|
|
|
|
}
|
2022-09-03 00:18:57 +00:00
|
|
|
}
|
|
|
|
|
2023-05-01 22:29:50 +00:00
|
|
|
fn clamp_bitmap(&mut self, bitmap: &mut Bitmap) -> bool {
|
|
|
|
let max_size = self.descriptors.limits.max_texture_dimension_2d;
|
|
|
|
if bitmap.width() > max_size || bitmap.height() > max_size {
|
|
|
|
let image =
|
|
|
|
image::RgbaImage::from_raw(bitmap.width(), bitmap.height(), bitmap.data().to_vec())
|
|
|
|
.expect("Width and height of bitmap must match bitmap data");
|
|
|
|
|
|
|
|
let ratio = bitmap.width() as f32 / bitmap.height() as f32;
|
|
|
|
let mut width = bitmap.width();
|
|
|
|
let mut height = bitmap.height();
|
|
|
|
if width > max_size {
|
|
|
|
width = max_size;
|
|
|
|
height = (max_size as f32 / ratio) as u32;
|
|
|
|
}
|
|
|
|
if height > max_size {
|
|
|
|
height = max_size;
|
|
|
|
width = (max_size as f32 * ratio) as u32;
|
|
|
|
}
|
|
|
|
let resized = image::imageops::resize(&image, width, height, FilterType::CatmullRom);
|
|
|
|
*bitmap = Bitmap::new(width, height, BitmapFormat::Rgba, resized.into_raw());
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-03 00:18:57 +00:00
|
|
|
pub fn descriptors(&self) -> &Arc<Descriptors> {
|
|
|
|
&self.descriptors
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn target(&self) -> &T {
|
|
|
|
&self.target
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn device(&self) -> &wgpu::Device {
|
|
|
|
&self.descriptors.device
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
|
|
|
|
fn set_viewport_dimensions(&mut self, dimensions: ViewportDimensions) {
|
|
|
|
// Avoid panics from creating 0-sized framebuffers.
|
|
|
|
// TODO: find a way to bubble an error when the size is too large
|
|
|
|
let width = std::cmp::max(
|
|
|
|
std::cmp::min(
|
|
|
|
dimensions.width,
|
|
|
|
self.descriptors.limits.max_texture_dimension_2d,
|
|
|
|
),
|
|
|
|
1,
|
|
|
|
);
|
|
|
|
let height = std::cmp::max(
|
|
|
|
std::cmp::min(
|
|
|
|
dimensions.height,
|
|
|
|
self.descriptors.limits.max_texture_dimension_2d,
|
|
|
|
),
|
|
|
|
1,
|
|
|
|
);
|
|
|
|
self.target.resize(&self.descriptors.device, width, height);
|
2022-09-20 18:48:25 +00:00
|
|
|
|
2022-09-20 20:26:39 +00:00
|
|
|
self.surface = Surface::new(
|
|
|
|
&self.descriptors,
|
2023-02-03 15:44:21 +00:00
|
|
|
self.surface.quality(),
|
2022-09-20 20:26:39 +00:00
|
|
|
width,
|
|
|
|
height,
|
2022-09-21 00:44:56 +00:00
|
|
|
self.target.format(),
|
2022-09-20 20:26:39 +00:00
|
|
|
);
|
2022-09-03 00:18:57 +00:00
|
|
|
|
|
|
|
self.viewport_scale_factor = dimensions.scale_factor;
|
2022-12-27 18:31:54 +00:00
|
|
|
self.texture_pool = TexturePool::new();
|
2022-09-03 00:18:57 +00:00
|
|
|
}
|
|
|
|
|
avm2: Partially implement Stage3D for wgpu backend
This PR implements core 'stage3D' APIs. We are now able
to render at least two demos from the Context3D docs - a simple
triangle render, and a rotating cube.
Implemented in this PR:
* Stage3D access and Context3D creation
* IndexBuffer3D and VertexBuffer3D creation, uploading, and usage
* Program3D uploading and usage (via `naga-agal`)
* Context3D: configureBackBuffer, clear, drawTriangles, and present
Not yet implemented:
* Any 'dispose()' methods
* Depth and stencil buffers
* Context3D texture apis
* Scissor rectangle
General implementation strategy:
A new `Object` variant is added for each of the Stage3D objects
(VertexBuffer3D, Program3D, etc). This stores a handle to the
parent `Context3D`, and (depending on the object) a handle
to the underlying native resource, via `Rc<dyn
SomeRenderBackendTrait>`).
Calling methods on Context3D does not usually result in an immediate
call to a `wgpu` method. Instead, we queue up commands in our
`Context3D` instance, and execute them all on a call to `present`.
This avoids some nasty wgpu lifetime issues, and is very similar
to the approah we use for normal rendering.
The actual rendering happens on a `Texture`, with dimensions
determined by `createBackBuffer`. During 'Stage' rendering,
we render all of these Stage3D textures *behind* the normal
stage (but in front of the overall stage background color).
2022-09-18 20:50:21 +00:00
|
|
|
fn create_context3d(
|
|
|
|
&mut self,
|
|
|
|
) -> Result<Box<dyn ruffle_render::backend::Context3D>, BitmapError> {
|
2023-04-01 05:37:55 +00:00
|
|
|
Ok(Box::new(WgpuContext3D::new(self.descriptors.clone())))
|
avm2: Partially implement Stage3D for wgpu backend
This PR implements core 'stage3D' APIs. We are now able
to render at least two demos from the Context3D docs - a simple
triangle render, and a rotating cube.
Implemented in this PR:
* Stage3D access and Context3D creation
* IndexBuffer3D and VertexBuffer3D creation, uploading, and usage
* Program3D uploading and usage (via `naga-agal`)
* Context3D: configureBackBuffer, clear, drawTriangles, and present
Not yet implemented:
* Any 'dispose()' methods
* Depth and stencil buffers
* Context3D texture apis
* Scissor rectangle
General implementation strategy:
A new `Object` variant is added for each of the Stage3D objects
(VertexBuffer3D, Program3D, etc). This stores a handle to the
parent `Context3D`, and (depending on the object) a handle
to the underlying native resource, via `Rc<dyn
SomeRenderBackendTrait>`).
Calling methods on Context3D does not usually result in an immediate
call to a `wgpu` method. Instead, we queue up commands in our
`Context3D` instance, and execute them all on a call to `present`.
This avoids some nasty wgpu lifetime issues, and is very similar
to the approah we use for normal rendering.
The actual rendering happens on a `Texture`, with dimensions
determined by `createBackBuffer`. During 'Stage' rendering,
we render all of these Stage3D textures *behind* the normal
stage (but in front of the overall stage background color).
2022-09-18 20:50:21 +00:00
|
|
|
}
|
|
|
|
|
2023-01-06 12:17:12 +00:00
|
|
|
#[instrument(level = "debug", skip_all)]
|
2023-04-01 05:37:55 +00:00
|
|
|
fn context3d_present(&mut self, context: &mut dyn Context3D) -> Result<(), BitmapError> {
|
avm2: Partially implement Stage3D for wgpu backend
This PR implements core 'stage3D' APIs. We are now able
to render at least two demos from the Context3D docs - a simple
triangle render, and a rotating cube.
Implemented in this PR:
* Stage3D access and Context3D creation
* IndexBuffer3D and VertexBuffer3D creation, uploading, and usage
* Program3D uploading and usage (via `naga-agal`)
* Context3D: configureBackBuffer, clear, drawTriangles, and present
Not yet implemented:
* Any 'dispose()' methods
* Depth and stencil buffers
* Context3D texture apis
* Scissor rectangle
General implementation strategy:
A new `Object` variant is added for each of the Stage3D objects
(VertexBuffer3D, Program3D, etc). This stores a handle to the
parent `Context3D`, and (depending on the object) a handle
to the underlying native resource, via `Rc<dyn
SomeRenderBackendTrait>`).
Calling methods on Context3D does not usually result in an immediate
call to a `wgpu` method. Instead, we queue up commands in our
`Context3D` instance, and execute them all on a call to `present`.
This avoids some nasty wgpu lifetime issues, and is very similar
to the approah we use for normal rendering.
The actual rendering happens on a `Texture`, with dimensions
determined by `createBackBuffer`. During 'Stage' rendering,
we render all of these Stage3D textures *behind* the normal
stage (but in front of the overall stage background color).
2022-09-18 20:50:21 +00:00
|
|
|
let context = context
|
|
|
|
.as_any_mut()
|
|
|
|
.downcast_mut::<WgpuContext3D>()
|
|
|
|
.unwrap();
|
2023-04-01 05:37:55 +00:00
|
|
|
context.present();
|
avm2: Partially implement Stage3D for wgpu backend
This PR implements core 'stage3D' APIs. We are now able
to render at least two demos from the Context3D docs - a simple
triangle render, and a rotating cube.
Implemented in this PR:
* Stage3D access and Context3D creation
* IndexBuffer3D and VertexBuffer3D creation, uploading, and usage
* Program3D uploading and usage (via `naga-agal`)
* Context3D: configureBackBuffer, clear, drawTriangles, and present
Not yet implemented:
* Any 'dispose()' methods
* Depth and stencil buffers
* Context3D texture apis
* Scissor rectangle
General implementation strategy:
A new `Object` variant is added for each of the Stage3D objects
(VertexBuffer3D, Program3D, etc). This stores a handle to the
parent `Context3D`, and (depending on the object) a handle
to the underlying native resource, via `Rc<dyn
SomeRenderBackendTrait>`).
Calling methods on Context3D does not usually result in an immediate
call to a `wgpu` method. Instead, we queue up commands in our
`Context3D` instance, and execute them all on a call to `present`.
This avoids some nasty wgpu lifetime issues, and is very similar
to the approah we use for normal rendering.
The actual rendering happens on a `Texture`, with dimensions
determined by `createBackBuffer`. During 'Stage' rendering,
we render all of these Stage3D textures *behind* the normal
stage (but in front of the overall stage background color).
2022-09-18 20:50:21 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-01-03 14:53:22 +00:00
|
|
|
fn debug_info(&self) -> Cow<'static, str> {
|
|
|
|
let mut result = vec![];
|
|
|
|
result.push("Renderer: wgpu".to_string());
|
|
|
|
|
|
|
|
let info = self.descriptors.adapter.get_info();
|
|
|
|
result.push(format!("Adapter Backend: {:?}", info.backend));
|
|
|
|
result.push(format!("Adapter Name: {:?}", info.name));
|
|
|
|
result.push(format!("Adapter Device Type: {:?}", info.device_type));
|
|
|
|
result.push(format!("Adapter Driver Name: {:?}", info.driver));
|
|
|
|
result.push(format!("Adapter Driver Info: {:?}", info.driver_info));
|
|
|
|
|
|
|
|
let enabled_features = self.descriptors.device.features();
|
|
|
|
let available_features = self.descriptors.adapter.features() - enabled_features;
|
|
|
|
let current_limits = &self.descriptors.limits;
|
|
|
|
|
|
|
|
result.push(format!("Enabled features: {enabled_features:?}"));
|
|
|
|
result.push(format!("Available features: {available_features:?}"));
|
|
|
|
result.push(format!("Current limits: {current_limits:?}"));
|
2023-02-03 15:44:21 +00:00
|
|
|
result.push(format!("Surface quality: {}", self.surface.quality()));
|
2023-01-03 14:53:22 +00:00
|
|
|
result.push(format!("Surface samples: {}", self.surface.sample_count()));
|
|
|
|
result.push(format!("Surface size: {:?}", self.surface.size()));
|
|
|
|
|
|
|
|
Cow::Owned(result.join("\n"))
|
|
|
|
}
|
|
|
|
|
2023-05-02 20:59:57 +00:00
|
|
|
fn name(&self) -> &'static str {
|
2023-05-02 21:59:12 +00:00
|
|
|
if cfg!(target_family = "wasm") {
|
|
|
|
let info = self.descriptors.adapter.get_info();
|
|
|
|
if info.backend == wgpu::Backend::BrowserWebGpu {
|
|
|
|
"webgpu"
|
|
|
|
} else {
|
|
|
|
"wgpu-webgl"
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
"wgpu"
|
|
|
|
}
|
2023-05-02 20:59:57 +00:00
|
|
|
}
|
|
|
|
|
2023-02-03 14:23:32 +00:00
|
|
|
fn set_quality(&mut self, quality: StageQuality) {
|
|
|
|
self.surface = Surface::new(
|
|
|
|
&self.descriptors,
|
2023-02-03 15:44:21 +00:00
|
|
|
quality,
|
2023-02-03 14:23:32 +00:00
|
|
|
self.surface.size().width,
|
|
|
|
self.surface.size().height,
|
|
|
|
self.target.format(),
|
|
|
|
);
|
|
|
|
}
|
2023-02-03 14:16:30 +00:00
|
|
|
|
2022-09-03 00:18:57 +00:00
|
|
|
fn viewport_dimensions(&self) -> ViewportDimensions {
|
|
|
|
ViewportDimensions {
|
|
|
|
width: self.target.width(),
|
|
|
|
height: self.target.height(),
|
|
|
|
scale_factor: self.viewport_scale_factor,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-06 12:17:12 +00:00
|
|
|
#[instrument(level = "debug", skip_all)]
|
2022-09-03 00:18:57 +00:00
|
|
|
fn register_shape(
|
|
|
|
&mut self,
|
|
|
|
shape: DistilledShape,
|
|
|
|
bitmap_source: &dyn BitmapSource,
|
|
|
|
) -> ShapeHandle {
|
|
|
|
let mesh = self.register_shape_internal(shape, bitmap_source);
|
2023-03-19 11:39:38 +00:00
|
|
|
ShapeHandle(Arc::new(mesh))
|
2022-09-03 00:18:57 +00:00
|
|
|
}
|
|
|
|
|
2023-01-06 12:17:12 +00:00
|
|
|
#[instrument(level = "debug", skip_all)]
|
2022-09-03 17:22:57 +00:00
|
|
|
fn submit_frame(&mut self, clear: Color, commands: CommandList) {
|
2022-09-04 19:19:16 +00:00
|
|
|
let frame_output = match self.target.get_next_texture() {
|
|
|
|
Ok(frame) => frame,
|
|
|
|
Err(e) => {
|
2023-01-06 12:17:12 +00:00
|
|
|
tracing::warn!("Couldn't begin new render frame: {}", e);
|
2022-09-04 19:19:16 +00:00
|
|
|
// Attempt to recreate the swap chain in this case.
|
|
|
|
self.target.resize(
|
|
|
|
&self.descriptors.device,
|
|
|
|
self.target.width(),
|
|
|
|
self.target.height(),
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-12-22 00:57:38 +00:00
|
|
|
let command_buffers = self.surface.draw_commands_to(
|
2022-09-21 00:44:56 +00:00
|
|
|
frame_output.view(),
|
2023-02-20 19:52:59 +00:00
|
|
|
RenderTargetMode::FreshBuffer(wgpu::Color {
|
2022-09-07 19:53:21 +00:00
|
|
|
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,
|
2022-09-28 15:47:35 +00:00
|
|
|
}),
|
2022-09-04 19:19:16 +00:00
|
|
|
&self.descriptors,
|
2022-09-07 19:53:21 +00:00
|
|
|
&mut self.uniform_buffers_storage,
|
2022-12-27 22:18:03 +00:00
|
|
|
&mut self.color_buffers_storage,
|
2022-09-07 09:33:12 +00:00
|
|
|
&self.meshes,
|
2022-09-07 19:53:21 +00:00
|
|
|
commands,
|
2022-12-23 23:13:26 +00:00
|
|
|
&mut self.texture_pool,
|
2022-09-07 14:54:05 +00:00
|
|
|
);
|
2022-09-04 19:19:16 +00:00
|
|
|
|
|
|
|
self.target.submit(
|
|
|
|
&self.descriptors.device,
|
|
|
|
&self.descriptors.queue,
|
|
|
|
command_buffers,
|
|
|
|
frame_output,
|
|
|
|
);
|
2022-12-23 23:13:26 +00:00
|
|
|
self.uniform_buffers_storage.recall();
|
2022-12-27 22:18:03 +00:00
|
|
|
self.color_buffers_storage.recall();
|
2023-01-02 23:00:50 +00:00
|
|
|
self.offscreen_texture_pool = TexturePool::new();
|
2022-09-03 17:22:57 +00:00
|
|
|
}
|
2022-09-03 00:18:57 +00:00
|
|
|
|
2023-01-06 12:17:12 +00:00
|
|
|
#[instrument(level = "debug", skip_all)]
|
2022-09-03 17:22:57 +00:00
|
|
|
fn register_bitmap(&mut self, bitmap: Bitmap) -> Result<BitmapHandle, BitmapError> {
|
2023-05-01 22:29:50 +00:00
|
|
|
let mut bitmap = bitmap.to_rgba();
|
|
|
|
|
|
|
|
self.clamp_bitmap(&mut bitmap);
|
2022-09-03 17:22:57 +00:00
|
|
|
|
|
|
|
let extent = wgpu::Extent3d {
|
|
|
|
width: bitmap.width(),
|
|
|
|
height: bitmap.height(),
|
|
|
|
depth_or_array_layers: 1,
|
2022-09-03 00:18:57 +00:00
|
|
|
};
|
|
|
|
|
2022-09-03 17:22:57 +00:00
|
|
|
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,
|
2023-01-19 02:32:23 +00:00
|
|
|
view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
|
2022-09-03 17:22:57 +00:00
|
|
|
usage: wgpu::TextureUsages::TEXTURE_BINDING
|
|
|
|
| wgpu::TextureUsages::COPY_DST
|
|
|
|
| wgpu::TextureUsages::RENDER_ATTACHMENT
|
|
|
|
| wgpu::TextureUsages::COPY_SRC,
|
|
|
|
});
|
2022-09-03 00:18:57 +00:00
|
|
|
|
2022-09-03 17:22:57 +00:00
|
|
|
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,
|
2023-04-24 20:06:16 +00:00
|
|
|
bytes_per_row: Some(4 * extent.width),
|
2022-09-03 17:22:57 +00:00
|
|
|
rows_per_image: None,
|
|
|
|
},
|
|
|
|
extent,
|
|
|
|
);
|
2022-09-03 00:18:57 +00:00
|
|
|
|
2022-09-30 21:25:54 +00:00
|
|
|
let handle = BitmapHandle(Arc::new(Texture {
|
|
|
|
texture: Arc::new(texture),
|
avm2: Partially implement Stage3D for wgpu backend
This PR implements core 'stage3D' APIs. We are now able
to render at least two demos from the Context3D docs - a simple
triangle render, and a rotating cube.
Implemented in this PR:
* Stage3D access and Context3D creation
* IndexBuffer3D and VertexBuffer3D creation, uploading, and usage
* Program3D uploading and usage (via `naga-agal`)
* Context3D: configureBackBuffer, clear, drawTriangles, and present
Not yet implemented:
* Any 'dispose()' methods
* Depth and stencil buffers
* Context3D texture apis
* Scissor rectangle
General implementation strategy:
A new `Object` variant is added for each of the Stage3D objects
(VertexBuffer3D, Program3D, etc). This stores a handle to the
parent `Context3D`, and (depending on the object) a handle
to the underlying native resource, via `Rc<dyn
SomeRenderBackendTrait>`).
Calling methods on Context3D does not usually result in an immediate
call to a `wgpu` method. Instead, we queue up commands in our
`Context3D` instance, and execute them all on a call to `present`.
This avoids some nasty wgpu lifetime issues, and is very similar
to the approah we use for normal rendering.
The actual rendering happens on a `Texture`, with dimensions
determined by `createBackBuffer`. During 'Stage' rendering,
we render all of these Stage3D textures *behind* the normal
stage (but in front of the overall stage background color).
2022-09-18 20:50:21 +00:00
|
|
|
bind_linear: Default::default(),
|
|
|
|
bind_nearest: Default::default(),
|
2022-09-30 21:25:54 +00:00
|
|
|
width: bitmap.width(),
|
|
|
|
height: bitmap.height(),
|
2023-03-22 21:33:08 +00:00
|
|
|
copy_count: Cell::new(0),
|
2022-09-30 21:25:54 +00:00
|
|
|
}));
|
2022-09-03 17:22:57 +00:00
|
|
|
|
|
|
|
Ok(handle)
|
|
|
|
}
|
|
|
|
|
2023-01-06 12:17:12 +00:00
|
|
|
#[instrument(level = "debug", skip_all)]
|
2022-09-03 17:22:57 +00:00
|
|
|
fn update_texture(
|
|
|
|
&mut self,
|
2022-09-30 21:25:54 +00:00
|
|
|
handle: &BitmapHandle,
|
2023-03-19 20:13:19 +00:00
|
|
|
bitmap: Bitmap,
|
2023-05-01 22:29:50 +00:00
|
|
|
mut region: PixelRegion,
|
2022-09-11 07:57:17 +00:00
|
|
|
) -> Result<(), BitmapError> {
|
2022-09-30 21:25:54 +00:00
|
|
|
let texture = as_texture(handle);
|
2022-09-03 17:22:57 +00:00
|
|
|
|
2023-05-01 22:29:50 +00:00
|
|
|
let mut bitmap = bitmap.to_rgba();
|
|
|
|
if self.clamp_bitmap(&mut bitmap) {
|
|
|
|
// If we're updating a resized texture, just redo the whole thing.
|
|
|
|
// We can't trivially map pixel regions as we use a filter to resize.
|
|
|
|
region = PixelRegion::for_whole_size(bitmap.width(), bitmap.height());
|
|
|
|
}
|
|
|
|
|
2022-09-03 17:22:57 +00:00
|
|
|
let extent = wgpu::Extent3d {
|
2023-04-04 14:06:43 +00:00
|
|
|
width: region.width(),
|
|
|
|
height: region.height(),
|
2022-09-03 17:22:57 +00:00
|
|
|
depth_or_array_layers: 1,
|
|
|
|
};
|
2022-09-03 00:18:57 +00:00
|
|
|
|
2022-09-03 17:22:57 +00:00
|
|
|
self.descriptors.queue.write_texture(
|
|
|
|
wgpu::ImageCopyTexture {
|
2022-09-30 21:25:54 +00:00
|
|
|
texture: &texture.texture,
|
2022-09-03 17:22:57 +00:00
|
|
|
mip_level: 0,
|
2023-03-22 15:46:52 +00:00
|
|
|
origin: wgpu::Origin3d {
|
2023-03-31 13:51:35 +00:00
|
|
|
x: region.x_min,
|
|
|
|
y: region.y_min,
|
2023-03-22 15:46:52 +00:00
|
|
|
z: 0,
|
|
|
|
},
|
2022-09-03 17:22:57 +00:00
|
|
|
aspect: wgpu::TextureAspect::All,
|
2022-09-03 00:18:57 +00:00
|
|
|
},
|
2023-04-04 14:06:43 +00:00
|
|
|
&bitmap.data()[(region.y_min * texture.width * 4) as usize
|
|
|
|
..(region.y_max * texture.width * 4) as usize],
|
2022-09-03 17:22:57 +00:00
|
|
|
wgpu::ImageDataLayout {
|
2023-03-31 13:51:35 +00:00
|
|
|
offset: (region.x_min * 4) as wgpu::BufferAddress,
|
2023-04-24 20:06:16 +00:00
|
|
|
bytes_per_row: Some(4 * texture.width),
|
2022-09-03 17:22:57 +00:00
|
|
|
rows_per_image: None,
|
|
|
|
},
|
|
|
|
extent,
|
|
|
|
);
|
|
|
|
|
2022-09-11 07:57:17 +00:00
|
|
|
Ok(())
|
2022-09-03 00:18:57 +00:00
|
|
|
}
|
|
|
|
|
2023-01-06 12:17:12 +00:00
|
|
|
#[instrument(level = "debug", skip_all)]
|
2022-09-03 00:18:57 +00:00
|
|
|
fn render_offscreen(
|
|
|
|
&mut self,
|
|
|
|
handle: BitmapHandle,
|
2022-09-03 17:22:57 +00:00
|
|
|
commands: CommandList,
|
2023-02-16 05:11:59 +00:00
|
|
|
quality: StageQuality,
|
2023-03-22 14:41:31 +00:00
|
|
|
bounds: PixelRegion,
|
2023-01-29 20:44:29 +00:00
|
|
|
) -> Option<Box<dyn SyncHandle>> {
|
2022-09-30 21:25:54 +00:00
|
|
|
let texture = as_texture(&handle);
|
2022-09-07 01:18:31 +00:00
|
|
|
|
|
|
|
let extent = wgpu::Extent3d {
|
2023-03-22 11:10:48 +00:00
|
|
|
width: texture.width,
|
|
|
|
height: texture.height,
|
2022-09-07 01:18:31 +00:00
|
|
|
depth_or_array_layers: 1,
|
|
|
|
};
|
|
|
|
|
2023-03-22 21:33:08 +00:00
|
|
|
let buffer_info = if texture.copy_count.get() > TEXTURE_READS_BEFORE_PROMOTION {
|
|
|
|
let copy_dimensions =
|
|
|
|
BufferDimensions::new(bounds.width() as usize, bounds.height() as usize);
|
|
|
|
let buffer = self
|
|
|
|
.offscreen_buffer_pool
|
|
|
|
.take(&self.descriptors, copy_dimensions.clone());
|
|
|
|
Some(TextureBufferInfo {
|
|
|
|
buffer: MaybeOwnedBuffer::Borrowed(buffer, copy_dimensions),
|
|
|
|
copy_area: bounds,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2022-09-07 01:18:31 +00:00
|
|
|
|
|
|
|
let mut target = TextureTarget {
|
|
|
|
size: extent,
|
2022-09-30 21:25:54 +00:00
|
|
|
texture: texture.texture.clone(),
|
2022-09-07 01:18:31 +00:00
|
|
|
format: wgpu::TextureFormat::Rgba8Unorm,
|
2023-03-22 21:33:08 +00:00
|
|
|
buffer: buffer_info,
|
2022-09-07 01:18:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let frame_output = target
|
|
|
|
.get_next_texture()
|
|
|
|
.expect("TextureTargetFrame.get_next_texture is infallible");
|
|
|
|
|
2022-09-21 00:44:56 +00:00
|
|
|
let mut surface = Surface::new(
|
|
|
|
&self.descriptors,
|
2023-02-16 05:11:59 +00:00
|
|
|
quality,
|
2023-03-22 11:10:48 +00:00
|
|
|
texture.width,
|
|
|
|
texture.height,
|
2022-09-21 00:44:56 +00:00
|
|
|
wgpu::TextureFormat::Rgba8Unorm,
|
|
|
|
);
|
2022-12-22 00:57:38 +00:00
|
|
|
let command_buffers = surface.draw_commands_to(
|
2022-09-07 19:53:21 +00:00
|
|
|
frame_output.view(),
|
2023-02-20 19:52:59 +00:00
|
|
|
RenderTargetMode::ExistingTexture(target.get_texture()),
|
2022-09-07 01:18:31 +00:00
|
|
|
&self.descriptors,
|
2022-09-07 19:53:21 +00:00
|
|
|
&mut self.uniform_buffers_storage,
|
2022-12-27 22:18:03 +00:00
|
|
|
&mut self.color_buffers_storage,
|
2022-09-07 09:33:12 +00:00
|
|
|
&self.meshes,
|
2022-09-07 19:53:21 +00:00
|
|
|
commands,
|
2023-01-02 23:00:50 +00:00
|
|
|
&mut self.offscreen_texture_pool,
|
2022-09-07 19:53:21 +00:00
|
|
|
);
|
2022-12-23 01:24:35 +00:00
|
|
|
let index = target.submit(
|
2022-09-07 01:18:31 +00:00
|
|
|
&self.descriptors.device,
|
|
|
|
&self.descriptors.queue,
|
2022-09-07 19:53:21 +00:00
|
|
|
command_buffers,
|
2022-09-07 01:18:31 +00:00
|
|
|
frame_output,
|
|
|
|
);
|
2022-12-23 23:13:26 +00:00
|
|
|
self.uniform_buffers_storage.recall();
|
2022-12-27 22:18:03 +00:00
|
|
|
self.color_buffers_storage.recall();
|
2022-09-07 01:18:31 +00:00
|
|
|
|
2023-03-22 21:33:08 +00:00
|
|
|
match target.take_buffer() {
|
|
|
|
None => Some(Box::new(QueueSyncHandle::NotCopied {
|
|
|
|
handle,
|
|
|
|
copy_area: bounds,
|
|
|
|
descriptors: self.descriptors.clone(),
|
|
|
|
pool: self.offscreen_buffer_pool.clone(),
|
|
|
|
})),
|
|
|
|
Some(TextureBufferInfo {
|
|
|
|
buffer: MaybeOwnedBuffer::Borrowed(buffer, copy_dimensions),
|
|
|
|
..
|
|
|
|
}) => Some(Box::new(QueueSyncHandle::AlreadyCopied {
|
|
|
|
index,
|
|
|
|
buffer,
|
|
|
|
copy_dimensions,
|
|
|
|
descriptors: self.descriptors.clone(),
|
|
|
|
})),
|
|
|
|
Some(TextureBufferInfo {
|
|
|
|
buffer: MaybeOwnedBuffer::Owned(..),
|
|
|
|
..
|
|
|
|
}) => unreachable!("Buffer must be Borrowed as it was set to be Borrowed earlier"),
|
|
|
|
}
|
2023-01-07 16:08:23 +00:00
|
|
|
}
|
2023-01-20 23:48:03 +00:00
|
|
|
|
2023-04-08 22:31:55 +00:00
|
|
|
fn is_filter_supported(&self, filter: &Filter) -> bool {
|
|
|
|
matches!(filter, Filter::BlurFilter(_) | Filter::ColorMatrixFilter(_))
|
|
|
|
}
|
|
|
|
|
2023-01-20 23:48:03 +00:00
|
|
|
fn apply_filter(
|
|
|
|
&mut self,
|
|
|
|
source: BitmapHandle,
|
|
|
|
source_point: (u32, u32),
|
|
|
|
source_size: (u32, u32),
|
|
|
|
destination: BitmapHandle,
|
|
|
|
dest_point: (u32, u32),
|
|
|
|
filter: Filter,
|
|
|
|
) -> Option<Box<dyn SyncHandle>> {
|
|
|
|
let source_texture = as_texture(&source);
|
|
|
|
let dest_texture = as_texture(&destination);
|
|
|
|
|
2023-03-22 21:33:08 +00:00
|
|
|
let copy_area = PixelRegion::for_whole_size(dest_texture.width, dest_texture.height);
|
|
|
|
let buffer_info = if dest_texture.copy_count.get() >= TEXTURE_READS_BEFORE_PROMOTION {
|
|
|
|
let copy_dimensions =
|
|
|
|
BufferDimensions::new(dest_texture.width as usize, dest_texture.height as usize);
|
|
|
|
let buffer = self
|
|
|
|
.offscreen_buffer_pool
|
|
|
|
.take(&self.descriptors, copy_dimensions.clone());
|
|
|
|
Some(TextureBufferInfo {
|
|
|
|
buffer: MaybeOwnedBuffer::Borrowed(buffer, copy_dimensions),
|
|
|
|
copy_area,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2023-03-22 13:38:49 +00:00
|
|
|
|
2023-01-20 23:48:03 +00:00
|
|
|
let mut target = TextureTarget {
|
|
|
|
size: wgpu::Extent3d {
|
|
|
|
width: dest_texture.width,
|
|
|
|
height: dest_texture.height,
|
|
|
|
depth_or_array_layers: 1,
|
|
|
|
},
|
|
|
|
texture: dest_texture.texture.clone(),
|
|
|
|
format: wgpu::TextureFormat::Rgba8Unorm,
|
2023-03-22 21:33:08 +00:00
|
|
|
buffer: buffer_info,
|
2023-01-20 23:48:03 +00:00
|
|
|
};
|
|
|
|
let frame_output = target
|
|
|
|
.get_next_texture()
|
|
|
|
.expect("TextureTargetFrame.get_next_texture is infallible");
|
|
|
|
let surface = Surface::new(
|
|
|
|
&self.descriptors,
|
2023-02-03 15:44:21 +00:00
|
|
|
self.surface.quality(),
|
2023-01-20 23:48:03 +00:00
|
|
|
dest_texture.width,
|
|
|
|
dest_texture.height,
|
|
|
|
wgpu::TextureFormat::Rgba8Unorm,
|
|
|
|
);
|
|
|
|
let label = create_debug_label!("Draw encoder");
|
|
|
|
let mut draw_encoder =
|
|
|
|
self.descriptors
|
|
|
|
.device
|
|
|
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
|
|
label: label.as_deref(),
|
|
|
|
});
|
|
|
|
surface.apply_filter(
|
|
|
|
&self.descriptors,
|
|
|
|
&mut draw_encoder,
|
|
|
|
&mut self.offscreen_texture_pool,
|
|
|
|
source_texture,
|
|
|
|
source_point,
|
|
|
|
source_size,
|
|
|
|
dest_texture,
|
|
|
|
dest_point,
|
|
|
|
filter,
|
|
|
|
);
|
|
|
|
let index = target.submit(
|
|
|
|
&self.descriptors.device,
|
|
|
|
&self.descriptors.queue,
|
|
|
|
Some(draw_encoder.finish()),
|
|
|
|
frame_output,
|
|
|
|
);
|
2023-03-22 13:38:49 +00:00
|
|
|
|
2023-03-22 21:33:08 +00:00
|
|
|
match target.take_buffer() {
|
|
|
|
None => Some(Box::new(QueueSyncHandle::NotCopied {
|
|
|
|
handle: destination,
|
|
|
|
copy_area,
|
|
|
|
descriptors: self.descriptors.clone(),
|
|
|
|
pool: self.offscreen_buffer_pool.clone(),
|
|
|
|
})),
|
|
|
|
Some(TextureBufferInfo {
|
|
|
|
buffer: MaybeOwnedBuffer::Borrowed(buffer, copy_dimensions),
|
|
|
|
..
|
|
|
|
}) => Some(Box::new(QueueSyncHandle::AlreadyCopied {
|
|
|
|
index,
|
|
|
|
buffer,
|
|
|
|
copy_dimensions,
|
|
|
|
descriptors: self.descriptors.clone(),
|
|
|
|
})),
|
|
|
|
Some(TextureBufferInfo {
|
|
|
|
buffer: MaybeOwnedBuffer::Owned(..),
|
|
|
|
..
|
|
|
|
}) => unreachable!("Buffer must be Borrowed as it was set to be Borrowed earlier"),
|
|
|
|
}
|
2023-01-20 23:48:03 +00:00
|
|
|
}
|
2022-09-03 17:22:57 +00:00
|
|
|
}
|
2022-09-03 00:18:57 +00:00
|
|
|
|
|
|
|
// We try to request the highest limits we can get away with
|
|
|
|
async fn request_device(
|
|
|
|
adapter: &wgpu::Adapter,
|
|
|
|
trace_path: Option<&Path>,
|
|
|
|
) -> Result<(wgpu::Device, wgpu::Queue), wgpu::RequestDeviceError> {
|
|
|
|
// We start off with the lowest limits we actually need - basically GL-ES 3.0
|
|
|
|
let mut limits = wgpu::Limits::downlevel_webgl2_defaults();
|
|
|
|
// Then we increase parts of it to the maximum supported by the adapter, to take advantage of
|
|
|
|
// more powerful hardware or capabilities
|
|
|
|
limits = limits.using_resolution(adapter.limits());
|
|
|
|
limits = limits.using_alignment(adapter.limits());
|
|
|
|
|
2023-01-06 17:41:24 +00:00
|
|
|
let mut features = Default::default();
|
2023-02-03 18:04:42 +00:00
|
|
|
|
2023-01-07 09:19:14 +00:00
|
|
|
let needed_size = (mem::size_of::<Transforms>() + mem::size_of::<ColorAdjustments>()) as u32;
|
2023-01-06 17:41:24 +00:00
|
|
|
if adapter.features().contains(wgpu::Features::PUSH_CONSTANTS)
|
2023-01-07 09:19:14 +00:00
|
|
|
&& adapter.limits().max_push_constant_size >= needed_size
|
2023-01-06 17:41:24 +00:00
|
|
|
{
|
2023-01-07 09:19:14 +00:00
|
|
|
limits.max_push_constant_size = needed_size;
|
2023-01-06 17:41:24 +00:00
|
|
|
features |= wgpu::Features::PUSH_CONSTANTS;
|
|
|
|
}
|
|
|
|
|
2023-02-03 18:04:42 +00:00
|
|
|
if adapter
|
|
|
|
.features()
|
|
|
|
.contains(wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES)
|
|
|
|
{
|
|
|
|
features |= wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES;
|
|
|
|
}
|
|
|
|
|
2022-09-03 00:18:57 +00:00
|
|
|
adapter
|
|
|
|
.request_device(
|
|
|
|
&wgpu::DeviceDescriptor {
|
|
|
|
label: None,
|
2023-01-06 17:41:24 +00:00
|
|
|
features,
|
2022-09-03 00:18:57 +00:00
|
|
|
limits,
|
|
|
|
},
|
|
|
|
trace_path,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
2023-02-20 19:52:59 +00:00
|
|
|
|
|
|
|
/// Determines how we choose our frame buffer
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub enum RenderTargetMode {
|
|
|
|
// Construct a new frame buffer, clearng it with the provided color.
|
|
|
|
// This is used when rendering to the actual display,
|
|
|
|
// or when applying a filter. In both cases, we have a fixed background color,
|
|
|
|
// and don't need to blend with anything else
|
|
|
|
FreshBuffer(wgpu::Color),
|
|
|
|
// Use the provided texture as our frame buffer. During rendering,
|
|
|
|
// we will blend with the previous contents of the texture.
|
|
|
|
// This is used in `render_offscreen`, as we need to blend with the previous
|
|
|
|
// contents of our `BitmapData` texture
|
|
|
|
ExistingTexture(Arc<wgpu::Texture>),
|
|
|
|
}
|