在Wayland上使用EGL创建多个窗口时,遇到片段着色器的问题
我在Odin中使用了自己对libwayland的绑定,能够一次打开好几个窗口。顶点着色器工作正常,但片段着色器有问题。似乎只有在我应用中创建的最后一个窗口里才起作用(见截图)。
这可能为什么会这样?

下面是我启动应用的代码:
package main
import "base:runtime"
import "core:c/libc"
import "core:fmt"
import "core:log"
import lg "core:math/linalg"
import "core:mem"
import "core:strings"
import "core:time"
import wl "packages/wayland"
import gl "vendor:OpenGL"
import "vendor:egl"
main :: proc() {
when ODIN_DEBUG {
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track)
context.logger = log.create_console_logger()
defer {
log.destroy_console_logger(context.logger)
if len(track.allocation_map) > 0 {
for _, entry in track.allocation_map {
log.warnf("%v leaked %v bytes\n", entry.location, entry.size)
}
}
mem.tracking_allocator_destroy(&track)
}
}
window_err := wl.create_new_window(200, 200, "TEST")
if !window_err {
fmt.println("Failed to create new window")
return
}
window_err = wl.create_new_window(230, 700, "TEST2")
if !window_err {
fmt.println("Failed to create new window")
return
}
olc := wl.olc
vertices : [dynamic]f32
append(&vertices,-1)
append(&vertices,0)
append(&vertices,0)
append(&vertices,1)
append(&vertices,0)
append(&vertices,0)
append(&vertices,1)
append(&vertices,1)
append(&vertices,0)
append(&vertices,0)
append(&vertices,0)
append(&vertices,1)
append(&vertices,0)
append(&vertices,1)
append(&vertices,0)
append(&vertices,1)
append(&vertices,0)
append(&vertices,1)
append(&vertices,0)
append(&vertices,1)
append(&vertices,1)
// useful utility procedures that are part of vendor:OpenGl
shaderProgram, program_ok := gl.load_shaders_source(vertex_source, fragment_source)
if !program_ok {
fmt.eprintln("Failed to create GLSL program")
return
}
defer gl.DeleteProgram(shaderProgram)
vao: u32
gl.GenVertexArrays(1, &vao)
defer gl.DeleteVertexArrays(1, &vao)
// initialization of OpenGL buffers
vbo: u32
gl.GenBuffers(1, &vbo)
defer gl.DeleteBuffers(1, &vbo)
for olc.running && wl.display_dispatch(olc.display) != -1 {
for w, idx in olc.windows {
ft := time.now()
if egl.MakeCurrent(w.egl_display, w.egl_surface, w.egl_surface, w.egl_context) {
gl.load_up_to(4, 6, egl.gl_set_proc_address)
gl.Viewport(0, 0, w.width, w.height)
gl.ClearColor(250.0 / 255, f32(idx) * 100 / 255, 158.0 / 255, 1)
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
if idx == 0 {
vertices[0] = lg.cos(f32(ft._nsec * 1000))
} else {
vertices[0] = -1
}
gl.BindVertexArray(vao)
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
gl.BufferData(
gl.ARRAY_BUFFER,
len(vertices) * size_of(f32),
raw_data(vertices[:]),
gl.DYNAMIC_DRAW,
)
gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 7 * size_of(f32), 0)
gl.VertexAttribPointer(1, 4, gl.FLOAT, false, 7 * size_of(f32), 3 * size_of(f32))
gl.EnableVertexAttribArray(0)
gl.EnableVertexAttribArray(1)
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.UseProgram(shaderProgram)
gl.BindVertexArray(vao)
gl.DrawArrays(gl.TRIANGLES, 0, 3)
egl.SwapBuffers(w.egl_display, w.egl_surface)
}
}
}
free(wl.olc)
when ODIN_DEBUG {
if len(track.bad_free_array) > 0 {
for b in track.bad_free_array {
log.errorf("Bad free at: %v", b.location)
}
libc.getchar()
panic("Bad free detected")
}
}
}
vertex_source := `#version 460 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 460 core
in vec4 v_color;
out vec4 fragColor;
void main() {
fragColor = v_color;
}
`
这是创建窗口的代码:
@(private = "file")
_init_egl :: proc(w: ^OL_Window) -> OL_Error {
EGL_SAMPLES :: 0x3031
EGL_SAMPLE_BUFFERS :: 0x3032
w^.egl_config_attr = make([dynamic]i32, olc.allocator)
append(&w.egl_config_attr, egl.SURFACE_TYPE)
append(&w.egl_config_attr, egl.WINDOW_BIT)
append(&w.egl_config_attr, egl.RED_SIZE)
append(&w.egl_config_attr, 8)
append(&w.egl_config_attr, egl.GREEN_SIZE)
append(&w.egl_config_attr, 8)
append(&w.egl_config_attr, egl.BLUE_SIZE)
append(&w.egl_config_attr, 8)
append(&w.egl_config_attr, egl.ALPHA_SIZE)
append(&w.egl_config_attr, 8)
append(&w.egl_config_attr, egl.DEPTH_SIZE)
append(&w.egl_config_attr, 24)
append(&w.egl_config_attr, egl.RENDERABLE_TYPE)
append(&w.egl_config_attr, egl.OPENGL_BIT)
append(&w.egl_config_attr, EGL_SAMPLE_BUFFERS)
append(&w.egl_config_attr, 1)
append(&w.egl_config_attr, EGL_SAMPLES)
append(&w.egl_config_attr, 4)
append(&w.egl_config_attr, egl.NONE)
w^.egl_display = egl.GetDisplay(egl.NativeDisplayType(olc.display))
if w.egl_display == egl.NO_DISPLAY {
return OL_Create_EGL_Display_Error{}
}
if !egl.Initialize(w.egl_display, &w.egl_major, &w.egl_minor) {
return OL_Initialize_EGL_Error{}
}
if !egl.ChooseConfig(
w.egl_display,
raw_data(w.egl_config_attr),
&w.egl_config,
1,
&w.egl_num_config,
) {
return OL_Create_EGL_Config_Error{}
}
egl.BindAPI(egl.OPENGL_API)
w^.egl_context = egl.CreateContext(w.egl_display, w.egl_config, egl.NO_CONTEXT, nil)
if w.egl_context == egl.NO_CONTEXT {
return OL_Create_EGL_Context_Error{}
}
w^.egl_window = egl_window_create(w.wl_surface, int(w.width), int(w.height))
w^.egl_surface = egl.CreateWindowSurface(
w.egl_display,
w.egl_config,
egl.NativeWindowType(w.egl_window),
nil,
)
if w.egl_surface == egl.NO_SURFACE {
return OL_Create_EGL_Surface_Error{}
}
if !egl.MakeCurrent(w.egl_display, w.egl_surface, w.egl_surface, w.egl_context) {
return OL_MakeCurrent_EGL_Error{}
} else {
egl.SwapInterval(w.egl_display, 1)
gl.load_up_to(4, 6, egl.gl_set_proc_address)
}
return nil
}
感谢Vipul Bondugula,我设法修改了代码,应用现在可以运行。现在我对每个窗口都创建着色器、缓冲区和VAO:
progs: [10]u32
vaos: [10]u32
vbos: [10]u32
for w, idx in olc.windows {
if egl.MakeCurrent(w.egl_display, w.egl_surface, w.egl_surface, w.egl_context) {
program_ok: bool
progs[idx], program_ok = gl.load_shaders_source(vertex_source, fragment_source)
if !program_ok {
fmt.eprintln("Failed to create GLSL program")
return
}
progs[idx], program_ok = gl.load_shaders_source(vertex_source, fragment_source)
if !program_ok {
fmt.eprintln("Failed to create GLSL program")
return
}
gl.GenVertexArrays(1, &vaos[idx])
gl.GenBuffers(1, &vbos[idx])
}
}
defer {
for w, idx in olc.windows {
if egl.MakeCurrent(w.egl_display, w.egl_surface, w.egl_surface, w.egl_context) {
defer gl.DeleteVertexArrays(1, &vaos[idx])
defer gl.DeleteBuffers(1, &vbos[idx])
defer gl.DeleteProgram(progs[idx])
}
}
}
解决方案
问题在于你为每个窗口创建了一个EGL/OpenGL上下文,但着色器程序、VAO与 VBO只创建了一次。
在 _init_egl() 中你调用了:
egl.CreateContext(.., egl.NO_CONTEXT, nil)
因此上下文并不共享。着色器程序是在窗口创建之后才创建的,而最后一个窗口的上下文始终是当前的。这意味着该程序、VAO和 VBO只属于那个上下文。当你后续对另一个窗口/上下文调用MakeCurrent() 并使用相同的对象ID时,这些ID在该上下文中就不是有效的GL对象。
你有两种常见的选项:
-
使用一个OpenGL上下文,在渲染每个窗口之前让它成为当前,并切换到不同的EGL表面。
-
为每个窗口保留一个上下文,但要么:
-
为每个上下文单独创建着色器/缓冲区/VAO,或
- 使用eglCreateContext的 share_context参数将上下文设为共享上下文。
即使使用共享上下文,VAO也不会在上下文之间共享,因此仍需要为每个上下文/窗口维护一个VAO。着色器程序和缓冲区可以共享,但VAO的状态应当在每个上下文中分别初始化。
因此,修复的方法很可能是将着色器初始化移到按窗口/按上下文的设置中,或创建一个共享上下文的设置并为每个窗口保留VAO。
调试建议:在glUseProgram()、glBindVertexArray()、glBindBuffer()、glVertexAttribPointer() 和glDrawArrays() 之后也调用glGetError()。在绘制其上下文未创建这些对象的窗口时,你可能会看到错误。
OpenGL对象名默认是与上下文相关的,除非显式共享上下文;并且根据OpenGL的 VAO规范,VAO也不能在上下文之间共享。 https://developer.download.nvidia.com/opengl/specs/GL_ARB_vertex_array_object.txt