renderer_api/
renderer_api.rs

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 framebuffer for demonstrating framebuffer rendering
23    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        // Create shader using Renderer API
56        let shader = renderer.create_shader(Some("Color Shader"), SHADER_SOURCE);
57
58        // Create texture using Renderer helper
59        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        // Create bind group using Renderer API
73        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        // Create pipeline using Renderer API with BlendMode
117        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                // Use BlendMode for transparent rendering
134                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        // Create vertex buffer using Renderer helper
169        let vertex_buffer = renderer.create_vertex_buffer(Some("Vertex Buffer"), vertices);
170
171        // Create offscreen framebuffer using the new Framebuffer abstraction
172        let offscreen_fb = Framebuffer::builder(400, 300)
173            .format(wgpu::TextureFormat::Rgba8UnormSrgb)
174            .label("Offscreen FB")
175            .build(&graphics_ctx);
176
177        // Create blit shader and pipeline for rendering framebuffer to surface
178        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                // Use PremultipliedAlpha for framebuffer blitting
236                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        // Global logic - update animation time
274        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        // Handle window-specific resize events
283        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        // Pass 1: Render to offscreen framebuffer with automatic scoping
295        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        // Pass 2: Blit framebuffer to surface with automatic scoping
308        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                // Draw fullscreen triangle
316                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"#;