Files
O2048/zephr/os_windows.odin
T

647 lines
20 KiB
Odin

// +build windows
// +private
package zephr
import "core:log"
import "core:fmt"
import "core:strings"
import "core:strconv"
import "core:os"
import "core:container/queue"
import win32 "core:sys/windows"
import gl "vendor:OpenGL"
OsEvent :: struct {
type: win32.UINT,
lparam: win32.LPARAM,
wparam: win32.WPARAM,
}
@(private="file")
hwnd : win32.HWND
@(private="file")
device_ctx : win32.HDC
@(private="file")
wgl_context : win32.HGLRC
@(private="file")
temp_hwnd : win32.HWND
@(private="file")
temp_device_ctx : win32.HDC
@(private="file")
temp_wgl_context: win32.HGLRC
backend_get_screen_size :: proc() -> Vec2 {
screen_size := Vec2{
cast(f32)win32.GetSystemMetrics(win32.SM_CXSCREEN),
cast(f32)win32.GetSystemMetrics(win32.SM_CYSCREEN),
}
return screen_size
}
backend_toggle_fullscreen :: proc(fullscreen: bool) {
// TODO:
}
@(private="file")
init_legacy_gl :: proc(class_name: win32.wstring, hInstance: win32.HINSTANCE) {
temp_hwnd = win32.CreateWindowExW(
0,
class_name,
win32.L("Fake Window"),
win32.WS_OVERLAPPEDWINDOW,
0, 0, 1, 1,
nil,
nil,
hInstance,
nil,
)
if temp_hwnd == nil {
log.fatal("Failed to create fake window")
}
temp_device_ctx = win32.GetDC(temp_hwnd)
if temp_device_ctx == nil {
log.error("Failed to create device context for fake window")
}
pfd := win32.PIXELFORMATDESCRIPTOR{
nSize = size_of(win32.PIXELFORMATDESCRIPTOR),
nVersion = 1,
dwFlags = win32.PFD_DRAW_TO_WINDOW | win32.PFD_SUPPORT_OPENGL,
iPixelType = win32.PFD_TYPE_RGBA,
cColorBits = 32,
cAlphaBits = 8,
cDepthBits = 24,
iLayerType = win32.PFD_MAIN_PLANE,
}
pixel_format := win32.ChoosePixelFormat(temp_device_ctx, &pfd)
if pixel_format == 0 {
log.error("Failed to choose pixel format for fake window")
}
status := win32.SetPixelFormat(temp_device_ctx, pixel_format, &pfd)
if !status {
log.error("Failed to set pixel format for fake window")
}
temp_wgl_context = win32.wglCreateContext(temp_device_ctx)
if temp_wgl_context == nil {
log.fatal("Failed to create WGL context")
return
}
gl.load_up_to(3, 3, win32.gl_set_proc_address)
win32.wglMakeCurrent(temp_device_ctx, temp_wgl_context)
gl_version := gl.GetString(gl.VERSION)
strs := strings.split(string(gl_version), " ")
ver := strings.split(strs[0], ".")
gl_major, gl_minor := strconv.atoi(ver[0]), strconv.atoi(ver[1])
log.debugf("Fake Window GL: %d.%d", gl_major, gl_minor)
if !(gl_major > 3 || (gl_major == 3 && gl_minor >= 3)) {
log.fatalf("You need at least OpenGL 3.3 to run this application. Your OpenGL version is %d.%d", gl_major, gl_minor)
os.exit(1)
}
}
@(private="file")
init_gl :: proc(class_name: win32.wstring, window_title: win32.wstring, window_size: Vec2, hInstance: win32.HINSTANCE) {
screen_size := Vec2{
cast(f32)win32.GetSystemMetrics(win32.SM_CXSCREEN),
cast(f32)win32.GetSystemMetrics(win32.SM_CYSCREEN),
}
win_x := screen_size.x / 2 - window_size.x / 2
win_y := screen_size.y / 2 - window_size.y / 2
rect := win32.RECT{0, 0, cast(i32)window_size.x, cast(i32)window_size.y}
win32.AdjustWindowRect(&rect, win32.WS_OVERLAPPEDWINDOW, false)
win_width := rect.right - rect.left
win_height := rect.bottom - rect.top
hwnd := win32.CreateWindowExW(
0,
class_name,
window_title,
win32.WS_OVERLAPPEDWINDOW,
cast(i32)win_x, cast(i32)win_y, win_width, win_height,
nil,
nil,
hInstance,
nil,
)
if hwnd == nil {
log.fatal("Failed to create window")
return
}
device_ctx = win32.GetDC(hwnd)
if device_ctx == nil {
log.fatal("Failed to create device context")
return
}
wglChoosePixelFormatARB := cast(win32.ChoosePixelFormatARBType)win32.wglGetProcAddress("wglChoosePixelFormatARB")
wglCreateContextAttribsARB := cast(win32.CreateContextAttribsARBType)win32.wglGetProcAddress("wglCreateContextAttribsARB")
wglSwapIntervalEXT := cast(win32.SwapIntervalEXTType)win32.wglGetProcAddress("wglSwapIntervalEXT")
pixel_attribs := []i32{
win32.WGL_DRAW_TO_WINDOW_ARB, 1,
win32.WGL_SUPPORT_OPENGL_ARB, 1,
win32.WGL_DOUBLE_BUFFER_ARB, 1,
// TODO: test to see if EXCHANGE actually causes problems in fullscreen
/* WGL_SWAP_EXCHANGE_ARB causes problems with window menu in fullscreen */
win32.WGL_SWAP_METHOD_ARB, win32.WGL_SWAP_COPY_ARB,
win32.WGL_PIXEL_TYPE_ARB, win32.WGL_TYPE_RGBA_ARB,
win32.WGL_ACCELERATION_ARB, win32.WGL_FULL_ACCELERATION_ARB,
win32.WGL_COLOR_BITS_ARB, 32,
win32.WGL_ALPHA_BITS_ARB, 8,
win32.WGL_DEPTH_BITS_ARB, 24,
win32.WGL_STENCIL_BITS_ARB, 0,
win32.WGL_SAMPLES_ARB, 4,
0,
}
ctx_attribs := []i32{
win32.WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
win32.WGL_CONTEXT_MINOR_VERSION_ARB, 3,
win32.WGL_CONTEXT_PROFILE_MASK_ARB, win32.WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
0,
}
pixel_format: i32
num_formats: u32
success := wglChoosePixelFormatARB(device_ctx, raw_data(pixel_attribs), nil, 1, &pixel_format, &num_formats)
if !success {
log.error("Failed to choose pixel format")
return
}
pfd: win32.PIXELFORMATDESCRIPTOR
win32.DescribePixelFormat(device_ctx, pixel_format, size_of(win32.PIXELFORMATDESCRIPTOR), &pfd)
success = win32.SetPixelFormat(device_ctx, pixel_format, &pfd)
if !success {
log.error("Failed to set pixel format")
return
}
wgl_context = wglCreateContextAttribsARB(device_ctx, nil, raw_data(ctx_attribs))
if wgl_context == nil {
log.fatal("Failed to create WGL context")
return
}
win32.wglMakeCurrent(temp_device_ctx, nil)
win32.wglDeleteContext(temp_wgl_context)
win32.ReleaseDC(temp_hwnd, temp_device_ctx)
win32.DestroyWindow(temp_hwnd)
win32.wglMakeCurrent(device_ctx, wgl_context)
gl_version := gl.GetString(gl.VERSION)
log.infof("GL Version: %s", gl_version)
new_success := wglSwapIntervalEXT(1)
if !new_success {
log.error("Failed to enable v-sync")
}
gl.load_up_to(3, 3, win32.gl_set_proc_address)
win32.ShowWindow(hwnd, win32.SW_NORMAL)
gl.Enable(gl.BLEND)
gl.Enable(gl.MULTISAMPLE)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
}
@(private="file")
os_event_to_zephr_event :: proc(msg: win32.UINT) -> EventType {
switch msg {
case win32.WM_CLOSE: return .WINDOW_CLOSED
case win32.WM_SIZE: return .WINDOW_RESIZED
case win32.WM_MOUSEMOVE: return .MOUSE_MOVED
case win32.WM_MOUSEWHEEL: return .MOUSE_SCROLL
case win32.WM_LBUTTONDOWN: fallthrough
case win32.WM_MBUTTONDOWN: fallthrough
case win32.WM_RBUTTONDOWN: fallthrough
case win32.WM_XBUTTONDOWN: return .MOUSE_BUTTON_PRESSED
case win32.WM_LBUTTONUP: fallthrough
case win32.WM_MBUTTONUP: fallthrough
case win32.WM_RBUTTONUP: fallthrough
case win32.WM_XBUTTONUP: return .MOUSE_BUTTON_RELEASED
case win32.WM_SYSKEYDOWN: fallthrough
case win32.WM_KEYDOWN: return .KEY_PRESSED
case win32.WM_SYSKEYUP: fallthrough
case win32.WM_KEYUP: return .KEY_RELEASED
}
return .UNKNOWN
}
window_proc :: proc(hwnd: win32.HWND, msg: win32.UINT, wparam: win32.WPARAM, lparam: win32.LPARAM) -> win32.LRESULT {
result: win32.LRESULT
switch msg {
case win32.WM_CLOSE: fallthrough
case win32.WM_SIZE: fallthrough
case win32.WM_MOUSEMOVE: fallthrough
case win32.WM_MOUSEWHEEL: fallthrough
case win32.WM_LBUTTONDOWN: fallthrough
case win32.WM_MBUTTONDOWN: fallthrough
case win32.WM_RBUTTONDOWN: fallthrough
case win32.WM_XBUTTONDOWN: fallthrough
case win32.WM_LBUTTONUP: fallthrough
case win32.WM_MBUTTONUP: fallthrough
case win32.WM_RBUTTONUP: fallthrough
case win32.WM_XBUTTONUP: fallthrough
case win32.WM_SYSKEYDOWN: fallthrough
case win32.WM_SYSKEYUP: fallthrough
case win32.WM_KEYDOWN: fallthrough
case win32.WM_KEYUP:
queue.push(&zephr_ctx.event_queue, OsEvent{
type = msg,
lparam = lparam,
wparam = wparam,
})
case:
result = win32.DefWindowProcW(hwnd, msg, wparam, lparam)
}
return result
}
backend_init :: proc(window_title: cstring, window_size: Vec2, icon_path: cstring, window_non_resizable: bool) {
context.logger = logger
class_name := win32.L("zephr.main_window")
window_title := win32.utf8_to_wstring(string(window_title))
hInstance := win32.HINSTANCE(win32.GetModuleHandleW(nil))
hIcon := win32.LoadImageW(nil, win32.utf8_to_wstring(string(icon_path)), win32.IMAGE_ICON, 0, 0, win32.LR_DEFAULTSIZE | win32.LR_LOADFROMFILE)
wc := win32.WNDCLASSEXW {
cbSize = size_of(win32.WNDCLASSEXW),
style = win32.CS_HREDRAW | win32.CS_VREDRAW | win32.CS_OWNDC,
lpfnWndProc = cast(win32.WNDPROC)window_proc,
hInstance = hInstance,
lpszClassName = class_name,
hIcon = cast(win32.HICON)hIcon,
hIconSm = cast(win32.HICON)hIcon, // TODO: maybe we can have a 16x16 icon here. can be used on linux too
}
status := win32.RegisterClassExW(&wc)
if status == 0 {
log.error("Failed to register class")
}
// Make process aware of system scaling per monitor
win32.SetProcessDpiAwarenessContext(win32.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
init_legacy_gl(class_name, hInstance)
init_gl(class_name, window_title, window_size, hInstance)
}
backend_get_os_events :: proc(e_out: ^Event) -> bool {
context.logger = logger
msg: win32.MSG
for win32.PeekMessageW(&msg, hwnd, 0, 0, win32.PM_REMOVE) != win32.FALSE {
win32.TranslateMessage(&msg)
win32.DispatchMessageW(&msg)
}
for queue.len(zephr_ctx.event_queue) != 0 {
event := queue.pop_front(&zephr_ctx.event_queue)
e_out.type = os_event_to_zephr_event(event.type)
switch event.type {
case win32.WM_CLOSE:
return true
case win32.WM_SIZE:
width := win32.LOWORD(auto_cast event.lparam)
height := win32.HIWORD(auto_cast event.lparam)
zephr_ctx.window.size = Vec2{cast(f32)width, cast(f32)height}
zephr_ctx.projection = orthographic_projection_2d(0, zephr_ctx.window.size.x, zephr_ctx.window.size.y, 0)
gl.Viewport(0, 0, cast(i32)zephr_ctx.window.size.x, cast(i32)zephr_ctx.window.size.y)
e_out.window.width = cast(u32)width
e_out.window.height = cast(u32)height
return true
case win32.WM_LBUTTONDOWN, win32.WM_LBUTTONUP:
x := win32.GET_X_LPARAM(event.lparam)
y := win32.GET_Y_LPARAM(event.lparam)
e_out.mouse.pos = Vec2{cast(f32)x, cast(f32)y}
e_out.mouse.button = .BUTTON_LEFT
zephr_ctx.mouse.button = .BUTTON_LEFT
if event.type == win32.WM_LBUTTONDOWN {
zephr_ctx.mouse.pressed = true
} else {
zephr_ctx.mouse.released = true
zephr_ctx.mouse.pressed = false
}
return true
case win32.WM_MBUTTONDOWN, win32.WM_MBUTTONUP:
x := win32.GET_X_LPARAM(event.lparam)
y := win32.GET_Y_LPARAM(event.lparam)
e_out.mouse.pos = Vec2{cast(f32)x, cast(f32)y}
e_out.mouse.button = .BUTTON_MIDDLE
zephr_ctx.mouse.button = .BUTTON_MIDDLE
if event.type == win32.WM_MBUTTONDOWN {
zephr_ctx.mouse.pressed = true
} else {
zephr_ctx.mouse.released = true
zephr_ctx.mouse.pressed = false
}
return true
case win32.WM_RBUTTONDOWN, win32.WM_RBUTTONUP:
x := win32.GET_X_LPARAM(event.lparam)
y := win32.GET_Y_LPARAM(event.lparam)
e_out.mouse.pos = Vec2{cast(f32)x, cast(f32)y}
e_out.mouse.button = .BUTTON_RIGHT
zephr_ctx.mouse.button = .BUTTON_RIGHT
if event.type == win32.WM_RBUTTONDOWN {
zephr_ctx.mouse.pressed = true
} else {
zephr_ctx.mouse.released = true
zephr_ctx.mouse.pressed = false
}
return true
case win32.WM_XBUTTONDOWN, win32.WM_XBUTTONUP:
x := win32.GET_X_LPARAM(event.lparam)
y := win32.GET_Y_LPARAM(event.lparam)
e_out.mouse.pos = Vec2{cast(f32)x, cast(f32)y}
btn := win32.HIWORD(auto_cast event.wparam)
if btn == win32.XBUTTON1 {
e_out.mouse.button = .BUTTON_BACK
zephr_ctx.mouse.button = .BUTTON_BACK
} else if btn == win32.XBUTTON2 {
e_out.mouse.button = .BUTTON_FORWARD
zephr_ctx.mouse.button = .BUTTON_FORWARD
}
if event.type == win32.WM_XBUTTONDOWN {
zephr_ctx.mouse.pressed = true
} else {
zephr_ctx.mouse.released = true
zephr_ctx.mouse.pressed = false
}
return true
case win32.WM_MOUSEMOVE:
x := win32.GET_X_LPARAM(event.lparam)
y := win32.GET_Y_LPARAM(event.lparam)
zephr_ctx.mouse.pos = Vec2{cast(f32)x, cast(f32)y}
e_out.mouse.pos = zephr_ctx.mouse.pos
return true
case win32.WM_MOUSEWHEEL:
// This will be a multiple of win32.WHEEL_DELTA which is 120
wheel_delta := win32.GET_WHEEL_DELTA_WPARAM(event.wparam)
e_out.mouse.scroll_direction = .UP if (wheel_delta > 0) else .DOWN
return true
case win32.WM_SYSKEYDOWN: fallthrough
case win32.WM_KEYDOWN:
log.debug(event.lparam)
// Bits 16-23 hold the scancode
system_scancode := (event.lparam & 0xFF0000) >> 16
scancode := scan1_scancode_to_zephr_scancode_map[system_scancode]
log.debug(system_scancode)
log.debug(scancode)
// TODO:
case win32.WM_SYSKEYUP: fallthrough
case win32.WM_KEYUP:
// TODO:
// 0101 0010
//log.debug("released key")
}
}
return false
}
backend_shutdown :: proc() {
win32.wglMakeCurrent(device_ctx, nil)
win32.wglDeleteContext(wgl_context)
win32.ReleaseDC(hwnd, device_ctx)
win32.DestroyWindow(hwnd)
}
backend_swapbuffers :: proc() {
win32.SwapBuffers(device_ctx)
}
// https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#scan-codes
// https://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf
@(private="file")
scan1_scancode_to_zephr_scancode_map :: proc(scancode: u8, is_extended: bool) -> Scancode {
switch scancode {
case 0: return .NULL
case 0x1E: return .A
case 0x30: return .B
case 0x2E: return .C
case 0x20: return .D
case 0x12: return .E
case 0x21: return .F
case 0x22: return .G
case 0x23: return .H
case 0x17: return .I
case 0x24: return .J
case 0x25: return .K
case 0x26: return .L
case 0x32: return .M
case 0x31: return .N
case 0x18: return .O
case 0x19: return .P
case 0x10: return .Q
case 0x13: return .R
case 0x1F: return .S
case 0x14: return .T
case 0x16: return .U
case 0x2F: return .V
case 0x11: return .W
case 0x2D: return .X
case 0x15: return .Y
case 0x2C: return .Z
case 0x02: return .KEY_1
case 0x03: return .KEY_2
case 0x04: return .KEY_3
case 0x05: return .KEY_4
case 0x06: return .KEY_5
case 0x07: return .KEY_6
case 0x08: return .KEY_7
case 0x09: return .KEY_8
case 0x0A: return .KEY_9
case 0x0B: return .KEY_0
case 0x1C: return .ENTER
case 0x01: return .ESCAPE
case 0x0E: return .BACKSPACE // Says "Keyboard Delete" on microsoft.com but it's actually backspace
case 0x0F: return .TAB
case 0x39: return .SPACE
case 0x0C: return .MINUS
case 0x0D: return .EQUALS
case 0x1A: return .LEFT_BRACKET
case 0x1B: return .RIGHT_BRACKET
case 0x2B: return .BACKSLASH
//case 0x2B: return = .NON_US_HASH, // European keyboards have a hash instead of a backslash. Maps to a different HID scancode
case 0x27: return .SEMICOLON
case 0x28: return .APOSTROPHE
case 0x29: return .GRAVE
case 0x33: return .COMMA
case 0x34: return .PERIOD
case 0x35: return .SLASH
case 0x3A: return .CAPS_LOCK
case 0x3B: return .F1
case 0x3C: return .F2
case 0x3D: return .F3
case 0x3E: return .F4
case 0x3F: return .F5
case 0x40: return .F6
case 0x41: return .F7
case 0x42: return .F8
case 0x43: return .F9
case 0x44: return .F10
case 0x57: return .F11
case 0x58: return .F12
case 0x54: return .PRINT_SCREEN
//case 0xE037 = .PRINT_SCREEN, // SysRq ???
case 0x46: return .SCROLL_LOCK
case 0x45: return .PAUSE
//case 0xE046 = .PAUSE, // Break ??
//case 0xE11D45 = .PAUSE, // Some legacy stuff ???
case 0x52:
// INSERT
// KP_0
case 0x47:
// HOME
// KP_7
case 0x49:
// PAGE_UP
// KP_9
case 0x53:
// DELETE
// KP_PERIOD
case 0x4F:
// END
// KP_1
case 0x51:
// PAGE_DOWN
// KP_3
case 0x4D:
// RIGHT
// KP_6
case 0x4B:
// LEFT
// KP_4
case 0x50:
// DOWN
// KP_2
case 0xE048:
// UP
// KP_8
}
//0x45 = .NUM_LOCK_OR_CLEAR, // conflicts with Pause, Pause doesn't have the extended bit set. don't know about this
//0xE045 = .NUM_LOCK_OR_CLEAR, // Legacy stuff
0xE035 = .KP_DIVIDE,
0x37 = .KP_MULTIPLY,
0x4A = .KP_MINUS,
0x4E = .KP_PLUS,
0xE01C = .KP_ENTER,
0x4C = .KP_5,
0x56 = .NON_US_BACKSLASH,
0xE05D = .APPLICATION,
0xE05E = .POWER,
0x59 = .KP_EQUALS,
0x64 = .F13,
0x65 = .F14,
0x66 = .F15,
0x67 = .F16,
0x68 = .F17,
0x69 = .F18,
0x6A = .F19,
0x6B = .F20,
0x6C = .F21,
0x6D = .F22,
0x6E = .F23,
0x76 = .F24,
0x7E = .KP_COMMA,
0x73 = .INTERNATIONAL1,
0x70 = .INTERNATIONAL2,
0x7D = .INTERNATIONAL3,
0x79 = .INTERNATIONAL4,
0x7B = .INTERNATIONAL5,
0x5C = .INTERNATIONAL6,
0x72 = .LANG1, // Only emitted on Key Release
0xF2 = .LANG1, // Legacy, Only emitted on Key Release
0x71 = .LANG2, // Only emitted on Key Release
0xF1 = .LANG2, // Legacy, Only emitted on Key Release
0x78 = .LANG3,
0x77 = .LANG4,
//0x76 = .LANG5, // Conflicts with F24
0x1D = .LEFT_CTRL,
0x2A = .LEFT_SHIFT,
0x38 = .LEFT_ALT,
0xE05B = .LEFT_META,
0xE01D = .RIGHT_CTRL,
0x36 = .RIGHT_SHIFT,
0xE038 = .RIGHT_ALT,
0xE05C = .RIGHT_META,
// End of Keyboard/Keypad section on microsoft.com
// We currently don't map or care about the Consumer section
}