wgpu: Fixed panic when rendering texture to itself, by always rendering to an intermediary

This commit is contained in:
Nathan Adams 2023-03-27 12:19:27 +02:00
parent 68343369a3
commit c85910b46d
3 changed files with 77 additions and 101 deletions

View File

@ -402,28 +402,21 @@ mod wrapper {
} }
pub fn render(&self, smoothing: bool, context: &mut RenderContext<'_, 'gc>) { pub fn render(&self, smoothing: bool, context: &mut RenderContext<'_, 'gc>) {
// if try_write fails, let mut inner_bitmap_data = self.0.write(context.gc_context);
// this is caused by recursive render attempt. TODO: support this. if inner_bitmap_data.disposed() {
if let Ok(mut inner_bitmap_data) = self.0.try_write(context.gc_context) { return;
if inner_bitmap_data.disposed() {
return;
}
// Note - we do a CPU -> GPU sync, but we do *not* do a GPU -> CPU sync
// (rendering is done on the GPU, so the CPU pixels don't need to be up-to-date).
inner_bitmap_data.update_dirty_texture(context.renderer);
let handle = inner_bitmap_data
.bitmap_handle(context.renderer)
.expect("Missing bitmap handle");
context.commands.render_bitmap(
handle,
context.transform_stack.transform(),
smoothing,
);
} else {
//this is caused by recursive render attempt. TODO: support this.
} }
// Note - we do a CPU -> GPU sync, but we do *not* do a GPU -> CPU sync
// (rendering is done on the GPU, so the CPU pixels don't need to be up-to-date).
inner_bitmap_data.update_dirty_texture(context.renderer);
let handle = inner_bitmap_data
.bitmap_handle(context.renderer)
.expect("Missing bitmap handle");
context
.commands
.render_bitmap(handle, context.transform_stack.transform(), smoothing);
} }
pub fn is_point_in_bounds(&self, x: i32, y: i32) -> bool { pub fn is_point_in_bounds(&self, x: i32, y: i32) -> bool {

View File

@ -111,27 +111,25 @@ impl Surface {
let mut buffers = vec![draw_encoder.finish()]; let mut buffers = vec![draw_encoder.finish()];
if let RenderTargetMode::FreshBuffer(_) = render_target_mode { let mut copy_encoder =
let mut copy_encoder = descriptors
descriptors .device
.device .create_command_encoder(&wgpu::CommandEncoderDescriptor {
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: create_debug_label!("Frame copy command encoder").as_deref(),
label: create_debug_label!("Frame copy command encoder").as_deref(), });
}); run_copy_pipeline(
run_copy_pipeline( descriptors,
descriptors, self.format,
self.format, self.actual_surface_format,
self.actual_surface_format, self.size,
self.size, frame_view,
frame_view, target.color_view(),
target.color_view(), target.whole_frame_bind_group(descriptors),
target.whole_frame_bind_group(descriptors), target.globals(),
target.globals(), 1,
1, &mut copy_encoder,
&mut copy_encoder, );
); buffers.push(copy_encoder.finish());
buffers.push(copy_encoder.finish());
}
buffers.insert(0, uniform_encoder.finish()); buffers.insert(0, uniform_encoder.finish());
uniform_buffer.finish(); uniform_buffer.finish();

View File

@ -28,6 +28,7 @@ impl ResolveBuffer {
} }
} }
#[allow(dead_code)]
pub fn new_manual(texture: Arc<wgpu::Texture>) -> Self { pub fn new_manual(texture: Arc<wgpu::Texture>) -> Self {
Self { Self {
texture: PoolOrArcTexture::Manual(( texture: PoolOrArcTexture::Manual((
@ -69,6 +70,7 @@ pub struct FrameBuffer {
/// (when doing an offscreen render to a BitmapData texture) /// (when doing an offscreen render to a BitmapData texture)
pub enum PoolOrArcTexture { pub enum PoolOrArcTexture {
Pool(PoolEntry<(wgpu::Texture, wgpu::TextureView), AlwaysCompatible>), Pool(PoolEntry<(wgpu::Texture, wgpu::TextureView), AlwaysCompatible>),
#[allow(dead_code)]
Manual((Arc<wgpu::Texture>, wgpu::TextureView)), Manual((Arc<wgpu::Texture>, wgpu::TextureView)),
} }
@ -98,6 +100,7 @@ impl FrameBuffer {
} }
} }
#[allow(dead_code)]
pub fn new_manual(texture: Arc<wgpu::Texture>, size: wgpu::Extent3d) -> Self { pub fn new_manual(texture: Arc<wgpu::Texture>, size: wgpu::Extent3d) -> Self {
Self { Self {
texture: PoolOrArcTexture::Manual(( texture: PoolOrArcTexture::Manual((
@ -232,67 +235,49 @@ impl CommandTarget {
let whole_frame_bind_group = OnceCell::new(); let whole_frame_bind_group = OnceCell::new();
let (frame_buffer, resolve_buffer) = match &render_target_mode { let frame_buffer = make_pooled_frame_buffer();
// In `FreshBuffer` mode, get a new frame buffer (and resolve buffer, if necessary) let resolve_buffer = if sample_count > 1 {
// from the pool. They will be cleared with the provided clear color Some(ResolveBuffer::new(
// in `color_attachments` descriptors,
RenderTargetMode::FreshBuffer(_) => { size,
let frame_buffer = make_pooled_frame_buffer(); format,
let resolve_buffer = if sample_count > 1 { wgpu::TextureUsages::COPY_SRC
Some(ResolveBuffer::new( | wgpu::TextureUsages::COPY_DST
descriptors, | wgpu::TextureUsages::TEXTURE_BINDING
size, | wgpu::TextureUsages::RENDER_ATTACHMENT,
format, pool,
wgpu::TextureUsages::COPY_SRC ))
| wgpu::TextureUsages::COPY_DST } else {
| wgpu::TextureUsages::TEXTURE_BINDING None
| wgpu::TextureUsages::RENDER_ATTACHMENT,
pool,
))
} else {
None
};
(frame_buffer, resolve_buffer)
}
// In `ExistingTexture` mode, we will use an existing texture
// as either the frame buffer or resolve buffer.
RenderTargetMode::ExistingTexture(texture) => {
if sample_count > 1 {
// The exising texture always has a sample count of 1,
// so we need to create a new texture for the multisampled frame
// buffer. Our existing texture will be used as the resolve buffer,
// which is downsampled from the frame buffer.
let frame_buffer = make_pooled_frame_buffer();
// Both our frame buffer and resolve buffer need to start out
// in the same state, so copy our existing texture to the freshly
// allocated frame buffer. We cannot use `copy_texture_to_texture`,
// since the sample counts are different.
run_copy_pipeline(
descriptors,
format,
format,
size,
frame_buffer.texture.view(),
&texture.create_view(&Default::default()),
get_whole_frame_bind_group(&whole_frame_bind_group, descriptors, size),
&globals,
sample_count,
encoder,
);
(
frame_buffer,
Some(ResolveBuffer::new_manual(texture.clone())),
)
} else {
// If multisampling is disabled, we don't need a resolve buffer.
// We can just use our existing texture as the frame buffer.
(FrameBuffer::new_manual(texture.clone(), size), None)
}
}
}; };
if let RenderTargetMode::ExistingTexture(texture) = &render_target_mode {
if sample_count > 1 {
// Both our frame buffer and resolve buffer need to start out
// in the same state, so copy our existing texture to the freshly
// allocated frame buffer. We cannot use `copy_texture_to_texture`,
// since the sample counts are different.
run_copy_pipeline(
descriptors,
format,
format,
size,
frame_buffer.texture.view(),
&texture.create_view(&Default::default()),
get_whole_frame_bind_group(&whole_frame_bind_group, descriptors, size),
&globals,
sample_count,
encoder,
);
} else {
encoder.copy_texture_to_texture(
texture.as_image_copy(),
frame_buffer.texture().as_image_copy(),
size,
);
}
}
Self { Self {
frame_buffer, frame_buffer,
blend_buffer: OnceCell::new(), blend_buffer: OnceCell::new(),