Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Fix wasm examples #4967

Closed
wants to merge 9 commits into from
1 change: 1 addition & 0 deletions crates/bevy_core_pipeline/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ keywords = ["bevy"]

[features]
trace = []
webgl = []

[dependencies]
# bevy
Expand Down
77 changes: 51 additions & 26 deletions crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use bevy_render::{
renderer::RenderContext,
view::{ExtractedView, ViewTarget},
};
#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;

pub struct MainPass2dNode {
query: QueryState<
Expand Down Expand Up @@ -57,37 +59,60 @@ impl Node for MainPass2dNode {
// no target
return Ok(());
};
{
#[cfg(feature = "trace")]
let _main_pass_2d = info_span!("main_pass_2d").entered();
let pass_descriptor = RenderPassDescriptor {
label: Some("main_pass_2d"),
color_attachments: &[target.get_color_attachment(Operations {
load: match camera_2d.clear_color {
ClearColorConfig::Default => {
LoadOp::Clear(world.resource::<ClearColor>().0.into())
}
ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()),
ClearColorConfig::None => LoadOp::Load,
},
store: true,
})],
depth_stencil_attachment: None,
};

let pass_descriptor = RenderPassDescriptor {
label: Some("main_pass_2d"),
color_attachments: &[target.get_color_attachment(Operations {
load: match camera_2d.clear_color {
ClearColorConfig::Default => {
LoadOp::Clear(world.resource::<ClearColor>().0.into())
}
ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()),
ClearColorConfig::None => LoadOp::Load,
},
store: true,
})],
depth_stencil_attachment: None,
};

let draw_functions = world.resource::<DrawFunctions<Transparent2d>>();
let draw_functions = world.resource::<DrawFunctions<Transparent2d>>();

let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);

let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
if let Some(viewport) = camera.viewport.as_ref() {
tracked_pass.set_camera_viewport(viewport);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
if let Some(viewport) = camera.viewport.as_ref() {
tracked_pass.set_camera_viewport(viewport);
}
for item in &transparent_phase.items {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
}
}
for item in &transparent_phase.items {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);

// If there was a camera with a viewport, finish with a renderpass without a custom viewport in webgl
#[cfg(feature = "webgl")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its worth directly calling out the behavior quirks of WebGL explicitly, to better justify this code to future readers.
On that note, I'm still a little unclear about what the behavior actually is. Is it just "the next renderpass will still have the viewport set, but every pass after that will be fine"?

Should we open a wgpu bug?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what it looks like without that extra render pass
Screenshot 2022-06-09 at 01 23 06

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried making the comment more explicit. I don't know if it's a wgpu bug or how WebGL2 works, but I found a lot of people trying to reset viewport when using OpenGL

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Its a bit unfortunate because this means users developing things like custom post-processing effects will need to manually hard-code the reset if they want to support wasm viewports. But thats not something we can reasonably fix in this pr.

if camera.viewport.is_some() {
#[cfg(feature = "trace")]
let _reset_viewport_pass_2d = info_span!("reset_viewport_pass_2d").entered();
let pass_descriptor = RenderPassDescriptor {
label: Some("reset_viewport_pass_2d"),
color_attachments: &[target.get_color_attachment(Operations {
load: LoadOp::Load,
store: true,
})],
depth_stencil_attachment: None,
};

render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
}

Ok(())
}
}
19 changes: 19 additions & 0 deletions crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,25 @@ impl Node for MainPass3dNode {
}
}

// If there was a camera with a viewport, finish with a renderpass without a custom viewport in webgl2
#[cfg(feature = "webgl")]
if camera.viewport.is_some() {
#[cfg(feature = "trace")]
let _main_transparent_pass_3d_span = info_span!("reset_viewport_pass_3d").entered();
let pass_descriptor = RenderPassDescriptor {
label: Some("reset_viewport_pass_3d"),
color_attachments: &[target.get_color_attachment(Operations {
load: LoadOp::Load,
store: true,
})],
depth_stencil_attachment: None,
};

render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
}

Ok(())
}
}
2 changes: 1 addition & 1 deletion crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ x11 = ["bevy_winit/x11"]
subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"]

# Optimise for WebGL2
webgl = ["bevy_pbr/webgl", "bevy_render/webgl"]
webgl = ["bevy_core_pipeline/webgl", "bevy_pbr/webgl", "bevy_render/webgl"]

# enable systems that allow for automated testing on CI
bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render/ci_limits"]
Expand Down
12 changes: 0 additions & 12 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ pub struct ShadowPipeline {
pub skinned_mesh_layout: BindGroupLayout,
pub point_light_sampler: Sampler,
pub directional_light_sampler: Sampler,
pub clustered_forward_buffer_binding_type: BufferBindingType,
}

// TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system
Expand All @@ -221,9 +220,6 @@ impl FromWorld for ShadowPipeline {
let world = world.cell();
let render_device = world.resource::<RenderDevice>();

let clustered_forward_buffer_binding_type = render_device
.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT);

let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
// View
Expand Down Expand Up @@ -268,7 +264,6 @@ impl FromWorld for ShadowPipeline {
compare: Some(CompareFunction::GreaterEqual),
..Default::default()
}),
clustered_forward_buffer_binding_type,
}
}
}
Expand Down Expand Up @@ -330,13 +325,6 @@ impl SpecializedMeshPipeline for ShadowPipeline {
bind_group_layout.push(self.mesh_layout.clone());
}

if !matches!(
self.clustered_forward_buffer_binding_type,
BufferBindingType::Storage { .. }
) {
shader_defs.push(String::from("NO_STORAGE_BUFFERS_SUPPORT"));
}

let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;

Ok(RenderPipelineDescriptor {
Expand Down
15 changes: 0 additions & 15 deletions crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,18 +573,6 @@ impl SpecializedMeshPipeline for MeshPipeline {
vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(4));
}

// TODO: consider exposing this in shaders in a more generally useful way, such as:
// # if AVAILABLE_STORAGE_BUFFER_BINDINGS == 3
// /* use storage buffers here */
// # elif
// /* use uniforms here */
if !matches!(
self.clustered_forward_buffer_binding_type,
BufferBindingType::Storage { .. }
) {
shader_defs.push(String::from("NO_STORAGE_BUFFERS_SUPPORT"));
}

let mut bind_group_layout = vec![self.view_layout.clone()];
if layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX)
&& layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT)
Expand Down Expand Up @@ -615,9 +603,6 @@ impl SpecializedMeshPipeline for MeshPipeline {
depth_write_enabled = true;
}

#[cfg(feature = "webgl")]
shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT"));

Ok(RenderPipelineDescriptor {
vertex: VertexState {
shader: MESH_SHADER_HANDLE.typed::<Shader>(),
Expand Down
24 changes: 22 additions & 2 deletions crates/bevy_render/src/render_resource/pipeline_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use bevy_ecs::system::{Res, ResMut};
use bevy_utils::{default, tracing::error, Entry, HashMap, HashSet};
use std::{hash::Hash, mem, ops::Deref, sync::Arc};
use thiserror::Error;
use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout as RawVertexBufferLayout};
use wgpu::{
BufferBindingType, PipelineLayoutDescriptor, ShaderModule,
VertexBufferLayout as RawVertexBufferLayout,
};

enum PipelineDescriptor {
RenderPipelineDescriptor(Box<RenderPipelineDescriptor>),
Expand Down Expand Up @@ -117,9 +120,26 @@ impl ShaderCache {
let module = match data.processed_shaders.entry(shader_defs.to_vec()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let mut shader_defs = shader_defs.to_vec();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious what @superdump thinks about this one. I think I'm on board for having a suite of "low level feature detection shader defs" that exist everywhere, but this is a big enough change to merit a discussion of its own.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah... I fixed a similar issue in #4949 and wanted to try a way that wouldn't need to fix it for other pipelines one by one

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think centralising these kinds of ‘global’ shader defs based on detected features/limits makes a lot of sense. They aren’t different in different renderers or pipelines and they are useful to be able to use without core changes / user code reimplementation, in my opinion. And while specialisation has different roots, the shader cache is centralised. Good idea. This is my initial 0730 after a night out take. :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

best kind of takes!

#[cfg(feature = "webgl")]
shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT"));

// TODO: 3 is the value from CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT declared in bevy_pbr
// consider exposing this in shaders in a more generally useful way, such as:
// # if AVAILABLE_STORAGE_BUFFER_BINDINGS == 3
// /* use storage buffers here */
// # elif
// /* use uniforms here */
if !matches!(
render_device.get_supported_read_only_binding_type(3),
BufferBindingType::Storage { .. }
) {
shader_defs.push(String::from("NO_STORAGE_BUFFERS_SUPPORT"));
}

let processed = self.processor.process(
shader,
shader_defs,
&shader_defs,
&self.shaders,
&self.import_path_shaders,
)?;
Expand Down
3 changes: 0 additions & 3 deletions crates/bevy_sprite/src/mesh2d/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,6 @@ impl SpecializedMeshPipeline for Mesh2dPipeline {
vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(4));
}

#[cfg(feature = "webgl")]
shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT"));

let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;

Ok(RenderPipelineDescriptor {
Expand Down
39 changes: 18 additions & 21 deletions examples/3d/split_screen.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
//! Renders two cameras to the same window to accomplish "split screen".

use bevy::{
core_pipeline::clear_color::ClearColorConfig,
prelude::*,
render::camera::Viewport,
window::{WindowId, WindowResized},
core_pipeline::clear_color::ClearColorConfig, prelude::*, render::camera::Viewport,
window::WindowResized,
};

fn main() {
Expand Down Expand Up @@ -83,26 +81,25 @@ fn set_camera_viewports(
mut resize_events: EventReader<WindowResized>,
mut left_camera: Query<&mut Camera, (With<LeftCamera>, Without<RightCamera>)>,
mut right_camera: Query<&mut Camera, With<RightCamera>>,
mut setup_done: Local<bool>,
) {
// We need to dynamically resize the camera's viewports whenever the window size changes
// so then each camera always takes up half the screen.
// A resize_event is sent when the window is first created, allowing us to reuse this system for initial setup.
for resize_event in resize_events.iter() {
if resize_event.id == WindowId::primary() {
let window = windows.primary();
let mut left_camera = left_camera.single_mut();
left_camera.viewport = Some(Viewport {
physical_position: UVec2::new(0, 0),
physical_size: UVec2::new(window.physical_width() / 2, window.physical_height()),
..default()
});
if resize_events.iter().next().is_some() || !*setup_done {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, maybe we should send an initial WindowResized event on platforms that don't do that automatically? That way userspace doesn't need this complexity and we close a "bevy behavior consistency gap"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can only test on macOS and wasm, where this event isn't sent. For me reading it in that comment was a first discovery 😄
Could someone confirm for Windows, X11 and Wayland?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm now sending the event on window creation. it shouldn't be an issue anyway if there are two events instead of one on some platforms

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I can test those platforms if nobody else beats me to it :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

X11 has a duplicate event now (and it doesn't on main). Gonna try wayland next (both xwayland and native wayland).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xwayland also has the duplicate event. native wayland is unclear because it crashed on startup for me (the default swap chain format wasnt compatible ... i queried the surface for the preferred format, but even after setting that, the device times out). Nvidia proprietary drivers + wayland are notoriously bad.

Windows also now has a duplicate event. (it actually produces 3 resize events on main, one with a higher dpi (2x), and two with the "actual" resolution). With this PR it produces 4 resize events.

So its safe to say that xwayland, x11, and windows don't need the extra event. Native wayland is unclear. I think we'll need someone else to test that. But "native wayland" is not the default behavior, and some platforms already produce duplicate events, so I don't think adding a potential duplicate to native wayland is particularly risky.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a gate for windows and x11, not sure how to gate for xwayland but not native wayland.

Anyway as you mention, this kind of event should not be expected to be unique so it should be OK

let window = windows.primary();
let mut left_camera = left_camera.single_mut();
left_camera.viewport = Some(Viewport {
physical_position: UVec2::new(0, 0),
physical_size: UVec2::new(window.physical_width() / 2, window.physical_height()),
..default()
});

let mut right_camera = right_camera.single_mut();
right_camera.viewport = Some(Viewport {
physical_position: UVec2::new(window.physical_width() / 2, 0),
physical_size: UVec2::new(window.physical_width() / 2, window.physical_height()),
..default()
});
}
let mut right_camera = right_camera.single_mut();
right_camera.viewport = Some(Viewport {
physical_position: UVec2::new(window.physical_width() / 2, 0),
physical_size: UVec2::new(window.physical_width() / 2, window.physical_height()),
..default()
});
*setup_done = true;
}
}