renderer_api/
renderer_api.rs

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 framebuffer for demonstrating framebuffer rendering
22    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        // Create shader using Renderer API
55        let shader = renderer.create_shader(Some("Color Shader"), SHADER_SOURCE);
56
57        // Create texture using Renderer helper
58        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        // Create bind group using Renderer API
72        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        // Create pipeline using Renderer API with BlendMode
116        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                // Use BlendMode for transparent rendering
133                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        // Create vertex buffer using Renderer helper
168        let vertex_buffer = renderer.create_vertex_buffer(Some("Vertex Buffer"), vertices);
169
170        // Create offscreen framebuffer using the new Framebuffer abstraction
171        let offscreen_fb = Framebuffer::builder(400, 300)
172            .format(wgpu::TextureFormat::Rgba8UnormSrgb)
173            .label("Offscreen FB")
174            .build(graphics_ctx);
175
176        // Create blit shader and pipeline for rendering framebuffer to surface
177        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                // Use PremultipliedAlpha for framebuffer blitting
235                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        // Global logic - update animation time
273        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        // Handle window-specific resize events
282        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        // Pass 1: Render to offscreen framebuffer using new simplified API
294        {
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        // Pass 2: Blit framebuffer to surface using new simplified API
314        {
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            // Draw fullscreen triangle
330            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"#;