Harbor

branch main
showing the latest snapshot on main
gl_context_linux.odin 4.9 KB · Plain text
backend/opengl/gl_context_linux.odin 0644 Raw
#+build linux
package opengl_backend

import "core:log"
import egl "vendor:egl"

import bk ".."

EGL_PBUFFER_BIT :: 0x0001

GL_Context :: struct {
	display: egl.Display,
	surface: egl.Surface,
	gl_ctx:  egl.Context,
	width:   u32,
	height:  u32,
	headless: bool,
}

create_gl_context :: proc(
	surface: bk.Surface_Desc,
	width, height: u32,
	title: cstring,
) -> (
	GL_Context,
	bool,
) {
	display: egl.Display
	native_window: egl.NativeWindowType
	headless := false

	switch surface.kind {
	case .X11:
		if surface.x11.display == nil || surface.x11.window == 0 {
			log.error("gpu/opengl: invalid X11 surface")
			return {}, false
		}
		display = egl.GetDisplay(egl.NativeDisplayType(surface.x11.display))
		native_window = egl.NativeWindowType(uintptr(surface.x11.window))
	case .Wayland:
		if surface.wayland.display == nil || surface.wayland.surface == nil {
			log.error("gpu/opengl: invalid Wayland surface")
			return {}, false
		}
		display = egl.GetDisplay(egl.NativeDisplayType(surface.wayland.display))
		native_window = egl.NativeWindowType(surface.wayland.surface)
	case .None:
		display = egl.GetPlatformDisplay(.SURFACELESS_MESA, nil, nil)
		native_window = nil
		headless = true
	case .Win32:
		log.error("gpu/opengl: Linux OpenGL backend cannot use Win32 surface")
		return {}, false
	}

	if display == egl.NO_DISPLAY {
		log.error("gpu/opengl: eglGetDisplay failed")
		return {}, false
	}

	major, minor: i32
	if !egl.Initialize(display, &major, &minor) {
		log.error("gpu/opengl: eglInitialize failed")
		return {}, false
	}

	if !egl.BindAPI(egl.OPENGL_API) {
		log.error("gpu/opengl: eglBindAPI(OpenGL) failed")
		egl.Terminate(display)
		return {}, false
	}

	window_config_attribs := [?]i32 {
		egl.SURFACE_TYPE,
		egl.WINDOW_BIT,
		egl.RENDERABLE_TYPE,
		egl.OPENGL_BIT,
		egl.RED_SIZE,
		8,
		egl.GREEN_SIZE,
		8,
		egl.BLUE_SIZE,
		8,
		egl.ALPHA_SIZE,
		8,
		egl.DEPTH_SIZE,
		24,
		egl.NONE,
	}
	headless_config_attribs := [?]i32 {
		egl.SURFACE_TYPE,
		EGL_PBUFFER_BIT,
		egl.RENDERABLE_TYPE,
		egl.OPENGL_BIT,
		egl.RED_SIZE,
		8,
		egl.GREEN_SIZE,
		8,
		egl.BLUE_SIZE,
		8,
		egl.ALPHA_SIZE,
		8,
		egl.DEPTH_SIZE,
		24,
		egl.NONE,
	}
	config: egl.Config
	config_count: i32
	if headless {
		if !egl.ChooseConfig(display, &headless_config_attribs[0], &config, 1, &config_count) ||
		   config_count == 0 {
			log.error("gpu/opengl: no compatible surfaceless EGL config")
			egl.Terminate(display)
			return {}, false
		}
	} else if !egl.ChooseConfig(display, &window_config_attribs[0], &config, 1, &config_count) ||
	          config_count == 0 {
		log.error("gpu/opengl: no compatible EGL config")
		egl.Terminate(display)
		return {}, false
	}

	egl_surface := egl.NO_SURFACE
	if !headless {
		egl_surface = egl.CreateWindowSurface(display, config, native_window, nil)
		if egl_surface == egl.NO_SURFACE {
			log.error("gpu/opengl: eglCreateWindowSurface failed")
			egl.Terminate(display)
			return {}, false
		}
	}

	context_attribs := [?]i32 {
		egl.CONTEXT_MAJOR_VERSION,
		4,
		egl.CONTEXT_MINOR_VERSION,
		5,
		egl.CONTEXT_OPENGL_PROFILE_MASK,
		egl.CONTEXT_OPENGL_CORE_PROFILE_BIT,
		egl.NONE,
	}
	gl_ctx := egl.CreateContext(display, config, egl.NO_CONTEXT, &context_attribs[0])
	if gl_ctx == egl.NO_CONTEXT {
		log.error("gpu/opengl: eglCreateContext(4.5 core) failed")
		egl.DestroySurface(display, egl_surface)
		egl.Terminate(display)
		return {}, false
	}

	if !egl.MakeCurrent(display, egl_surface, egl_surface, gl_ctx) {
		log.error("gpu/opengl: eglMakeCurrent failed")
		egl.DestroyContext(display, gl_ctx)
		if egl_surface != egl.NO_SURFACE do egl.DestroySurface(display, egl_surface)
		egl.Terminate(display)
		return {}, false
	}

	if !headless do egl.SwapInterval(display, 1)
	return {
			display = display,
			surface = egl_surface,
			gl_ctx = gl_ctx,
			width = width,
			height = height,
			headless = headless,
		},
		true
}

destroy_gl_context :: proc(ctx: ^GL_Context) {
	if ctx == nil || ctx.display == egl.NO_DISPLAY do return
	egl.MakeCurrent(ctx.display, egl.NO_SURFACE, egl.NO_SURFACE, egl.NO_CONTEXT)
	if ctx.gl_ctx != egl.NO_CONTEXT do egl.DestroyContext(ctx.display, ctx.gl_ctx)
	if ctx.surface != egl.NO_SURFACE do egl.DestroySurface(ctx.display, ctx.surface)
	egl.Terminate(ctx.display)
	ctx^ = {}
}

make_gl_context_current :: proc(ctx: ^GL_Context) -> bool {
	if ctx == nil || ctx.display == egl.NO_DISPLAY || ctx.gl_ctx == egl.NO_CONTEXT do return false
	return egl.MakeCurrent(ctx.display, ctx.surface, ctx.surface, ctx.gl_ctx) == egl.TRUE
}

swap_gl_context :: proc(ctx: ^GL_Context) -> bool {
	if ctx == nil || ctx.display == egl.NO_DISPLAY do return false
	if ctx.headless do return true
	if ctx.surface == egl.NO_SURFACE do return false
	return egl.SwapBuffers(ctx.display, ctx.surface) == egl.TRUE
}

resize_gl_context :: proc(ctx: ^GL_Context, width, height: u32) {
	if ctx == nil do return
	ctx.width = width
	ctx.height = height
}

gl_context_set_proc_address :: proc(p: rawptr, name: cstring) {
	(^rawptr)(p)^ = egl.GetProcAddress(name)
}