WIP fix animation

Nate Butler created

Change summary

Cargo.lock                         |  7 ++
crates/gpui/Cargo.toml             |  1 
crates/gpui/examples/metal_view.rs | 94 +++++++++++++------------------
crates/gpui/src/scene.rs           |  2 
4 files changed, 49 insertions(+), 55 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -7019,6 +7019,12 @@ dependencies = [
  "gix-validate 0.9.4",
 ]
 
+[[package]]
+name = "glam"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945"
+
 [[package]]
 name = "glob"
 version = "0.3.2"
@@ -7168,6 +7174,7 @@ dependencies = [
  "font-kit",
  "foreign-types 0.5.0",
  "futures 0.3.31",
+ "glam",
  "gpui_macros",
  "http_client",
  "image",

crates/gpui/Cargo.toml 🔗

@@ -87,6 +87,7 @@ collections.workspace = true
 ctor.workspace = true
 derive_more.workspace = true
 etagere = "0.2"
+glam = "0.24"
 futures.workspace = true
 gpui_macros.workspace = true
 http_client = { optional = true, workspace = true }

crates/gpui/examples/metal_view.rs 🔗

@@ -1,15 +1,16 @@
 use gpui::{prelude::*, *};
 use std::sync::Arc;
+use std::time::Instant;
 
 #[cfg(target_os = "macos")]
 use metal::{Device, MTLPrimitiveType, RenderCommandEncoderRef, RenderPipelineState, TextureRef};
 
 struct MetalViewExample {
+    start_time: Instant,
     #[cfg(target_os = "macos")]
     pipeline_state: Option<RenderPipelineState>,
     #[cfg(target_os = "macos")]
     device: Option<Device>,
-    epoch: u64,
 }
 
 impl MetalViewExample {
@@ -19,28 +20,21 @@ impl MetalViewExample {
             pipeline_state: None,
             #[cfg(target_os = "macos")]
             device: None,
-            epoch: 0,
+            start_time: Instant::now(),
         }
     }
 
-    fn update_epoch(&mut self, cx: &mut Context<Self>) {
-        const MAX_EPOCH: u64 = 1024;
-        self.epoch = (self.epoch + 1) % MAX_EPOCH;
-        cx.notify();
-    }
-
     #[cfg(target_os = "macos")]
     fn setup_metal(&mut self) {
         let device = Device::system_default().expect("no Metal device");
 
-        // Shader that properly handles viewport transformation
+        // Simplified shader for debugging
         let shader_source = r#"
             #include <metal_stdlib>
             using namespace metal;
 
             struct Uniforms {
-                float2 viewport_size;
-                float epoch;
+                float time;
             };
 
             struct VertexOut {
@@ -54,35 +48,30 @@ impl MetalViewExample {
             ) {
                 VertexOut out;
 
-                // Define a quad in pixel coordinates (0,0 to viewport_size)
-                float2 positions[6] = {
-                    float2(0.0, 0.0),                                      // top-left
-                    float2(uniforms.viewport_size.x, 0.0),                // top-right
-                    float2(0.0, uniforms.viewport_size.y),                // bottom-left
-                    float2(uniforms.viewport_size.x, 0.0),                // top-right
-                    float2(uniforms.viewport_size.x, uniforms.viewport_size.y), // bottom-right
-                    float2(0.0, uniforms.viewport_size.y),                // bottom-left
+                // Define triangle vertices in normalized device coordinates
+                float2 positions[3] = {
+                    float2( 0.0,  0.5),  // Top
+                    float2(-0.5, -0.5),  // Bottom left
+                    float2( 0.5, -0.5)   // Bottom right
                 };
 
-                // Transform from pixel coordinates to normalized device coordinates
-                float2 pos = positions[vid];
-                float2 ndc = (pos / uniforms.viewport_size) * 2.0 - 1.0;
-                ndc.y = -ndc.y; // Flip Y axis to match screen coordinates
-
-                out.position = float4(ndc, 0.0, 1.0);
-
-                // Create an animated gradient using epoch
-                float2 uv = pos / uniforms.viewport_size;
-                float time = uniforms.epoch * 0.01;
+                float3 colors[3] = {
+                    float3(1.0, 0.0, 0.0),  // Red
+                    float3(0.0, 1.0, 0.0),  // Green
+                    float3(0.0, 0.0, 1.0)   // Blue
+                };
 
-                // Animate the gradient with some trigonometric functions
-                out.color = float4(
-                    0.5 + 0.5 * sin(uv.x * 3.14159 + time),          // Red
-                    0.5 + 0.5 * sin(uv.y * 3.14159 + time * 1.3),    // Green
-                    0.5 + 0.5 * sin((uv.x + uv.y) * 3.14159 - time * 0.7), // Blue
-                    1.0                                                // Full opacity
+                // Apply rotation
+                float2 pos = positions[vid];
+                float c = cos(uniforms.time);
+                float s = sin(uniforms.time);
+                float2 rotated = float2(
+                    pos.x * c - pos.y * s,
+                    pos.x * s + pos.y * c
                 );
 
+                out.position = float4(rotated, 0.0, 1.0);
+                out.color = float4(colors[vid], 1.0);
                 return out;
             }
 
@@ -98,7 +87,7 @@ impl MetalViewExample {
         let vertex_function = library.get_function("vertex_main", None).unwrap();
         let fragment_function = library.get_function("fragment_main", None).unwrap();
 
-        // Create pipeline state
+        // Create pipeline state - no vertex descriptor needed for vertex_id based rendering
         let pipeline_descriptor = metal::RenderPipelineDescriptor::new();
         pipeline_descriptor.set_vertex_function(Some(&vertex_function));
         pipeline_descriptor.set_fragment_function(Some(&fragment_function));
@@ -109,6 +98,9 @@ impl MetalViewExample {
             .unwrap();
         color_attachment.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
 
+        // Note: Depth testing is not enabled for now as it requires proper depth buffer setup
+        // in the GPUI rendering pipeline
+
         // Enable blending
         color_attachment.set_blending_enabled(true);
         color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
@@ -127,7 +119,7 @@ impl MetalViewExample {
     }
 
     #[cfg(target_os = "macos")]
-    fn create_render_callback(&self, epoch: u64) -> MetalRenderCallback {
+    fn create_render_callback(&self, time_delta: f32) -> MetalRenderCallback {
         let pipeline_state = self.pipeline_state.clone().unwrap();
 
         Arc::new(
@@ -158,29 +150,21 @@ impl MetalViewExample {
                 };
                 encoder.set_scissor_rect(scissor_rect);
 
-                // Pass viewport size as uniform
+                // Pass time as uniform
+                let time = time_delta * 2.0; // Scale for reasonable rotation speed
                 #[repr(C)]
                 struct Uniforms {
-                    viewport_size: [f32; 2],
-                    epoch: f32,
+                    time: f32,
                 }
-
-                let uniforms = Uniforms {
-                    viewport_size: [
-                        bounds.size.width.0 * scale_factor,
-                        bounds.size.height.0 * scale_factor,
-                    ],
-                    epoch: epoch as f32,
-                };
-
+                let uniforms = Uniforms { time };
                 encoder.set_vertex_bytes(
                     0,
                     std::mem::size_of::<Uniforms>() as u64,
                     &uniforms as *const Uniforms as *const _,
                 );
 
-                // Draw the quad
-                encoder.draw_primitives(MTLPrimitiveType::Triangle, 0, 6);
+                // Draw triangle using vertex_id - no vertex buffer needed
+                encoder.draw_primitives(MTLPrimitiveType::Triangle, 0, 3);
             },
         )
     }
@@ -194,8 +178,7 @@ impl Render for MetalViewExample {
             self.setup_metal();
         }
 
-        // Update epoch and request animation frame
-        self.update_epoch(cx);
+        // Request animation frame
         window.request_animation_frame();
 
         div()
@@ -218,14 +201,15 @@ impl Render for MetalViewExample {
             )
             .child(
                 div()
-                    .child("This is useful for special effects, custom visualizations, or when you need GPU performance that GPUI's standard elements can't provide")
+                    .child("This example shows a rotating 3D cube - the 'Hello World' of 3D graphics programming")
                     .text_sm()
                     .text_color(rgb(0x888888)),
             )
             .child(div().overflow_hidden().child(
                 #[cfg(target_os = "macos")]
                 {
-                    let callback = self.create_render_callback(self.epoch);
+                    let elapsed = self.start_time.elapsed().as_secs_f32();
+                    let callback = self.create_render_callback(elapsed);
                     metal_view()
                         .render_with_shared(callback)
                         .w(px(600.0))

crates/gpui/src/scene.rs 🔗

@@ -43,6 +43,8 @@ impl Scene {
         self.monochrome_sprites.clear();
         self.polychrome_sprites.clear();
         self.surfaces.clear();
+        #[cfg(target_os = "macos")]
+        self.metal_views.clear();
     }
 
     #[cfg_attr(