Skip to content

Loading function pointers

gingerBill edited this page Sep 6, 2021 · 3 revisions

Introduction

In the following we will use the process of grabbing OpenGL functions from the graphics driver as an example, but the same process can be generalized to any other function (or, procedure) pointer loading.

Summary

We have three options on how to fetch an OpenGL function pointer from the driver. If I want to use the procedure glfw.GetProcAddress, then

// assigning directly, but casting the result of a glfw.GetProcAddressprocedure call
CullFace = cast(proc"c"(u32))glfw.GetProcAddress("glCullFace")

// passing a pointer to the procedure variable to a set_proc_address procedure, but interpreting it as a rawptr.
set_proc_address :: proc(p: rawptr, name: cstring) {
	(cast(^rawptr)p)^ = glfw.GetProcAddress(name)
}
set_proc_address(&CullFace, "glCullFace")

// passing a pointer to the procedure variable to a set_proc_address procedure, but interpreting it as a ^rawptr
set_proc_address :: proc(p: ^rawptr, name: cstring) {
	p^ = glfw.GetProcAddress(name)
}
set_proc_address(cast(^rawptr)&CullFace, "glCullFace")

Explanation

Modern OpenGL functions has to be loaded at runtime from the dynamic/shared library supplied by your driver (opengl32.dll in Windows and libGL.so in Linux). To use them we need to declare all OpenGL functions we want to use as function pointers, then we call the appropriate OS API to fetch the function pointers from driver at runtime. The last part is typically done by calling a function like wglGetProcAddress (in Windows), or glXGetProcAddress (in Linux), or an OS wrapper function like glfwGetProcAddress with the function name as a string for input.

Consider the first function listed in gl.odin, glCullFace. It is declared as:

CullFace: proc "c" (mode: u32)

and is simply just a procedure variable (equivalent to a function pointer in C).

Say I wanted to use glfw.GetProcAddress to grab the function pointers from the driver. I could then assign to the CullFace variable like

CullFace = cast(proc"c"(u32))glfw.GetProcAddress("glCullFace")

But this is a bit unwieldy to handle for a huge library such as gl.odin (a lot of extra typing for the explicit casting). Instead we can pass a pointer to the procedure variable as a parameter to a set_proc_address procedure that sets it for us

set_proc_address :: proc(p: rawptr, name: cstring) {
	(cast(^rawptr)p)^ = glfw.GetProcAddress(name)
}
set_proc_address(&CullFace, "glCullFace")

where the casting of p to a pointer reflects that the input to the set_proc_address is a pointer to a function pointer!

The reason one makes the p parameter a rawptr instead of a pointer to a rawptr is that in that case one would have to cast upon calling set_proc_address, which looks worse.

set_proc_address :: proc(p: ^rawptr, name: cstring) {
	p^ = glfw.GetProcAddress(name)
}
set_proc_address(cast(^rawptr)&CullFace, "glCullFace")

Every pointer type can be assigned to a rawptr variable, but not a ^rawptr (which is not a rawptr!).

Depending on what you want to do, you might prefer one approach over the other. If you want to fetch pointers in bulk, you might prefer the second option. If you want to only fetch a select few you might prefer the first option.