Last active
November 22, 2024 07:03
-
-
Save GrumpyLion/7298282a6c7e2bcab699a72806d2ca59 to your computer and use it in GitHub Desktop.
OpenGL and win32 window creation with Odin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
// This is how you can manually initialize a window and OpenGL context on windows, instead of using SDL or GLFW. | |
// Also renders a colorful quad onto the screen. | |
// I made this as an exercise and getting into Odin. | |
// This is my first "complete" odin program, so there's probably much better ways of doing this :D | |
import "base:runtime" | |
import "core:fmt" | |
import "core:c" | |
import win32 "core:sys/windows" | |
import glm "core:math/linalg/glsl" | |
import gl "vendor:OpenGL" | |
vertex_source := ` | |
#version 330 core | |
layout(location=0) in vec3 a_position; | |
layout(location=1) in vec4 a_color; | |
out vec4 v_color; | |
void main() { | |
gl_Position = vec4(a_position, 1.0); | |
v_color = a_color; | |
} | |
` | |
fragment_source := ` | |
#version 330 core | |
in vec4 v_color; | |
out vec4 o_color; | |
void main() { | |
o_color = v_color; | |
} | |
` | |
wglSwapIntervalEXT : win32.SwapIntervalEXTType | |
running : bool = false | |
// is also available in core/sys/windows/wgl.odin | |
gl_set_proc_address :: proc(p: rawptr, name: cstring) { | |
func := win32.wglGetProcAddress(name) | |
switch uintptr(func) { | |
case 0, 1, 2, 3, ~uintptr(0): | |
module := win32.LoadLibraryW(win32.L("opengl32.dll")) | |
func = win32.GetProcAddress(module, name) | |
} | |
(^rawptr)(p)^ = func | |
} | |
GL_MAJOR_VERSION : c.int : 4 | |
GL_MINOR_VERSION :: 6 | |
CLASSNAME : cstring : "OpenGLClass" | |
APPNAME : cstring : "OpenGL" | |
WinProc :: proc "stdcall" ( | |
window: win32.HWND, | |
message: win32.UINT, | |
wparam: win32.WPARAM, | |
lparam: win32.LPARAM | |
) -> win32.LRESULT { | |
result: win32.LRESULT | |
switch message { | |
case win32.WM_DESTROY: | |
win32.DestroyWindow(window) | |
win32.PostQuitMessage(0) | |
running = false | |
result = 0 | |
case: | |
result = win32.DefWindowProcW( window, message, wparam, lparam) | |
} | |
return result | |
} | |
gl_debug_callback :: proc "c" (source: u32, type: u32, id: u32, severity: u32, length: i32, message: cstring, userParam: rawptr) { | |
context = runtime.default_context() | |
fmt.println(message) | |
} | |
main :: proc() { | |
hinst : win32.HANDLE = auto_cast win32.GetModuleHandleW(nil) | |
icon := win32.LoadIconA(nil, win32.IDI_APPLICATION) | |
cursor := win32.LoadCursorA(nil, win32.IDC_ARROW) | |
window_context := win32.WNDCLASSEXW { | |
size_of(win32.WNDCLASSEXW), | |
win32.CS_HREDRAW | win32.CS_VREDRAW, | |
WinProc, | |
0,0, | |
hinst,icon,cursor, nil, nil, | |
win32.L(CLASSNAME), | |
nil, | |
} | |
if error := win32.RegisterClassExW(&window_context); error == 0 { | |
win32.MessageBoxW(nil, win32.L("Unable to Register Window Class"), win32.L("Error"), win32.MB_ICONERROR) | |
return | |
} | |
wglChoosePixelFormatARB : win32.ChoosePixelFormatARBType | |
wglCreateContextAttribsARB : win32.CreateContextAttribsARBType | |
dw_style := win32.WS_OVERLAPPEDWINDOW | |
// fake window and context for acquiring functions pointers | |
hwnd := win32.CreateWindowExW( | |
win32.WS_EX_OVERLAPPEDWINDOW, | |
win32.L(CLASSNAME), | |
win32.L(APPNAME), | |
dw_style, | |
win32.CW_USEDEFAULT, win32.CW_USEDEFAULT, win32.CW_USEDEFAULT, win32.CW_USEDEFAULT, | |
nil, nil, hinst, nil, | |
) | |
if hwnd == nil { | |
win32.MessageBoxW(nil, win32.L("Unable to Create Window"),win32.L("Error"), win32.MB_ICONERROR) | |
return | |
} | |
fake_dc := win32.GetDC(hwnd) | |
if fake_dc == nil { | |
win32.MessageBoxW(nil, win32.L("Unable to Get DC"), win32.L("Error"), win32.MB_ICONERROR) | |
return | |
} | |
pfd := win32.PIXELFORMATDESCRIPTOR{ | |
nSize = size_of(win32.PIXELFORMATDESCRIPTOR), | |
nVersion = 1, | |
dwFlags = win32.PFD_DRAW_TO_WINDOW | win32.PFD_SUPPORT_OPENGL | win32.PFD_DOUBLEBUFFER, | |
iPixelType = win32.PFD_TYPE_RGBA, | |
cColorBits = 32, | |
cAlphaBits = 8, | |
cDepthBits = 24 | |
} | |
pixel_format := win32.ChoosePixelFormat(fake_dc, &pfd) | |
if pixel_format == 0 { | |
win32.MessageBoxW(nil, win32.L("Failed to get pixel format"), win32.L("Error"), win32.MB_ICONERROR) | |
return | |
} | |
if !win32.SetPixelFormat(fake_dc, pixel_format, &pfd) { | |
win32.MessageBoxW(nil, win32.L("Failed to set pixel format"), win32.L("Error"), win32.MB_ICONERROR) | |
return | |
} | |
fake_rc := win32.wglCreateContext(fake_dc) | |
if fake_rc == nil { | |
win32.MessageBoxW(nil, win32.L("Failed to create context"), win32.L("Error"), win32.MB_ICONERROR) | |
return | |
} | |
if !win32.wglMakeCurrent(fake_dc, fake_rc) { | |
win32.MessageBoxW(nil, win32.L("Failed to Get Pixel Format"), win32.L("Error"), win32.MB_ICONERROR) | |
return | |
} | |
gl_set_proc_address(&wglChoosePixelFormatARB, "wglChoosePixelFormatARB") | |
gl_set_proc_address(&wglCreateContextAttribsARB, "wglCreateContextAttribsARB") | |
gl_set_proc_address(&wglSwapIntervalEXT, "wglSwapIntervalEXT") | |
win32.wglMakeCurrent(fake_dc, nil) | |
win32.wglDeleteContext(fake_rc) | |
win32.ReleaseDC(hwnd, fake_dc) | |
win32.DestroyWindow(hwnd) | |
// actual window and context creation | |
width : i32 = 1024 | |
height : i32 = 600 | |
border_rect : win32.RECT = {} | |
win32.AdjustWindowRectEx(&border_rect, dw_style, false, 0) | |
width += border_rect.right - border_rect.left | |
height += border_rect.bottom - border_rect.top | |
hwnd = win32.CreateWindowExW( | |
win32.WS_EX_OVERLAPPEDWINDOW, | |
win32.L(CLASSNAME), | |
win32.L(APPNAME), | |
dw_style, | |
100, 100, width, height, | |
nil,nil,hinst,nil, | |
) | |
defer win32.DestroyWindow(hwnd) | |
if hwnd == nil { | |
win32.MessageBoxW(nil, win32.L("Unable to Create Window"), win32.L("Error"), win32.MB_ICONERROR) | |
return | |
} | |
dc := win32.GetDC(hwnd) | |
defer win32.ReleaseDC(hwnd, dc) | |
pixel_attribs : []i32 = { | |
win32.WGL_DRAW_TO_WINDOW_ARB, 1, | |
win32.WGL_SUPPORT_OPENGL_ARB, 1, | |
win32.WGL_DOUBLE_BUFFER_ARB, 1, | |
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, | |
0 | |
} | |
pixel_format = 0 | |
num_pixel_formats : win32.UINT32 = 0 | |
if !wglChoosePixelFormatARB(dc, raw_data(pixel_attribs), nil, 1, &pixel_format, &num_pixel_formats) { | |
win32.MessageBoxW(nil,win32.L("Failed to choose a pixel format"), win32.L("Error"), win32.MB_ICONERROR) | |
return | |
} | |
pixel_format_descriptor : win32.PIXELFORMATDESCRIPTOR | |
win32.DescribePixelFormat(dc, pixel_format, size_of(win32.PIXELFORMATDESCRIPTOR), &pixel_format_descriptor) | |
if !win32.SetPixelFormat(dc, pixel_format, &pixel_format_descriptor) { | |
win32.MessageBoxW(nil,win32.L("Unable to set pixle format"), win32.L("Error"), win32.MB_ICONERROR) | |
return | |
} | |
context_attribs : []i32 = { | |
win32.WGL_CONTEXT_MAJOR_VERSION_ARB, GL_MAJOR_VERSION, | |
win32.WGL_CONTEXT_MINOR_VERSION_ARB, GL_MINOR_VERSION, | |
win32.WGL_CONTEXT_PROFILE_MASK_ARB, win32.WGL_CONTEXT_CORE_PROFILE_BIT_ARB, | |
win32.WGL_CONTEXT_FLAGS_ARB, win32.WGL_CONTEXT_DEBUG_BIT_ARB, | |
0 | |
} | |
render_context := wglCreateContextAttribsARB(dc, nil, raw_data(context_attribs)) | |
defer win32.wglDeleteContext(render_context) | |
if !win32.wglMakeCurrent(dc, render_context) { | |
win32.MessageBoxW(nil,win32.L("Failed to make the current context"), win32.L("Error"), win32.MB_ICONERROR) | |
return | |
} | |
defer win32.wglMakeCurrent(dc, nil) | |
win32.ShowWindow(hwnd, win32.SW_SHOWNORMAL) | |
// opengl init | |
gl.load_up_to(int(GL_MAJOR_VERSION), GL_MINOR_VERSION, gl_set_proc_address) | |
gl.Enable(gl.DEBUG_OUTPUT) | |
gl.Enable(gl.DEBUG_OUTPUT_SYNCHRONOUS) | |
gl.DebugMessageCallback(gl_debug_callback, nil) | |
gl.Viewport(0, 0, width, height) | |
// shader init | |
program, program_ok := gl.load_shaders_source(vertex_source, fragment_source) | |
if !program_ok { | |
win32.MessageBoxW(nil,win32.L("Failed to create GLSL program"), win32.L("Error"), win32.MB_ICONERROR) | |
return | |
} | |
defer gl.DeleteProgram(program) | |
gl.UseProgram(program) | |
uniforms := gl.get_uniforms_from_program(program) | |
defer delete(uniforms) | |
// vertex struct | |
Vertex :: struct { | |
pos: glm.vec3, | |
col: glm.vec4, | |
} | |
vertices := []Vertex{ | |
{{-0.5, +0.5, 0}, {1.0, 0.0, 0.0, 0.75}}, | |
{{-0.5, -0.5, 0}, {1.0, 1.0, 0.0, 0.75}}, | |
{{+0.5, -0.5, 0}, {0.0, 1.0, 0.0, 0.75}}, | |
{{+0.5, +0.5, 0}, {0.0, 0.0, 1.0, 0.75}}, | |
} | |
indices := []u16{ | |
0, 1, 2, | |
2, 3, 0, | |
} | |
// buffers init | |
vao: u32 | |
gl.CreateVertexArrays(1, &vao); defer gl.DeleteVertexArrays(1, &vao) | |
vbo, ebo: u32 | |
gl.CreateBuffers(1, &vbo); defer gl.DeleteBuffers(1, &vbo) | |
gl.CreateBuffers(1, &ebo); defer gl.DeleteBuffers(1, &ebo) | |
gl.NamedBufferData(vbo, len(vertices) * size_of(Vertex), raw_data(vertices), gl.STATIC_DRAW) | |
gl.NamedBufferData(ebo, len(indices) * size_of(u16), raw_data(indices), gl.STATIC_DRAW) | |
gl.EnableVertexArrayAttrib(vao, 0) | |
gl.VertexArrayAttribBinding(vao, 0, 0) | |
gl.VertexArrayAttribFormat(vao, 0, 3, gl.FLOAT, false, 0) | |
gl.EnableVertexArrayAttrib(vao, 1) | |
gl.VertexArrayAttribBinding(vao, 1, 0) | |
gl.VertexArrayAttribFormat(vao, 1, 4, gl.FLOAT, false, 3 * size_of(f32)) | |
gl.VertexArrayVertexBuffer(vao, 0, vbo, 0, size_of(Vertex)) | |
gl.VertexArrayElementBuffer(vao, ebo) | |
msg : win32.MSG | |
running = true | |
for running { | |
// window handling | |
for win32.PeekMessageA(&msg, hwnd, 0, 0, win32.PM_REMOVE) { | |
win32.TranslateMessage(&msg) | |
win32.DispatchMessageW(&msg) | |
} | |
// draw | |
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) | |
gl.ClearColor(0.5, 0.5, 0.5, 1.0) | |
gl.BindVertexArray(vao) | |
gl.UseProgram(program) | |
gl.DrawElements(gl.TRIANGLES, i32(len(indices)), gl.UNSIGNED_SHORT, nil) | |
win32.SwapBuffers(dc) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment