Как стать автором
Поиск
Написать публикацию
Обновить

Создание интро в 2кб на Rust за вечер

Уровень сложностиСредний
Время на прочтение15 мин
Количество просмотров3.9K

В данной статье будет рассказано, как можно довольно просто сделать маленькое интро, используя язык Rust. Будет очень много Unsafe и WinAPI кода, а так же предполагается, что читатель уже хоть немного знаком с OpenGL 3.3

Вот что должно получиться на выходе:

Размер: 1 661 байт
Размер: 1 661 байт

Подготовка инструментов

Необходимое ПО:

  1. Nightly-версия Rust (для нестабильных функций и unsafe)

    rustup toolchain install nightly
  2. Установка целевой архитектуры

    rustup target add i686-pc-windows-msvc
  1. Crinkler - компрессирующий линкер и упаковщик

Создаем проект:

cargo new --bin 4kb

Теперь нам нужно убрать почти всё, что обычно поставляется по умолчанию в обычную программу.

main.rs

// Сами определяем точку входа для нашей программы
#![no_main]
// Отключаем поддержку стандартной библиотеки
#![no_std]

// Заставляем компилятор не менять имя нашей функции на своё
#[unsafe(no_mangle)]
fn main() -> ! {

}

// Теперь нам придется самим определять функцию при ошибках

/*
  rust-analyzer может ругаться и указывать на ошибку:
    found duplicate lang item `panic_impl`
    the lang item is first defined in crate `std` (which `test` depends on)

  Но мы не обращаем на это внимание
*/
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_: &PanicInfo<'_>) -> ! {
    loop {}
}

Автоматизируем сборку, чтобы избавиться от ручного удаления файлов и запускать всё одной командой

build.bat

del demo.exe
del demo.o
cargo +nightly rustc -Z build-std=core --target i686-pc-windows-msvc --release -Z build-std-features=panic_immediate_abort --bin 4kb -- --emit obj="demo.o"
Crinkler.exe demo.o /OUT:demo.exe /SUBSYSTEM:WINDOWS /ENTRY:main "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22000.0\um\x86" gdi32.lib user32.lib opengl32.lib kernel32.lib winmm.lib
demo.exe
echo %ErrorLevel%

Разберем по порядку:

  • -Z build-std=core - сборка только с core версией

  • --target i686-pc-window-msvc - сборка под x86

  • -Z build-std-features=panic_immediate_abort - Это сгенерирует каждую панику как инструкцию прерывания без форматирования сообщения о панике. 

  • --emit obj="demo.o" - Создаем объектный файл, который потом слинкуется с windows lib и потом только создастся exe файл

  • /OUT:demo.exe - имя выходного файла

  • /SUBSYSTEM:WINDOWS - Здесь может быть два варианта Windows и Console. Windows подразумевает, что мы будем использовать окно

  • /ENTRY:main - Указываем имя нашей входной точки

  • "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22000.0\um\x86" gdi32.lib user32.lib opengl32.lib kernel32.lib winmm.lib - Путь до системных файлов

Хорошо, у нас пустая программа которая ничего не делает

Изменим наш Cargo.toml, добавив лишь одну внешнюю зависимость и так же укажем в release сборке, что хотим убрать:

[dependencies]
windows-sys = { version = "0.52.0", features = [
	"Win32_Foundation",
	"Win32_System",
	"Win32_System_Threading",
	"Win32_System_Memory",
	"Win32_UI_WindowsAndMessaging",
	"Win32_Graphics_Gdi",
	"Win32_System_LibraryLoader",
	"Win32_Graphics_OpenGL",
	"Win32_Media",
	"Win32_Media_Audio",
	"Win32_Media_Multimedia",
    "Win32_System_Console",
]}

[profile.release]
# В случаи panic просто аварийно закрываем нашу программу
panic = "abort"
# Никаких отладочных символом в release сборке
strip = true
# Уменьшить количество единиц codegen для повышения оптимизации. 
codegen-units = 1
# Оптимизируем размер
opt-level = "z"

Сейчас мы можем уже создать простое окно, я постарался хорошо продукоментировать код

window.rs


use windows_sys::{
    Win32::Foundation::*,
    Win32::Graphics::{Gdi::*, OpenGL::*},
    Win32::System::LibraryLoader::GetModuleHandleA,
    Win32::UI::WindowsAndMessaging::*,
};

use core::{mem, ptr};

/// Обработка сообщений от Windows
pub fn handle_message(_window: HWND) -> bool {
    let mut msg: mem::MaybeUninit<MSG> = mem::MaybeUninit::uninit();

    loop {
        unsafe {

            // Проверяем есть ли сообщения?
            if PeekMessageA(msg.as_mut_ptr(), 0 as HWND, 0, 0, PM_REMOVE) == 0 {
                // Если нет, то выходим
                return true;
            }

            // Преобразуем MaybeUninit в инициализированную структуру MSG
            let msg = msg.assume_init();

            // Проверяем, не является ли сообщение командой на выход
            if msg.message == WM_QUIT {
                // Получен сигнал завершения работы приложения
                return false;
            }

            // Преобразуем виртуальные клавиши в символы (для клавиатурного ввода)
            TranslateMessage(&msg);
            
            // Отправляем сообщение в процедуру окна для обработки
            DispatchMessageA(&msg);
        }
    }
}

/*
  HWND - идентификатор окна
  HDC - контекст устройства для рисования
*/

#[must_use]
pub fn create(width: i32, height: i32) -> (HWND, HDC) {

    unsafe {

        let instance = GetModuleHandleA(ptr::null());

        // Сами выбираем имя окну
        let window_class = b"window\0";

        let wc = WNDCLASSA {
            hCursor: LoadCursorW(0, IDC_ARROW),  // курсор мыши - стрелка
            hInstance: instance,                 // идентификатор программы
            lpszClassName: window_class.as_ptr(),// имя класса окна
            style: CS_HREDRAW | CS_VREDRAW,      // перерисовывать при изменении размера
            lpfnWndProc: Some(wndproc),          // функция обработки сообщений
            /*
                Остальные параметры нас не интересуют, подробнее о них
                можно узнать из документации к WNDCLASS:
                https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassa
            */
            cbClsExtra: 0,
            cbWndExtra: 0,
            hIcon: 0,
            hbrBackground: 0,
            lpszMenuName: ptr::null(),
        };

        let _atom = RegisterClassA(&wc);
        let title = c"Pixel";

        // Создаем окно с расширенными параметрами
        let h_wnd = CreateWindowExA(
            // Расширенные стили окна (0 - стили по умолчанию)
            0,
            
            // Указатель на имя зарегистрированного класса окна
            window_class.as_ptr(),
            
            // Заголовок окна (преобразуем в указатель на C-строку)
            title.as_ptr() as *const _,

            // Стили окна: стандартное перекрывающееся окно + сразу видимое
            WS_OVERLAPPEDWINDOW | WS_VISIBLE,

            // Позиция окна: используем значения по умолчанию
            CW_USEDEFAULT,  // X-позиция
            CW_USEDEFAULT,  // Y-позиция

            // Размеры окна в пикселях:
            width,
            height,

            // Родительское окно (None - нет родителя)
            0 as HWND,

            // Меню (None - нет меню)
            0 as HMENU,

            // Дескриптор экземпляра приложения
            instance,
            // Дополнительные параметры создания (None)
            ptr::null(),
        );

        let h_dc: HDC = GetDC(h_wnd);

        let mut pfd: PIXELFORMATDESCRIPTOR = mem::zeroed();
        pfd.nSize = mem::size_of::<PIXELFORMATDESCRIPTOR>() as u16;
        pfd.nVersion = 1;
        // Отрисовка в экран, поддрежка OpenGL, Двойная буферизация
        pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
        pfd.iPixelType = PFD_TYPE_RGBA;
        // Количество бит на цвет
        pfd.cColorBits = 32;
        // 8 бит на Alpha канал(Прозрачность)
        pfd.cAlphaBits = 8;
        // 24 бита на глубину цвета
        pfd.cDepthBits = 24;

        // Выбираем формат пикселей из нашего предыдущего описания
        let pfd_id = ChoosePixelFormat(h_dc, &pfd);
        // устанавливаем формат
        SetPixelFormat(h_dc, pfd_id, &pfd);

        // wglCreateContext и wglMakeCurrent получаем из Opengl32.lib
        let gl_context: HGLRC = wglCreateContext(h_dc);
        // Делаем gl context текущим
        wglMakeCurrent(h_dc, gl_context);

        (h_wnd, h_dc)
    }
}

// Внешняя функция с соглашением о вызовах "system" для WinAPI
extern "system" fn wndproc(
    window: HWND,          // Дескриптор окна, к которому пришло сообщение
    message: u32,          // Код сообщения (например, WM_PAINT, WM_DESTROY)
    wparam: WPARAM,        // Дополнительный параметр сообщения (зависит от типа сообщения)
    lparam: LPARAM         // Дополнительный параметр сообщения (зависит от типа сообщения)
) -> LRESULT {             // Возвращаемое значение - результат обработки сообщения
    
    unsafe {  // Блок unsafe, так как работаем с сырыми указателями WinAPI
        match message {  // Обработка различных типов сообщений
            WM_PAINT => {  // Сообщение о необходимости перерисовать окно
                // Сообщаем системе, что вся клиентская область валидна
                // и не требует дальнейшей перерисовки
                ValidateRect(window, ptr::null());
                0  // Возвращаем 0, сообщая об успешной обработке
            }
            
            WM_DESTROY => {  // Сообщение о закрытии/уничтожении окна
                // Помещаем в очередь сообщения сообщение о выходе
                // с кодом возврата 0
                PostQuitMessage(0);
                0  // Возвращаем 0 после инициализации закрытия приложения
            }
            
            // Все остальные сообщения передаем стандартной процедуре окна
            // для обработки по умолчанию
            _ => DefWindowProcA(window, message, wparam, lparam),
        }
    }
}

обновим main.rs


#[unsafe(no_mangle)]
fn main() -> ! {

    let (HWND, HDC) = window::create();

    loop {
        if !window::handle_message(HWND) {
            break;
        }
    }

    unsafe { ExitProcess(0) };
}

Теперь у нас должно появится такое окно

Пустой экран, который корректно закрывается
Пустой экран, который корректно закрывается

Теперь нужно загрузить функции OpenGL 3.3, для этого придется слинковаться с opengl32.lib для получения функций, которые дадут адреса функций OpenGL 3.3

#![allow(non_snake_case)]
// Доступ к статичной изменяемой переменной
#![allow(static_mut_refs)]
// rust-analyer - Да, мы будем использовать usafe функции в unsafe функциях
#![allow(unsafe_op_in_unsafe_fn)]

use core::mem;
use windows_sys::Win32::{
    Graphics::OpenGL::wglGetProcAddress,
    System::LibraryLoader::{GetProcAddress, LoadLibraryA},
};

// Используем CVoid как ffi::с_void
pub struct CVoid;

// сдедаем удобные типы
pub type GlBoolean = u8;
pub type GlChar = u8;
pub type GlFloat = f32;
pub type GlEnum = u32;
pub type GlInt = i32;
pub type GlUint = u32;
pub type GlSizeI = i32;
pub type GlSizeIPtr = isize;

/// Определения найдем в каком нибудь gl.h файле
pub const FALSE: GlBoolean = 0;
pub const TRIANGLE_STRIP: GlEnum = 0x0005;
pub const FLOAT: GlEnum = 0x1406;
pub const COLOR: GlEnum = 0x1800;
pub const FRAGMENT_SHADER: GlEnum = 0x8B30;
pub const VERTEX_SHADER: GlEnum = 0x8B31;
pub const COMPILE_STATUS: GlEnum = 0x8B81;
pub const LINK_STATUS: GlEnum = 0x8B82;
pub const ARRAY_BUFFER: GlEnum = 0x8892;
pub const STATIC_DRAW: GlEnum = 0x88E4;

/// Определям порядок наших функций
const GEN_BUFFERS_IDX: u8 = 0;
const GEN_VERTEX_ARRAYS_IDX: u8 = 1;
const BIND_VERTEX_ARRAY_IDX: u8 = 2;
const BIND_BUFFER_IDX: u8 = 3;
const BUFFER_DATA_IDX: u8 = 4;
const CREATE_PROGRAM_IDX: u8 = 5;
const ATTACH_SHADER_IDX: u8 = 6;
const LINK_PROGRAM_IDX: u8 = 7;
const DETACH_SHADER_IDX: u8 = 8;
const CREATE_SHADER_IDX: u8 = 9;
const SHADER_SOURCE_IDX: u8 = 10;
const COMPILE_SHADER_IDX: u8 = 11;
const ENABLE_VERTEX_ATTRIB_ARRAY_IDX: u8 = 12;
const VERTEX_ATTRIB_POINTER_IDX: u8 = 13;
const CLEAR_BUFFERFV_IDX: u8 = 14;
const GET_PROGRAM_IV_IDX: u8 = 15;
const GET_SHADER_IV_IDX: u8 = 16;
const GET_SHADER_INFO_LOG_IDX: u8 = 17;
const WGL_SWAP_INTERVAL_IDX: u8 = 18;
const USE_PROGRAM_IDX: u8 = 19;
const GET_UNIFORM_LOCATION_IDX: u8 = 20;
const UNIFORM_1F_IDX: u8 = 21;
const DRAW_ARRAYS_IDX: u8 = 22;
const UNIFORM_2F_IDX: u8 = 23;

const N_FUNCTIONS: usize = 24;

static mut GL_API: [usize; N_FUNCTIONS] = [0; N_FUNCTIONS];

/*
  Все дальнейшие функции будут построены по следующему алгоритму:
  
    1) Получение адреса функции(числа usize)  
  
      *GL_API.get_unchecked(NAME_IDX as usize),
  
    2) Вызов transmute и интепретация числа, как адрес функции и вызов функции
  
    P.S
      Использование extern "system" означает использование соглашения о
      вызовах функции как в windows os - это обязательно, иначе: Segmentation fault

      mem::transmute::<_, extern "system" fn(params) -> ()>(
          *GL_API.get_unchecked(NAME_IDX as usize),
      )(params)
*/

pub unsafe fn GenBuffers(n: GlSizeI, buffers: *mut GlUint) {
    mem::transmute::<_, extern "system" fn(GlSizeI, *mut GlUint) -> ()>(
        *GL_API.get_unchecked(GEN_BUFFERS_IDX as usize),
    )(n, buffers)
}

pub unsafe fn GenVertexArrays(n: GlSizeI, arrays: *mut GlUint) {
    mem::transmute::<_, extern "system" fn(GlSizeI, *mut GlUint) -> ()>(
        *GL_API.get_unchecked(GEN_VERTEX_ARRAYS_IDX as usize),
    )(n, arrays)
}

pub unsafe fn BindVertexArray(array: GlUint) {
    mem::transmute::<_, extern "system" fn(GlUint) -> ()>(
        *GL_API.get_unchecked(BIND_VERTEX_ARRAY_IDX as usize),
    )(array)
}

pub unsafe fn BindBuffer(target: GlEnum, buffer: GlUint) {
    mem::transmute::<_, extern "system" fn(GlEnum, GlUint) -> ()>(
        *GL_API.get_unchecked(BIND_BUFFER_IDX as usize),
    )(target, buffer)
}

pub unsafe fn BufferData(target: GlEnum, size: GlSizeIPtr, data: *const CVoid, usage: GlEnum) {
    mem::transmute::<_, extern "system" fn(GlEnum, GlSizeIPtr, *const CVoid, GlEnum) -> ()>(
        *GL_API.get_unchecked(BUFFER_DATA_IDX as usize),
    )(target, size, data, usage)
}

pub unsafe fn CreateProgram() -> GlUint {
    mem::transmute::<_, extern "system" fn() -> GlUint>(
        *GL_API.get_unchecked(CREATE_PROGRAM_IDX as usize),
    )()
}

pub unsafe fn AttachShader(program: GlUint, shader: GlUint) {
    mem::transmute::<_, extern "system" fn(GlUint, GlUint) -> ()>(
        *GL_API.get_unchecked(ATTACH_SHADER_IDX as usize),
    )(program, shader)
}

pub unsafe fn LinkProgram(program: GlUint) {
    mem::transmute::<_, extern "system" fn(GlUint) -> ()>(
        *GL_API.get_unchecked(LINK_PROGRAM_IDX as usize),
    )(program)
}

pub unsafe fn DetachShader(program: GlUint, shader: GlUint) {
    mem::transmute::<_, extern "system" fn(GlUint, GlUint) -> ()>(
        *GL_API.get_unchecked(DETACH_SHADER_IDX as usize),
    )(program, shader)
}

#[must_use]
pub unsafe fn CreateShader(kind: GlEnum) -> GlUint {
    mem::transmute::<_, extern "system" fn(GlEnum) -> GlUint>(
        *GL_API.get_unchecked(CREATE_SHADER_IDX as usize),
    )(kind)
}

pub unsafe fn ShaderSource(
    shader: GlUint,
    count: GlSizeI,
    string: *const *const GlChar,
    length: *const GlInt,
) {
    mem::transmute::<_, extern "system" fn(GlUint, GlSizeI, *const *const GlChar, *const GlInt) -> ()>(
        *GL_API.get_unchecked(SHADER_SOURCE_IDX as usize),
    )(shader, count, string, length)
}

pub unsafe fn CompileShader(shader: GlUint) {
    mem::transmute::<_, extern "system" fn(GlUint) -> ()>(
        *GL_API.get_unchecked(COMPILE_SHADER_IDX as usize),
    )(shader)
}

pub unsafe fn EnableVertexAttribArray(index: GlUint) {
    mem::transmute::<_, extern "system" fn(GlUint) -> ()>(
        *GL_API.get_unchecked(ENABLE_VERTEX_ATTRIB_ARRAY_IDX as usize),
    )(index)
}

pub unsafe fn ClearBufferfv(buffer: GlEnum, drawbuffer: GlInt, value: *const GlFloat) {
    mem::transmute::<_, extern "system" fn(GlEnum, GlInt, *const GlFloat) -> ()>(
        *GL_API.get_unchecked(CLEAR_BUFFERFV_IDX as usize),
    )(buffer, drawbuffer, value)
}

pub unsafe fn VertexAttribPointer(
    index: GlUint,
    size: GlInt,
    type_: GlEnum,
    normalized: GlBoolean,
    stride: GlSizeI,
    pointer: *const CVoid,
) {
    mem::transmute::<
        _,
        extern "system" fn(GlUint, GlInt, GlEnum, GlBoolean, GlSizeI, *const CVoid) -> (),
    >(*GL_API.get_unchecked(VERTEX_ATTRIB_POINTER_IDX as usize))(
        index, size, type_, normalized, stride, pointer,
    )
}

pub unsafe fn GetProgramIv(program: GlUint, pname: GlEnum, params: *mut GlInt) {
    mem::transmute::<_, extern "system" fn(GlUint, GlEnum, *mut GlInt) -> ()>(
        *GL_API.get_unchecked(GET_PROGRAM_IV_IDX as usize),
    )(program, pname, params)
}

pub unsafe fn glGetShaderIv(shader: GlUint, sname: GlEnum, params: *mut GlInt) {
    mem::transmute::<_, extern "system" fn(GlUint, GlEnum, *mut GlInt) -> ()>(
        *GL_API.get_unchecked(GET_SHADER_IV_IDX as usize),
    )(shader, sname, params)
}

pub unsafe fn wglSwapIntervalEXT(interval: GlInt) -> GlUint {
    mem::transmute::<_, extern "system" fn(GlInt) -> GlUint>(
        *GL_API.get_unchecked(WGL_SWAP_INTERVAL_IDX as usize),
    )(interval)
}

pub unsafe fn UseProgram(program: GlUint) {
    mem::transmute::<_, extern "system" fn(GlUint) -> ()>(
        *GL_API.get_unchecked(USE_PROGRAM_IDX as usize),
    )(program)
}

#[must_use]
pub unsafe fn GetUniformLocation(program: GlUint, name: *const GlChar) -> GlInt {
    mem::transmute::<_, extern "system" fn(GlUint, *const GlChar) -> GlInt>(
        *GL_API.get_unchecked(GET_UNIFORM_LOCATION_IDX as usize),
    )(program, name)
}

pub unsafe fn Uniform1f(location: GlInt, v0: GlFloat) {
    mem::transmute::<_, extern "system" fn(GlInt, GlFloat)>(
        *GL_API.get_unchecked(UNIFORM_1F_IDX as usize),
    )(location, v0)
}

pub unsafe fn DrawArrays(mode: GlEnum, first: GlInt, count: GlSizeI) {
    mem::transmute::<_, extern "system" fn(GlEnum, GlInt, GlSizeI)>(
        *GL_API.get_unchecked(DRAW_ARRAYS_IDX as usize),
    )(mode, first, count)
}

pub unsafe fn Uniform2f(location: GlInt, v0: GlFloat, v1: GlFloat) {
    mem::transmute::<_, extern "system" fn(GlInt, GlFloat, GlFloat)>(
        *GL_API.get_unchecked(UNIFORM_1F_IDX as usize),
    )(location, v0, v1)
}

/// Главная функция инициализация gl функций
pub fn init() {
    const LOAD_DESCRIPTOR: [(u8, &'static str); N_FUNCTIONS] = [
        (GEN_BUFFERS_IDX, "glGenBuffers\0"),
        (GEN_VERTEX_ARRAYS_IDX, "glGenVertexArrays\0"),
        (BIND_VERTEX_ARRAY_IDX, "glBindVertexArray\0"),
        (BIND_BUFFER_IDX, "glBindBuffer\0"),
        (BUFFER_DATA_IDX, "glBufferData\0"),
        (CREATE_PROGRAM_IDX, "glCreateProgram\0"),
        (ATTACH_SHADER_IDX, "glAttachShader\0"),
        (LINK_PROGRAM_IDX, "glLinkProgram\0"),
        (DETACH_SHADER_IDX, "glDetachShader\0"),
        (CREATE_SHADER_IDX, "glCreateShader\0"),
        (SHADER_SOURCE_IDX, "glShaderSource\0"),
        (COMPILE_SHADER_IDX, "glCompileShader\0"),
        (ENABLE_VERTEX_ATTRIB_ARRAY_IDX,"glEnableVertexAttribArray\0",),
        (VERTEX_ATTRIB_POINTER_IDX, "glVertexAttribPointer\0"),
        (CLEAR_BUFFERFV_IDX, "glClearBufferfv\0"),
        (GET_PROGRAM_IV_IDX, "glGetProgramiv\0"),
        (GET_SHADER_IV_IDX, "glGetShaderiv\0"),
        (GET_SHADER_INFO_LOG_IDX, "glGetShaderInfoLog\0"),
        (WGL_SWAP_INTERVAL_IDX, "wglSwapIntervalEXT\0"),
        (USE_PROGRAM_IDX, "glUseProgram\0"),
        (GET_UNIFORM_LOCATION_IDX, "glGetUniformLocation\0"),
        (UNIFORM_1F_IDX, "glUniform1f\0"),
        (DRAW_ARRAYS_IDX, "glDrawArrays\0"),
        (UNIFORM_2F_IDX, "glUniform2f\0"),
    ];

    /// Загружаем .dll
    let handle = unsafe { LoadLibraryA("Opengl32.dll\0".as_ptr() as *const u8) };

    for i in 0..LOAD_DESCRIPTOR.len() {
            let (index, name) = LOAD_DESCRIPTOR[i];
        unsafe {

            // сначала получаем функции с помощью GetProcAddress OpneGL 1.0+, потом
            // используем wglGetProcAddress, если функция из более современного стандарта

            let addr = GetProcAddress(handle, name.as_ptr() as *const u8)
                .or_else(|| wglGetProcAddress(name.as_ptr() as *const u8))
                .unwrap() as usize;

            /// Записываем результат в глобальную статическую переменную
            /// Естественно это unsafe операция
            GL_API[index as usize] = addr;
        }
    }
}

pub fn program_from_shaders(vtx_shader: GlUint, frag_shader: GlUint) -> GlUint {
    let program_id;
  
    //Можем полуить статус создания программы
    //let mut success: GlInt = 1;

    unsafe {
        program_id = CreateProgram();
        AttachShader(program_id, vtx_shader);
        AttachShader(program_id, frag_shader);
        LinkProgram(program_id);
        DetachShader(program_id, vtx_shader);
        DetachShader(program_id, frag_shader);
        //GetProgramIv(program_id, LINK_STATUS, &mut success);
    }

    program_id
}

pub fn shader_from_source(shader_source: &str, kind: GlEnum) -> GlUint {
    let shader_id;

    //Можем полуить статус создания программы
    //let mut success: GlInt = 1;
    unsafe {
        shader_id = CreateShader(kind);
        ShaderSource(shader_id, 1, &shader_source.as_ptr(), 0 as *const _);
        CompileShader(shader_id);
        //glGetShaderIv(shader_id, COMPILE_STATUS, &mut success);
    }

    shader_id
}

Осталось лишь найти красивый и подходящий шейдер, обычно кучу красивых шейдеров можно найти на сайте: https://www.shadertoy.com/, но их потребуется немного переписать/дописать под оригинальный glsl

Код вершинного шейдера:

#version 330 core

layout(location = 0) in vec3 Pos;

void main() {
    gl_Position = vec4(Pos, 1.0);
}

Фрагментный:

#version 330

uniform float iTime;

void main() {

    // Разрешение экрана
    vec2 iResolution = vec2(720.0, 720.0);
    // нормализованные координаты
    vec2 uv = gl_FragCoord.xy/iResolution.xy;

    // смещаем, так, чтобы каринка была в центре
    uv -= 0.5;
    uv *= 3.0;
    uv.x *= (iResolution.x/iResolution.y);

    // результирующий цвет
    vec3 color = vec3(0.0, 0.0, 0.0);

    // хотим нарисовать 10 шариков
    for(int i = 0; i < 10; i++) {

        // Возьмем какой-нибудь угол между шариками
        float angle = float(i) * 2.0 * 3.14159 / 5.0;

        // Посчитаем центр и добавим смещения
        vec2 center = vec2(
            cos((angle + iTime) * 0.5) * 0.5,
            sin((angle + iTime) * 0.5) * 0.5
        );

        // Считаем длину от нашего центра
        float d = length(uv - center);
        // Можно считать эту операцию за вычисления интенсивности света
        d = 0.02 / d;

        // Здесь можно поиграться с цветами
        vec3 circleColor = vec3(
            0.3 * sin(iTime/10.0 * float(i)) + 0.1,
            0.3 * sin(iTime/10.0 * float(i)) + 0.2,
            0.3 * sin(iTime/10.0 * float(i)) + 0.3
        );

        // Записываем результат
        color += circleColor * d;
    }

    gl_FragColor = vec4(color, 1.0);
}

Теперь переделаем наш main.rs

#![no_main]
#![no_std]
// Обязательная строка! Без нее Crinkler не сможет сделать стандартное приложение с окном
#![windows_subsystem = "windows"]

use music::play;
use windows_sys::Win32::{
  Graphics::OpenGL::SwapBuffers, 
  System::{
    Memory::{
      GetProcessHeap, 
      HeapAlloc
    }, 
    Threading::ExitProcess
  }
};

mod gl;
mod window;

#[unsafe(no_mangle)]
fn main() -> ! {

    let (HWND, HDC) = window::create();
    gl::init();

    unsafe {

        // concat! Во время компиляции соединит две строки. Добавим заканчивающий \0
        let vertex_shader_src: &'static str = concat!(include_str!("../shaders/vs.glsl"), "\0");
        let frag_shader_src: &'static str = concat!(include_str!("../shaders/fs.glsl"), "\0");

        /// Два треугольника отрисовываем на весь экран, 
        //  последняя вершина соединяется с первой из за TRIANGLE_STRIP
        let vertex_coords: [[gl::GlFloat; 3]; 4] =
        [
            [-1.0, -1.0, 0.0],
            [1.0, -1.0, 0.0],
            [-1.0, 1.0, 0.0],
            [1.0, 1.0, 0.0]
        ];

        let vertex_shader = gl::shader_from_source(vertex_shader_src, gl::VERTEX_SHADER);
        let frag_shader = gl::shader_from_source(frag_shader_src, gl::FRAGMENT_SHADER);
        let shader_prog = gl::program_from_shaders(vertex_shader, frag_shader);

        // OpenGL setup
        let mut vertex_buffer_id: gl::GlUint = 0;
        let mut vertex_array_id: gl::GlUint = 0;

        // Получаем 1 буффер для вершин
        gl::GenBuffers(1, &mut vertex_buffer_id);
        // Получаем 1 массив вершин
        gl::GenVertexArrays(1, &mut vertex_array_id);
        
        // Привязываем Vertex Array Object (VAO) для хранения конфигурации вершинных атрибутов
        gl::BindVertexArray(vertex_array_id);
        
        // Привязываем Vertex Buffer Object (VBO) для работы с ним
        gl::BindBuffer(gl::ARRAY_BUFFER, vertex_buffer_id);
        
        // Загружаем данные вершин в видеопамять
        gl::BufferData(
            gl::ARRAY_BUFFER, // Тип буфера: вершинный буфер
            mem::size_of::<gl::GlFloat>() as isize * 12,  
            // Размер данных: 12 float'ов (4 вершины × 3 координаты)
            vtx_coords.as_ptr() as *const gl::CVoid, // Указатель на массив с вершин
            gl::STATIC_DRAW, // Режим использования: данные не будут изменяться
        );
        
        // Включаем вершинный атрибут с индексом 0
        gl::EnableVertexAttribArray(0);
        
        // Настраиваем формат и расположение вершинных данных
        gl::VertexAttribPointer(
            0, //layout(location = 0) в шейдере)
            3, // Количество компонентов на вершину (x, y, z)
            gl::FLOAT, // Тип данных каждого компонента
            gl::FALSE, // Нормализация не требуется
            3 * mem::size_of::<gl::GlFloat>() as gl::GlInt,  
            // Шаг между вершинами (12 байт для 3×float)
            0 as *const gl::CVoid,              
            // Смещение до первых данных (0 - данные начинаются с начала буфера)
        );

        // Для работы со временем предпочтительнее использовать специализированные функции,
        // но Instant::now() доступен только в стандартной библиотеке (std), а не в core.
        // В чисто no_std окружении необходимо использовать средства предоставляемые WinAPI
        let mut tick = 0.0;

        loop {

            if !window::handle_message(HWND) {
                break;
            }

            /// Цвет очистки
            let rgba = &[0.0, 0.0, 0.0, 0.0];

            /// Очищаем экран
            gl::ClearBufferfv(gl::COLOR, 0, rgba.as_ptr());
            /// Используем уже скомпилированную программу
            gl::UseProgram(shader_prog);
            // Получаем Uniform 
            let tick_loc = gl::GetUniformLocation(shader_prog, "iTime\0".as_ptr());
            // Обновляем значение Uniform
            gl::Uniform1f(tick_loc, tick);
            // Bind вершин
            gl::BindVertexArray(vertex_array_id);
            // Рисуем
            gl::DrawArrays(gl::TRIANGLE_STRIP, 0, 4);
            // Обновляем буффер кадра
            SwapBuffers(HDC);

            tick += 0.01;

        }

        ExitProcess(0);
    }
}

use core::panic::PanicInfo;
#[panic_handler]
fn panic(_: &PanicInfo<'_>) -> ! {
    loop {}
}

На этом всё! Если вы спросите, зачем использовать Rust для демосцены и кому вообще нужны эти интро — я процитирую Кейва Джонсона:

❝ Наука не терпит вопроса «почему». Главный вопрос — «почему бы и нет». Почему наши исследования так опасны? Почему бы не заняться чем-нибудь менее опасным? Действительно, почему бы не изобрести безопасную дверь, которая не ударит вас по заднице по дороге отсюда, потому что вы уволены! Да, вы! Соберите свои вещи. На выход. К парковке. В машину. Прощайте. ❞

🎮 Portal 2 🔗 https://citaty.info/quote/533173

Весь код можно просмотреть у меня в репозитории: https://github.com/olejaaaaaaaa/4kb, но там код немного изменен

Теги:
Хабы:
+8
Комментарии9

Публикации

Ближайшие события