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