Skip to content

Commit ba86de1

Browse files
committed
wgpu: implement wgpu renderer, generated
1 parent 96b628a commit ba86de1

File tree

12 files changed

+663
-1
lines changed

12 files changed

+663
-1
lines changed

generated/graphics/wgpu/cargo-gpu/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spir
1818
[workspace.dependencies]
1919
# API
2020
wgpu = { version = "27.0.1", default-features = false, features = ["std", "parking_lot", "vulkan", "vulkan-portability", "spirv", "wgsl"] }
21+
pollster = "0.4.0"
2122

2223
# rust-gpu
2324
cargo-gpu = { git = "https://github.com/Rust-GPU/cargo-gpu", rev = "bf24eb6060e0c7b0013eceddd23b5d7cee68f4cc" }

generated/graphics/wgpu/cargo-gpu/mygraphics/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mygraphics-shaders = { path = "../mygraphics-shaders" }
1616

1717
# API
1818
wgpu.workspace = true
19+
pollster.workspace = true
1920

2021
# other
2122
raw-window-handle.workspace = true
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use crate::wgpu_renderer::swapchain::MySwapchainManager;
2+
use anyhow::Context;
3+
use mygraphics_shaders::ShaderConstants;
4+
use std::sync::Arc;
5+
use winit::event::{Event, WindowEvent};
6+
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
7+
8+
mod render_pipeline;
9+
mod renderer;
10+
mod swapchain;
11+
12+
pub fn main() -> anyhow::Result<()> {
13+
pollster::block_on(main_inner())
14+
}
15+
16+
pub async fn main_inner() -> anyhow::Result<()> {
17+
// env_logger::init();
18+
let event_loop = EventLoop::new()?;
19+
// FIXME(eddyb) incomplete `winit` upgrade, follow the guides in:
20+
// https://github.com/rust-windowing/winit/releases/tag/v0.30.0
21+
#[allow(deprecated)]
22+
let window = Arc::new(
23+
event_loop.create_window(
24+
winit::window::Window::default_attributes()
25+
.with_title("Rust GPU - wgpu")
26+
.with_inner_size(winit::dpi::LogicalSize::new(
27+
f64::from(1280),
28+
f64::from(720),
29+
)),
30+
)?,
31+
);
32+
33+
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::from_env_or_default());
34+
let surface = instance.create_surface(window.clone())?;
35+
let adapter =
36+
wgpu::util::initialize_adapter_from_env_or_default(&instance, Some(&surface)).await?;
37+
38+
let required_features = wgpu::Features::PUSH_CONSTANTS;
39+
let required_limits = wgpu::Limits {
40+
max_push_constant_size: 128,
41+
..Default::default()
42+
};
43+
let (device, queue) = adapter
44+
.request_device(&wgpu::DeviceDescriptor {
45+
label: None,
46+
required_features,
47+
required_limits,
48+
experimental_features: wgpu::ExperimentalFeatures::disabled(),
49+
memory_hints: wgpu::MemoryHints::Performance,
50+
trace: Default::default(),
51+
})
52+
.await
53+
.context("Failed to create device")?;
54+
55+
let mut swapchain = MySwapchainManager::new(adapter.clone(), device.clone(), window, surface);
56+
let renderer = renderer::MyRenderer::new(device, queue, swapchain.format())?;
57+
58+
let start = std::time::Instant::now();
59+
let mut event_handler =
60+
move |event: Event<_>, event_loop_window_target: &ActiveEventLoop| match event {
61+
Event::AboutToWait => swapchain.render(|render_target| {
62+
renderer.render(
63+
&ShaderConstants {
64+
time: start.elapsed().as_secs_f32(),
65+
width: render_target.texture().width(),
66+
height: render_target.texture().height(),
67+
},
68+
render_target,
69+
);
70+
}),
71+
Event::WindowEvent { event, .. } => {
72+
match event {
73+
WindowEvent::KeyboardInput {
74+
event:
75+
winit::event::KeyEvent {
76+
logical_key:
77+
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape),
78+
state: winit::event::ElementState::Pressed,
79+
..
80+
},
81+
..
82+
}
83+
| WindowEvent::CloseRequested => event_loop_window_target.exit(),
84+
WindowEvent::Resized(_) => swapchain.should_recreate(),
85+
_ => {}
86+
}
87+
Ok(())
88+
}
89+
_ => {
90+
event_loop_window_target.set_control_flow(ControlFlow::Poll);
91+
Ok(())
92+
}
93+
};
94+
95+
// FIXME(eddyb) incomplete `winit` upgrade, follow the guides in:
96+
// https://github.com/rust-windowing/winit/releases/tag/v0.30.0
97+
#[allow(deprecated)]
98+
event_loop.run(move |event, event_loop_window_target| {
99+
event_handler(event, event_loop_window_target).unwrap();
100+
})?;
101+
Ok(())
102+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use mygraphics_shaders::ShaderConstants;
2+
use wgpu::{
3+
include_spirv, ColorTargetState, ColorWrites, Device, FragmentState, FrontFace,
4+
MultisampleState, PolygonMode, PrimitiveState, PrimitiveTopology, RenderPass, RenderPipeline,
5+
RenderPipelineDescriptor, ShaderStages, TextureFormat, VertexState,
6+
};
7+
8+
pub struct MyRenderPipeline {
9+
pipeline: RenderPipeline,
10+
}
11+
12+
impl MyRenderPipeline {
13+
pub fn new(device: &Device, out_format: TextureFormat) -> anyhow::Result<Self> {
14+
let module = device.create_shader_module(include_spirv!(env!("SHADER_SPV_PATH")));
15+
Ok(Self {
16+
pipeline: device.create_render_pipeline(&RenderPipelineDescriptor {
17+
label: Some("MyRenderPipeline"),
18+
layout: None,
19+
vertex: VertexState {
20+
module: &module,
21+
entry_point: None,
22+
compilation_options: Default::default(),
23+
buffers: &[],
24+
},
25+
primitive: PrimitiveState {
26+
topology: PrimitiveTopology::TriangleList,
27+
strip_index_format: None,
28+
front_face: FrontFace::Ccw,
29+
cull_mode: None,
30+
unclipped_depth: false,
31+
polygon_mode: PolygonMode::Fill,
32+
conservative: false,
33+
},
34+
depth_stencil: None,
35+
multisample: MultisampleState::default(),
36+
fragment: Some(FragmentState {
37+
module: &module,
38+
entry_point: None,
39+
compilation_options: Default::default(),
40+
targets: &[Some(ColorTargetState {
41+
format: out_format,
42+
blend: None,
43+
write_mask: ColorWrites::ALL,
44+
})],
45+
}),
46+
multiview: None,
47+
cache: None,
48+
}),
49+
})
50+
}
51+
52+
pub fn draw(&self, rpass: &mut RenderPass<'_>, shader_constants: &ShaderConstants) {
53+
rpass.set_pipeline(&self.pipeline);
54+
rpass.set_push_constants(
55+
ShaderStages::VERTEX_FRAGMENT,
56+
0,
57+
bytemuck::bytes_of(shader_constants),
58+
);
59+
rpass.draw(0..3, 0..1);
60+
}
61+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use crate::wgpu_renderer::render_pipeline::MyRenderPipeline;
2+
use mygraphics_shaders::ShaderConstants;
3+
use wgpu::wgt::CommandEncoderDescriptor;
4+
use wgpu::{
5+
Color, Device, LoadOp, Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor,
6+
StoreOp, TextureFormat, TextureView,
7+
};
8+
9+
pub struct MyRenderer {
10+
pub device: Device,
11+
pub queue: Queue,
12+
pipeline: MyRenderPipeline,
13+
}
14+
15+
impl MyRenderer {
16+
pub fn new(device: Device, queue: Queue, out_format: TextureFormat) -> anyhow::Result<Self> {
17+
Ok(Self {
18+
pipeline: MyRenderPipeline::new(&device, out_format)?,
19+
device,
20+
queue,
21+
})
22+
}
23+
24+
pub fn render(&self, shader_constants: &ShaderConstants, output: TextureView) {
25+
let mut cmd = self
26+
.device
27+
.create_command_encoder(&CommandEncoderDescriptor {
28+
label: Some("main draw"),
29+
});
30+
31+
let mut rpass = cmd.begin_render_pass(&RenderPassDescriptor {
32+
label: Some("main renderpass"),
33+
color_attachments: &[Some(RenderPassColorAttachment {
34+
view: &output,
35+
depth_slice: None,
36+
resolve_target: None,
37+
ops: Operations {
38+
load: LoadOp::Clear(Color::BLACK),
39+
store: StoreOp::Store,
40+
},
41+
})],
42+
depth_stencil_attachment: None,
43+
timestamp_writes: None,
44+
occlusion_query_set: None,
45+
});
46+
self.pipeline.draw(&mut rpass, shader_constants);
47+
drop(rpass);
48+
49+
self.queue.submit(std::iter::once(cmd.finish()));
50+
}
51+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use anyhow::Context;
2+
use std::sync::Arc;
3+
use wgpu::{Adapter, Device, Surface, SurfaceError, TextureFormat, TextureView};
4+
use winit::dpi::PhysicalSize;
5+
use winit::window::Window;
6+
7+
pub struct MySwapchainManager<'a> {
8+
adapter: Adapter,
9+
device: Device,
10+
window: Arc<Window>,
11+
surface: Surface<'a>,
12+
format: TextureFormat,
13+
14+
// state below
15+
active: Option<ActiveConfiguration>,
16+
should_recreate: bool,
17+
}
18+
19+
pub struct ActiveConfiguration {
20+
size: PhysicalSize<u32>,
21+
}
22+
23+
impl<'a> MySwapchainManager<'a> {
24+
pub fn new(
25+
adapter: Adapter,
26+
device: Device,
27+
window: Arc<Window>,
28+
surface: Surface<'a>,
29+
) -> Self {
30+
let caps = surface.get_capabilities(&adapter);
31+
Self {
32+
adapter,
33+
device,
34+
window,
35+
surface,
36+
format: caps.formats[0],
37+
active: None,
38+
should_recreate: true,
39+
}
40+
}
41+
42+
#[inline]
43+
pub fn should_recreate(&mut self) {
44+
self.should_recreate = true;
45+
}
46+
47+
pub fn format(&self) -> TextureFormat {
48+
self.format
49+
}
50+
51+
pub fn render<R>(&mut self, f: impl FnOnce(TextureView) -> R) -> anyhow::Result<R> {
52+
let size = self.window.inner_size();
53+
if let Some(active) = &self.active {
54+
if active.size != size {
55+
self.should_recreate();
56+
}
57+
} else {
58+
self.should_recreate();
59+
}
60+
61+
const RECREATE_ATTEMPTS: u32 = 10;
62+
for _ in 0..RECREATE_ATTEMPTS {
63+
if self.should_recreate {
64+
self.should_recreate = false;
65+
self.configure_surface(size)?;
66+
}
67+
68+
match self.surface.get_current_texture() {
69+
Ok(surface_texture) => {
70+
if surface_texture.suboptimal {
71+
self.should_recreate = true;
72+
}
73+
let output_view =
74+
surface_texture
75+
.texture
76+
.create_view(&wgpu::TextureViewDescriptor {
77+
format: Some(self.format),
78+
..wgpu::TextureViewDescriptor::default()
79+
});
80+
let r = f(output_view);
81+
surface_texture.present();
82+
return Ok(r);
83+
}
84+
Err(SurfaceError::Outdated | SurfaceError::Lost) => {
85+
self.should_recreate = true;
86+
}
87+
Err(e) => {
88+
anyhow::bail!("get_current_texture() failed: {}", e)
89+
}
90+
};
91+
}
92+
anyhow::bail!(
93+
"looped {RECREATE_ATTEMPTS} times trying to acquire swapchain image and failed repeatedly!"
94+
);
95+
}
96+
97+
fn configure_surface(&mut self, size: PhysicalSize<u32>) -> anyhow::Result<()> {
98+
let mut surface_config = self
99+
.surface
100+
.get_default_config(&self.adapter, size.width, size.height)
101+
.with_context(|| {
102+
format!(
103+
"Incompatible adapter for surface, returned capabilities: {:?}",
104+
self.surface.get_capabilities(&self.adapter)
105+
)
106+
})?;
107+
108+
// force srgb surface format
109+
surface_config.view_formats.push(self.format);
110+
// limit framerate to vsync
111+
surface_config.present_mode = wgpu::PresentMode::AutoVsync;
112+
self.surface.configure(&self.device, &surface_config);
113+
114+
self.active = Some(ActiveConfiguration { size });
115+
Ok(())
116+
}
117+
}

generated/graphics/wgpu/spirv-builder/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spir
1818
[workspace.dependencies]
1919
# API
2020
wgpu = { version = "27.0.1", default-features = false, features = ["std", "parking_lot", "vulkan", "vulkan-portability", "spirv", "wgsl"] }
21+
pollster = "0.4.0"
2122

2223
# rust-gpu
2324
# The version of the dependencies `spirv-builder` and `spirv-std` must match exactly!

generated/graphics/wgpu/spirv-builder/mygraphics/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mygraphics-shaders = { path = "../mygraphics-shaders" }
2020

2121
# API
2222
wgpu.workspace = true
23+
pollster.workspace = true
2324

2425
# other
2526
raw-window-handle.workspace = true

0 commit comments

Comments
 (0)