contrast_renderer 0.1.3

A web-gpu based 2D render engine
Documentation
#[path = "../application_framework.rs"]
mod application_framework;

use contrast_renderer::renderer::RenderOperation;
use geometric_algebra::{
    ppga3d::{Motor, Rotor, Translator},
    GeometricProduct, One,
};

const OPEN_SANS_TTF: &[u8] = include_bytes!("../fonts/OpenSans-Regular.ttf");
const MSAA_SAMPLE_COUNT: u32 = 4;

struct Application {
    depth_stencil_texture_view: Option<wgpu::TextureView>,
    msaa_color_texture_view: Option<wgpu::TextureView>,
    renderer: contrast_renderer::renderer::Renderer,
    dynamic_stroke_options: [contrast_renderer::path::DynamicStrokeOptions; 1],
    instance_buffers: [contrast_renderer::renderer::Buffer; 2],
    shape: contrast_renderer::renderer::Shape,
    viewport_size: wgpu::Extent3d,
    view_rotation: Rotor,
    view_distance: f32,
}

impl application_framework::Application for Application {
    fn new(device: &wgpu::Device, _queue: &mut wgpu::Queue, surface_configuration: &wgpu::SurfaceConfiguration) -> Self {
        let renderer = contrast_renderer::renderer::Renderer::new(
            device,
            contrast_renderer::renderer::Configuration {
                blending: wgpu::ColorTargetState {
                    format: surface_configuration.format,
                    blend: Some(wgpu::BlendState {
                        color: wgpu::BlendComponent {
                            src_factor: wgpu::BlendFactor::One,
                            dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
                            operation: wgpu::BlendOperation::Add,
                        },
                        alpha: wgpu::BlendComponent {
                            src_factor: wgpu::BlendFactor::One,
                            dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
                            operation: wgpu::BlendOperation::Add,
                        },
                    }),
                    write_mask: wgpu::ColorWrites::ALL,
                },
                cull_mode: Some(wgpu::Face::Back),
                depth_stencil_format: wgpu::TextureFormat::Depth24PlusStencil8,
                depth_compare: wgpu::CompareFunction::LessEqual,
                depth_write_enabled: true,
                color_attachment_in_stencil_pass: true,
                msaa_sample_count: MSAA_SAMPLE_COUNT,
                clip_nesting_counter_bits: 4,
                winding_counter_bits: 4,
                alpha_layer_count: 0,
            },
        )
        .unwrap();

        let dynamic_stroke_options = [contrast_renderer::path::DynamicStrokeOptions::Dashed {
            join: contrast_renderer::path::Join::Miter,
            pattern: vec![contrast_renderer::path::DashInterval {
                gap_start: 3.0.into(),
                gap_end: 4.0.into(),
                dash_start: contrast_renderer::path::Cap::Butt,
                dash_end: contrast_renderer::path::Cap::Butt,
            }],
            phase: 0.0.into(),
        }];

        let font_face = ttf_parser::Face::from_slice(OPEN_SANS_TTF, 0).unwrap();
        let mut paths = contrast_renderer::text::paths_of_text(
            &font_face,
            &contrast_renderer::text::Layout {
                size: 2.7.into(),
                orientation: contrast_renderer::text::Orientation::LeftToRight,
                major_alignment: contrast_renderer::text::Alignment::Center,
                minor_alignment: contrast_renderer::text::Alignment::Center,
            },
            "Hello World",
            None,
        );
        for path in &mut paths {
            path.reverse();
        }
        paths.insert(0, contrast_renderer::path::Path::from_rounded_rect([0.0, 0.0], [5.8, 1.3], 0.5));
        paths[0].stroke_options = Some(contrast_renderer::path::StrokeOptions {
            width: 0.1.into(),
            offset: 0.0.into(),
            miter_clip: 1.0.into(),
            closed: true,
            dynamic_stroke_options_group: 0,
            curve_approximation: contrast_renderer::path::CurveApproximation::UniformTangentAngle(0.1.into()),
        });
        let shape = contrast_renderer::renderer::Shape::from_paths(device, &renderer, &dynamic_stroke_options, paths.as_slice(), None).unwrap();

        Self {
            depth_stencil_texture_view: None,
            msaa_color_texture_view: None,
            renderer,
            dynamic_stroke_options,
            instance_buffers: [
                contrast_renderer::renderer::Buffer::new(device, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, &[]),
                contrast_renderer::renderer::Buffer::new(device, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, &[]),
            ],
            shape,
            viewport_size: wgpu::Extent3d::default(),
            view_rotation: Rotor::one(),
            view_distance: 5.0,
        }
    }

    fn resize(&mut self, device: &wgpu::Device, _queue: &mut wgpu::Queue, surface_configuration: &wgpu::SurfaceConfiguration) {
        self.viewport_size = wgpu::Extent3d {
            width: surface_configuration.width,
            height: surface_configuration.height,
            depth_or_array_layers: 1,
        };
        let depth_stencil_texture_descriptor = wgpu::TextureDescriptor {
            size: self.viewport_size,
            mip_level_count: 1,
            sample_count: MSAA_SAMPLE_COUNT,
            dimension: wgpu::TextureDimension::D2,
            format: self.renderer.get_config().depth_stencil_format,
            view_formats: &[self.renderer.get_config().depth_stencil_format],
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            label: None,
        };
        let depth_stencil_texture = device.create_texture(&depth_stencil_texture_descriptor);
        self.depth_stencil_texture_view = Some(depth_stencil_texture.create_view(&wgpu::TextureViewDescriptor {
            dimension: Some(wgpu::TextureViewDimension::D2),
            ..wgpu::TextureViewDescriptor::default()
        }));
        if MSAA_SAMPLE_COUNT > 1 {
            let msaa_color_texture_descriptor = wgpu::TextureDescriptor {
                size: self.viewport_size,
                mip_level_count: 1,
                sample_count: MSAA_SAMPLE_COUNT,
                dimension: wgpu::TextureDimension::D2,
                format: surface_configuration.format,
                view_formats: &[surface_configuration.format],
                usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
                label: None,
            };
            let msaa_color_texture = device.create_texture(&msaa_color_texture_descriptor);
            self.msaa_color_texture_view = Some(msaa_color_texture.create_view(&wgpu::TextureViewDescriptor {
                dimension: Some(wgpu::TextureViewDimension::D2),
                ..wgpu::TextureViewDescriptor::default()
            }));
        }
        self.renderer
            .resize_internal_buffers(device, self.viewport_size, self.msaa_color_texture_view.as_ref().unwrap());
    }

    fn render(&mut self, device: &wgpu::Device, queue: &mut wgpu::Queue, frame: &wgpu::SurfaceTexture, animation_time: f64) {
        match &mut self.dynamic_stroke_options[0] {
            contrast_renderer::path::DynamicStrokeOptions::Dashed { phase, .. } => {
                *phase = (animation_time as f32 * 2.0).into();
            }
            _ => unreachable!(),
        }
        self.shape.set_dynamic_stroke_options(queue, 0, &self.dynamic_stroke_options[0]).unwrap();
        let projection_matrix = contrast_renderer::utils::matrix_multiplication(
            &contrast_renderer::utils::perspective_projection(
                std::f32::consts::PI * 0.5,
                self.viewport_size.width as f32 / self.viewport_size.height as f32,
                1.0,
                1000.0,
            ),
            &contrast_renderer::utils::motor3d_to_mat4(
                &Translator::new(1.0, 0.0, 0.0, -0.5 * self.view_distance).geometric_product(self.view_rotation),
            ),
        );
        const ROWS: usize = 9;
        const COLUMNS: usize = 5;
        let mut instances_transform: Vec<[geometric_algebra::ppga3d::Point; 4]> = Vec::with_capacity(1 + ROWS * COLUMNS);
        let mut instances_color: Vec<contrast_renderer::renderer::Color> = Vec::with_capacity(1 + ROWS * COLUMNS);
        instances_transform.push(projection_matrix);
        instances_color.push([1.0, 1.0, 1.0, 1.0].into());
        for y in 0..ROWS {
            for x in 0..COLUMNS {
                instances_transform.push(contrast_renderer::utils::matrix_multiplication(
                    &projection_matrix,
                    &contrast_renderer::utils::motor3d_to_mat4(
                        &(Motor::new(
                            1.0,
                            0.0,
                            0.0,
                            0.0,
                            0.0,
                            (x as f32 + 0.5 - COLUMNS as f32 * 0.5) * 7.0,
                            (y as f32 + 0.5 - ROWS as f32 * 0.5) * 3.0,
                            -5.0,
                        )),
                    ),
                ));
                let red = x as f32 / COLUMNS as f32;
                let green = y as f32 / ROWS as f32;
                instances_color.push([red, green, 1.0 - red - green, 1.0].into());
            }
        }
        self.instance_buffers[0].update(device, queue, &contrast_renderer::concat_buffers!([&instances_transform]).1);
        self.instance_buffers[1].update(device, queue, &contrast_renderer::concat_buffers!([&instances_color]).1);
        let frame_view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
        let msaa_frame_view = if MSAA_SAMPLE_COUNT == 1 {
            &frame_view
        } else {
            self.msaa_color_texture_view.as_ref().unwrap()
        };
        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
        {
            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: None,
                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                    view: msaa_frame_view,
                    resolve_target: if MSAA_SAMPLE_COUNT == 1 { None } else { Some(&frame_view) },
                    ops: wgpu::Operations {
                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
                        store: wgpu::StoreOp::Store,
                    },
                })],
                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                    view: self.depth_stencil_texture_view.as_ref().unwrap(),
                    depth_ops: Some(wgpu::Operations {
                        load: wgpu::LoadOp::Clear(1.0),
                        store: wgpu::StoreOp::Store,
                    }),
                    stencil_ops: Some(wgpu::Operations {
                        load: wgpu::LoadOp::Clear(0),
                        store: wgpu::StoreOp::Store,
                    }),
                }),
                timestamp_writes: None,
                occlusion_query_set: None,
            });
            render_pass.set_vertex_buffer(0, self.instance_buffers[0].buffer.slice(..));
            for instance_index in 0..(1 + ROWS * COLUMNS) as u32 {
                self.shape.render(
                    &self.renderer,
                    &mut render_pass,
                    instance_index..instance_index + 1,
                    RenderOperation::Stencil,
                );
                render_pass.set_vertex_buffer(1, self.instance_buffers[1].buffer.slice(..));
                self.shape.render(
                    &self.renderer,
                    &mut render_pass,
                    instance_index..instance_index + 1,
                    RenderOperation::Color,
                );
            }
        }
        queue.submit(Some(encoder.finish()));
    }

    fn window_event(&mut self, event: winit::event::WindowEvent) {
        match event {
            winit::event::WindowEvent::CursorMoved { position, .. } => {
                let position = [
                    std::f32::consts::PI * 1.2 * (position.x as f32 / self.viewport_size.width as f32 - 0.5),
                    std::f32::consts::PI * 1.2 * (position.y as f32 / self.viewport_size.height as f32 - 0.5),
                ];
                self.view_rotation = contrast_renderer::utils::rotate_around_axis(position[0], &[0.0, 1.0, 0.0])
                    .geometric_product(contrast_renderer::utils::rotate_around_axis(position[1], &[1.0, 0.0, 0.0]));
            }
            winit::event::WindowEvent::MouseWheel { delta, .. } => {
                let difference = match delta {
                    winit::event::MouseScrollDelta::LineDelta(x, y) => [x, y],
                    winit::event::MouseScrollDelta::PixelDelta(delta) => [delta.x as f32 * 0.1, delta.y as f32 * 0.1],
                };
                self.view_distance = (self.view_distance + difference[1]).clamp(2.0, 100.0);
            }
            _ => {}
        }
    }
}

fn main() {
    application_framework::ApplicationManager::run::<Application>("Contrast Renderer - Showcase");
}