diff options
author | pommicket <pommicket@gmail.com> | 2022-12-11 11:39:36 -0500 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2022-12-11 11:39:36 -0500 |
commit | 1d06c1f0d3fa058b67e8fe999c654d10d7f46896 (patch) | |
tree | f4aabd7d15b8a57ab7b20462e28fa7f292b02cc4 /src |
shaders
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 56 | ||||
-rw-r--r-- | src/sdl.rs | 1116 | ||||
-rw-r--r-- | src/win.rs | 866 |
3 files changed, 2038 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..fdd2f54 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,56 @@ +mod sdl; +pub mod win; + +fn try_main() -> Result<(), String> { + let mut window = win::Window::new("AutoSDF", 1280, 720, true) + .map_err(|e| format!("Error creating window: {e}"))?; + + let program = window.create_program( + "attribute vec2 v_pos; + void main() { + gl_Position = vec4(v_pos, 0.0, 1.0); + }", + "void main() { + o_color = vec4(1.0, 0.0, 0.0, 1.0); + }" + ).map_err(|e| format!("Error compiling shader:\n{e}"))?; + + let mut buffer = window.create_buffer(); + let data: &[[f32; 2]] = &[ + [-1.0, -1.0], + [1.0, -1.0], + [1.0, 1.0], + [-1.0, -1.0], + [1.0, 1.0], + [-1.0, 1.0], + ]; + window.set_buffer_data(&mut buffer, data); + let mut array = window.create_vertex_array(buffer, &program); + window.array_attrib2f(&mut array, "v_pos", 0); + + 'mainloop: loop { + while let Some(event) = window.next_event() { + use win::Event::*; + match event { + Quit => break 'mainloop, + _ => {}, + } + println!("{event:?}"); + } + + window.clear_screen(win::ColorF32::BLACK); + window.use_program(&program); + + window.draw_array(&array); + + window.swap(); + } + + Ok(()) +} + +fn main() { + if let Err(e) = try_main() { + win::display_error_message(&e); + } +} diff --git a/src/sdl.rs b/src/sdl.rs new file mode 100644 index 0000000..11873aa --- /dev/null +++ b/src/sdl.rs @@ -0,0 +1,1116 @@ +#![allow(non_camel_case_types)] +#![allow(dead_code)] +#![allow(non_snake_case)] + +use std::ffi::{c_char, c_float, c_int, c_void, CStr, CString}; +use std::mem; + +// not a real type, just here to differentiate *mut () from SDL_GLContext +#[repr(C)] +pub struct SDL_GLContextData(u8); +pub type SDL_bool = c_int; +pub type SDL_EventType = u32; +#[repr(C)] +pub struct SDL_Window(u8); +pub type SDL_SysWMmsg = c_void; +pub type SDL_Keycode = u32; +pub type SDL_Scancode = i32; +pub type SDL_JoystickID = i32; +pub type SDL_JoystickPowerLevel = c_int; +pub type SDL_GestureID = i64; +pub type SDL_TouchID = i64; +pub type SDL_FingerID = i64; +pub type SDL_AudioFormat = u16; +pub type SDL_AudioCallback = extern "C" fn(*mut c_void, *mut u8, c_int); +pub type SDL_AudioDeviceID = u32; +pub type SDL_WindowEventID = c_int; +pub type SDL_GLContext = *mut SDL_GLContextData; +pub type SDL_GLattr = c_int; + +pub const SDL_WINDOWPOS_UNDEFINED: c_int = 0x1FFF0000; +pub const SDL_QUIT: SDL_EventType = 0x100; +pub const SDL_APP_TERMINATING: SDL_EventType = 0x101; +pub const SDL_APP_LOWMEMORY: SDL_EventType = 0x102; +pub const SDL_APP_WILLENTERBACKGROUND: SDL_EventType = 0x103; +pub const SDL_APP_DIDENTERBACKGROUND: SDL_EventType = 0x104; +pub const SDL_APP_WILLENTERFOREGROUND: SDL_EventType = 0x105; +pub const SDL_APP_DIDENTERFOREGROUND: SDL_EventType = 0x106; +pub const SDL_LOCALECHANGED: SDL_EventType = 0x107; +pub const SDL_DISPLAYEVENT: SDL_EventType = 0x150; +pub const SDL_WINDOWEVENT: SDL_EventType = 0x200; +pub const SDL_SYSWMEVENT: SDL_EventType = 0x201; +pub const SDL_KEYDOWN: SDL_EventType = 0x300; +pub const SDL_KEYUP: SDL_EventType = 0x301; +pub const SDL_TEXTEDITING: SDL_EventType = 0x302; +pub const SDL_TEXTINPUT: SDL_EventType = 0x303; +pub const SDL_KEYMAPCHANGED: SDL_EventType = 0x304; +pub const SDL_TEXTEDITING_EXT: SDL_EventType = 0x305; +pub const SDL_MOUSEMOTION: SDL_EventType = 0x400; +pub const SDL_MOUSEBUTTONDOWN: SDL_EventType = 0x401; +pub const SDL_MOUSEBUTTONUP: SDL_EventType = 0x402; +pub const SDL_MOUSEWHEEL: SDL_EventType = 0x403; +pub const SDL_JOYAXISMOTION: SDL_EventType = 0x600; +pub const SDL_JOYBALLMOTION: SDL_EventType = 0x601; +pub const SDL_JOYHATMOTION: SDL_EventType = 0x602; +pub const SDL_JOYBUTTONDOWN: SDL_EventType = 0x603; +pub const SDL_JOYBUTTONUP: SDL_EventType = 0x604; +pub const SDL_JOYDEVICEADDED: SDL_EventType = 0x605; +pub const SDL_JOYDEVICEREMOVED: SDL_EventType = 0x606; +pub const SDL_JOYBATTERYUPDATED: SDL_EventType = 0x607; +pub const SDL_CONTROLLERAXISMOTION: SDL_EventType = 0x650; +pub const SDL_CONTROLLERBUTTONDOWN: SDL_EventType = 0x651; +pub const SDL_CONTROLLERBUTTONUP: SDL_EventType = 0x652; +pub const SDL_CONTROLLERDEVICEADDED: SDL_EventType = 0x653; +pub const SDL_CONTROLLERDEVICEREMOVED: SDL_EventType = 0x654; +pub const SDL_CONTROLLERDEVICEREMAPPED: SDL_EventType = 0x655; +pub const SDL_CONTROLLERTOUCHPADDOWN: SDL_EventType = 0x656; +pub const SDL_CONTROLLERTOUCHPADMOTION: SDL_EventType = 0x657; +pub const SDL_CONTROLLERTOUCHPADUP: SDL_EventType = 0x658; +pub const SDL_CONTROLLERSENSORUPDATE: SDL_EventType = 0x659; +pub const SDL_FINGERDOWN: SDL_EventType = 0x700; +pub const SDL_FINGERUP: SDL_EventType = 0x701; +pub const SDL_FINGERMOTION: SDL_EventType = 0x702; +pub const SDL_DOLLARGESTURE: SDL_EventType = 0x800; +pub const SDL_DOLLARRECORD: SDL_EventType = 0x801; +pub const SDL_MULTIGESTURE: SDL_EventType = 0x802; +pub const SDL_CLIPBOARDUPDATE: SDL_EventType = 0x900; +pub const SDL_DROPFILE: SDL_EventType = 0x1000; +pub const SDL_DROPTEXT: SDL_EventType = 0x1001; +pub const SDL_DROPBEGIN: SDL_EventType = 0x1002; +pub const SDL_DROPCOMPLETE: SDL_EventType = 0x1003; +pub const SDL_AUDIODEVICEADDED: SDL_EventType = 0x1100; +pub const SDL_AUDIODEVICEREMOVED: SDL_EventType = 0x1101; +pub const SDL_SENSORUPDATE: SDL_EventType = 0x1200; +pub const SDL_RENDER_TARGETS_RESET: SDL_EventType = 0x2000; +pub const SDL_RENDER_DEVICE_RESET: SDL_EventType = 0x2001; +pub const SDL_POLLSENTINEL: SDL_EventType = 0x7F00; +pub const SDL_USEREVENT: SDL_EventType = 0x8000; + +pub const SDL_WINDOWEVENT_SHOWN: SDL_WindowEventID = 1; +pub const SDL_WINDOWEVENT_HIDDEN: SDL_WindowEventID = 2; +pub const SDL_WINDOWEVENT_EXPOSED: SDL_WindowEventID = 3; +pub const SDL_WINDOWEVENT_MOVED: SDL_WindowEventID = 4; +pub const SDL_WINDOWEVENT_RESIZED: SDL_WindowEventID = 5; +pub const SDL_WINDOWEVENT_SIZE_CHANGED: SDL_WindowEventID = 6; +pub const SDL_WINDOWEVENT_MINIMIZED: SDL_WindowEventID = 7; +pub const SDL_WINDOWEVENT_MAXIMIZED: SDL_WindowEventID = 8; +pub const SDL_WINDOWEVENT_RESTORED: SDL_WindowEventID = 9; +pub const SDL_WINDOWEVENT_ENTER: SDL_WindowEventID = 10; +pub const SDL_WINDOWEVENT_LEAVE: SDL_WindowEventID = 11; +pub const SDL_WINDOWEVENT_FOCUS_GAINED: SDL_WindowEventID = 12; +pub const SDL_WINDOWEVENT_FOCUS_LOST: SDL_WindowEventID = 13; +pub const SDL_WINDOWEVENT_CLOSE: SDL_WindowEventID = 14; +pub const SDL_WINDOWEVENT_TAKE_FOCUS: SDL_WindowEventID = 15; +pub const SDL_WINDOWEVENT_HIT_TEST: SDL_WindowEventID = 16; +pub const SDL_WINDOWEVENT_ICCPROF_CHANGED: SDL_WindowEventID = 17; +pub const SDL_WINDOWEVENT_DISPLAY_CHANGED: SDL_WindowEventID = 18; + +pub const SDL_INIT_TIMER: u32 = 0x00000001; +pub const SDL_INIT_AUDIO: u32 = 0x00000010; +pub const SDL_INIT_VIDEO: u32 = 0x00000020; +pub const SDL_INIT_JOYSTICK: u32 = 0x00000200; +pub const SDL_INIT_HAPTIC: u32 = 0x00001000; +pub const SDL_INIT_GAMECONTROLLER: u32 = 0x00002000; +pub const SDL_INIT_EVENTS: u32 = 0x00004000; +pub const SDL_INIT_SENSOR: u32 = 0x00008000; +pub const SDL_INIT_EVERYTHING: u32 = SDL_INIT_TIMER + | SDL_INIT_AUDIO + | SDL_INIT_VIDEO + | SDL_INIT_EVENTS + | SDL_INIT_JOYSTICK + | SDL_INIT_HAPTIC + | SDL_INIT_GAMECONTROLLER + | SDL_INIT_SENSOR; + +pub const SDL_WINDOW_FULLSCREEN: u32 = 0x00000001; +pub const SDL_WINDOW_OPENGL: u32 = 0x00000002; +pub const SDL_WINDOW_SHOWN: u32 = 0x00000004; +pub const SDL_WINDOW_HIDDEN: u32 = 0x00000008; +pub const SDL_WINDOW_BORDERLESS: u32 = 0x00000010; +pub const SDL_WINDOW_RESIZABLE: u32 = 0x00000020; +pub const SDL_WINDOW_MINIMIZED: u32 = 0x00000040; +pub const SDL_WINDOW_MAXIMIZED: u32 = 0x00000080; +pub const SDL_WINDOW_MOUSE_GRABBED: u32 = 0x00000100; +pub const SDL_WINDOW_INPUT_FOCUS: u32 = 0x00000200; +pub const SDL_WINDOW_MOUSE_FOCUS: u32 = 0x00000400; +pub const SDL_WINDOW_FULLSCREEN_DESKTOP: u32 = SDL_WINDOW_FULLSCREEN | 0x00001000; +pub const SDL_WINDOW_FOREIGN: u32 = 0x00000800; +pub const SDL_WINDOW_ALLOW_HIGHDPI: u32 = 0x00002000; +pub const SDL_WINDOW_MOUSE_CAPTURE: u32 = 0x00004000; +pub const SDL_WINDOW_ALWAYS_ON_TOP: u32 = 0x00008000; +pub const SDL_WINDOW_SKIP_TASKBAR: u32 = 0x00010000; +pub const SDL_WINDOW_UTILITY: u32 = 0x00020000; +pub const SDL_WINDOW_TOOLTIP: u32 = 0x00040000; +pub const SDL_WINDOW_POPUP_MENU: u32 = 0x00080000; +pub const SDL_WINDOW_KEYBOARD_GRABBED: u32 = 0x00100000; +pub const SDL_WINDOW_VULKAN: u32 = 0x10000000; +pub const SDL_WINDOW_METAL: u32 = 0x20000000; + +pub const fn sdl_button_mask(x: u8) -> u32 { + 1u32 << (x - 1) +} + +pub const SDL_BUTTON_LEFT: u8 = 1; +pub const SDL_BUTTON_MIDDLE: u8 = 2; +pub const SDL_BUTTON_RIGHT: u8 = 3; +pub const SDL_BUTTON_X1: u8 = 4; +pub const SDL_BUTTON_X2: u8 = 5; +pub const SDL_BUTTON_LMASK: u32 = sdl_button_mask(SDL_BUTTON_LEFT); +pub const SDL_BUTTON_MMASK: u32 = sdl_button_mask(SDL_BUTTON_MIDDLE); +pub const SDL_BUTTON_RMASK: u32 = sdl_button_mask(SDL_BUTTON_RIGHT); +pub const SDL_BUTTON_X1MASK: u32 = sdl_button_mask(SDL_BUTTON_X1); +pub const SDL_BUTTON_X2MASK: u32 = sdl_button_mask(SDL_BUTTON_X2); + +pub const SDL_GL_RED_SIZE: SDL_GLattr = 0; +pub const SDL_GL_GREEN_SIZE: SDL_GLattr = 1; +pub const SDL_GL_BLUE_SIZE: SDL_GLattr = 2; +pub const SDL_GL_ALPHA_SIZE: SDL_GLattr = 3; +pub const SDL_GL_BUFFER_SIZE: SDL_GLattr = 4; +pub const SDL_GL_DOUBLEBUFFER: SDL_GLattr = 5; +pub const SDL_GL_DEPTH_SIZE: SDL_GLattr = 6; +pub const SDL_GL_STENCIL_SIZE: SDL_GLattr = 7; +pub const SDL_GL_ACCUM_RED_SIZE: SDL_GLattr = 8; +pub const SDL_GL_ACCUM_GREEN_SIZE: SDL_GLattr = 9; +pub const SDL_GL_ACCUM_BLUE_SIZE: SDL_GLattr = 10; +pub const SDL_GL_ACCUM_ALPHA_SIZE: SDL_GLattr = 11; +pub const SDL_GL_STEREO: SDL_GLattr = 12; +pub const SDL_GL_MULTISAMPLEBUFFERS: SDL_GLattr = 13; +pub const SDL_GL_MULTISAMPLESAMPLES: SDL_GLattr = 14; +pub const SDL_GL_ACCELERATED_VISUAL: SDL_GLattr = 15; +pub const SDL_GL_RETAINED_BACKING: SDL_GLattr = 16; +pub const SDL_GL_CONTEXT_MAJOR_VERSION: SDL_GLattr = 17; +pub const SDL_GL_CONTEXT_MINOR_VERSION: SDL_GLattr = 18; +pub const SDL_GL_CONTEXT_DEBUG_FLAG: i32 = 0x0001; +pub const SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG: i32 = 0x0002; +pub const SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG: i32 = 0x0004; +pub const SDL_GL_CONTEXT_RESET_ISOLATION_FLAG: i32 = 0x0008; +pub const SDL_GL_CONTEXT_EGL: SDL_GLattr = 19; +pub const SDL_GL_CONTEXT_FLAGS: SDL_GLattr = 20; +pub const SDL_GL_CONTEXT_PROFILE_MASK: SDL_GLattr = 21; +pub const SDL_GL_SHARE_WITH_CURRENT_CONTEXT: SDL_GLattr = 22; +pub const SDL_GL_FRAMEBUFFER_SRGB_CAPABLE: SDL_GLattr = 23; +pub const SDL_GL_CONTEXT_RELEASE_BEHAVIOR: SDL_GLattr = 24; +pub const SDL_GL_CONTEXT_RESET_NOTIFICATION: SDL_GLattr = 25; +pub const SDL_GL_CONTEXT_NO_ERROR: SDL_GLattr = 26; +pub const SDL_GL_FLOATBUFFERS: SDL_GLattr = 27; + +pub const SDL_MESSAGEBOX_ERROR: u32 = 0x00000010; +pub const SDL_MESSAGEBOX_WARNING: u32 = 0x00000020; +pub const SDL_MESSAGEBOX_INFORMATION: u32 = 0x00000040; + +pub const SDL_AUDIO_ALLOW_FREQUENCY_CHANGE: c_int = 0x00000001; +pub const SDL_AUDIO_ALLOW_FORMAT_CHANGE: c_int = 0x00000002; +pub const SDL_AUDIO_ALLOW_CHANNELS_CHANGE: c_int = 0x00000004; +pub const SDL_AUDIO_ALLOW_SAMPLES_CHANGE: c_int = 0x00000008; + +pub const AUDIO_U8: u16 = 0x0008; +pub const AUDIO_S8: u16 = 0x8008; +pub const AUDIO_U16LSB: u16 = 0x0010; +pub const AUDIO_S16LSB: u16 = 0x8010; +pub const AUDIO_U16MSB: u16 = 0x1010; +pub const AUDIO_S16MSB: u16 = 0x9010; +pub const AUDIO_U16: u16 = AUDIO_U16LSB; +pub const AUDIO_S16: u16 = AUDIO_S16LSB; +pub const AUDIO_S32LSB: u16 = 0x8020; +pub const AUDIO_S32MSB: u16 = 0x9020; +pub const AUDIO_S32: u16 = AUDIO_S32LSB; +pub const AUDIO_F32LSB: u16 = 0x8120; +pub const AUDIO_F32MSB: u16 = 0x9120; +pub const AUDIO_F32: u16 = AUDIO_F32LSB; + +// NOTE: ideally we wouldn't need Copy on all of these +// but otherwise we wouldn't be able to put them in a union + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_CommonEvent { + pub r#type: u32, + pub timestamp: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_DisplayEvent { + pub r#type: u32, + pub timestamp: u32, + pub display: u32, + pub event: u8, + pub padding1: u8, + pub padding2: u8, + pub padding3: u8, + pub data1: i32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_WindowEvent { + pub r#type: u32, + pub timestamp: u32, + pub windowID: u32, + pub event: u8, + pub padding1: u8, + pub padding2: u8, + pub padding3: u8, + pub data1: i32, + pub data2: i32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_Keysym { + pub scancode: SDL_Scancode, + pub sym: SDL_Keycode, + pub r#mod: u16, + pub unused: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_KeyboardEvent { + pub r#type: u32, + pub timestamp: u32, + pub windowID: u32, + pub state: u8, + pub repeat: u8, + pub padding2: u8, + pub padding3: u8, + pub keysym: SDL_Keysym, +} + +pub const SDL_TEXTEDITINGEVENT_TEXT_SIZE: usize = 32; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_TextEditingEvent { + pub r#type: u32, + pub timestamp: u32, + pub windowID: u32, + pub text: [c_char; SDL_TEXTEDITINGEVENT_TEXT_SIZE], + pub start: i32, + pub length: i32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_TextEditingExtEvent { + pub r#type: u32, + pub timestamp: u32, + pub windowID: u32, + pub text: *mut c_char, + pub start: i32, + pub length: i32, +} + +pub const SDL_TEXTINPUTEVENT_TEXT_SIZE: usize = 32; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_TextInputEvent { + pub r#type: u32, + pub timestamp: u32, + pub windowID: u32, + pub text: [c_char; SDL_TEXTINPUTEVENT_TEXT_SIZE], +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_MouseMotionEvent { + pub r#type: u32, + pub timestamp: u32, + pub windowID: u32, + pub which: u32, + pub state: u32, + pub x: i32, + pub y: i32, + pub xrel: i32, + pub yrel: i32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_MouseButtonEvent { + pub r#type: u32, + pub timestamp: u32, + pub windowID: u32, + pub which: u32, + pub button: u8, + pub state: u8, + pub clicks: u8, + pub padding1: u8, + pub x: i32, + pub y: i32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_MouseWheelEvent { + pub r#type: u32, + pub timestamp: u32, + pub windowID: u32, + pub which: u32, + pub x: i32, + pub y: i32, + pub direction: u32, + pub preciseX: c_float, + pub preciseY: c_float, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_JoyAxisEvent { + pub r#type: u32, + pub timestamp: u32, + pub which: SDL_JoystickID, + pub axis: u8, + pub padding1: u8, + pub padding2: u8, + pub padding3: u8, + pub value: i16, + pub padding4: u16, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_JoyBallEvent { + pub r#type: u32, + pub timestamp: u32, + pub which: SDL_JoystickID, + pub ball: u8, + pub padding1: u8, + pub padding2: u8, + pub padding3: u8, + pub xrel: i16, + pub yrel: i16, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_JoyHatEvent { + pub r#type: u32, + pub timestamp: u32, + pub which: SDL_JoystickID, + pub hat: u8, + pub value: u8, + pub padding1: u8, + pub padding2: u8, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_JoyButtonEvent { + pub r#type: u32, + pub timestamp: u32, + pub which: SDL_JoystickID, + pub button: u8, + pub state: u8, + pub padding1: u8, + pub padding2: u8, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_JoyDeviceEvent { + pub r#type: u32, + pub timestamp: u32, + pub which: i32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_JoyBatteryEvent { + pub r#type: u32, + pub timestamp: u32, + pub which: SDL_JoystickID, + pub level: SDL_JoystickPowerLevel, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_ControllerAxisEvent { + pub r#type: u32, + pub timestamp: u32, + pub which: SDL_JoystickID, + pub axis: u8, + pub padding1: u8, + pub padding2: u8, + pub padding3: u8, + pub value: i16, + pub padding4: u16, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_ControllerButtonEvent { + pub r#type: u32, + pub timestamp: u32, + pub which: SDL_JoystickID, + pub button: u8, + pub state: u8, + pub padding1: u8, + pub padding2: u8, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_ControllerDeviceEvent { + pub r#type: u32, + pub timestamp: u32, + pub which: i32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_ControllerTouchpadEvent { + pub r#type: u32, + pub timestamp: u32, + pub which: SDL_JoystickID, + pub touchpad: i32, + pub finger: i32, + pub x: c_float, + pub y: c_float, + pub pressure: c_float, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_ControllerSensorEvent { + pub r#type: u32, + pub timestamp: u32, + pub which: SDL_JoystickID, + pub sensor: i32, + pub data: [c_float; 3], +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_AudioDeviceEvent { + pub r#type: u32, + pub timestamp: u32, + pub which: u32, + pub iscapture: u8, + pub padding1: u8, + pub padding2: u8, + pub padding3: u8, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_TouchFingerEvent { + pub r#type: u32, + pub timestamp: u32, + pub touchId: SDL_TouchID, + pub fingerId: SDL_FingerID, + pub x: c_float, + pub y: c_float, + pub dx: c_float, + pub dy: c_float, + pub pressure: c_float, + pub windowID: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_MultiGestureEvent { + pub r#type: u32, + pub timestamp: u32, + pub touchId: SDL_TouchID, + pub dTheta: c_float, + pub dDist: c_float, + pub x: c_float, + pub y: c_float, + pub numFingers: u16, + pub padding: u16, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_DollarGestureEvent { + pub r#type: u32, + pub timestamp: u32, + pub touchId: SDL_TouchID, + pub gestureId: SDL_GestureID, + pub numFingers: u32, + pub error: c_float, + pub x: c_float, + pub y: c_float, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_DropEvent { + pub r#type: u32, + pub timestamp: u32, + pub file: *mut c_char, + pub windowID: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_SensorEvent { + pub r#type: u32, + pub timestamp: u32, + pub which: i32, + pub data: [c_float; 6], +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_QuitEvent { + pub r#type: u32, + pub timestamp: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_OSEvent { + pub r#type: u32, + pub timestamp: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_UserEvent { + pub r#type: u32, + pub timestamp: u32, + pub windowID: u32, + pub code: i32, + pub data1: *mut c_void, + pub data2: *mut c_void, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SDL_SysWMEvent { + pub r#type: u32, + pub timestamp: u32, + pub msg: *mut SDL_SysWMmsg, +} + +#[repr(C)] +pub union SDL_Event { + pub r#type: u32, + pub common: SDL_CommonEvent, + pub display: SDL_DisplayEvent, + pub window: SDL_WindowEvent, + pub key: SDL_KeyboardEvent, + pub edit: SDL_TextEditingEvent, + pub editExt: SDL_TextEditingExtEvent, + pub text: SDL_TextInputEvent, + pub motion: SDL_MouseMotionEvent, + pub button: SDL_MouseButtonEvent, + pub wheel: SDL_MouseWheelEvent, + pub jaxis: SDL_JoyAxisEvent, + pub jball: SDL_JoyBallEvent, + pub jhat: SDL_JoyHatEvent, + pub jbutton: SDL_JoyButtonEvent, + pub jdevice: SDL_JoyDeviceEvent, + pub jbattery: SDL_JoyBatteryEvent, + pub caxis: SDL_ControllerAxisEvent, + pub cbutton: SDL_ControllerButtonEvent, + pub cdevice: SDL_ControllerDeviceEvent, + pub ctouchpad: SDL_ControllerTouchpadEvent, + pub csensor: SDL_ControllerSensorEvent, + pub adevice: SDL_AudioDeviceEvent, + pub sensor: SDL_SensorEvent, + pub quit: SDL_QuitEvent, + pub user: SDL_UserEvent, + pub syswm: SDL_SysWMEvent, + pub tfinger: SDL_TouchFingerEvent, + pub mgesture: SDL_MultiGestureEvent, + pub dgesture: SDL_DollarGestureEvent, + pub r#drop: SDL_DropEvent, +} + +#[repr(C)] +pub struct SDL_AudioSpec { + pub freq: c_int, + pub format: SDL_AudioFormat, + pub channels: u8, + pub silence: u8, + pub samples: u16, + pub size: u32, + pub callback: SDL_AudioCallback, + pub userdata: *mut c_void, +} + +impl SDL_AudioSpec { + pub fn new( + callback: SDL_AudioCallback, + userdata: *mut c_void, + channels: u8, + format: u16, + freq: c_int, + samples: u16, + ) -> Self { + Self { + callback, + userdata, + channels, + format, + freq, + samples, + silence: 0, + size: 0, + } + } +} + +#[link(name = "SDL2", kind = "dylib")] +extern "C" { + fn SDL_Init(flags: u32) -> c_int; + fn SDL_CreateWindow( + title: *const c_char, + x: c_int, + y: c_int, + w: c_int, + h: c_int, + flags: u32, + ) -> *mut SDL_Window; + fn SDL_ShowWindow(window: *mut SDL_Window); + fn SDL_ShowSimpleMessageBox( + flags: u32, + title: *const c_char, + message: *const c_char, + window: *mut SDL_Window, + ); + fn SDL_DestroyWindow(window: *mut SDL_Window); + fn SDL_GetWindowSize(window: *mut SDL_Window, w: *mut c_int, h: *mut c_int); + fn SDL_GetWindowID(window: *mut SDL_Window) -> u32; + fn SDL_SetWindowResizable(window: *mut SDL_Window, resizable: SDL_bool); + fn SDL_SetWindowSize(window: *mut SDL_Window, w: c_int, h: c_int); + fn SDL_GetError() -> *const c_char; + fn SDL_SetHint(name: *const c_char, value: *const c_char) -> SDL_bool; + fn SDL_GL_SetAttribute(attr: SDL_GLattr, value: c_int); + fn SDL_GL_SetSwapInterval(interval: c_int) -> c_int; + fn SDL_GL_CreateContext(window: *mut SDL_Window) -> SDL_GLContext; + fn SDL_GL_DeleteContext(ctx: SDL_GLContext); + fn SDL_GL_MakeCurrent(window: *mut SDL_Window, context: SDL_GLContext) -> c_int; + fn SDL_GL_SwapWindow(window: *mut SDL_Window); + fn SDL_GL_GetProcAddress(proc: *const c_char) -> *mut c_void; + fn SDL_PollEvent(event: *mut SDL_Event) -> c_int; + fn SDL_GetKeyboardState(numkeys: *mut c_int) -> *const u8; + fn SDL_OpenAudioDevice( + device: *const c_char, + iscapture: c_int, + desired: *const SDL_AudioSpec, + obtained: *mut SDL_AudioSpec, + allowed_changes: c_int, + ) -> SDL_AudioDeviceID; + fn SDL_PauseAudioDevice(dev: SDL_AudioDeviceID, pause_on: c_int); + fn SDL_CloseAudioDevice(dev: SDL_AudioDeviceID); +} + +pub mod scancode { + use crate::sdl::SDL_Scancode; + pub const UNKNOWN: SDL_Scancode = 0; + pub const A: SDL_Scancode = 4; + pub const B: SDL_Scancode = 5; + pub const C: SDL_Scancode = 6; + pub const D: SDL_Scancode = 7; + pub const E: SDL_Scancode = 8; + pub const F: SDL_Scancode = 9; + pub const G: SDL_Scancode = 10; + pub const H: SDL_Scancode = 11; + pub const I: SDL_Scancode = 12; + pub const J: SDL_Scancode = 13; + pub const K: SDL_Scancode = 14; + pub const L: SDL_Scancode = 15; + pub const M: SDL_Scancode = 16; + pub const N: SDL_Scancode = 17; + pub const O: SDL_Scancode = 18; + pub const P: SDL_Scancode = 19; + pub const Q: SDL_Scancode = 20; + pub const R: SDL_Scancode = 21; + pub const S: SDL_Scancode = 22; + pub const T: SDL_Scancode = 23; + pub const U: SDL_Scancode = 24; + pub const V: SDL_Scancode = 25; + pub const W: SDL_Scancode = 26; + pub const X: SDL_Scancode = 27; + pub const Y: SDL_Scancode = 28; + pub const Z: SDL_Scancode = 29; + pub const N1: SDL_Scancode = 30; + pub const N2: SDL_Scancode = 31; + pub const N3: SDL_Scancode = 32; + pub const N4: SDL_Scancode = 33; + pub const N5: SDL_Scancode = 34; + pub const N6: SDL_Scancode = 35; + pub const N7: SDL_Scancode = 36; + pub const N8: SDL_Scancode = 37; + pub const N9: SDL_Scancode = 38; + pub const N0: SDL_Scancode = 39; + pub const RETURN: SDL_Scancode = 40; + pub const ESCAPE: SDL_Scancode = 41; + pub const BACKSPACE: SDL_Scancode = 42; + pub const TAB: SDL_Scancode = 43; + pub const SPACE: SDL_Scancode = 44; + pub const MINUS: SDL_Scancode = 45; + pub const EQUALS: SDL_Scancode = 46; + pub const LEFTBRACKET: SDL_Scancode = 47; + pub const RIGHTBRACKET: SDL_Scancode = 48; + pub const BACKSLASH: SDL_Scancode = 49; + pub const NONUSHASH: SDL_Scancode = 50; + pub const SEMICOLON: SDL_Scancode = 51; + pub const APOSTROPHE: SDL_Scancode = 52; + pub const GRAVE: SDL_Scancode = 53; + pub const COMMA: SDL_Scancode = 54; + pub const PERIOD: SDL_Scancode = 55; + pub const SLASH: SDL_Scancode = 56; + pub const CAPSLOCK: SDL_Scancode = 57; + pub const F1: SDL_Scancode = 58; + pub const F2: SDL_Scancode = 59; + pub const F3: SDL_Scancode = 60; + pub const F4: SDL_Scancode = 61; + pub const F5: SDL_Scancode = 62; + pub const F6: SDL_Scancode = 63; + pub const F7: SDL_Scancode = 64; + pub const F8: SDL_Scancode = 65; + pub const F9: SDL_Scancode = 66; + pub const F10: SDL_Scancode = 67; + pub const F11: SDL_Scancode = 68; + pub const F12: SDL_Scancode = 69; + pub const PRINTSCREEN: SDL_Scancode = 70; + pub const SCROLLLOCK: SDL_Scancode = 71; + pub const PAUSE: SDL_Scancode = 72; + pub const INSERT: SDL_Scancode = 73; + pub const HOME: SDL_Scancode = 74; + pub const PAGEUP: SDL_Scancode = 75; + pub const DELETE: SDL_Scancode = 76; + pub const END: SDL_Scancode = 77; + pub const PAGEDOWN: SDL_Scancode = 78; + pub const RIGHT: SDL_Scancode = 79; + pub const LEFT: SDL_Scancode = 80; + pub const DOWN: SDL_Scancode = 81; + pub const UP: SDL_Scancode = 82; + pub const NUMLOCKCLEAR: SDL_Scancode = 83; + pub const KP_DIVIDE: SDL_Scancode = 84; + pub const KP_MULTIPLY: SDL_Scancode = 85; + pub const KP_MINUS: SDL_Scancode = 86; + pub const KP_PLUS: SDL_Scancode = 87; + pub const KP_ENTER: SDL_Scancode = 88; + pub const KP_1: SDL_Scancode = 89; + pub const KP_2: SDL_Scancode = 90; + pub const KP_3: SDL_Scancode = 91; + pub const KP_4: SDL_Scancode = 92; + pub const KP_5: SDL_Scancode = 93; + pub const KP_6: SDL_Scancode = 94; + pub const KP_7: SDL_Scancode = 95; + pub const KP_8: SDL_Scancode = 96; + pub const KP_9: SDL_Scancode = 97; + pub const KP_0: SDL_Scancode = 98; + pub const KP_PERIOD: SDL_Scancode = 99; + pub const NONUSBACKSLASH: SDL_Scancode = 100; + pub const APPLICATION: SDL_Scancode = 101; + pub const POWER: SDL_Scancode = 102; + pub const KP_EQUALS: SDL_Scancode = 103; + pub const F13: SDL_Scancode = 104; + pub const F14: SDL_Scancode = 105; + pub const F15: SDL_Scancode = 106; + pub const F16: SDL_Scancode = 107; + pub const F17: SDL_Scancode = 108; + pub const F18: SDL_Scancode = 109; + pub const F19: SDL_Scancode = 110; + pub const F20: SDL_Scancode = 111; + pub const F21: SDL_Scancode = 112; + pub const F22: SDL_Scancode = 113; + pub const F23: SDL_Scancode = 114; + pub const F24: SDL_Scancode = 115; + pub const EXECUTE: SDL_Scancode = 116; + pub const HELP: SDL_Scancode = 117; + pub const MENU: SDL_Scancode = 118; + pub const SELECT: SDL_Scancode = 119; + pub const STOP: SDL_Scancode = 120; + pub const AGAIN: SDL_Scancode = 121; + pub const UNDO: SDL_Scancode = 122; + pub const CUT: SDL_Scancode = 123; + pub const COPY: SDL_Scancode = 124; + pub const PASTE: SDL_Scancode = 125; + pub const FIND: SDL_Scancode = 126; + pub const MUTE: SDL_Scancode = 127; + pub const VOLUMEUP: SDL_Scancode = 128; + pub const VOLUMEDOWN: SDL_Scancode = 129; + pub const KP_COMMA: SDL_Scancode = 133; + pub const KP_EQUALSAS400: SDL_Scancode = 134; + pub const INTERNATIONAL1: SDL_Scancode = 135; + pub const INTERNATIONAL2: SDL_Scancode = 136; + pub const INTERNATIONAL3: SDL_Scancode = 137; + pub const INTERNATIONAL4: SDL_Scancode = 138; + pub const INTERNATIONAL5: SDL_Scancode = 139; + pub const INTERNATIONAL6: SDL_Scancode = 140; + pub const INTERNATIONAL7: SDL_Scancode = 141; + pub const INTERNATIONAL8: SDL_Scancode = 142; + pub const INTERNATIONAL9: SDL_Scancode = 143; + pub const LANG1: SDL_Scancode = 144; + pub const LANG2: SDL_Scancode = 145; + pub const LANG3: SDL_Scancode = 146; + pub const LANG4: SDL_Scancode = 147; + pub const LANG5: SDL_Scancode = 148; + pub const LANG6: SDL_Scancode = 149; + pub const LANG7: SDL_Scancode = 150; + pub const LANG8: SDL_Scancode = 151; + pub const LANG9: SDL_Scancode = 152; + pub const ALTERASE: SDL_Scancode = 153; + pub const SYSREQ: SDL_Scancode = 154; + pub const CANCEL: SDL_Scancode = 155; + pub const CLEAR: SDL_Scancode = 156; + pub const PRIOR: SDL_Scancode = 157; + pub const RETURN2: SDL_Scancode = 158; + pub const SEPARATOR: SDL_Scancode = 159; + pub const OUT: SDL_Scancode = 160; + pub const OPER: SDL_Scancode = 161; + pub const CLEARAGAIN: SDL_Scancode = 162; + pub const CRSEL: SDL_Scancode = 163; + pub const EXSEL: SDL_Scancode = 164; + pub const KP_00: SDL_Scancode = 176; + pub const KP_000: SDL_Scancode = 177; + pub const THOUSANDSSEPARATOR: SDL_Scancode = 178; + pub const DECIMALSEPARATOR: SDL_Scancode = 179; + pub const CURRENCYUNIT: SDL_Scancode = 180; + pub const CURRENCYSUBUNIT: SDL_Scancode = 181; + pub const KP_LEFTPAREN: SDL_Scancode = 182; + pub const KP_RIGHTPAREN: SDL_Scancode = 183; + pub const KP_LEFTBRACE: SDL_Scancode = 184; + pub const KP_RIGHTBRACE: SDL_Scancode = 185; + pub const KP_TAB: SDL_Scancode = 186; + pub const KP_BACKSPACE: SDL_Scancode = 187; + pub const KP_A: SDL_Scancode = 188; + pub const KP_B: SDL_Scancode = 189; + pub const KP_C: SDL_Scancode = 190; + pub const KP_D: SDL_Scancode = 191; + pub const KP_E: SDL_Scancode = 192; + pub const KP_F: SDL_Scancode = 193; + pub const KP_XOR: SDL_Scancode = 194; + pub const KP_POWER: SDL_Scancode = 195; + pub const KP_PERCENT: SDL_Scancode = 196; + pub const KP_LESS: SDL_Scancode = 197; + pub const KP_GREATER: SDL_Scancode = 198; + pub const KP_AMPERSAND: SDL_Scancode = 199; + pub const KP_DBLAMPERSAND: SDL_Scancode = 200; + pub const KP_VERTICALBAR: SDL_Scancode = 201; + pub const KP_DBLVERTICALBAR: SDL_Scancode = 202; + pub const KP_COLON: SDL_Scancode = 203; + pub const KP_HASH: SDL_Scancode = 204; + pub const KP_SPACE: SDL_Scancode = 205; + pub const KP_AT: SDL_Scancode = 206; + pub const KP_EXCLAM: SDL_Scancode = 207; + pub const KP_MEMSTORE: SDL_Scancode = 208; + pub const KP_MEMRECALL: SDL_Scancode = 209; + pub const KP_MEMCLEAR: SDL_Scancode = 210; + pub const KP_MEMADD: SDL_Scancode = 211; + pub const KP_MEMSUBTRACT: SDL_Scancode = 212; + pub const KP_MEMMULTIPLY: SDL_Scancode = 213; + pub const KP_MEMDIVIDE: SDL_Scancode = 214; + pub const KP_PLUSMINUS: SDL_Scancode = 215; + pub const KP_CLEAR: SDL_Scancode = 216; + pub const KP_CLEARENTRY: SDL_Scancode = 217; + pub const KP_BINARY: SDL_Scancode = 218; + pub const KP_OCTAL: SDL_Scancode = 219; + pub const KP_DECIMAL: SDL_Scancode = 220; + pub const KP_HEXADECIMAL: SDL_Scancode = 221; + pub const LCTRL: SDL_Scancode = 224; + pub const LSHIFT: SDL_Scancode = 225; + pub const LALT: SDL_Scancode = 226; + pub const LGUI: SDL_Scancode = 227; + pub const RCTRL: SDL_Scancode = 228; + pub const RSHIFT: SDL_Scancode = 229; + pub const RALT: SDL_Scancode = 230; + pub const RGUI: SDL_Scancode = 231; + pub const MODE: SDL_Scancode = 257; + pub const AUDIONEXT: SDL_Scancode = 258; + pub const AUDIOPREV: SDL_Scancode = 259; + pub const AUDIOSTOP: SDL_Scancode = 260; + pub const AUDIOPLAY: SDL_Scancode = 261; + pub const AUDIOMUTE: SDL_Scancode = 262; + pub const MEDIASELECT: SDL_Scancode = 263; + pub const WWW: SDL_Scancode = 264; + pub const MAIL: SDL_Scancode = 265; + pub const CALCULATOR: SDL_Scancode = 266; + pub const COMPUTER: SDL_Scancode = 267; + pub const AC_SEARCH: SDL_Scancode = 268; + pub const AC_HOME: SDL_Scancode = 269; + pub const AC_BACK: SDL_Scancode = 270; + pub const AC_FORWARD: SDL_Scancode = 271; + pub const AC_STOP: SDL_Scancode = 272; + pub const AC_REFRESH: SDL_Scancode = 273; + pub const AC_BOOKMARKS: SDL_Scancode = 274; + pub const BRIGHTNESSDOWN: SDL_Scancode = 275; + pub const BRIGHTNESSUP: SDL_Scancode = 276; + pub const DISPLAYSWITCH: SDL_Scancode = 277; + pub const KBDILLUMTOGGLE: SDL_Scancode = 278; + pub const KBDILLUMDOWN: SDL_Scancode = 279; + pub const KBDILLUMUP: SDL_Scancode = 280; + pub const EJECT: SDL_Scancode = 281; + pub const SLEEP: SDL_Scancode = 282; + pub const APP1: SDL_Scancode = 283; + pub const APP2: SDL_Scancode = 284; + pub const AUDIOREWIND: SDL_Scancode = 285; + pub const AUDIOFASTFORWARD: SDL_Scancode = 286; + pub const SOFTLEFT: SDL_Scancode = 287; + pub const SOFTRIGHT: SDL_Scancode = 288; + pub const CALL: SDL_Scancode = 289; + pub const ENDCALL: SDL_Scancode = 290; + pub const NUM_SCANCODES: SDL_Scancode = 512; +} + +fn cstring(s: &str) -> Result<CString, String> { + CString::new(s).map_err(|e| format!("{e}")) +} + +unsafe fn get_err() -> String { + let cstr = CStr::from_ptr(SDL_GetError()); + String::from_utf8_lossy(cstr.to_bytes()).to_string() +} + +pub unsafe fn create_window( + title: &str, + width: i32, + height: i32, + flags: u32, +) -> Result<*mut SDL_Window, String> { + let tstr = cstring(title)?; + let window = SDL_CreateWindow( + tstr.as_ptr(), + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + width, + height, + flags, + ); + if window.is_null() { + Err(get_err()) + } else { + Ok(window) + } +} + +pub unsafe fn gl_create_context(window: *mut SDL_Window) -> Result<SDL_GLContext, String> { + let ctx = SDL_GL_CreateContext(window); + if ctx.is_null() { + Err(get_err()) + } else { + Ok(ctx) + } +} + +// NOTE: the buffer returned by SDL does really have a static lifetime. we're not lying here. +pub unsafe fn get_keyboard_state() -> &'static [u8] { + let state = SDL_GetKeyboardState(0 as _); + std::slice::from_raw_parts(state, scancode::NUM_SCANCODES as usize) +} + +pub unsafe fn poll_event() -> Option<SDL_Event> { + let mut event = mem::MaybeUninit::zeroed(); + if SDL_PollEvent(event.as_mut_ptr()) != 0 { + let event = event.assume_init(); + Some(event) + } else { + None + } +} + +pub unsafe fn init() -> Result<(), String> { + if SDL_Init(SDL_INIT_EVERYTHING) == 0 { + Ok(()) + } else { + Err(get_err()) + } +} + +pub unsafe fn set_hint(hint: &str, value: &str) { + // NOTE: hint,value are probably string literals, so cstring is very unlikely to fail + if let Ok(hstr) = cstring(hint) { + if let Ok(vstr) = cstring(value) { + SDL_SetHint(hstr.as_ptr(), vstr.as_ptr()); + } + } +} + +pub unsafe fn gl_set_attribute(attr: i32, value: i32) { + SDL_GL_SetAttribute(attr, value); +} + +pub unsafe fn gl_set_context_version(major: i32, minor: i32) { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor); +} + +pub unsafe fn gl_get_proc_address(name: &str) -> *const std::os::raw::c_void { + match cstring(name) { + Ok(cstr) => SDL_GL_GetProcAddress(cstr.as_ptr()), + Err(_) => 0 as _, + } +} + +pub unsafe fn gl_set_swap_interval(interval: i32) { + SDL_GL_SetSwapInterval(interval); +} + +pub unsafe fn show_window(window: *mut SDL_Window) { + SDL_ShowWindow(window); +} + +pub unsafe fn gl_swap_window(window: *mut SDL_Window) { + SDL_GL_SwapWindow(window); +} + +pub unsafe fn gl_delete_context(ctx: SDL_GLContext) { + SDL_GL_DeleteContext(ctx); +} + +pub unsafe fn destroy_window(window: *mut SDL_Window) { + SDL_DestroyWindow(window); +} + +pub unsafe fn show_simple_message_box( + flags: u32, + title: &str, + message: &str, + window: *mut SDL_Window, +) -> Result<(), String> { + let tstr = cstring(title)?; + let mstr = cstring(message)?; + SDL_ShowSimpleMessageBox(flags, tstr.as_ptr(), mstr.as_ptr(), window); + Ok(()) +} + +pub unsafe fn get_window_size(window: *mut SDL_Window, x: &mut i32, y: &mut i32) { + SDL_GetWindowSize(window, x as _, y as _); +} + +pub unsafe fn open_audio_device( + device: Option<&str>, + iscapture: bool, + desired: &SDL_AudioSpec, + allowed_changes: c_int, +) -> Result<(SDL_AudioSpec, SDL_AudioDeviceID), String> { + let mut obtained = mem::MaybeUninit::zeroed(); + let mut devstr = None; + if let Some(dev) = device { + devstr = CString::new(dev).ok(); + } + + let devptr: *const c_char = match devstr { + None => 0 as _, + Some(s) => s.as_ptr(), + }; + let id = SDL_OpenAudioDevice( + devptr, + iscapture.into(), + desired as _, + obtained.as_mut_ptr(), + allowed_changes, + ); + if id == 0 { + return Err(get_err()); + } + let obtained = obtained.assume_init(); + Ok((obtained, id)) +} + +pub unsafe fn pause_audio_device(dev: SDL_AudioDeviceID, pause_on: bool) { + SDL_PauseAudioDevice(dev, pause_on.into()); +} + +pub unsafe fn close_audio_device(dev: SDL_AudioDeviceID) { + SDL_CloseAudioDevice(dev); +} diff --git a/src/win.rs b/src/win.rs new file mode 100644 index 0000000..781156d --- /dev/null +++ b/src/win.rs @@ -0,0 +1,866 @@ +// all OpenGL calls are done through the Window. +// this is because OpenGL is not thread safe. +use crate::sdl; +use gl::types::{GLchar, GLenum, GLint, GLsizei, GLuint}; +#[allow(unused_imports)] +use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString}; +use std::sync::Mutex; +use std::{fmt, mem}; +use mem::size_of; + +pub type AudioCallback = fn(sample_rate: u32, samples: &mut [f32]); + +struct AudioData { + callback: AudioCallback, + device: sdl::SDL_AudioDeviceID, + sample_rate: u32, +} + +pub struct Window { + sdlwin: *mut sdl::SDL_Window, + glctx: sdl::SDL_GLContext, + used_program: GLuint, + audio_data: Option<Box<AudioData>>, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Key { + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + N0, + N1, + N2, + N3, + N4, + N5, + N6, + N7, + N8, + N9, + Up, + Left, + Right, + Down, + Space, + Enter, + Escape, +} + +impl Key { + fn from_sdl(scancode: sdl::SDL_Scancode) -> Option<Self> { + use sdl::scancode::*; + Some(match scancode { + A => Key::A, + B => Key::B, + C => Key::C, + D => Key::D, + E => Key::E, + F => Key::F, + G => Key::G, + H => Key::H, + I => Key::I, + J => Key::J, + K => Key::K, + L => Key::L, + M => Key::M, + N => Key::N, + O => Key::O, + P => Key::P, + Q => Key::Q, + R => Key::R, + S => Key::S, + T => Key::T, + U => Key::U, + V => Key::V, + W => Key::W, + X => Key::X, + Y => Key::Y, + Z => Key::Z, + RETURN => Key::Enter, + SPACE => Key::Space, + N0 => Key::N0, + N1 => Key::N1, + N2 => Key::N2, + N3 => Key::N3, + N4 => Key::N4, + N5 => Key::N5, + N6 => Key::N6, + N7 => Key::N7, + N8 => Key::N8, + N9 => Key::N9, + UP => Key::Up, + LEFT => Key::Left, + RIGHT => Key::Right, + DOWN => Key::Down, + ESCAPE => Key::Escape, + _ => return None, + }) + } + + fn to_sdl(self) -> sdl::SDL_Scancode { + use sdl::scancode::*; + match self { + Key::A => A, + Key::B => B, + Key::C => C, + Key::D => D, + Key::E => E, + Key::F => F, + Key::G => G, + Key::H => H, + Key::I => I, + Key::J => J, + Key::K => K, + Key::L => L, + Key::M => M, + Key::N => N, + Key::O => O, + Key::P => P, + Key::Q => Q, + Key::R => R, + Key::S => S, + Key::T => T, + Key::U => U, + Key::V => V, + Key::W => W, + Key::X => X, + Key::Y => Y, + Key::Z => Z, + Key::Enter => RETURN, + Key::Space => SPACE, + Key::N0 => N0, + Key::N1 => N1, + Key::N2 => N2, + Key::N3 => N3, + Key::N4 => N4, + Key::N5 => N5, + Key::N6 => N6, + Key::N7 => N7, + Key::N8 => N8, + Key::N9 => N9, + Key::Up => UP, + Key::Left => LEFT, + Key::Right => RIGHT, + Key::Down => DOWN, + Key::Escape => ESCAPE, + } + } +} + +#[derive(Debug)] +pub enum Event { + Quit, + KeyDown(Key), + KeyUp(Key), +} + +pub fn display_error_message(message: &str) { + let result = unsafe { + sdl::show_simple_message_box(sdl::SDL_MESSAGEBOX_ERROR, "Error", message, 0 as _) + }; + if result.is_err() { + eprintln!("{}", message); + } +} + +#[derive(Clone, Copy)] +pub struct ColorF32 { + pub r: f32, + pub g: f32, + pub b: f32, + pub a: f32, +} + +#[derive(Clone, Copy)] +pub struct ColorU8 { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +impl fmt::Display for ColorU8 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "#{:02x}{:02x}{:02x}{:02x}", + self.r, self.g, self.b, self.a + ) + } +} + +impl fmt::Debug for ColorU8 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self) + } +} + +impl ColorU8 { + pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self { + ColorU8 { r, g, b, a } + } + + pub const fn rgb(r: u8, g: u8, b: u8) -> Self { + ColorU8 { r, g, b, a: 255 } + } + + pub fn tint(&mut self, other: ColorU8) { + self.r = ((self.r as u32 * other.r as u32) >> 8) as u8; + self.g = ((self.g as u32 * other.g as u32) >> 8) as u8; + self.b = ((self.b as u32 * other.b as u32) >> 8) as u8; + self.a = ((self.a as u32 * other.a as u32) >> 8) as u8; + } + + pub fn from_bytes(bytes: &[u8]) -> Self { + assert!(bytes.len() >= 4); + Self { + r: bytes[0], + g: bytes[1], + b: bytes[2], + a: bytes[3], + } + } + + pub fn as_bytes(self, bytes: &mut [u8]) { + assert!(bytes.len() >= 4); + bytes[0] = self.r; + bytes[1] = self.g; + bytes[2] = self.b; + bytes[3] = self.a; + } + + pub fn as_tuple(self) -> (u8, u8, u8, u8) { + (self.r, self.g, self.b, self.a) + } +} + +impl From<u32> for ColorU8 { + fn from(color: u32) -> ColorU8 { + ColorU8::new( + (color >> 24) as u8, + (color >> 16) as u8, + (color >> 8) as u8, + color as u8, + ) + } +} + +impl ColorF32 { + pub const BLACK: Self = Self::rgb(0.0, 0.0, 0.0); + + pub const fn rgb(r: f32, g: f32, b: f32) -> Self { + ColorF32 { r, g, b, a: 1.0 } + } + + pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self { + ColorF32 { r, g, b, a } + } +} + +pub struct Shader { + id: GLuint, +} + +impl Shader { + fn new(r#type: GLenum, source: &str) -> Result<Self, String> { + let id = unsafe { gl::CreateShader(r#type) }; + let result = Self::new_with_id(id, r#type, source); + if result.is_err() { + unsafe { gl::DeleteShader(id) }; + } + result + } + + fn new_with_id(id: GLuint, r#type: GLenum, source: &str) -> Result<Self, String> { + if id == 0 { + return Err(format!("couldn't create shader (GL error {})", unsafe { + gl::GetError() + })); + } + + { + //set source + // @TODO(eventually): support for older versions of GLSL + let header = if r#type == gl::FRAGMENT_SHADER { "#version 130 +#define IN in +#define OUT out +#define gl_FragColor o_color +out vec4 o_color; +#line 1 +" } else { +"#version 130 +#define IN in +#define OUT out +#define ATTRIBUTE in +#line 1 +" +}; + let hdrptr = header.as_bytes().as_ptr() as *const GLchar; + let srcptr = source.as_bytes().as_ptr() as *const GLchar; + let sources = [hdrptr, srcptr]; + let lengths = [header.len() as GLint, source.len() as GLint]; + + let sources_ptr = &sources[0] as *const *const GLchar; + let lengths_ptr = &lengths[0] as *const GLint; + + unsafe { gl::ShaderSource(id, sources.len() as _, sources_ptr, lengths_ptr) }; + } + + unsafe { gl::CompileShader(id) }; + { + //check log + let mut log = [0u8; 1024]; + let mut len: GLsizei = 0; + let logp = &mut log as *mut u8 as *mut GLchar; + let lenp = &mut len as *mut GLsizei; + unsafe { gl::GetShaderInfoLog(id, log.len() as GLsizei, lenp, logp) }; + if len > 0 { + eprintln!("{}", String::from_utf8_lossy(&log[..len as usize])); + } + } + { + let mut status: GLint = 0; + unsafe { gl::GetShaderiv(id, gl::COMPILE_STATUS, (&mut status) as _) }; + if status == 0 { + return Err(format!("failed to compile")); + } + } + + Ok(Self { id }) + } +} + +impl Drop for Shader { + fn drop(&mut self) { + unsafe { gl::DeleteShader(self.id) }; + } +} + +pub struct Program { + id: GLuint, +} + +impl Program { + fn new(shaders: &[Shader]) -> Result<Self, String> { + let id = unsafe { gl::CreateProgram() }; + let result = Self::new_with_id(id, shaders); + if result.is_err() { + unsafe { gl::DeleteShader(id) }; + } + result + } + + fn new_with_id(id: GLuint, shaders: &[Shader]) -> Result<Self, String> { + for shader in shaders { + unsafe { gl::AttachShader(id, shader.id) }; + } + unsafe { gl::LinkProgram(id) }; + { + // check log + let mut log = [0u8; 1024]; + let mut len: GLsizei = 0; + let logp = &mut log as *mut u8 as *mut GLchar; + let lenp = &mut len as *mut GLsizei; + unsafe { gl::GetProgramInfoLog(id, log.len() as GLsizei, lenp, logp) }; + if len > 0 { + eprintln!("{}", String::from_utf8_lossy(&log[..len as usize])); + } + } + + { + let mut status: GLint = 0; + unsafe { gl::GetProgramiv(id, gl::LINK_STATUS, (&mut status) as _) }; + if status == 0 { + return Err(format!("failed to link")); + } + } + + Ok(Self { id }) + } +} + +impl Drop for Program { + fn drop(&mut self) { + unsafe { gl::DeleteProgram(self.id) }; + } +} + +pub struct Buffer { + id: GLuint, + stride: u32, + count: u32, +} + +impl Buffer { + fn new() -> Self { + let mut id = 0; + unsafe { gl::CreateBuffers(1, &mut id as *mut GLuint) }; + Self { + id, + stride: 0, + count: 0, + } + } + + fn bind(&self) { + unsafe { gl::BindBuffer(gl::ARRAY_BUFFER, self.id) }; + } + + fn set_data<T>(&mut self, data: &[T]) { + unsafe { + gl::BindBuffer(gl::ARRAY_BUFFER, self.id); + } + self.count = data.len() as u32; + self.stride = mem::size_of::<T>() as u32; + + unsafe { + gl::BufferData( + gl::ARRAY_BUFFER, + (self.count * self.stride) as _, + data.as_ptr() as _, + gl::STATIC_DRAW, + ); + } + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + unsafe { gl::DeleteBuffers(1, &self.id as *const GLuint) }; + } +} + +pub struct VertexArray { + buffer: Buffer, + id: GLuint, + program: GLuint, +} + +impl VertexArray { + fn new(buffer: Buffer, program: &Program) -> Self { + let mut id: GLuint = 0; + + unsafe { gl::GenVertexArrays(1, &mut id as *mut GLuint) }; + + Self { id, buffer, program: program.id } + } + + fn bind(&self) { + unsafe { gl::BindVertexArray(self.id) }; + } + + fn attribnf(&mut self, n: u8, name: &str, offset: usize) -> bool { + let Ok(cstring) = CString::new(name) else { return false }; + let cstr = cstring.as_ptr() as *const GLchar; + let loc = unsafe { gl::GetAttribLocation(self.program, cstr) }; + let Ok(loc) = loc.try_into() else { return false }; + + if offset + usize::from(n) * size_of::<f32>() > self.buffer.stride as usize { + // offset too large + return false; + } + + self.bind(); + self.buffer.bind(); + unsafe { + gl::VertexAttribPointer(loc, n.into(), gl::FLOAT, 0, self.buffer.stride as _, offset as _) + }; + unsafe { gl::EnableVertexAttribArray(loc) }; + true + } + + fn draw(&self) { + self.bind(); + unsafe { gl::DrawArrays(gl::TRIANGLES, 0, self.buffer.count as i32) }; + } +} + +impl Drop for VertexArray { + fn drop(&mut self) { + unsafe { gl::DeleteVertexArrays(1, &self.id as *const GLuint) }; + } +} + +#[cfg(debug_assertions)] +extern "system" fn gl_message_callback( + _source: GLenum, + _type: GLenum, + _id: c_uint, + severity: GLenum, + _length: GLsizei, + message: *const c_char, + _user_param: *mut c_void, +) { + let message = String::from_utf8_lossy(unsafe { CStr::from_ptr(message) }.to_bytes()); + if severity == gl::DEBUG_SEVERITY_NOTIFICATION { + return; + } + println!("Message from opengl: {message}"); +} + +pub struct Texture { + id: GLuint, + params: TextureParams, +} + +impl Drop for Texture { + fn drop(&mut self) { + unsafe { gl::DeleteTextures(1, (&self.id) as *const GLuint) }; + } +} + +#[derive(Copy, Clone)] +pub enum TextureFilter { + Nearest, + Linear, +} + +impl TextureFilter { + fn to_gl(self) -> GLint { + use TextureFilter::*; + match self { + Nearest => gl::NEAREST as _, + Linear => gl::LINEAR as _, + } + } +} + +#[derive(Clone)] +pub struct TextureParams { + pub min_filter: TextureFilter, + pub mag_filter: TextureFilter, +} + +impl Default for TextureParams { + fn default() -> Self { + Self { + min_filter: TextureFilter::Nearest, + mag_filter: TextureFilter::Linear, + } + } +} + +impl Window { + pub fn new(title: &str, width: i32, height: i32, shown: bool) -> Result<Self, String> { + { + static WINDOW_CREATED: Mutex<bool> = Mutex::new(false); + let guard = WINDOW_CREATED.lock(); + match guard { + Err(_) => return Err("couldn't lock mutex.".to_string()), + Ok(x) if *x => return Err("window already created".to_string()), + Ok(mut x) => *x = true, + } + } + + unsafe { + sdl::set_hint("SDL_NO_SIGNAL_HANDLERS", "1"); // don't replace Ctrl+C, TERM with quit event + sdl::init()?; + #[cfg(debug_assertions)] + { + sdl::gl_set_context_version(4, 3); + sdl::gl_set_attribute(sdl::SDL_GL_CONTEXT_FLAGS, sdl::SDL_GL_CONTEXT_DEBUG_FLAG); + } + #[cfg(not(debug_assertions))] + sdl::gl_set_context_version(3, 0); + } + let mut flags = sdl::SDL_WINDOW_OPENGL; + if !shown { + flags |= sdl::SDL_WINDOW_HIDDEN; + } + let sdlwin = unsafe { sdl::create_window(title, width, height, flags) }?; + let ctx = unsafe { sdl::gl_create_context(sdlwin) }?; + gl::load_with(|name| unsafe { sdl::gl_get_proc_address(name) }); + unsafe { + sdl::gl_set_swap_interval(1); + let mut flags: GLint = 0; + gl::GetIntegerv(gl::CONTEXT_FLAGS, (&mut flags) as _); + #[cfg(debug_assertions)] + if (flags as GLuint & gl::CONTEXT_FLAG_DEBUG_BIT) != 0 { + gl::DebugMessageCallback(Some(gl_message_callback), 0 as _); + gl::DebugMessageControl( + gl::DONT_CARE, + gl::DONT_CARE, + gl::DONT_CARE, + 0, + 0 as _, + gl::TRUE, + ); + gl::Enable(gl::DEBUG_OUTPUT); + gl::Enable(gl::DEBUG_OUTPUT_SYNCHRONOUS); + } + } + + Ok(Window { + sdlwin, + glctx: ctx, + used_program: 0, + audio_data: None, + }) + } + + pub fn show(&mut self) { + unsafe { sdl::show_window(self.sdlwin) }; + } + + pub fn create_program( + &mut self, + source_vshader: &str, + source_fshader: &str, + ) -> Result<Program, String> { + let vshader = Shader::new(gl::VERTEX_SHADER, source_vshader)?; + let fshader = Shader::new(gl::FRAGMENT_SHADER, source_fshader)?; + Program::new(&[vshader, fshader]) + } + + pub fn create_buffer(&mut self) -> Buffer { + Buffer::new() + } + + pub fn set_buffer_data<T>(&mut self, buffer: &mut Buffer, data: &[T]) { + buffer.set_data(data); + } + + pub fn create_vertex_array(&mut self, buffer: Buffer, program: &Program) -> VertexArray { + VertexArray::new(buffer, program) + } + + fn array_attribnf(&mut self, array: &mut VertexArray, n: u8, name: &str, offset: usize) -> bool { + array.attribnf(n, name, offset) + } + + pub fn array_attrib2f(&mut self, array: &mut VertexArray, name: &str, offset: usize) -> bool { + self.array_attribnf(array, 2, name, offset) + } + + pub fn array_attrib3f(&mut self, array: &mut VertexArray, name: &str, offset: usize) -> bool { + self.array_attribnf(array, 3, name, offset) + } + + pub fn array_attrib4f(&mut self, array: &mut VertexArray, name: &str, offset: usize) -> bool { + self.array_attribnf(array, 4, name, offset) + } + + pub fn size(&mut self) -> (i32, i32) { + let mut x = 0; + let mut y = 0; + unsafe { sdl::get_window_size(self.sdlwin, &mut x, &mut y) }; + (x, y) + } + + pub fn viewport(&mut self, x: i32, y: i32, w: i32, h: i32) { + unsafe { gl::Viewport(x, y, w, h) }; + } + + pub fn next_event(&mut self) -> Option<Event> { + loop { + let sdl = unsafe { sdl::poll_event() }?; + let r#type = unsafe { sdl.r#type }; + match r#type { + sdl::SDL_QUIT => return Some(Event::Quit), + sdl::SDL_KEYDOWN | sdl::SDL_KEYUP => { + let scancode = unsafe { sdl.key }.keysym.scancode; + if let Some(k) = Key::from_sdl(scancode) { + if r#type == sdl::SDL_KEYDOWN { + return Some(Event::KeyDown(k)); + } else { + return Some(Event::KeyUp(k)); + } + } + } + _ => {} + } + } + } + + pub fn clear_screen(&mut self, color: ColorF32) { + unsafe { + gl::ClearColor(color.r, color.g, color.b, color.a); + gl::Clear(gl::COLOR_BUFFER_BIT); + } + } + + pub fn create_rgba_texture(&mut self, params: &TextureParams) -> Texture { + let mut id: GLuint = 0; + unsafe { + gl::GenTextures(1, (&mut id) as *mut GLuint); + } + Texture { id, params: params.clone() } + } + + pub fn set_texture_data( + &mut self, + texture: &mut Texture, + data: &[u8], + width: usize, + height: usize, + ) -> Result<(), String> { + let width = width as GLsizei; + let height = height as GLsizei; + let expected_len = 4 * width * height; + if data.len() as GLsizei != expected_len { + return Err(format!( + "bad data length (expected {}, got {})", + expected_len, + data.len() + )); + } + let params = &texture.params; + unsafe { + gl::BindTexture(gl::TEXTURE_2D, texture.id); + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGBA as _, + width, + height, + 0, + gl::RGBA, + gl::UNSIGNED_BYTE, + data.as_ptr() as _, + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_MIN_FILTER, + params.min_filter.to_gl(), + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_MAG_FILTER, + params.mag_filter.to_gl(), + ); + } + Ok(()) + } + + pub fn set_audio_callback(&mut self, callback: AudioCallback) -> Result<(), String> { + if self.audio_data.is_some() { + return Err("audio callback already set.".into()); + } + + extern "C" fn sdl_callback(userdata: *mut c_void, stream: *mut u8, len: c_int) { + let data = unsafe { (userdata as *const AudioData).as_ref() }.unwrap(); + // this should never panick, since SDL shouldn't pass us a negative length. + let samples: usize = (len / 4).try_into().unwrap(); + let slice = unsafe { std::slice::from_raw_parts_mut(stream as *mut f32, samples) }; + slice.fill(0.0); + (data.callback)(data.sample_rate, slice); + } + + let mut data = Box::new(AudioData { + callback, + device: 0, + sample_rate: 0, + }); + + let desired = sdl::SDL_AudioSpec::new( + sdl_callback, + &*data as *const AudioData as *mut c_void, + 2, + sdl::AUDIO_F32, + 44100, + 4096, + ); + + let (obtained, id) = unsafe { + sdl::open_audio_device( + None, + false, + &desired, + sdl::SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | sdl::SDL_AUDIO_ALLOW_SAMPLES_CHANGE, + )? + }; + data.sample_rate = obtained.freq.try_into().unwrap(); + data.device = id; + + self.audio_data = Some(data); + + unsafe { sdl::pause_audio_device(id, false) }; + + Ok(()) + } + + pub fn use_program(&mut self, program: &Program) { + if self.used_program != program.id { + unsafe { gl::UseProgram(program.id) }; + self.used_program = program.id; + } + } + + fn get_uniform_location(&self, name: &str) -> Option<GLint> { + if self.used_program == 0 { + return None; + } + let cstring = CString::new(name).ok()?; + let cstr = cstring.as_ptr() as *const GLchar; + let loc = unsafe { gl::GetUniformLocation(self.used_program, cstr) }; + if loc == -1 { + None + } else { + Some(loc) + } + } + + pub fn active_texture(&mut self, slot: u32, texture: &Texture) { + unsafe { + gl::ActiveTexture(gl::TEXTURE0 + slot); + gl::BindTexture(gl::TEXTURE_2D, texture.id); + } + } + + pub fn uniform_texture(&mut self, name: &str, slot: u32) { + let loc = self.get_uniform_location(name).unwrap_or(-1); + unsafe { + gl::Uniform1i(loc, slot as i32); + } + } + + pub fn draw_array(&mut self, array: &VertexArray) { + array.draw(); + } + + #[allow(dead_code)] + pub fn is_key_down(&mut self, key: Key) -> bool { + let kbd_state = unsafe { sdl::get_keyboard_state() }; + kbd_state[key.to_sdl() as usize] != 0 + } + + pub fn swap(&mut self) { + unsafe { sdl::gl_swap_window(self.sdlwin) }; + } +} + +impl Drop for Window { + fn drop(&mut self) { + unsafe { + if let Some(audio_data) = &self.audio_data { + sdl::close_audio_device(audio_data.device); + } + sdl::gl_delete_context(self.glctx); + sdl::destroy_window(self.sdlwin); + } + } +} |