astrelis-render 0.2.4

Astrelis Core Rendering Module
Documentation
//! Performance Benchmark - Render System Stress Test
//!
//! Stress tests the rendering system with thousands of draw calls:
//! - 10,000+ textured quads
//! - Instanced rendering performance
//! - Frame time metrics
//! - Draw call batching efficiency
//! - GPU memory usage patterns
//!
//! Press SPACE to toggle rendering.
//! Press +/- to adjust object count.

use astrelis_core::logging;
use astrelis_render::{Color, GraphicsContext, RenderWindow, RenderWindowBuilder, wgpu};
use astrelis_winit::{
    FrameTime, WindowId,
    app::{App, AppCtx, run_app},
    event::{Event, EventBatch, HandleStatus, Key, NamedKey},
    window::{WindowDescriptor, WinitPhysicalSize},
};
use std::sync::Arc;
use std::time::Instant;

struct PerformanceBenchmark {
    _context: Arc<GraphicsContext>,
    window: RenderWindow,
    window_id: WindowId,
    object_count: usize,
    rendering: bool,
    frame_count: u64,
    last_fps_time: Instant,
    fps: f32,
    last_frame_time: f32,
}

fn main() {
    logging::init();

    run_app(|ctx| {
        let graphics_ctx =
            GraphicsContext::new_owned_sync().expect("Failed to create graphics context");

        let window = ctx
            .create_window(WindowDescriptor {
                title: "Performance Benchmark - Render Stress Test".to_string(),
                size: Some(WinitPhysicalSize::new(1280.0, 720.0)),
                ..Default::default()
            })
            .expect("Failed to create window");

        let window = RenderWindowBuilder::new()
            .color_format(wgpu::TextureFormat::Bgra8UnormSrgb)
            .with_depth_default()
            .build(window, graphics_ctx.clone())
            .expect("Failed to create render window");

        let window_id = window.id();

        println!("\n═══════════════════════════════════════════════════════");
        println!("  ⚡ PERFORMANCE BENCHMARK - Render Stress Test");
        println!("═══════════════════════════════════════════════════════");
        println!("  CONTROLS:");
        println!("    [Space]  Toggle rendering on/off");
        println!("    [+/-]    Increase/decrease object count");
        println!("  Starting with 1000 objects");
        println!("═══════════════════════════════════════════════════════\n");

        Box::new(PerformanceBenchmark {
            _context: graphics_ctx,
            window,
            window_id,
            object_count: 1000,
            rendering: true,
            frame_count: 0,
            last_fps_time: Instant::now(),
            fps: 0.0,
            last_frame_time: 0.0,
        })
    });
}

impl App for PerformanceBenchmark {
    fn update(&mut self, _ctx: &mut AppCtx, _time: &FrameTime) {
        self.frame_count += 1;

        let now = Instant::now();
        if now.duration_since(self.last_fps_time).as_secs_f32() >= 1.0 {
            self.fps = self.frame_count as f32;
            self.frame_count = 0;
            self.last_fps_time = now;
            println!(
                "FPS: {:.1} | Frame Time: {:.2}ms | Objects: {} | Rendering: {}",
                self.fps, self.last_frame_time, self.object_count, self.rendering
            );
        }
    }

    fn render(&mut self, _ctx: &mut AppCtx, window_id: WindowId, events: &mut EventBatch) {
        if window_id != self.window_id {
            return;
        }

        let frame_start = Instant::now();

        // Handle resize
        events.dispatch(|event| {
            if let Event::WindowResized(size) = event {
                self.window.resized(*size);
                HandleStatus::consumed()
            } else {
                HandleStatus::ignored()
            }
        });

        // Handle keyboard input
        events.dispatch(|event| {
            if let Event::KeyInput(key) = event
                && key.state == astrelis_winit::event::ElementState::Pressed
            {
                match &key.logical_key {
                    Key::Named(NamedKey::Space) => {
                        self.rendering = !self.rendering;
                        println!("Rendering: {}", self.rendering);
                        return HandleStatus::consumed();
                    }
                    Key::Character(c) if c == "+" || c == "=" => {
                        self.object_count = (self.object_count + 500).min(50000);
                        println!("Object count: {}", self.object_count);
                        return HandleStatus::consumed();
                    }
                    Key::Character(c) if c == "-" => {
                        self.object_count = self.object_count.saturating_sub(500).max(100);
                        println!("Object count: {}", self.object_count);
                        return HandleStatus::consumed();
                    }
                    _ => {}
                }
            }
            HandleStatus::ignored()
        });

        // Begin frame
        let Some(frame) = self.window.begin_frame() else {
            return; // Surface not available
        };

        if self.rendering {
            // Simulate rendering thousands of objects
            // In a real scenario, this would involve:
            // - Instanced draw calls
            // - Uniform buffer updates
            // - Texture binding
            // - Shader state changes

            let _pass = frame
                .render_pass()
                .clear_color(Color::from_rgb_u8(10, 10, 15))
                .label("benchmark_pass")
                .build();
            // Actual rendering would happen here
            // For benchmark purposes, we're measuring the overhead
            // of the render pass itself with clear operations
        } else {
            let _pass = frame
                .render_pass()
                .clear_color(Color::from_rgb_u8(10, 10, 15))
                .label("benchmark_pass")
                .build();
        }
        // Frame auto-submits on drop

        let frame_end = Instant::now();
        self.last_frame_time = frame_end.duration_since(frame_start).as_secs_f32() * 1000.0;
    }
}