diff --git a/Cargo.toml b/Cargo.toml index 8bb5b59..390b887 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] +glutin = "0.32.2" +glutin-winit = "0.5.0" +winit = "0.30.9" +raw-window-handle = "0.6.2" +bytemuck = "1.22.0" +glow = "0.16.0" diff --git a/src/main.rs b/src/main.rs index e7a11a9..8a6dd95 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,258 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use glow::HasContext; +use glutin::{ + config::{ConfigSurfaceTypes, ConfigTemplateBuilder}, + context::{ContextAttributesBuilder, GlProfile}, + display::GetGlDisplay, + prelude::*, + surface::{SurfaceAttributesBuilder, WindowSurface}, +}; +use glutin_winit::DisplayBuilder; +use raw_window_handle::HasRawWindowHandle; +use std::ffi::CString; +use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::Window, +}; + fn main() { - println!("Hello, world!"); + // Create an event loop + let event_loop = EventLoop::new().unwrap(); + event_loop.set_control_flow(ControlFlow::Poll); + + // Create configuration for glutin + let template = ConfigTemplateBuilder::new() + .with_alpha_size(8) + .with_transparency(false) + .with_surface_type(ConfigSurfaceTypes::WINDOW); + + let window_attributes = Window::default_attributes(); + + // Create window and OpenGL context + let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes)); + + let (window, gl_config) = display_builder + .build(&event_loop, template, |configs| { + configs + .reduce(|accum, config| { + let transparency_check = config.supports_transparency().unwrap_or(false) + && !accum.supports_transparency().unwrap_or(false); + + if transparency_check || config.num_samples() > accum.num_samples() { + config + } else { + accum + } + }) + .unwrap() + }) + .expect("Failed to create window and GL config"); + + let window = window.expect("Window creation failed"); + + // Create OpenGL context attributes + let context_attributes = ContextAttributesBuilder::new() + .with_profile(GlProfile::Core) + .build(window.raw_window_handle().ok()); + + // Create the OpenGL surface attributes + let surface_attributes = SurfaceAttributesBuilder::::new().build( + window.raw_window_handle().unwrap(), + std::num::NonZeroU32::new(800).unwrap(), + std::num::NonZeroU32::new(600).unwrap(), + ); + + // Create surface and context + let display = gl_config.display(); + let gl_surface = unsafe { + display + .create_window_surface(&gl_config, &surface_attributes) + .expect("Failed to create surface") + }; + + let gl_context = unsafe { + display + .create_context(&gl_config, &context_attributes) + .expect("Failed to create context") + .make_current(&gl_surface) + .expect("Failed to make context current") + }; + + // Load OpenGL functions using glow + let gl = unsafe { + glow::Context::from_loader_function(|s| { + display.get_proc_address(&CString::new(s).unwrap()) as *const _ + }) + }; + + // Initialize OpenGL state + unsafe { + gl.clear_color(0.2, 0.3, 0.3, 1.0); + } + + // Create shader program and resources + let (program, vao) = unsafe { + // Vertex Shader + let vertex_shader = gl + .create_shader(glow::VERTEX_SHADER) + .expect("Cannot create shader"); + let vertex_shader_src = r#" + #version 330 core + layout (location = 0) in vec3 aPos; + layout (location = 1) in vec3 aColor; + out vec3 ourColor; + void main() { + gl_Position = vec4(aPos, 1.0); + ourColor = aColor; + } + "#; + gl.shader_source(vertex_shader, vertex_shader_src); + gl.compile_shader(vertex_shader); + + // Check for shader compilation errors + if !gl.get_shader_compile_status(vertex_shader) { + let info = gl.get_shader_info_log(vertex_shader); + panic!("Shader compilation error: {}", info); + } + + // Fragment Shader + let fragment_shader = gl + .create_shader(glow::FRAGMENT_SHADER) + .expect("Cannot create shader"); + let fragment_shader_src = r#" + #version 330 core + in vec3 ourColor; + out vec4 FragColor; + void main() { + FragColor = vec4(ourColor, 1.0); + } + "#; + gl.shader_source(fragment_shader, fragment_shader_src); + gl.compile_shader(fragment_shader); + + // Check for shader compilation errors + if !gl.get_shader_compile_status(fragment_shader) { + let info = gl.get_shader_info_log(fragment_shader); + panic!("Shader compilation error: {}", info); + } + + // Link shaders into a program + let program = gl.create_program().expect("Cannot create program"); + gl.attach_shader(program, vertex_shader); + gl.attach_shader(program, fragment_shader); + gl.link_program(program); + + // Check for linking errors + if !gl.get_program_link_status(program) { + let info = gl.get_program_info_log(program); + panic!("Program linking error: {}", info); + } + + // Delete the shaders as they're linked into our program now and no longer necessary + gl.delete_shader(vertex_shader); + gl.delete_shader(fragment_shader); + + // Set up vertex data and buffers + // ------------------------------------------------------------------ + // Triangle vertices with positions and colors (x, y, z, r, g, b) + let vertices: [f32; 18] = [ + // positions // colors + 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, // bottom right + -0.5, -0.5, 0.0, 0.0, 1.0, 0.0, // bottom left + 0.0, 0.5, 0.0, 0.0, 0.0, 1.0, // top + ]; + + // Create a Vertex Array Object (VAO) + let vao = gl.create_vertex_array().expect("Cannot create VAO"); + gl.bind_vertex_array(Some(vao)); + + // Create a Vertex Buffer Object (VBO) + let vbo = gl.create_buffer().expect("Cannot create buffer"); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo)); + + // Copy the vertex data to the buffer + gl.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + bytemuck::cast_slice(&vertices), + glow::STATIC_DRAW, + ); + + // Position attribute + gl.vertex_attrib_pointer_f32( + 0, // location + 3, // size (3 floats per vertex position) + glow::FLOAT, // type + false, // normalized + 6 * std::mem::size_of::() as i32, // stride + 0, // offset + ); + gl.enable_vertex_attrib_array(0); + + // Color attribute + gl.vertex_attrib_pointer_f32( + 1, // location + 3, // size (3 floats per vertex color) + glow::FLOAT, // type + false, // normalized + 6 * std::mem::size_of::() as i32, // stride + 3 * std::mem::size_of::() as i32, // offset + ); + gl.enable_vertex_attrib_array(1); + + // Unbind the VAO and VBO + gl.bind_vertex_array(None); + gl.bind_buffer(glow::ARRAY_BUFFER, None); + + (program, vao) + }; + + // Run the event loop + event_loop + .run(move |event, event_loop| { + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::Resized(size) => { + // Update the surface size + gl_surface.resize( + &gl_context, + std::num::NonZeroU32::new(size.width).unwrap(), + std::num::NonZeroU32::new(size.height).unwrap(), + ); + + // Update the viewport + unsafe { + gl.viewport(0, 0, size.width as i32, size.height as i32); + } + + window.request_redraw(); + } + WindowEvent::RedrawRequested => { + // Clear the screen + unsafe { + gl.clear(glow::COLOR_BUFFER_BIT); + + // Draw our triangle + gl.use_program(Some(program)); + gl.bind_vertex_array(Some(vao)); + gl.draw_arrays(glow::TRIANGLES, 0, 3); + } + + // Swap buffers + gl_surface + .swap_buffers(&gl_context) + .expect("Failed to swap buffers"); + } + _ => (), + }, + _ => (), + } + }) + .unwrap(); }