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