Пример SDL2 и OpenGL в Rust

Внезапно решил попробовать использовать SDL2 и OpenGL в Rust.  В сети есть соответствующие библиотеки и примеры к ним, но оказалось что все примеры устарели и больше не компилируются. Пришлось решить эту проблему.

OpenGL в Rust
OpenGL в Rust

Я взял за основу примеры от SDL2, OpenGL биндинга для Rust и адаптировал их для Rust 1.18.0. Так как я только начал изучать Rust, мог где-нибудь напортачить. Тем не менее, этот пример компилируются и работает.

Файл main.rs:

extern crate gl;
extern crate sdl2;

use sdl2::EventPump;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::video::GLProfile;
use gl::types::*;
use std::mem;
use std::ptr;
use std::str;
use std::ffi::CStr;
use std::ffi::CString;


// Vertex data
static VERTEX_DATA: [GLfloat; 6] = [0.0, 0.5, 0.5, -0.5, -0.5, -0.5];

// Shader sources
static VS_SRC: &'static str =
    "#version 150\n\
    in vec2 position;\n\
    void main() {\n\
    gl_Position = vec4(position, 0.0, 1.0);\n\
    }";

static FS_SRC: &'static str =
    "#version 150\n\
    out vec4 out_color;\n\
    void main() {\n\
       out_color = vec4(1.0, 1.0, 1.0, 1.0);\n\
    }";


// compile shader
fn compile_shader(src: &str, ty: GLenum) -> GLuint {
    let shader;
    unsafe {
        shader = gl::CreateShader(ty);
        // Attempt to compile the shader
        let c_str = CString::new(src.as_bytes()).unwrap();
        gl::ShaderSource(shader, 1, &c_str.as_ptr(), ptr::null());
        gl::CompileShader(shader);

        // Get the compile status
        let mut status = gl::FALSE as GLint;
        gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);

        // Fail on error
        if status != (gl::TRUE as GLint) {
            let mut len = 0;
            gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
            let mut buf = Vec::with_capacity(len as usize);
            buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character
            gl::GetShaderInfoLog(shader,
                                 len,
                                 ptr::null_mut(),
                                 buf.as_mut_ptr() as *mut GLchar);
            panic!("{}",
                   str::from_utf8(&buf)
                       .ok()
                       .expect("ShaderInfoLog not valid utf8"));
        }
    }
    shader
}


// link fragment and vertex shader
fn link_program(vs: GLuint, fs: GLuint) -> GLuint {
    unsafe {
        let program = gl::CreateProgram();
        gl::AttachShader(program, vs);
        gl::AttachShader(program, fs);
        gl::LinkProgram(program);
        // Get the link status
        let mut status = gl::FALSE as GLint;
        gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);

        // Fail on error
        if status != (gl::TRUE as GLint) {
            let mut len: GLint = 0;
            gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
            let mut buf = Vec::with_capacity(len as usize);
            buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character
            gl::GetProgramInfoLog(program,
                                  len,
                                  ptr::null_mut(),
                                  buf.as_mut_ptr() as *mut GLchar);
            panic!("{}",
                   str::from_utf8(&buf)
                       .ok()
                       .expect("ProgramInfoLog not valid utf8"));
        }
        program
    }
}


// convert OpenGL native string to string
unsafe fn gl_to_str(c_str: *const u8) -> &'static str {
    mem::transmute(str::from_utf8(CStr::from_ptr(c_str as *const i8).to_bytes()).unwrap())
}


// simulate SDL_WaitEvent(NULL) function because it is not implemented in rust-sdl2
fn wait_for_event(events: &sdl2::EventSubsystem, pump: &mut EventPump) {
    let event = pump.wait_event();
    events.push_event(event).ok();
}


fn main() {
    // initialize SDL and video subsystem
    let sdl_context = sdl2::init().unwrap();
    let video_subsystem = sdl_context.video().unwrap();

    let gl_attr = video_subsystem.gl_attr();
    // Don't use deprecated OpenGL functions
    gl_attr.set_context_profile(GLProfile::Core);
    // Set the context into debug mode
    gl_attr.set_context_flags().debug().forward_compatible().set();
    // Set the OpenGL context version (OpenGL 3.2)
    gl_attr.set_context_version(3, 2);
    // Enable anti-aliasing
    gl_attr.set_multisample_buffers(1);
    gl_attr.set_multisample_samples(4);

    let window = video_subsystem.window("Triangle", 640, 480)
        .position_centered()
        .opengl()
        .build()
        .unwrap();

    // Yes, we're still using the Core profile
    assert_eq!(gl_attr.context_profile(), GLProfile::Core);
    // ... and we're still using OpenGL 3.2
    assert_eq!(gl_attr.context_version(), (3, 2));

    // init OpenGL function pointers
    gl::load_with(|name| video_subsystem.gl_get_proc_address(name) as *const _);

    // create OpenGL context and make it current
    let gl_context = window.gl_create_context().unwrap();
    window.gl_make_current(&gl_context).ok();

    // print drivers information
    unsafe {
        println!("Vendor: {}", gl_to_str(gl::GetString(gl::VENDOR)));
        println!("Renderer: {}", gl_to_str(gl::GetString(gl::RENDERER)));
        println!("GLSL version: {}", gl_to_str(gl::GetString(gl::SHADING_LANGUAGE_VERSION)));
        println!("GL version: {}", gl_to_str(gl::GetString(gl::VERSION)));
    }

    // load shaders
    let vs = compile_shader(VS_SRC, gl::VERTEX_SHADER);
    let fs = compile_shader(FS_SRC, gl::FRAGMENT_SHADER);
    let program = link_program(vs, fs);

    // prepare vertex array object and vertex buffer object
    let mut vao = 0;
    let mut vbo = 0;
    unsafe {
        // Create Vertex Array Object
        gl::GenVertexArrays(1, &mut vao);
        gl::BindVertexArray(vao);

        // Create a Vertex Buffer Object and copy the vertex data to it
        gl::GenBuffers(1, &mut vbo);
        gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
        gl::BufferData(gl::ARRAY_BUFFER,
                       (VERTEX_DATA.len() * mem::size_of::()) as GLsizeiptr,
                       mem::transmute(&VERTEX_DATA[0]),
                       gl::STATIC_DRAW);

        // Use shader program
        gl::UseProgram(program);
        gl::BindFragDataLocation(program, 0, CString::new("out_color").unwrap().as_ptr());

        // Specify the layout of the vertex data
        let pos_attr = gl::GetAttribLocation(program, CString::new("position").unwrap().as_ptr());
        gl::EnableVertexAttribArray(pos_attr as GLuint);
        gl::VertexAttribPointer(pos_attr as GLuint, 2, gl::FLOAT,
                                gl::FALSE as GLboolean, 0, ptr::null());
    }

    // main event loop
    let mut event_pump = sdl_context.event_pump().unwrap();
    'running: loop {

        // do rendering
        unsafe {
            // Clear the screen to black
            gl::ClearColor(0.3, 0.3, 0.3, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT);

            // Draw a triangle from the 3 vertices
            gl::DrawArrays(gl::TRIANGLES, 0, 3);
        }
        // copy buffer to screen
        window.gl_swap_window();

        // wait for event
        wait_for_event(&sdl_context.event().unwrap(), &mut event_pump);

        // handle all arrived events
        for event in event_pump.poll_iter() {
            match event {
                Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
                    break 'running
                },
                _ => {}
            }
        }
    }

    // Cleanup
    unsafe {
        gl::DeleteProgram(program);
        gl::DeleteShader(fs);
        gl::DeleteShader(vs);
        gl::DeleteBuffers(1, &vbo);
        gl::DeleteVertexArrays(1, &vao);
    }
}

Теперь содержимое файла Cargo.toml:

[package]
name = "triangle"
version = "0.1.0"
authors = [ "Alexander" ]

[dependencies]
gl = "0.6.3"

[dependencies.sdl2]
features = [ "use_mac_framework" ]
version = "0.30.0"

Этот пример рассчитан на компиляцию в MacOS X. Так как SDL2 у меня установлен как фреймворк, потребовалось указать дополнительную опцию “use_mac_framework”. На других платформах она не нужна.

Как ни странно, в оболочке для Rust не оказалось аналога функции SDL_WaitEvent(NULL), которая ждет прихода следующего события, но не удаляет его из очереди. Пришлось реализовать её подручными средствами. Надеюсь, в будущих версиях rust-sdl2 это исправят.

Добавить комментарий