В данной статье будет рассказано, как можно довольно просто сделать маленькое интро, используя язык Rust. Будет очень много Unsafe и WinAPI кода, а так же предполагается, что читатель уже хоть немного знаком с OpenGL 3.3
Вот что должно получиться на выходе:
Подготовка инструментов
Необходимое ПО:
-
Nightly-версия Rust (для нестабильных функций и unsafe)
rustup toolchain install nightly -
Установка целевой архитектуры
rustup target add i686-pc-windows-msvc
-
Crinkler — компрессирующий линкер и упаковщик
-
Скачать можно здесь: github.com/runestubbe/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/
Добавить комментарий