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