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
Scope guard types #3
Comments
Do I understand correctly that the problem is that the I think I like the general approach much better than the current macro based solution since it's easier to reason about the lifetimes. Can you clarify a few things for me? I'm not sure I follow yet in the details:
/// Profiler scope that takes a mutable reference to the wgpu pass/encoder
pub fn scope<Recorder, F>(&mut self, label: &str, encoder_or_pass: &mut Recorder, device: &wgpu::Device, func: F)
where
F: FnOnce(&mut Self, &mut Recorder, &wgpu::Device),
Recorder: ProfilerCommandRecorder,
{
self.begin_scope(label, encoder_or_pass, device);
func(self, encoder_or_pass, device);
self.end_scope(encoder_or_pass);
}
/// Profiler scope that takes ownership of the wgpu pass/encoder
pub fn scope_owning<Recorder, F>(&mut self, label: &str, mut encoder_or_pass: Recorder, device: &wgpu::Device, func: F)
where
F: FnOnce(&mut Self, Recorder, &wgpu::Device) -> Recorder,
Recorder: ProfilerCommandRecorder,
{
self.begin_scope(label, &mut encoder_or_pass, device);
let mut encoder_or_pass = func(self, encoder_or_pass, device);
self.end_scope(&mut encoder_or_pass);
} .. and if so would that be better or worse than your suggestion? (I'm honestly not sure, but before we discuss that I'd like to know what that's missing because like I feel this isn't the full story) In any case I'd like to have something added to the crate here that solves your usecase :). Thank you very much for contributing! |
So for drawing each frame we generate this transient pub struct Drawer<'frame> {
encoder: Option<ManualOwningScope<'frame, wgpu::CommandEncoder>>,
borrow: RendererBorrow<'frame>,
swap_tex: wgpu::SwapChainTexture,
globals: &'frame GlobalsBindGroup,
} Then there are specific method on this that e.g. starts a render pass which takes a mutable borrow of the pub fn first_pass(&mut self) -> FirstPassDrawer {
let encoder = self.encoder.as_mut().unwrap();
let device = self.borrow.device;
let mut render_pass =
encoder.scoped_render_pass(device, "first_pass", &wgpu::RenderPassDescriptor {
label: Some("first pass"),
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: &self.borrow.views.tgt_color,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: true,
},
}],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor {
attachment: &self.borrow.views.tgt_depth,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(0.0),
store: true,
}),
stencil_ops: None,
}),
});
render_pass.set_bind_group(0, &self.globals.bind_group, &[]);
render_pass.set_bind_group(1, &self.borrow.shadow.bind.bind_group, &[]);
FirstPassDrawer {
render_pass,
borrow: &self.borrow,
globals: self.globals,
}
} this then has methods on it that can be used to enter a certain pipeline state continuing the borrow chain pub fn draw_terrain<'data: 'pass>(
&mut self,
col_lights: &'data ColLights<terrain::Locals>,
) -> TerrainDrawer<'_, 'pass> {
let mut render_pass = self.render_pass.scope(self.borrow.device, "terrain");
render_pass.set_pipeline(&self.borrow.pipelines.terrain.pipeline);
render_pass.set_bind_group(3, &col_lights.bind_group, &[]);
TerrainDrawer { render_pass }
} and this finally has a method for actually drawing something pub struct TerrainDrawer<'pass_ref, 'pass: 'pass_ref> {
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
}
impl<'pass_ref, 'pass: 'pass_ref> TerrainDrawer<'pass_ref, 'pass> {
pub fn draw<'data: 'pass>(
&mut self,
model: &'data Model<terrain::Vertex>,
locals: &'data terrain::BoundLocals,
) {
self.render_pass.set_bind_group(2, &locals.bind_group, &[]);
self.render_pass.set_vertex_buffer(0, model.buf().slice(..));
self.render_pass.draw(0..model.len() as u32, 0..1)
}
} finally impl<'frame> Drop for Drawer<'frame> {
fn drop(&mut self) {
let (mut encoder, profiler) = self.encoder.take().unwrap().end_scope();
profiler.resolve_queries(&mut encoder);
self.borrow.queue.submit(std::iter::once(encoder.finish()));
profiler
.end_frame()
.expect("Gpu profiler error! Maybe there was an unclosed scope?");
}
} |
The code that uses this looks like: span!(_guard, "render", "<Session as PlayState>::render");
let mut drawer = match renderer
.start_recording_frame(self.scene.global_bind_group())
.unwrap()
{
Some(d) => d,
// Couldn't get swap chain texture this frame
None => return,
};
// Render world
{
let client = self.client.borrow();
let scene_data = SceneData {
/* snip */
};
self.scene.render(
&mut drawer,
client.state(),
client.entity(),
client.get_tick(),
&scene_data,
);
}
// Clouds
drawer.second_pass().draw_clouds();
// PostProcess and UI
let mut third_pass = drawer.third_pass();
third_pass.draw_post_process();
// Draw the UI to the screen
self.hud.render(&mut third_pass.draw_ui());
} /// Render the scene using the provided `Drawer`.
pub fn render<'a>(
&'a self,
drawer: &mut Drawer<'a>,
state: &State,
player_entity: EcsEntity,
tick: u64,
scene_data: &SceneData,
) {
span!(_guard, "render", "Scene::render");
let sun_dir = scene_data.get_sun_dir();
let is_daylight = sun_dir.z < 0.0;
let focus_pos = self.camera.get_focus_pos();
let cam_pos = self.camera.dependents().cam_pos + focus_pos.map(|e| e.trunc());
let camera_data = (&self.camera, scene_data.figure_lod_render_distance);
// would instead have this as an extension.
if drawer.render_mode().shadow.is_map() && (is_daylight || !self.light_data.is_empty()) {
if is_daylight {
if let Some(mut shadow_pass) = drawer.shadow_pass() {
// Render terrain directed shadows.
self.terrain
.render_shadows(&mut shadow_pass.draw_terrain_shadows(), focus_pos);
// Render figure directed shadows.
self.figure_mgr.render_shadows(
&mut shadow_pass.draw_figure_shadows(),
state,
tick,
camera_data,
);
}
}
// Render terrain point light shadows.
drawer.draw_point_shadows(
&self.data.point_light_matrices,
self.terrain.chunks_for_point_shadows(focus_pos),
)
}
let mut first_pass = drawer.first_pass();
self.figure_mgr.render_player(
&mut first_pass.draw_figures(),
state,
player_entity,
tick,
camera_data,
);
self.terrain.render(&mut first_pass, focus_pos);
self.figure_mgr.render(
&mut first_pass.draw_figures(),
state,
player_entity,
tick,
camera_data,
);
self.lod.render(&mut first_pass);
// Render the skybox.
first_pass.draw_skybox(&self.skybox.model);
// Draws translucent terrain and sprites
self.terrain.render_translucent(
&mut first_pass,
focus_pos,
cam_pos,
scene_data.sprite_render_distance,
);
// Render particle effects.
self.particle_mgr
.render(&mut first_pass.draw_particles(), scene_data);
} |
Yep, once the returned value goes out of scope/is dropped rust knows we can access the original value that was borrowed mutably here again. The explicit lifetime actually isn't needed for this though since the lifetime ellision rules will already do this except that the
It could be done manually, however it does add convenience with the helper methods (e.g. not having to pass in the profiler everywhere) and prevents forgetting to end the scope (since you need to get the
I think this would have the same issue as the macro does with the setup I outlined above. |
thanks for illustrating all this in such a great detail. Yeah I can see now that the scope method variant won't do it here (or at least it's not obvious how that would work out) Would you mind PRing your initial suggestion? Just put it into a separate file ( |
I would be hesitant to remove the macro as I suspect it probably fits some use cases nicely, but not mine so I don't have any proof of that 🙂. edit: maybe the best way to test is to remove it and wait for requests to add it back |
Might be a few days FYI |
The way the project I work on handles state transitions between e.g. pipelines while drawing with
wgpu
it isn't feasible to put things inside a scope macro. So I made the scope types (see below) which wrap theCommandEncoder
/RenderPass
and utilizeDrop
. I was wondering if something like these would be useful in this crate or make more sense in a separate crate or simply created by users as needed?The text was updated successfully, but these errors were encountered: