Первый треугольник
rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::sync::Arc;
use tokio::runtime;
use tokio::runtime::Runtime;
use wgpu::{
Backends, BlendComponent, BlendState, Color, ColorTargetState, ColorWrites,
CommandEncoderDescriptor, CompositeAlphaMode, Device, DeviceDescriptor, ExperimentalFeatures,
Features, FragmentState, FrontFace, Instance, InstanceDescriptor, Limits, LoadOp, MemoryHints,
MultisampleState, Operations, PipelineCompilationOptions, PolygonMode, PowerPreference,
PresentMode, PrimitiveState, PrimitiveTopology, Queue, RenderPassColorAttachment,
RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, RequestAdapterOptions, StoreOp,
Surface, SurfaceConfiguration, SurfaceError, TextureFormat, TextureUsages,
TextureViewDescriptor, VertexState, include_wgsl,
};
use winit::application::ApplicationHandler;
use winit::dpi::PhysicalSize;
use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::keyboard::{KeyCode, PhysicalKey};
use winit::window::{Window, WindowAttributes, WindowId};
enum App {
Loading,
Ready {
window: Arc<Window>,
renderer: Box<Renderer>,
need_to_resize_surface: bool,
},
}
struct Renderer {
device: Device,
queue: Queue,
surface: Surface<'static>,
surface_config: SurfaceConfiguration,
surface_format: TextureFormat,
pipeline: Option<RenderPipeline>,
}
impl Renderer {
fn new(window: Arc<Window>, runtime: Arc<Runtime>) -> Self {
let mut physical_size = window.inner_size();
physical_size.width = physical_size.width.max(1);
physical_size.height = physical_size.height.max(1);
let instance = Instance::new(&InstanceDescriptor {
backends: Backends::PRIMARY,
..Default::default()
});
let surface = instance
.create_surface(window)
.expect("Failed to create surface");
let adapter = runtime.block_on(async {
instance
.request_adapter(&RequestAdapterOptions {
power_preference: PowerPreference::default(),
force_fallback_adapter: false,
compatible_surface: Some(&surface),
})
.await
.expect("Failed to request adapter")
});
let (device, queue) = runtime.block_on(async {
adapter
.request_device(&DeviceDescriptor {
label: Some("Main device"),
required_features: adapter.features() & Features::default(),
required_limits: Limits::default().using_resolution(adapter.limits()),
memory_hints: MemoryHints::Performance,
trace: Default::default(),
experimental_features: ExperimentalFeatures::disabled(),
})
.await
.expect("Failed to request device")
});
let surface_capabilities = surface.get_capabilities(&adapter);
let surface_format = surface_capabilities
.formats
.iter()
.copied()
.find(TextureFormat::is_srgb)
.or_else(|| surface_capabilities.formats.first().copied())
.expect("Failed to get surface format");
let surface_config = SurfaceConfiguration {
usage: TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: physical_size.width,
height: physical_size.height,
present_mode: PresentMode::AutoNoVsync,
desired_maximum_frame_latency: 2,
alpha_mode: CompositeAlphaMode::Auto,
view_formats: vec![],
};
let mut renderer = Self {
device,
queue,
surface,
surface_config,
surface_format,
pipeline: None,
};
renderer.resize_surface(physical_size);
let shader_module = renderer
.device
.create_shader_module(include_wgsl!("shader.wgsl"));
renderer.pipeline = Some(renderer.device.create_render_pipeline(
&RenderPipelineDescriptor {
label: Some("Main Render Pipeline"),
layout: None,
vertex: VertexState {
module: &shader_module,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: PipelineCompilationOptions::default(),
},
fragment: Some(FragmentState {
module: &shader_module,
entry_point: Some("fs_main"),
targets: &[Some(ColorTargetState {
format: renderer.surface_format,
blend: Some(BlendState {
color: BlendComponent::REPLACE,
alpha: BlendComponent::REPLACE,
}),
write_mask: ColorWrites::ALL,
})],
compilation_options: PipelineCompilationOptions::default(),
}),
primitive: PrimitiveState {
topology: PrimitiveTopology::TriangleList,
front_face: FrontFace::Ccw,
polygon_mode: PolygonMode::Fill,
..Default::default()
},
depth_stencil: None,
multisample: MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
},
));
renderer
}
fn resize_surface(&self, size: PhysicalSize<u32>) {
let width = size.width.max(1);
let height = size.height.max(1);
self.surface.configure(
&self.device,
&SurfaceConfiguration {
width,
height,
..self.surface_config.clone()
},
);
}
fn render(&mut self, window: Arc<Window>) {
match self.surface.get_current_texture() {
Ok(frame) => {
let mut encoder = self
.device
.create_command_encoder(&CommandEncoderDescriptor { label: Some("Main command encoder") });
let view = frame.texture.create_view(&TextureViewDescriptor::default());
{
let mut rpass = encoder.begin_render_pass(&RenderPassDescriptor {
label: Some("Main Render Pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: Operations {
load: LoadOp::Clear(Color::GREEN),
store: StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(self.pipeline.as_ref().expect("Failed to get pipeline"));
rpass.draw(0..3, 0..1);
}
self.queue.submit([encoder.finish()]);
window.pre_present_notify();
frame.present();
}
Err(error) => match error {
SurfaceError::OutOfMemory => {
panic!("Surface error: {error}")
}
_ => {
window.request_redraw();
}
},
};
}
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if let Self::Loading = self {
let runtime = Arc::new(
runtime::Builder::new_current_thread()
.build()
.expect("Failed to create tokio runtime"),
);
let window_attributes = WindowAttributes::default()
.with_title("WGPU Tutorial")
.with_visible(false);
let window = Arc::new(
event_loop
.create_window(window_attributes)
.expect("Failed to create window"),
);
center_window(window.clone());
event_loop.set_control_flow(ControlFlow::Wait);
let renderer = Renderer::new(window.clone(), runtime.clone());
*self = Self::Ready {
window,
renderer: Box::new(renderer),
need_to_resize_surface: false,
}
}
let Self::Ready {
window, renderer, ..
} = self
else {
return;
};
renderer.render(window.clone());
window.set_visible(true);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
let Self::Ready {
window,
renderer,
need_to_resize_surface,
..
} = self
else {
return;
};
match event {
WindowEvent::RedrawRequested => {
if *need_to_resize_surface {
let size = window.inner_size();
renderer.resize_surface(size);
*need_to_resize_surface = false;
}
renderer.render(window.clone());
window.request_redraw();
}
WindowEvent::Resized(_) => {
*need_to_resize_surface = true;
window.request_redraw();
}
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::KeyboardInput { event, .. } => handle_keyboard_input(event_loop, event),
_ => {}
}
}
}
fn main() {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
let event_loop = EventLoop::new().expect("Failed to create event loop");
let mut app = App::Loading;
event_loop
.run_app(&mut app)
.expect("Failed to run event loop");
}
fn handle_keyboard_input(event_loop: &ActiveEventLoop, event: KeyEvent) {
match (event.physical_key, event.state) {
(PhysicalKey::Code(KeyCode::Escape), ElementState::Pressed) => {
event_loop.exit();
}
_ => {}
}
}
fn center_window(window: Arc<Window>) {
if let Some(monitor) = window.current_monitor() {
let screen_size = monitor.size();
let window_size = window.outer_size();
window.set_outer_position(winit::dpi::PhysicalPosition {
x: screen_size.width.saturating_sub(window_size.width) as f64 / 2.0
+ monitor.position().x as f64,
y: screen_size.height.saturating_sub(window_size.height) as f64 / 2.0
+ monitor.position().y as f64,
});
}
}
Получаем доступные возможности нашей поверхности, которые исходят из возможностей переданного адаптера
Далее нам нужно определиться с форматом кадров, которые мы будем выводить на поверхность, то есть в окно. На данный момент мы просто можем взять поддерживаемый список форматов поверхности, попытаться выбрать формат с поддержкой SRGB
, и взять первый попавшийся, если такого не нашлось.
В реальном приложении вы, скорее всего, захотите организовать здесь более сложную логику выбора формата.
Теперь, получив подходящий формат поверхности, мы можем создать объект ее конфигурации SurfaceConfiguration
:
usage
- как мы будем использовать эту поверхность. На самом деле это параметр текстуры, поскольку отрисовку мы производим именно в нее. Но эта текстура находится в поверхности, поэтому данный параметр задается здесь. ВыбираемRENDER_ATTACHMENT
, то есть указываем, что данная текстура в поверхности будет использоваться как цель рендера, в нее будет выводиться полученная картинка.эformat
- полученный нами формат поверхностиwidth
- ширина поверхности. Передаем вычисленное ранее значениеheight
- высота поверхности. Передаем вычисленное ранее значениеpresent_mode
- режим отображения кадров на поверхности. Режимов достаточно много, и разные устройства поддерживают разные режимы. Но wgpu сильно облегчает нам здесь жизнь, предоставляя опцииAutoVsync
иAutoNoVsync
. Эти опции будут перебирать разные режимы отображения, пока не наткнутся на первый поддерживаемый нашим устройством. Различие между ними, соответственно, в наличии или отсутствии вертикальной синхронизации, то есть ограничения частоты вывода кадров частотой монитора.desired_maximum_frame_latency
- число кадров, которое может одновременно ожидать вывода на экран. Обычно указывается от 1 до 3, но может быть и больше. Не может быть меньше 1. Слишком низкое значение может привести к простою GPU, которой просто некуда будет отдавать готовый кадр, если поверхность не успевает их отрисовывать. По-умолчанию принято ставить 2, как оптимальное значение для большинства случаев.alpha_mode
- режим альфа-канала, отвечающего за прозрачность цветов. В данном случае выбираем автоматическое определение на основании поддерживаемогоview_formats
- форматы представлений текстур, которые будут поддерживаться данной поверхностью для рендера. Собственный формат поверхности поддерживается всегда, поэтому нет нужды что-либо туда передавать.