WebGPU03-Begin-The Surface,成功实现!


Begin | WebGPU

2.The Surface-2

译者注:本节内容紧随上节,建议在学习本节时打开对应的Github代码库进行对比学习。

LearnWgpu项目代码库

  • resize():尺寸

    如果我们想要调整应用程序的窗口尺寸,我们得在每次修改窗口时重新配置surfcae域。

    这便是我们存储物理size(尺寸)与 用于配置surfaceconfig(配置)的原因(译者:?)

    总之,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,因此可能有部分文本不易理解或出现错误,如有发现还望告知,本人必定及时更改。