WebGPU02-Begin-The Surface,那是什么?
Begin | WebGPU
2.The Surface-1
译者注:本节及下节中的所有代码根据本人亲自尝试,应当放入 lib.rs 文件而非 main.rs,建议在学习本节时打开对应的Github代码库进行对比学习。
LearnWgpu项目代码库
-
首先,做好准备工作
方便起见,我们将把所有域(field)包入一个结构体(struct)内并在其中创建一些方法。
//lib.rs use winit::window::Window; struct State { surface: wgpu::Surface, device: wgpu::Device, queue: wgpu::Queue, config: wgpu::SurfaceConfiguration, size: winit::dpi::PhysicalSize
, } impl State { // Creating some of the wgpu types requires async code async fn new(window: &Window) -> Self { todo!() } fn resize(&mut self, new_size: winit::dpi::PhysicalSize ) { todo!() } fn input(&mut self, event: &WindowEvent) -> bool { todo!() } fn update(&mut self) { todo!() } fn render(&mut self) -> Result<(), wgpu::SurfaceError> { todo!() } } 接下来暂且不管(glossing over)
State
域(field),但要知道在我讲述以上代码背后的方法时,它们也很重要!译者注:本节之后的操作均在上方代码中的
new()
函数中进行,注意todo!()
即为之后操作的代码存放处! -
State::new()
以下代码比较简单,但我们仍要将它分解以便于理解。
impl State { // ... async fn new(window: &Window) -> Self { let size = window.inner_size(); // The instance is a handle to our GPU // Backends::all => Vulkan + Metal + DX12 + Browser WebGPU let instance = wgpu::Instance::new(wgpu::Backends::all()); let surface = unsafe { instance.create_surface(window) }; let adapter = instance.request_adapter( &wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::default(), compatible_surface: Some(&surface), force_fallback_adapter: false, }, ).await.unwrap();
-
Instance and Adapter
在使用 wgpu 时首先创建的是
instance(实例)
,它的主要目的是创建Adapter(适配器)
和Surface(界面)
。Adapter(适配器)
对我们实际存在的图形卡来说是一个句柄。你可以用它来获得关于图形卡的信息例如它的名字和它的后端使用了什么适配器(adapter)。我们将用它创建我们的Device
和Queue
。接下来我们将仔细剖解
RequestAdapterOptions
中的域(field)-
power_preference
此域有两种变体:
LowPower(低功耗)
和HighPerformance(高性能)
。这使得 wgpu 在使用过程中:
使用集成显卡时
LowPower
部分将会选择适合电池寿命的的适配器(adapter)使用独立显卡时
HighPerformance
部分将会选择更大功耗的适配器(adapter)当没有适于
HighPerformance
的适配器(adapter)时,WGPU将选用LowPower
。 -
compatible_surface
此域让 wgpu 找到 能够找到支持界面(supplied surface) 的适配器(adapter)。
-
force_fallback_adapter
此域让 wgpu 找到 能在所有硬件上正常运行 wpgu(work on all hardware) 的适配器(adapter)。
这使得 wgpu 在使用过程中:
将在后端中使用软件系统,而非硬件系统
译者理解:也就是说使开发者不必考虑硬件相关的问题
-
Passed Part
此处不译,与特殊情况相关,可以考虑查看。
The options I've passed to
request_adapter
aren't guaranteed to work for all devices, but will work for most of them. If wgpu can't find an adapter with the required permissions,request_adapter
will returnNone
. If you want to get all adapters for a particular backend you can useenumerate_adapters
. This will give you an iterator that you can loop over to check if one of the adapters works for your needs.let adapter = instance .enumerate_adapters(wgpu::Backends::all()) .filter(|adapter| { // Check if this adapter supports our surface surface.get_preferred_format(&adapter).is_some() }) .next() .unwrap() Copied!
Another thing to note is that
Adapter
s are locked to a specific backend. If you are on Windows and have 2 graphics cards you'll have at least 4 adapters available to use, 2 Vulkan and 2 DirectX.For more fields you can use to refine your search check out the docs (opens new window).
-
-
The Surface
surface(界面)
是我们在绘制窗口中的一部分。我们需要使用它来在屏幕上直接地绘图。
我们的
window(窗口)
需要实施 raw-window-handle 中的HasRawWindowHandle
接口来创建界面(surface)。幸运的是,winit 中的Window(窗口)
填补了这一空缺。我们也需要它(surface)来访问我们的
adapter(适配器)
。 -
Device and Queue
接下来让我们用
adapter(适配器)
来创建设备(device)和队列(queue)。let (device, queue) = adapter.request_device( &wgpu::DeviceDescriptor { features: wgpu::Features::empty(), // WebGL doesn't support all of wgpu's features, so if // we're building for the web we'll have to disable some. limits: if cfg!(target_arch = "wasm32") { wgpu::Limits::downlevel_webgl2_defaults() } else { wgpu::Limits::default() }, label: None, }, None, // Trace path ).await.unwrap();
在
DeviceDescriptor
上的feature(特性)
域,允许我们指定我们想使用的额外特性。在这个简单的例子里,我选择不使用任何额外特性。此处不译,与延申内容相关,可以考虑查看
The graphics card you have limits the features you can use. If you want to use certain features you may need to limit what devices you support, or provide workarounds.
You can get a list of features supported by your device using
adapter.features()
, ordevice.features()
.You can view a full list of features here (opens new window).
limits(限制)
域描述了 我们可以创建的通常类型 的限制,我们在本向导中使用默认配置,以便于支持大部分设备。你可以查看限制名单。let config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: surface.get_preferred_format(&adapter).unwrap(), width: size.width, height: size.height, present_mode: wgpu::PresentMode::Fifo, }; surface.configure(&device, &config);
此处我们在为我们的界面定义配置,这将定义界面怎样创建它潜在的
SurfaceTexture(界面组成)
。等我们讲至
render
函数时,我们再讨论SurfaceTexture
。现在让我们来剖析此配置的域
-
usage
此域描述了
SurfaceTexture
将被怎样使用。RENDER_ATTACHMENT
提起这些组成(texture)将被用于写向窗口(我们会在之后探讨更多TextureUsage
) -
format
此域定义了
SurfaceTexture
将怎样被存储入显卡(GPU)。不同的显示器(display)有着不同的格式(format)。
我们使用
surface.get_preferred_format(&adapter)
基于你使用的显示器(display)找出最好的格式(format)。 -
width
&height
此两域是以像素格式表示的
SurfaceTexture
的高度和宽度。它们一般作为窗口(window)的高度与宽度
注:Make sure that the width and height of the
SurfaceTexture
are not 0, as that can cause your app to crash. -
present_mode
此域使用
wgpu::PresentMode
枚举,这定义了如何将界面与你的显示器同步。我们选择的选项
FIFO(先进先出)
,将用显示帧率(displays framerate)覆盖 显示率(display rate)。这便是必要的垂直同步(VSync),在手机上也是最佳的显示模式。
这里还有其他的选项,你可以在文档中看到全部
现在我们已经正确地配置好了我们的界面(surface),我们可以在方法(method)的最后添加新的域(field)。
Self { surface, device, queue, config, size, } } // ... }
因为我们的
State::new()
方法是异步的,我们需要更改run()
为异步,以便我们可以等待它。//run()z pub async fn run() { // Window setup... let mut state = State::new(&window).await; // Event loop... }
现在
run()
是异步的了,main()
将需要某种方式来等待结果。我们可以使用一些包(crate)比如 tokio 或 async-std,但我将要使用更加轻量级的 pollster。
添加如下代码到你的
Cargo.toml
文件://cargo.toml [dependencies] # other deps... pollster = "0.2"
接下来使用由pollster包提供的
block_on
函数来等待我们的结果://main.rs fn main() { pollster::block_on(run()); }
注:Don't use
block_on
inside of an async function if you plan to support WASM. Futures have to be run using the browsers executor. If you try to bring your own you code will crash when you encounter a future that doesn't execute immediately.此处不译,与Web中的应用相关,暂不必查看:
If we try to build WASM now it will fail because
wasm-bindgen
doesn't support using async functions asstart
methods. You could switch to callingrun
manually in javascript, but for simplicity we'll add the wasm-bindgen-futures (opens new window)crate to our WASM dependencies as that doesn't require us to change any code. Your dependecies should look something like this:[dependencies] cfg-if = "1" winit = "0.26" env_logger = "0.9" log = "0.4" wgpu = "0.12" pollster = "0.2" [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = "0.1.6" console_log = "0.2.0" wgpu = { version = "0.12", features = ["webgl"]} wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" web-sys = { version = "0.3", features = [ "Document", "Window", "Element", ]}
-
-
该博客由本人个人翻译自[Learn Wgpu】(https://sotrh.github.io/learn-wgpu/),因此可能有部分文本不易理解或出现错误,如有发现还望告知,本人必定及时更改。