Skip to main content

rust_animation/
play.rs

1// Copyright (c) 2021 Joone Hur <joone@chromium.org> All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use cgmath::{Matrix4, SquareMatrix};
6use std::collections::HashMap;
7use stretch::{geometry::Size, node::Stretch};
8
9use crate::layer::EventHandler;
10use crate::layer::Key;
11use crate::layer::LayoutMode;
12use crate::layer::Layer;
13use crate::wgpu_context::WgpuContext;
14
15// WGSL shader source
16const SHADER_SOURCE: &str = r#"
17struct VertexInput {
18    @location(0) position: vec3<f32>,
19    @location(1) tex_coords: vec2<f32>,
20}
21
22struct VertexOutput {
23    @builtin(position) clip_position: vec4<f32>,
24    @location(0) tex_coords: vec2<f32>,
25}
26
27struct Uniforms {
28    transform: mat4x4<f32>,
29    projection: mat4x4<f32>,
30    color: vec4<f32>,
31    use_texture: u32,
32}
33
34@group(0) @binding(0)
35var<uniform> uniforms: Uniforms;
36
37@group(1) @binding(0)
38var t_texture: texture_2d<f32>;
39@group(1) @binding(1)
40var t_sampler: sampler;
41
42@vertex
43fn vs_main(vertex: VertexInput) -> VertexOutput {
44    var out: VertexOutput;
45    out.clip_position = uniforms.projection * uniforms.transform * vec4<f32>(vertex.position, 1.0);
46    out.tex_coords = vertex.tex_coords;
47    return out;
48}
49
50@fragment
51fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
52    if (uniforms.use_texture > 0u) {
53        return textureSample(t_texture, t_sampler, in.tex_coords);
54    } else {
55        return uniforms.color;
56    }
57}
58"#;
59
60pub fn render(name: String) {
61  println!("Render {}", name);
62}
63
64pub struct Play {
65  _name: String,
66  stage_list: Vec<Layer>,
67  stage_map: HashMap<String, usize>,
68  projection: Matrix4<f32>,
69  pub stretch: Option<Stretch>,
70  pub wgpu_context: Option<WgpuContext>,
71  render_pipeline: Option<wgpu::RenderPipeline>,
72  bind_group_layout: Option<wgpu::BindGroupLayout>,
73  texture_bind_group_layout: Option<wgpu::BindGroupLayout>,
74  default_texture: Option<wgpu::Texture>,
75  default_texture_view: Option<wgpu::TextureView>,
76  sampler: Option<wgpu::Sampler>,
77}
78
79impl Play {
80  pub fn new(
81    name: String,
82    viewport_width: i32,
83    viewport_height: i32,
84    layout_mode: LayoutMode,
85  ) -> Self {
86    let mut stretch = None;
87    match layout_mode {
88      LayoutMode::Flex => {
89        stretch = Some(Stretch::new());
90      }
91      LayoutMode::UserDefine => {
92        print!("UserDefine");
93      }
94    }
95
96    let mut play = Play {
97      _name: name,
98      stage_list: Vec::new(),
99      stage_map: HashMap::new(),
100      projection: Matrix4::identity(),
101      stretch: stretch,
102      wgpu_context: None,
103      render_pipeline: None,
104      bind_group_layout: None,
105      texture_bind_group_layout: None,
106      default_texture: None,
107      default_texture_view: None,
108      sampler: None,
109    };
110
111    // Apply orthographic projection matrix: left, right, bottom, top, near, far
112    let orth_matrix = cgmath::ortho(
113      0.0,
114      viewport_width as f32,
115      viewport_height as f32,
116      0.0,
117      1.0,
118      -1.0,
119    );
120    play.projection = orth_matrix;
121
122    play
123  }
124
125  pub fn init_wgpu(&mut self) {
126    // Initialize wgpu context (offscreen for library use)
127    self.wgpu_context = Some(pollster::block_on(WgpuContext::new_offscreen()));
128  }
129
130  /// Initialize wgpu with a window surface for rendering to screen
131  pub fn init_wgpu_with_surface(
132    &mut self,
133    window: impl Into<wgpu::SurfaceTarget<'static>>,
134    width: u32,
135    height: u32,
136  ) {
137    self.wgpu_context = Some(pollster::block_on(WgpuContext::new_with_surface(
138      window, width, height,
139    )));
140    
141    // Set up the render pipeline after wgpu context is created
142    self.setup_render_pipeline();
143    self.create_default_texture();
144  }
145
146  /// Set up the render pipeline for drawing
147  fn setup_render_pipeline(&mut self) {
148    let Some(ref context) = self.wgpu_context else {
149      return;
150    };
151
152    let device = &context.device;
153
154    // Create shader module
155    let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
156      label: Some("Shader"),
157      source: wgpu::ShaderSource::Wgsl(SHADER_SOURCE.into()),
158    });
159
160    // Create bind group layout for uniforms
161    let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
162      label: Some("Uniform Bind Group Layout"),
163      entries: &[wgpu::BindGroupLayoutEntry {
164        binding: 0,
165        visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
166        ty: wgpu::BindingType::Buffer {
167          ty: wgpu::BufferBindingType::Uniform,
168          has_dynamic_offset: false,
169          min_binding_size: None,
170        },
171        count: None,
172      }],
173    });
174
175    // Create bind group layout for texture
176    let texture_bind_group_layout =
177      device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
178        label: Some("Texture Bind Group Layout"),
179        entries: &[
180          wgpu::BindGroupLayoutEntry {
181            binding: 0,
182            visibility: wgpu::ShaderStages::FRAGMENT,
183            ty: wgpu::BindingType::Texture {
184              multisampled: false,
185              view_dimension: wgpu::TextureViewDimension::D2,
186              sample_type: wgpu::TextureSampleType::Float { filterable: true },
187            },
188            count: None,
189          },
190          wgpu::BindGroupLayoutEntry {
191            binding: 1,
192            visibility: wgpu::ShaderStages::FRAGMENT,
193            ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
194            count: None,
195          },
196        ],
197      });
198
199    // Create pipeline layout
200    let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
201      label: Some("Render Pipeline Layout"),
202      bind_group_layouts: &[&bind_group_layout, &texture_bind_group_layout],
203      push_constant_ranges: &[],
204    });
205
206    // Get surface format
207    let surface_format = context
208      .surface_config
209      .as_ref()
210      .map(|c| c.format)
211      .unwrap_or(wgpu::TextureFormat::Bgra8UnormSrgb);
212
213    // Create render pipeline
214    let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
215      label: Some("Render Pipeline"),
216      layout: Some(&render_pipeline_layout),
217      vertex: wgpu::VertexState {
218        module: &shader,
219        entry_point: "vs_main",
220        buffers: &[crate::layer::Vertex::desc()],
221        compilation_options: wgpu::PipelineCompilationOptions::default(),
222      },
223      fragment: Some(wgpu::FragmentState {
224        module: &shader,
225        entry_point: "fs_main",
226        targets: &[Some(wgpu::ColorTargetState {
227          format: surface_format,
228          blend: Some(wgpu::BlendState::ALPHA_BLENDING),
229          write_mask: wgpu::ColorWrites::ALL,
230        })],
231        compilation_options: wgpu::PipelineCompilationOptions::default(),
232      }),
233      primitive: wgpu::PrimitiveState {
234        topology: wgpu::PrimitiveTopology::TriangleList,
235        strip_index_format: None,
236        front_face: wgpu::FrontFace::Ccw,
237        cull_mode: Some(wgpu::Face::Back),
238        polygon_mode: wgpu::PolygonMode::Fill,
239        unclipped_depth: false,
240        conservative: false,
241      },
242      depth_stencil: None,
243      multisample: wgpu::MultisampleState {
244        count: 1,
245        mask: !0,
246        alpha_to_coverage_enabled: false,
247      },
248      multiview: None,
249      cache: None,
250    });
251
252    self.render_pipeline = Some(render_pipeline);
253    self.bind_group_layout = Some(bind_group_layout);
254    self.texture_bind_group_layout = Some(texture_bind_group_layout);
255
256    // Create sampler
257    self.sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor {
258      address_mode_u: wgpu::AddressMode::ClampToEdge,
259      address_mode_v: wgpu::AddressMode::ClampToEdge,
260      address_mode_w: wgpu::AddressMode::ClampToEdge,
261      mag_filter: wgpu::FilterMode::Linear,
262      min_filter: wgpu::FilterMode::Linear,
263      mipmap_filter: wgpu::FilterMode::Nearest,
264      ..Default::default()
265    }));
266  }
267
268  /// Create a default 1x1 white texture for layers without textures
269  fn create_default_texture(&mut self) {
270    let Some(ref context) = self.wgpu_context else {
271      return;
272    };
273
274    let device = &context.device;
275    let queue = &context.queue;
276
277    let texture_size = wgpu::Extent3d {
278      width: 1,
279      height: 1,
280      depth_or_array_layers: 1,
281    };
282
283    let texture = device.create_texture(&wgpu::TextureDescriptor {
284      label: Some("Default White Texture"),
285      size: texture_size,
286      mip_level_count: 1,
287      sample_count: 1,
288      dimension: wgpu::TextureDimension::D2,
289      format: wgpu::TextureFormat::Rgba8UnormSrgb,
290      usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
291      view_formats: &[],
292    });
293
294    // Write white color to the texture
295    queue.write_texture(
296      wgpu::ImageCopyTexture {
297        texture: &texture,
298        mip_level: 0,
299        origin: wgpu::Origin3d::ZERO,
300        aspect: wgpu::TextureAspect::All,
301      },
302      &[255, 255, 255, 255],
303      wgpu::ImageDataLayout {
304        offset: 0,
305        bytes_per_row: Some(4),
306        rows_per_image: Some(1),
307      },
308      texture_size,
309    );
310
311    let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
312    self.default_texture = Some(texture);
313    self.default_texture_view = Some(texture_view);
314  }
315
316  /// Resize the rendering surface
317  pub fn resize(&mut self, width: u32, height: u32) {
318    if let Some(ref mut context) = self.wgpu_context {
319      context.resize(width, height);
320    }
321  }
322
323  /// Recursively render a layer and its sublayers
324  fn render_layer(
325    layer: &mut Layer,
326    render_pass: &mut wgpu::RenderPass,
327    parent_transform: Option<&Matrix4<f32>>,
328    context: &WgpuContext,
329    projection: &Matrix4<f32>,
330    render_pipeline: &wgpu::RenderPipeline,
331    bind_group_layout: &wgpu::BindGroupLayout,
332    texture_bind_group_layout: &wgpu::BindGroupLayout,
333    sampler: &wgpu::Sampler,
334    default_texture_view: &wgpu::TextureView,
335  ) {
336    if !layer.visible {
337      return;
338    }
339
340    // Initialize buffers if needed
341    if layer.vertex_buffer.is_none() || layer.index_buffer.is_none() {
342      layer.init_buffers(&context.device);
343    }
344
345    // Load texture if image path is set and texture not loaded
346    if !layer.image_path.is_empty() && layer.texture.is_none() {
347      layer.load_image_texture(&context.device, &context.queue);
348    }
349
350    // Early return if buffers don't exist
351    if layer.vertex_buffer.is_none() || layer.index_buffer.is_none() {
352      return;
353    }
354
355    // Calculate transform
356    let mut transform = layer.model_matrix();
357    if let Some(parent) = parent_transform {
358      transform = transform * parent;
359    }
360
361    // Create uniform buffer
362    let uniform_buffer = layer.create_uniform_buffer(&context.device, &transform, projection);
363
364    // Create bind group for uniforms
365    let uniform_bind_group = context.device.create_bind_group(&wgpu::BindGroupDescriptor {
366      label: Some("Uniform Bind Group"),
367      layout: bind_group_layout,
368      entries: &[wgpu::BindGroupEntry {
369        binding: 0,
370        resource: uniform_buffer.as_entire_binding(),
371      }],
372    });
373
374    // Get or create texture bind group
375    let texture_bind_group = layer.get_or_create_bind_group(
376      &context.device,
377      texture_bind_group_layout,
378      sampler,
379      default_texture_view,
380    );
381
382    // Set pipeline and bindings
383    render_pass.set_pipeline(render_pipeline);
384    render_pass.set_bind_group(0, &uniform_bind_group, &[]);
385    render_pass.set_bind_group(1, texture_bind_group, &[]);
386    render_pass.set_vertex_buffer(0, layer.vertex_buffer.as_ref().unwrap().slice(..));
387    render_pass.set_index_buffer(
388      layer.index_buffer.as_ref().unwrap().slice(..),
389      wgpu::IndexFormat::Uint16,
390    );
391    render_pass.draw_indexed(0..6, 0, 0..1);
392
393    // Render sublayers (non-focused first, then focused)
394    for (i, sub_layer) in layer.sub_layer_list.iter_mut().enumerate() {
395      if !sub_layer.focused && i != layer.focused_sub_layer {
396        Self::render_layer(
397          sub_layer,
398          render_pass,
399          Some(&transform),
400          context,
401          projection,
402          render_pipeline,
403          bind_group_layout,
404          texture_bind_group_layout,
405          sampler,
406          default_texture_view,
407        );
408      }
409    }
410
411    // Render focused sublayer last
412    if !layer.sub_layer_list.is_empty() && layer.focused_sub_layer < layer.sub_layer_list.len() {
413      Self::render_layer(
414        &mut layer.sub_layer_list[layer.focused_sub_layer],
415        render_pass,
416        Some(&transform),
417        context,
418        projection,
419        render_pipeline,
420        bind_group_layout,
421        texture_bind_group_layout,
422        sampler,
423        default_texture_view,
424      );
425    }
426  }
427
428  pub fn new_layer(
429    name: String,
430    w: u32,
431    h: u32,
432    event_handler: Option<Box<dyn EventHandler>>,
433  ) -> Layer {
434    Layer::new(name, w, h, event_handler)
435  }
436
437  pub fn add_new_layer_to_stage(&mut self, stage_name: &String, layer: Layer) {
438    match self.stage_map.get(stage_name) {
439      Some(&index) => {
440        self.stage_list[index].add_sub_layer(layer);
441      }
442      _ => println!("Can't find the stage with the given name: {}", stage_name),
443    }
444  }
445
446  pub fn set_visible_stage(&mut self, name: &String, visible: bool) {
447    match self.stage_map.get(name) {
448      Some(&index) => {
449        self.stage_list[index].set_visible(visible);
450        self.stage_list[index].needs_update = true;
451      }
452      _ => println!("Can't find the stage with the given name: {}", name),
453    }
454  }
455
456  pub fn add_stage(&mut self, stage: Layer) -> String {
457    let stage_name = stage.name.to_string();
458    self.stage_list.push(stage);
459    self
460      .stage_map
461      .insert(stage_name.to_string(), self.stage_list.len() - 1);
462
463    stage_name
464  }
465
466  pub fn handle_input(&mut self, key: Key) {
467    // println!("key: {}", key);
468    for stage in self.stage_list.iter_mut() {
469      stage.handle_input(key);
470    }
471  }
472
473  pub fn render(&mut self) {
474    // Update animations and layout
475    for stage in self.stage_list.iter_mut() {
476      if stage.needs_update {
477        stage.layout_sub_layers(None, &mut self.stretch);
478
479        if let Some(stretch_obj) = &mut self.stretch {
480          stretch_obj
481            .compute_layout(stage.node.unwrap(), Size::undefined())
482            .unwrap();
483        }
484
485        stage.update_layout(&mut self.stretch);
486        stage.needs_update = false;
487      }
488
489      stage.animate();
490      stage.render(None, &self.projection);
491    }
492
493    // Perform actual wgpu rendering if surface is available
494    if let Some(ref context) = self.wgpu_context {
495      if let Some(ref surface) = context.surface {
496        let output = match surface.get_current_texture() {
497          Ok(output) => output,
498          Err(e) => {
499            eprintln!("Failed to get current texture: {:?}", e);
500            return;
501          }
502        };
503
504        let view = output
505          .texture
506          .create_view(&wgpu::TextureViewDescriptor::default());
507
508        let mut encoder = context
509          .device
510          .create_command_encoder(&wgpu::CommandEncoderDescriptor {
511            label: Some("Render Encoder"),
512          });
513
514        {
515          let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
516            label: Some("Render Pass"),
517            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
518              view: &view,
519              resolve_target: None,
520              ops: wgpu::Operations {
521                load: wgpu::LoadOp::Clear(wgpu::Color {
522                  r: 0.1,
523                  g: 0.2,
524                  b: 0.3,
525                  a: 1.0,
526                }),
527                store: wgpu::StoreOp::Store,
528              },
529            })],
530            depth_stencil_attachment: None,
531            timestamp_writes: None,
532            occlusion_query_set: None,
533          });
534          
535          // Render all stages
536          for stage in self.stage_list.iter_mut() {
537            Self::render_layer(
538              stage,
539              &mut render_pass,
540              None,
541              context,
542              &self.projection,
543              self.render_pipeline.as_ref().unwrap(),
544              self.bind_group_layout.as_ref().unwrap(),
545              self.texture_bind_group_layout.as_ref().unwrap(),
546              self.sampler.as_ref().unwrap(),
547              self.default_texture_view.as_ref().unwrap(),
548            );
549          }
550        }
551
552        context.queue.submit(std::iter::once(encoder.finish()));
553        output.present();
554      }
555    }
556  }
557}