WebGPU03-Begin-The Surface,成功实现!
Begin | WebGPU
2.The Surface-2
译者注:本节内容紧随上节,建议在学习本节时打开对应的Github代码库进行对比学习。
LearnWgpu项目代码库
-
resize():尺寸
如果我们想要调整应用程序的窗口尺寸,我们得在每次修改窗口时重新配置
surfcae
域。这便是我们存储物理
size(尺寸)
与 用于配置surface
的config(配置)
的原因(译者:?)总之,resize方法十分简单:
// impl State pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize
) { if new_size.width > 0 && new_size.height > 0 { self.size = new_size; self.config.width = new_size.width; self.config.height = new_size.height; self.surface.configure(&self.device, &self.config); } } 上述代码与首先的
surface
配置并无区别,因此我们不必再过多赘述。我们在事件循环中的 main() 中调用此方法,以处理以下事件:
match event { // ... } if window_id == window.id() => if !state.input(event) { match event { // ... WindowEvent::Resized(physical_size) => { state.resize(*physical_size); } WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { // new_inner_size is &&mut so we have to dereference it twice state.resize(**new_inner_size); } // ... }
-
input():输入
imput()
返回bool
类型来表明一个事件是否已被完全处理。如果此方法返回了
true
,那么主循环将不再处理此事件。目前我们只考虑返回
false
,因为我们尚未有需要捕获的事件:// impl State fn input(&mut self, event: &WindowEvent) -> bool { false }
我们需要做一些小工作在此循环事件,我们想要让
State
的优先级大于main()
。因此你的循环应该看起来像这样:
//run()中 event_loop.run(move |event, _, control_flow| { match event { Event::WindowEvent { ref event, window_id, } if window_id == window.id() => if !state.input(event) { // UPDATED! match event { WindowEvent::CloseRequested | WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, virtual_keycode: Some(VirtualKeyCode::Escape), .. }, .. } => *control_flow = ControlFlow::Exit, WindowEvent::Resized(physical_size) => { state.resize(*physical_size); } WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { state.resize(**new_inner_size); } _ => {} } } _ => {} } });
-
update():更新
目前我们尚未有任何东西需要更新,所以暂且留下这个空方法:
fn update(&mut self) { // remove `todo!()` }
之后我们会在其中添加一些代码来使目标得以活动。
-
render():渲染
接下来便是见证奇迹的时刻!首先我们需要搭建一个框架以开始渲染:
// impl State fn render(&mut self) -> Result<(), wgpu::SurfaceError> { let output = self.surface.get_current_texture()?;
get_current_texture
函数将等待surface
提供一个新的SurfaceTexture
以供我们进行渲染。我们待会儿会把这些存储进output
。let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
这行代码创建了一个采用默认设定的
TextureView
。这样做是因为我们想要控制渲染代码与纹理的交互方式。
同时我们也需要创建一个
CommandEncoder
来创建实际命令(actual command)并将其发送至显卡(gpu)。大多数现代图形框架(modern graphics framework)期望命令在被发送向显卡(gpu)前被存储进命令缓冲区(command buffer)中。
encoder(编码器)
搭建了一个我们可以发送向显卡(gpu)的命令缓冲区(command buffer):let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder"), });
现在我们可以实际开始清除屏幕了(终章到来)。
我们要用
encoder
创建一个RenderPass(渲染通道)
,RenderPass
有着关于绘图的所有方法。创建
RenderPass
的代码有点嵌套,所以在讨论它的各个部分前,我会复制全部代码。{ let _render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Render Pass"), color_attachments: &[wgpu::RenderPassColorAttachment { view: &view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.1, g: 0.2, b: 0.3, a: 1.0, }), store: true, }, }], depth_stencil_attachment: None, }); } // submit will accept anything that implements IntoIter self.queue.submit(std::iter::once(encoder.finish())); output.present(); Ok(()) }
首先,让我们来谈谈在
encoder.begin_render_pass(...)
周围的额外大括号({}
)。begin_render_pass()
向encoder
进行了可变的借用(又名&mut self
)。我们将不能再访问encoder.finish()
直到我们释放此可变借用。此大括号({})让 rust 在代码离开该范围时,删除其中的所以变量,从而释放在
encoder
上的可变借用,使得我们能够finish()
它。如果你不喜欢
{}
,你也可以用drop(render_pass)
来达到同样的效果。我们可以通过删除
{}
和let _render_pass =
行来获得相同的结果,但我们需要在下一个教程中访问_render_pass
,所以让代码保持原样。代码的最后几行告诉
wgpu
完成命令缓冲区,并将其提交至显卡(gpu)的渲染队列。我们需要再次更新事件循环(event_loop)才能调用该方法。 我们也会在它之前调用
update
。//run()中 event_loop.run(move |event, _, control_flow| { match event { // ... Event::RedrawRequested(window_id) if window_id == window.id() => { state.update(); match state.render() { Ok(_) => {} // Reconfigure the surface if lost Err(wgpu::SurfaceError::Lost) => state.resize(state.size), // The system is out of memory, we should probably quit Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, // All other errors (Outdated, Timeout) should be resolved by the next frame Err(e) => eprintln!("{:?}", e), } } Event::MainEventsCleared => { // RedrawRequested will only trigger once, unless we manually // request it. window.request_redraw(); } // ... } });
运行
main.rs
中的代码,你应该会看到如下的窗口:恭喜你,学习Wgpu的道路正式开启!
该博客由本人个人翻译自Learn Wgpu,因此可能有部分文本不易理解或出现错误,如有发现还望告知,本人必定及时更改。