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

от автора

В данной статье будет рассказано, как можно довольно просто сделать маленькое интро, используя язык 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, но там код немного изменен


ссылка на оригинал статьи https://habr.com/ru/articles/942630/