textured_window/
textured_window.rs

1use astrelis_core::logging;
2use astrelis_render::{
3    Color, GraphicsContext, RenderTarget, RenderableWindow, WindowContextDescriptor,
4};
5use astrelis_winit::{
6    WindowId,
7    app::run_app,
8    window::{WindowBackend, WindowDescriptor},
9};
10
11struct App {
12    window: RenderableWindow,
13    window_id: WindowId,
14    pipeline: wgpu::RenderPipeline,
15    bind_group: wgpu::BindGroup,
16    vertex_buffer: wgpu::Buffer,
17}
18
19fn main() {
20    logging::init();
21
22    run_app(|ctx| {
23        let graphics_ctx = GraphicsContext::new_owned_sync_or_panic();
24
25        let window = ctx
26            .create_window(WindowDescriptor {
27                title: "Textured Window".to_string(),
28                ..Default::default()
29            })
30            .expect("Failed to create window");
31
32        let window = RenderableWindow::new_with_descriptor(
33            window,
34            graphics_ctx.clone(),
35            WindowContextDescriptor {
36                format: Some(wgpu::TextureFormat::Bgra8UnormSrgb),
37                ..Default::default()
38            },
39        ).expect("Failed to create renderable window");
40
41        let shader = graphics_ctx
42            .device
43            .create_shader_module(wgpu::ShaderModuleDescriptor {
44                label: Some("Texture Shader"),
45                source: wgpu::ShaderSource::Wgsl(include_str!("textured_window.wgsl").into()),
46            });
47
48        let texture_size = wgpu::Extent3d {
49            width: 256,
50            height: 256,
51            depth_or_array_layers: 1,
52        };
53
54        let texture = graphics_ctx
55            .device
56            .create_texture(&wgpu::TextureDescriptor {
57                label: Some("Example Texture"),
58                size: texture_size,
59                mip_level_count: 1,
60                sample_count: 1,
61                dimension: wgpu::TextureDimension::D2,
62                format: wgpu::TextureFormat::Rgba8UnormSrgb,
63                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
64                view_formats: &[],
65            });
66
67        let mut texture_data = vec![0u8; (256 * 256 * 4) as usize];
68        for y in 0..256 {
69            for x in 0..256 {
70                let idx = ((y * 256 + x) * 4) as usize;
71                texture_data[idx] = x as u8;
72                texture_data[idx + 1] = y as u8;
73                texture_data[idx + 2] = ((x + y) / 2) as u8;
74                texture_data[idx + 3] = 255;
75            }
76        }
77
78        graphics_ctx.queue.write_texture(
79            wgpu::TexelCopyTextureInfo {
80                texture: &texture,
81                mip_level: 0,
82                origin: wgpu::Origin3d::ZERO,
83                aspect: wgpu::TextureAspect::All,
84            },
85            &texture_data,
86            wgpu::TexelCopyBufferLayout {
87                offset: 0,
88                bytes_per_row: Some(256 * 4),
89                rows_per_image: Some(256),
90            },
91            texture_size,
92        );
93
94        let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
95        let sampler = graphics_ctx
96            .device
97            .create_sampler(&wgpu::SamplerDescriptor {
98                address_mode_u: wgpu::AddressMode::ClampToEdge,
99                address_mode_v: wgpu::AddressMode::ClampToEdge,
100                address_mode_w: wgpu::AddressMode::ClampToEdge,
101                mag_filter: wgpu::FilterMode::Linear,
102                min_filter: wgpu::FilterMode::Nearest,
103                mipmap_filter: wgpu::FilterMode::Nearest,
104                ..Default::default()
105            });
106
107        let bind_group_layout =
108            graphics_ctx
109                .device
110                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
111                    label: Some("Texture Bind Group Layout"),
112                    entries: &[
113                        wgpu::BindGroupLayoutEntry {
114                            binding: 0,
115                            visibility: wgpu::ShaderStages::FRAGMENT,
116                            ty: wgpu::BindingType::Texture {
117                                multisampled: false,
118                                view_dimension: wgpu::TextureViewDimension::D2,
119                                sample_type: wgpu::TextureSampleType::Float { filterable: true },
120                            },
121                            count: None,
122                        },
123                        wgpu::BindGroupLayoutEntry {
124                            binding: 1,
125                            visibility: wgpu::ShaderStages::FRAGMENT,
126                            ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
127                            count: None,
128                        },
129                    ],
130                });
131
132        let bind_group = graphics_ctx
133            .device
134            .create_bind_group(&wgpu::BindGroupDescriptor {
135                label: Some("Texture Bind Group"),
136                layout: &bind_group_layout,
137                entries: &[
138                    wgpu::BindGroupEntry {
139                        binding: 0,
140                        resource: wgpu::BindingResource::TextureView(&texture_view),
141                    },
142                    wgpu::BindGroupEntry {
143                        binding: 1,
144                        resource: wgpu::BindingResource::Sampler(&sampler),
145                    },
146                ],
147            });
148
149        let pipeline_layout =
150            graphics_ctx
151                .device
152                .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
153                    label: Some("Render Pipeline Layout"),
154                    bind_group_layouts: &[&bind_group_layout],
155                    push_constant_ranges: &[],
156                });
157
158        let pipeline =
159            graphics_ctx
160                .device
161                .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
162                    label: Some("Render Pipeline"),
163                    layout: Some(&pipeline_layout),
164                    vertex: wgpu::VertexState {
165                        module: &shader,
166                        entry_point: Some("vs_main"),
167                        buffers: &[wgpu::VertexBufferLayout {
168                            array_stride: 4 * 4,
169                            step_mode: wgpu::VertexStepMode::Vertex,
170                            attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2],
171                        }],
172                        compilation_options: wgpu::PipelineCompilationOptions::default(),
173                    },
174                    fragment: Some(wgpu::FragmentState {
175                        module: &shader,
176                        entry_point: Some("fs_main"),
177                        targets: &[Some(wgpu::ColorTargetState {
178                            format: wgpu::TextureFormat::Bgra8UnormSrgb,
179                            blend: Some(wgpu::BlendState::REPLACE),
180                            write_mask: wgpu::ColorWrites::ALL,
181                        })],
182                        compilation_options: wgpu::PipelineCompilationOptions::default(),
183                    }),
184                    primitive: wgpu::PrimitiveState {
185                        topology: wgpu::PrimitiveTopology::TriangleList,
186                        strip_index_format: None,
187                        front_face: wgpu::FrontFace::Ccw,
188                        cull_mode: Some(wgpu::Face::Back),
189                        polygon_mode: wgpu::PolygonMode::Fill,
190                        unclipped_depth: false,
191                        conservative: false,
192                    },
193                    depth_stencil: None,
194                    multisample: wgpu::MultisampleState {
195                        count: 1,
196                        mask: !0,
197                        alpha_to_coverage_enabled: false,
198                    },
199                    multiview: None,
200                    cache: None,
201                });
202
203        #[rustfmt::skip]
204        let vertices: &[f32] = &[
205            -0.8, -0.8,  0.0, 1.0,
206             0.8, -0.8,  1.0, 1.0,
207             0.8,  0.8,  1.0, 0.0,
208            -0.8, -0.8,  0.0, 1.0,
209             0.8,  0.8,  1.0, 0.0,
210            -0.8,  0.8,  0.0, 0.0,
211        ];
212
213        let vertex_buffer = graphics_ctx.device.create_buffer(&wgpu::BufferDescriptor {
214            label: Some("Vertex Buffer"),
215            size: (vertices.len() * std::mem::size_of::<f32>()) as u64,
216            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
217            mapped_at_creation: false,
218        });
219
220        graphics_ctx
221            .queue
222            .write_buffer(&vertex_buffer, 0, bytemuck::cast_slice(vertices));
223
224        let window_id = window.id();
225
226        Box::new(App {
227            window,
228            window_id,
229            pipeline,
230            bind_group,
231            vertex_buffer,
232        })
233    });
234}
235
236impl astrelis_winit::app::App for App {
237    fn update(&mut self, _ctx: &mut astrelis_winit::app::AppCtx, _time: &astrelis_winit::FrameTime) {
238        // Global logic (none needed for this example)
239    }
240
241    fn render(
242        &mut self,
243        _ctx: &mut astrelis_winit::app::AppCtx,
244        window_id: WindowId,
245        events: &mut astrelis_winit::event::EventBatch,
246    ) {
247        if window_id != self.window_id {
248            return;
249        }
250
251        // Handle window resize events
252        events.dispatch(|event| {
253            if let astrelis_winit::event::Event::WindowResized(size) = event {
254                self.window.resized(*size);
255                astrelis_winit::event::HandleStatus::consumed()
256            } else {
257                astrelis_winit::event::HandleStatus::ignored()
258            }
259        });
260
261        let mut frame = self.window.begin_drawing();
262
263        // Render with automatic scoping (no manual {} block needed)
264        frame.clear_and_render(
265            RenderTarget::Surface,
266            Color::rgb(0.1, 0.2, 0.3),
267            |pass| {
268                let pass = pass.descriptor();
269                pass.set_pipeline(&self.pipeline);
270                pass.set_bind_group(0, &self.bind_group, &[]);
271                pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
272                pass.draw(0..6, 0..1);
273            },
274        );
275
276        frame.finish();
277    }
278}