1use astrelis_core::logging;
2use astrelis_render::{
3 BlendMode, Framebuffer, GraphicsContext, RenderPassBuilder, RenderTarget, RenderableWindow,
4 Renderer, WindowContextDescriptor, wgpu,
5};
6use astrelis_winit::{
7 WindowId,
8 app::{App, AppCtx, run_app},
9 event::EventBatch,
10 window::{PhysicalSize, WindowBackend, WindowDescriptor},
11};
12
13struct RendererApp {
14 context: &'static GraphicsContext,
15 renderer: Renderer,
16 window: RenderableWindow,
17 window_id: WindowId,
18 pipeline: wgpu::RenderPipeline,
19 bind_group: wgpu::BindGroup,
20 vertex_buffer: wgpu::Buffer,
21 offscreen_fb: Framebuffer,
23 blit_pipeline: wgpu::RenderPipeline,
24 blit_bind_group: wgpu::BindGroup,
25 time: f32,
26}
27
28fn main() {
29 logging::init();
30
31 run_app(|ctx| {
32 let graphics_ctx = GraphicsContext::new_sync();
33 let renderer = Renderer::new(graphics_ctx);
34
35 let window = ctx
36 .create_window(WindowDescriptor {
37 title: "Renderer API Example".to_string(),
38 size: Some(PhysicalSize::new(800.0, 600.0)),
39 ..Default::default()
40 })
41 .expect("Failed to create window");
42
43 let window = RenderableWindow::new_with_descriptor(
44 window,
45 graphics_ctx,
46 WindowContextDescriptor {
47 format: Some(wgpu::TextureFormat::Bgra8UnormSrgb),
48 ..Default::default()
49 },
50 );
51
52 let window_id = window.id();
53
54 let shader = renderer.create_shader(Some("Color Shader"), SHADER_SOURCE);
56
57 let texture_data = create_gradient_texture();
59 let texture = renderer.create_texture_2d(
60 Some("Gradient Texture"),
61 256,
62 256,
63 wgpu::TextureFormat::Rgba8UnormSrgb,
64 wgpu::TextureUsages::TEXTURE_BINDING,
65 &texture_data,
66 );
67
68 let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
69 let sampler = renderer.create_linear_sampler(Some("Linear Sampler"));
70
71 let bind_group_layout = renderer.create_bind_group_layout(
73 Some("Texture Bind Group Layout"),
74 &[
75 wgpu::BindGroupLayoutEntry {
76 binding: 0,
77 visibility: wgpu::ShaderStages::FRAGMENT,
78 ty: wgpu::BindingType::Texture {
79 multisampled: false,
80 view_dimension: wgpu::TextureViewDimension::D2,
81 sample_type: wgpu::TextureSampleType::Float { filterable: true },
82 },
83 count: None,
84 },
85 wgpu::BindGroupLayoutEntry {
86 binding: 1,
87 visibility: wgpu::ShaderStages::FRAGMENT,
88 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
89 count: None,
90 },
91 ],
92 );
93
94 let bind_group = renderer.create_bind_group(
95 Some("Texture Bind Group"),
96 &bind_group_layout,
97 &[
98 wgpu::BindGroupEntry {
99 binding: 0,
100 resource: wgpu::BindingResource::TextureView(&texture_view),
101 },
102 wgpu::BindGroupEntry {
103 binding: 1,
104 resource: wgpu::BindingResource::Sampler(&sampler),
105 },
106 ],
107 );
108
109 let pipeline_layout = renderer.create_pipeline_layout(
110 Some("Render Pipeline Layout"),
111 &[&bind_group_layout],
112 &[],
113 );
114
115 let pipeline = renderer.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
117 label: Some("Render Pipeline"),
118 layout: Some(&pipeline_layout),
119 vertex: wgpu::VertexState {
120 module: &shader,
121 entry_point: Some("vs_main"),
122 buffers: &[wgpu::VertexBufferLayout {
123 array_stride: 4 * 4,
124 step_mode: wgpu::VertexStepMode::Vertex,
125 attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2],
126 }],
127 compilation_options: wgpu::PipelineCompilationOptions::default(),
128 },
129 fragment: Some(wgpu::FragmentState {
130 module: &shader,
131 entry_point: Some("fs_main"),
132 targets: &[Some(
134 BlendMode::Alpha.to_color_target_state(wgpu::TextureFormat::Rgba8UnormSrgb),
135 )],
136 compilation_options: wgpu::PipelineCompilationOptions::default(),
137 }),
138 primitive: wgpu::PrimitiveState {
139 topology: wgpu::PrimitiveTopology::TriangleList,
140 strip_index_format: None,
141 front_face: wgpu::FrontFace::Ccw,
142 cull_mode: Some(wgpu::Face::Back),
143 polygon_mode: wgpu::PolygonMode::Fill,
144 unclipped_depth: false,
145 conservative: false,
146 },
147 depth_stencil: None,
148 multisample: wgpu::MultisampleState {
149 count: 1,
150 mask: !0,
151 alpha_to_coverage_enabled: false,
152 },
153 multiview: None,
154 cache: None,
155 });
156
157 #[rustfmt::skip]
158 let vertices: &[f32] = &[
159 -0.8, -0.8, 0.0, 1.0,
160 0.8, -0.8, 1.0, 1.0,
161 0.8, 0.8, 1.0, 0.0,
162 -0.8, -0.8, 0.0, 1.0,
163 0.8, 0.8, 1.0, 0.0,
164 -0.8, 0.8, 0.0, 0.0,
165 ];
166
167 let vertex_buffer = renderer.create_vertex_buffer(Some("Vertex Buffer"), vertices);
169
170 let offscreen_fb = Framebuffer::builder(400, 300)
172 .format(wgpu::TextureFormat::Rgba8UnormSrgb)
173 .label("Offscreen FB")
174 .build(graphics_ctx);
175
176 let blit_shader = renderer.create_shader(Some("Blit Shader"), BLIT_SHADER_SOURCE);
178
179 let blit_bind_group_layout = renderer.create_bind_group_layout(
180 Some("Blit Bind Group Layout"),
181 &[
182 wgpu::BindGroupLayoutEntry {
183 binding: 0,
184 visibility: wgpu::ShaderStages::FRAGMENT,
185 ty: wgpu::BindingType::Texture {
186 multisampled: false,
187 view_dimension: wgpu::TextureViewDimension::D2,
188 sample_type: wgpu::TextureSampleType::Float { filterable: true },
189 },
190 count: None,
191 },
192 wgpu::BindGroupLayoutEntry {
193 binding: 1,
194 visibility: wgpu::ShaderStages::FRAGMENT,
195 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
196 count: None,
197 },
198 ],
199 );
200
201 let blit_bind_group = renderer.create_bind_group(
202 Some("Blit Bind Group"),
203 &blit_bind_group_layout,
204 &[
205 wgpu::BindGroupEntry {
206 binding: 0,
207 resource: wgpu::BindingResource::TextureView(offscreen_fb.color_view()),
208 },
209 wgpu::BindGroupEntry {
210 binding: 1,
211 resource: wgpu::BindingResource::Sampler(&sampler),
212 },
213 ],
214 );
215
216 let blit_pipeline_layout = renderer.create_pipeline_layout(
217 Some("Blit Pipeline Layout"),
218 &[&blit_bind_group_layout],
219 &[],
220 );
221
222 let blit_pipeline = renderer.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
223 label: Some("Blit Pipeline"),
224 layout: Some(&blit_pipeline_layout),
225 vertex: wgpu::VertexState {
226 module: &blit_shader,
227 entry_point: Some("vs_main"),
228 buffers: &[],
229 compilation_options: wgpu::PipelineCompilationOptions::default(),
230 },
231 fragment: Some(wgpu::FragmentState {
232 module: &blit_shader,
233 entry_point: Some("fs_main"),
234 targets: &[Some(
236 BlendMode::PremultipliedAlpha
237 .to_color_target_state(wgpu::TextureFormat::Bgra8UnormSrgb),
238 )],
239 compilation_options: wgpu::PipelineCompilationOptions::default(),
240 }),
241 primitive: wgpu::PrimitiveState {
242 topology: wgpu::PrimitiveTopology::TriangleList,
243 ..Default::default()
244 },
245 depth_stencil: None,
246 multisample: wgpu::MultisampleState::default(),
247 multiview: None,
248 cache: None,
249 });
250
251 tracing::info!("Renderer initialized successfully");
252 tracing::info!("Device: {:?}", renderer.context().info());
253
254 Box::new(RendererApp {
255 context: graphics_ctx,
256 renderer,
257 window,
258 window_id,
259 pipeline,
260 bind_group,
261 vertex_buffer,
262 offscreen_fb,
263 blit_pipeline,
264 blit_bind_group,
265 time: 0.0,
266 })
267 });
268}
269
270impl App for RendererApp {
271 fn update(&mut self, _ctx: &mut AppCtx) {
272 self.time += 0.016;
274 }
275
276 fn render(&mut self, _ctx: &mut AppCtx, window_id: WindowId, events: &mut EventBatch) {
277 if window_id != self.window_id {
278 return;
279 }
280
281 events.dispatch(|event| {
283 if let astrelis_winit::event::Event::WindowResized(size) = event {
284 self.window.resized(*size);
285 astrelis_winit::event::HandleStatus::consumed()
286 } else {
287 astrelis_winit::event::HandleStatus::ignored()
288 }
289 });
290
291 let mut frame = self.window.begin_drawing();
292
293 {
295 let mut render_pass = RenderPassBuilder::new()
296 .label("Offscreen Pass")
297 .target(RenderTarget::Framebuffer(&self.offscreen_fb))
298 .clear_color(wgpu::Color {
299 r: 0.2,
300 g: 0.1,
301 b: 0.3,
302 a: 1.0,
303 })
304 .build(&mut frame);
305
306 let pass = render_pass.descriptor();
307 pass.set_pipeline(&self.pipeline);
308 pass.set_bind_group(0, &self.bind_group, &[]);
309 pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
310 pass.draw(0..6, 0..1);
311 }
312
313 {
315 let mut render_pass = RenderPassBuilder::new()
316 .label("Surface Pass")
317 .target(RenderTarget::Surface)
318 .clear_color(wgpu::Color {
319 r: 0.1,
320 g: 0.2,
321 b: 0.3,
322 a: 1.0,
323 })
324 .build(&mut frame);
325
326 let pass = render_pass.descriptor();
327 pass.set_pipeline(&self.blit_pipeline);
328 pass.set_bind_group(0, &self.blit_bind_group, &[]);
329 pass.draw(0..3, 0..1);
331 }
332
333 frame.finish();
334 }
335}
336
337fn create_gradient_texture() -> Vec<u8> {
338 let mut texture_data = vec![0u8; (256 * 256 * 4) as usize];
339 for y in 0..256 {
340 for x in 0..256 {
341 let idx = ((y * 256 + x) * 4) as usize;
342 texture_data[idx] = x as u8;
343 texture_data[idx + 1] = y as u8;
344 texture_data[idx + 2] = ((x + y) / 2) as u8;
345 texture_data[idx + 3] = 255;
346 }
347 }
348 texture_data
349}
350
351const SHADER_SOURCE: &str = r#"
352struct VertexInput {
353 @location(0) position: vec2<f32>,
354 @location(1) tex_coords: vec2<f32>,
355}
356
357struct VertexOutput {
358 @builtin(position) clip_position: vec4<f32>,
359 @location(0) tex_coords: vec2<f32>,
360}
361
362@vertex
363fn vs_main(in: VertexInput) -> VertexOutput {
364 var out: VertexOutput;
365 out.clip_position = vec4<f32>(in.position, 0.0, 1.0);
366 out.tex_coords = in.tex_coords;
367 return out;
368}
369
370@group(0) @binding(0)
371var t_diffuse: texture_2d<f32>;
372@group(0) @binding(1)
373var s_diffuse: sampler;
374
375@fragment
376fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
377 return textureSample(t_diffuse, s_diffuse, in.tex_coords);
378}
379"#;
380
381const BLIT_SHADER_SOURCE: &str = r#"
382struct VertexOutput {
383 @builtin(position) clip_position: vec4<f32>,
384 @location(0) tex_coords: vec2<f32>,
385}
386
387@vertex
388fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
389 // Fullscreen triangle
390 var positions = array<vec2<f32>, 3>(
391 vec2<f32>(-1.0, -1.0),
392 vec2<f32>(3.0, -1.0),
393 vec2<f32>(-1.0, 3.0)
394 );
395 var tex_coords = array<vec2<f32>, 3>(
396 vec2<f32>(0.0, 1.0),
397 vec2<f32>(2.0, 1.0),
398 vec2<f32>(0.0, -1.0)
399 );
400
401 var out: VertexOutput;
402 out.clip_position = vec4<f32>(positions[vertex_index], 0.0, 1.0);
403 out.tex_coords = tex_coords[vertex_index];
404 return out;
405}
406
407@group(0) @binding(0)
408var t_source: texture_2d<f32>;
409@group(0) @binding(1)
410var s_source: sampler;
411
412@fragment
413fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
414 return textureSample(t_source, s_source, in.tex_coords);
415}
416"#;