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 and update projection matrix
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    // Update projection matrix for new viewport size
323    let orth_matrix = cgmath::ortho(
324      0.0,
325      width as f32,
326      height as f32,
327      0.0,
328      1.0,
329      -1.0,
330    );
331    self.projection = orth_matrix;
332  }
333
334  /// Recursively render a layer and its sublayers
335  fn render_layer(
336    layer: &mut Layer,
337    render_pass: &mut wgpu::RenderPass,
338    parent_transform: Option<&Matrix4<f32>>,
339    context: &WgpuContext,
340    projection: &Matrix4<f32>,
341    render_pipeline: &wgpu::RenderPipeline,
342    bind_group_layout: &wgpu::BindGroupLayout,
343    texture_bind_group_layout: &wgpu::BindGroupLayout,
344    sampler: &wgpu::Sampler,
345    default_texture_view: &wgpu::TextureView,
346  ) {
347    if !layer.visible {
348      return;
349    }
350
351    // Initialize buffers if needed
352    if layer.vertex_buffer.is_none() || layer.index_buffer.is_none() {
353      layer.init_buffers(&context.device);
354    }
355
356    // Load texture if image path is set and texture not loaded
357    if !layer.image_path.is_empty() && layer.texture.is_none() {
358      layer.load_image_texture(&context.device, &context.queue);
359    }
360
361    // Early return if buffers don't exist
362    if layer.vertex_buffer.is_none() || layer.index_buffer.is_none() {
363      return;
364    }
365
366    // Calculate transform
367    let mut transform = layer.model_matrix();
368    if let Some(parent) = parent_transform {
369      transform = transform * parent;
370    }
371
372    // Create uniform buffer
373    let uniform_buffer = layer.create_uniform_buffer(&context.device, &transform, projection);
374
375    // Create bind group for uniforms
376    let uniform_bind_group = context.device.create_bind_group(&wgpu::BindGroupDescriptor {
377      label: Some("Uniform Bind Group"),
378      layout: bind_group_layout,
379      entries: &[wgpu::BindGroupEntry {
380        binding: 0,
381        resource: uniform_buffer.as_entire_binding(),
382      }],
383    });
384
385    // Get or create texture bind group
386    let texture_bind_group = layer.get_or_create_bind_group(
387      &context.device,
388      texture_bind_group_layout,
389      sampler,
390      default_texture_view,
391    );
392
393    // Set pipeline and bindings
394    render_pass.set_pipeline(render_pipeline);
395    render_pass.set_bind_group(0, &uniform_bind_group, &[]);
396    render_pass.set_bind_group(1, texture_bind_group, &[]);
397    render_pass.set_vertex_buffer(0, layer.vertex_buffer.as_ref().unwrap().slice(..));
398    render_pass.set_index_buffer(
399      layer.index_buffer.as_ref().unwrap().slice(..),
400      wgpu::IndexFormat::Uint16,
401    );
402    render_pass.draw_indexed(0..6, 0, 0..1);
403
404    // Render sublayers (non-focused first, then focused)
405    for (i, sub_layer) in layer.sub_layer_list.iter_mut().enumerate() {
406      if !sub_layer.focused && i != layer.focused_sub_layer {
407        Self::render_layer(
408          sub_layer,
409          render_pass,
410          Some(&transform),
411          context,
412          projection,
413          render_pipeline,
414          bind_group_layout,
415          texture_bind_group_layout,
416          sampler,
417          default_texture_view,
418        );
419      }
420    }
421
422    // Render focused sublayer last
423    if !layer.sub_layer_list.is_empty() && layer.focused_sub_layer < layer.sub_layer_list.len() {
424      Self::render_layer(
425        &mut layer.sub_layer_list[layer.focused_sub_layer],
426        render_pass,
427        Some(&transform),
428        context,
429        projection,
430        render_pipeline,
431        bind_group_layout,
432        texture_bind_group_layout,
433        sampler,
434        default_texture_view,
435      );
436    }
437  }
438
439  pub fn new_layer(
440    name: String,
441    w: u32,
442    h: u32,
443    event_handler: Option<Box<dyn EventHandler>>,
444  ) -> Layer {
445    Layer::new(name, w, h, event_handler)
446  }
447
448  pub fn add_new_layer_to_stage(&mut self, stage_name: &String, layer: Layer) {
449    match self.stage_map.get(stage_name) {
450      Some(&index) => {
451        self.stage_list[index].add_sub_layer(layer);
452      }
453      _ => println!("Can't find the stage with the given name: {}", stage_name),
454    }
455  }
456
457  pub fn set_visible_stage(&mut self, name: &String, visible: bool) {
458    match self.stage_map.get(name) {
459      Some(&index) => {
460        self.stage_list[index].set_visible(visible);
461        self.stage_list[index].needs_update = true;
462      }
463      _ => println!("Can't find the stage with the given name: {}", name),
464    }
465  }
466
467  pub fn add_stage(&mut self, stage: Layer) -> String {
468    let stage_name = stage.name.to_string();
469    self.stage_list.push(stage);
470    self
471      .stage_map
472      .insert(stage_name.to_string(), self.stage_list.len() - 1);
473
474    stage_name
475  }
476
477  pub fn handle_input(&mut self, key: Key) {
478    // println!("key: {}", key);
479    for stage in self.stage_list.iter_mut() {
480      stage.handle_input(key);
481    }
482  }
483
484  pub fn render(&mut self) {
485    // Update animations and layout
486    for stage in self.stage_list.iter_mut() {
487      if stage.needs_update {
488        stage.layout_sub_layers(None, &mut self.stretch);
489
490        if let Some(stretch_obj) = &mut self.stretch {
491          stretch_obj
492            .compute_layout(stage.node.unwrap(), Size::undefined())
493            .unwrap();
494        }
495
496        stage.update_layout(&mut self.stretch);
497        stage.needs_update = false;
498      }
499
500      stage.animate();
501      stage.render(None, &self.projection);
502    }
503
504    // Perform actual wgpu rendering if surface is available
505    if let Some(ref context) = self.wgpu_context {
506      if let Some(ref surface) = context.surface {
507        let output = match surface.get_current_texture() {
508          Ok(output) => output,
509          Err(e) => {
510            eprintln!("Failed to get current texture: {:?}", e);
511            return;
512          }
513        };
514
515        let view = output
516          .texture
517          .create_view(&wgpu::TextureViewDescriptor::default());
518
519        let mut encoder = context
520          .device
521          .create_command_encoder(&wgpu::CommandEncoderDescriptor {
522            label: Some("Render Encoder"),
523          });
524
525        {
526          let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
527            label: Some("Render Pass"),
528            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
529              view: &view,
530              resolve_target: None,
531              ops: wgpu::Operations {
532                load: wgpu::LoadOp::Clear(wgpu::Color {
533                  r: 0.1,
534                  g: 0.2,
535                  b: 0.3,
536                  a: 1.0,
537                }),
538                store: wgpu::StoreOp::Store,
539              },
540            })],
541            depth_stencil_attachment: None,
542            timestamp_writes: None,
543            occlusion_query_set: None,
544          });
545          
546          // Render all stages
547          for stage in self.stage_list.iter_mut() {
548            Self::render_layer(
549              stage,
550              &mut render_pass,
551              None,
552              context,
553              &self.projection,
554              self.render_pipeline.as_ref().unwrap(),
555              self.bind_group_layout.as_ref().unwrap(),
556              self.texture_bind_group_layout.as_ref().unwrap(),
557              self.sampler.as_ref().unwrap(),
558              self.default_texture_view.as_ref().unwrap(),
559            );
560          }
561        }
562
563        context.queue.submit(std::iter::once(encoder.finish()));
564        output.present();
565      }
566    }
567  }
568}