1//! Submission through OpenGL 2.1 with EXT_framebuffer_object, EXT_framebuffer_blit and
2//! the sRGB extensions, which every Mac OS X 10.6 driver has and OpenGL.framework
3//! exports; on Windows, where opengl32.dll exports only OpenGL 1.1, the driver supplies
4//! them through `wglGetProcAddress`. Frames draw into an sRGB framebuffer object, so
5//! blending happens in linear light as it does in wgpu's sRGB targets.
6#![allow(non_snake_case)]
7use super::*;
8use std::{
9 ffi::{CStr, CString, c_char, c_void},
10 ptr,
11};
12
13type GLenum = u32;
14type GLuint = u32;
15type GLint = i32;
16type GLsizei = i32;
17
18const TRIANGLES: GLenum = 0x0004;
19const COLOR_BUFFER_BIT: GLenum = 0x4000;
20const ZERO: GLenum = 0;
21const ONE: GLenum = 1;
22const SRC_ALPHA: GLenum = 0x0302;
23const ONE_MINUS_SRC_ALPHA: GLenum = 0x0303;
24const DST_COLOR: GLenum = 0x0306;
25const BLEND: GLenum = 0x0BE2;
26const SCISSOR_TEST: GLenum = 0x0C11;
27const MAX_TEXTURE_SIZE: GLenum = 0x0D33;
28const TEXTURE_2D: GLenum = 0x0DE1;
29const UNSIGNED_BYTE: GLenum = 0x1401;
30const FLOAT: GLenum = 0x1406;
31const RGBA: GLenum = 0x1908;
32const NEAREST: GLint = 0x2600;
33const LINEAR: GLint = 0x2601;
34const TEXTURE_MAG_FILTER: GLenum = 0x2800;
35const TEXTURE_MIN_FILTER: GLenum = 0x2801;
36const TEXTURE_WRAP_S: GLenum = 0x2802;
37const TEXTURE_WRAP_T: GLenum = 0x2803;
38const CLAMP_TO_EDGE: GLint = 0x812F;
39const TEXTURE0: GLenum = 0x84C0;
40const ARRAY_BUFFER: GLenum = 0x8892;
41const STREAM_DRAW: GLenum = 0x88E0;
42const STATIC_DRAW: GLenum = 0x88E4;
43const FRAGMENT_SHADER: GLenum = 0x8B30;
44const VERTEX_SHADER: GLenum = 0x8B31;
45const COMPILE_STATUS: GLenum = 0x8B81;
46const LINK_STATUS: GLenum = 0x8B82;
47const INFO_LOG_LENGTH: GLenum = 0x8B84;
48const SRGB8_ALPHA8: GLint = 0x8C43;
49const READ_FRAMEBUFFER: GLenum = 0x8CA8;
50const DRAW_FRAMEBUFFER: GLenum = 0x8CA9;
51const FRAMEBUFFER_COMPLETE: GLenum = 0x8CD5;
52const COLOR_ATTACHMENT0: GLenum = 0x8CE0;
53const FRAMEBUFFER: GLenum = 0x8D40;
54const FRAMEBUFFER_SRGB: GLenum = 0x8DB9;
55
56/// The entry points: linked where the system library exports them all, and on Windows
57/// loaded with the first context and called through `FUNCTIONS`.
58macro_rules! functions {
59 ($(fn $name:ident($($arg:ident: $ty:ty),* $(,)?) $(-> $ret:ty)?;)*) => {
60 #[cfg(not(windows))]
61 #[cfg_attr(target_os = "macos", link(name = "OpenGL", kind = "framework"))]
62 unsafe extern "C" {
63 $(fn $name($($arg: $ty),*) $(-> $ret)?;)*
64 }
65
66 #[cfg(windows)]
67 struct Functions {
68 $($name: unsafe extern "system" fn($($ty),*) $(-> $ret)?,)*
69 }
70
71 #[cfg(windows)]
72 static FUNCTIONS: std::sync::OnceLock<Functions> = std::sync::OnceLock::new();
73
74 /// Loads the entry points from the context current on this thread.
75 #[cfg(windows)]
76 fn load() -> Result<(), String> {
77 if FUNCTIONS.get().is_none() {
78 let functions = Functions {
79 $($name: unsafe {
80 std::mem::transmute::<
81 *const c_void,
82 unsafe extern "system" fn($($ty),*) $(-> $ret)?,
83 >(windows::address(concat!(stringify!($name), "\0"))?)
84 },)*
85 };
86 let _ = FUNCTIONS.set(functions);
87 }
88 Ok(())
89 }
90
91 $(
92 #[cfg(windows)]
93 #[allow(clippy::too_many_arguments)]
94 unsafe fn $name($($arg: $ty),*) $(-> $ret)? {
95 unsafe { (FUNCTIONS.get().expect("OpenGL is loaded").$name)($($arg),*) }
96 }
97 )*
98 };
99}
100
101#[cfg(not(windows))]
102fn load() -> Result<(), String> {
103 Ok(())
104}
105
106#[cfg(windows)]
107mod windows {
108 use std::ffi::{c_char, c_void};
109
110 #[link(name = "opengl32")]
111 unsafe extern "system" {
112 fn wglGetProcAddress(name: *const c_char) -> *const c_void;
113 }
114
115 unsafe extern "system" {
116 fn GetModuleHandleA(name: *const c_char) -> *mut c_void;
117 fn GetProcAddress(module: *mut c_void, name: *const c_char) -> *const c_void;
118 }
119
120 /// OpenGL function `name`, NUL-terminated: from the driver, or opengl32.dll's own for
121 /// OpenGL 1.1. An `EXT` name the driver lacks falls back to its core name.
122 pub(super) fn address(name: &str) -> Result<*const c_void, String> {
123 let find = |name: &str| unsafe {
124 let driver = wglGetProcAddress(name.as_ptr().cast());
125 // Some drivers answer a missing name with a small integer rather than null.
126 if !matches!(driver as isize, -1..=3) {
127 return Some(driver);
128 }
129 let system = GetProcAddress(
130 GetModuleHandleA(c"opengl32.dll".as_ptr()),
131 name.as_ptr().cast(),
132 );
133 (!system.is_null()).then_some(system)
134 };
135 find(name)
136 .or_else(|| {
137 let core = name.strip_suffix("EXT\0")?;
138 find(&format!("{core}\0"))
139 })
140 .ok_or_else(|| format!("OpenGL has no {}", name.trim_end_matches('\0')))
141 }
142}
143
144functions! {
145 fn glEnable(cap: GLenum);
146 fn glDisable(cap: GLenum);
147 fn glGetIntegerv(name: GLenum, value: *mut GLint);
148 fn glViewport(x: GLint, y: GLint, width: GLsizei, height: GLsizei);
149 fn glScissor(x: GLint, y: GLint, width: GLsizei, height: GLsizei);
150 fn glClearColor(r: f32, g: f32, b: f32, a: f32);
151 fn glClear(mask: GLenum);
152 fn glBlendFuncSeparate(src_rgb: GLenum, dst_rgb: GLenum, src_a: GLenum, dst_a: GLenum);
153 fn glReadPixels(
154 x: GLint,
155 y: GLint,
156 w: GLsizei,
157 h: GLsizei,
158 format: GLenum,
159 ty: GLenum,
160 data: *mut c_void,
161 );
162
163 fn glGenTextures(n: GLsizei, textures: *mut GLuint);
164 fn glDeleteTextures(n: GLsizei, textures: *const GLuint);
165 fn glBindTexture(target: GLenum, texture: GLuint);
166 fn glActiveTexture(unit: GLenum);
167 fn glTexParameteri(target: GLenum, name: GLenum, value: GLint);
168 fn glTexImage2D(
169 target: GLenum,
170 level: GLint,
171 internal: GLint,
172 w: GLsizei,
173 h: GLsizei,
174 border: GLint,
175 format: GLenum,
176 ty: GLenum,
177 data: *const c_void,
178 );
179 fn glTexSubImage2D(
180 target: GLenum,
181 level: GLint,
182 x: GLint,
183 y: GLint,
184 w: GLsizei,
185 h: GLsizei,
186 format: GLenum,
187 ty: GLenum,
188 data: *const c_void,
189 );
190
191 fn glGenBuffers(n: GLsizei, buffers: *mut GLuint);
192 fn glBindBuffer(target: GLenum, buffer: GLuint);
193 fn glBufferData(target: GLenum, size: isize, data: *const c_void, usage: GLenum);
194 fn glEnableVertexAttribArray(index: GLuint);
195 fn glDisableVertexAttribArray(index: GLuint);
196 fn glVertexAttribPointer(
197 index: GLuint,
198 size: GLint,
199 ty: GLenum,
200 normalized: u8,
201 stride: GLsizei,
202 offset: *const c_void,
203 );
204 fn glDrawArrays(mode: GLenum, first: GLint, count: GLsizei);
205
206 fn glCreateShader(kind: GLenum) -> GLuint;
207 fn glShaderSource(
208 shader: GLuint,
209 count: GLsizei,
210 sources: *const *const c_char,
211 lengths: *const GLint,
212 );
213 fn glCompileShader(shader: GLuint);
214 fn glGetShaderiv(shader: GLuint, name: GLenum, value: *mut GLint);
215 fn glGetShaderInfoLog(shader: GLuint, size: GLsizei, length: *mut GLsizei, log: *mut c_char);
216 fn glCreateProgram() -> GLuint;
217 fn glAttachShader(program: GLuint, shader: GLuint);
218 fn glBindAttribLocation(program: GLuint, index: GLuint, name: *const c_char);
219 fn glLinkProgram(program: GLuint);
220 fn glGetProgramiv(program: GLuint, name: GLenum, value: *mut GLint);
221 fn glGetProgramInfoLog(program: GLuint, size: GLsizei, length: *mut GLsizei, log: *mut c_char);
222 fn glUseProgram(program: GLuint);
223 fn glGetUniformLocation(program: GLuint, name: *const c_char) -> GLint;
224 fn glUniform1i(location: GLint, value: GLint);
225 fn glUniform2f(location: GLint, x: f32, y: f32);
226
227 fn glGenFramebuffersEXT(n: GLsizei, framebuffers: *mut GLuint);
228 fn glDeleteFramebuffersEXT(n: GLsizei, framebuffers: *const GLuint);
229 fn glBindFramebufferEXT(target: GLenum, framebuffer: GLuint);
230 fn glFramebufferTexture2DEXT(
231 target: GLenum,
232 attachment: GLenum,
233 textarget: GLenum,
234 texture: GLuint,
235 level: GLint,
236 );
237 fn glCheckFramebufferStatusEXT(target: GLenum) -> GLenum;
238 fn glBlitFramebufferEXT(
239 sx0: GLint,
240 sy0: GLint,
241 sx1: GLint,
242 sy1: GLint,
243 dx0: GLint,
244 dy0: GLint,
245 dx1: GLint,
246 dy1: GLint,
247 mask: GLenum,
248 filter: GLenum,
249 );
250}
251
252/// An sRGB RGBA texture, deleted with its value.
253pub(super) struct Image {
254 name: GLuint,
255 size: [u32; 2],
256}
257
258impl Image {
259 fn new(size: [u32; 2], filter: GLint, pixels: Option<&[u8]>) -> Self {
260 let mut name = 0;
261 unsafe {
262 glGenTextures(1, &mut name);
263 glBindTexture(TEXTURE_2D, name);
264 glTexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, filter);
265 glTexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, filter);
266 glTexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE);
267 glTexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE);
268 glTexImage2D(
269 TEXTURE_2D,
270 0,
271 SRGB8_ALPHA8,
272 size[0] as GLsizei,
273 size[1] as GLsizei,
274 0,
275 RGBA,
276 UNSIGNED_BYTE,
277 pixels.map_or(ptr::null(), |pixels| pixels.as_ptr().cast()),
278 );
279 }
280 Self { name, size }
281 }
282}
283
284impl Drop for Image {
285 fn drop(&mut self) {
286 unsafe { glDeleteTextures(1, &self.name) };
287 }
288}
289
290impl AsRef<Image> for Image {
291 fn as_ref(&self) -> &Image {
292 self
293 }
294}
295
296/// An offscreen frame a window shows by `present`, the context's default framebuffer
297/// being no sRGB target.
298pub struct Target {
299 framebuffer: GLuint,
300 texture: Image,
301}
302
303impl Target {
304 /// Sampled with `filter`.
305 fn new(size: [u32; 2], filter: GLint) -> Result<Self, String> {
306 let texture = Image::new(size, filter, None);
307 let mut framebuffer = 0;
308 unsafe {
309 glGenFramebuffersEXT(1, &mut framebuffer);
310 glBindFramebufferEXT(FRAMEBUFFER, framebuffer);
311 glFramebufferTexture2DEXT(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, texture.name, 0);
312 let status = glCheckFramebufferStatusEXT(FRAMEBUFFER);
313 glBindFramebufferEXT(FRAMEBUFFER, 0);
314 if status != FRAMEBUFFER_COMPLETE {
315 glDeleteFramebuffersEXT(1, &framebuffer);
316 return Err(format!("Framebuffer incomplete: {status:#x}"));
317 }
318 }
319 Ok(Self {
320 framebuffer,
321 texture,
322 })
323 }
324
325 pub fn size(&self) -> [u32; 2] {
326 self.texture.size
327 }
328}
329
330impl Drop for Target {
331 fn drop(&mut self) {
332 unsafe { glDeleteFramebuffersEXT(1, &self.framebuffer) };
333 }
334}
335
336pub(super) struct Gpu {
337 program: GLuint,
338 buffer: GLuint,
339 /// `translucent.glsl`, and the triangle it covers a window with.
340 translucent: GLuint,
341 triangle: GLuint,
342 atlas: Image,
343 max_texture: u32,
344 /// Offscreen pictures for groups, the target's size.
345 groups: Vec<Target>,
346}
347
348impl Gpu {
349 pub(super) fn new() -> Result<Self, String> {
350 load()?;
351 let names = ATTRIBUTES.map(|(name, _, _)| name);
352 let draw = unsafe { program(include_str!("../draw.glsl"), &names)? };
353 let translucent = unsafe { program(include_str!("translucent.glsl"), &[c"position"])? };
354 let [mut buffer, mut triangle] = [0; 2];
355 let mut max_texture = 0;
356 unsafe {
357 glGenBuffers(1, &mut buffer);
358 glGenBuffers(1, &mut triangle);
359 glBindBuffer(ARRAY_BUFFER, triangle);
360 let corners: [f32; 6] = [-1.0, -1.0, 3.0, -1.0, -1.0, 3.0];
361 glBufferData(
362 ARRAY_BUFFER,
363 size_of_val(&corners) as isize,
364 corners.as_ptr().cast(),
365 STATIC_DRAW,
366 );
367 glGetIntegerv(MAX_TEXTURE_SIZE, &mut max_texture);
368 }
369 Ok(Self {
370 program: draw,
371 buffer,
372 translucent,
373 triangle,
374 atlas: Image::new([ATLAS_SIZE; 2], NEAREST, None),
375 max_texture: max_texture as u32,
376 groups: Vec::new(),
377 })
378 }
379
380 /// Needs the renderer's context current, as every method here does.
381 pub(super) fn target(&self, size: [u32; 2]) -> Result<Target, String> {
382 Target::new(size, NEAREST)
383 }
384
385 /// Copies `target` to the context's window, the same size; if `translucent`,
386 /// premultiplying each pixel again in sRGB, as a window server compositing a transparent
387 /// surface needs.
388 pub(super) fn present(&self, target: &Target, translucent: bool) {
389 if !translucent {
390 let [width, height] = target.size().map(|side| side as GLint);
391 unsafe {
392 glBindFramebufferEXT(READ_FRAMEBUFFER, target.framebuffer);
393 glBindFramebufferEXT(DRAW_FRAMEBUFFER, 0);
394 glBlitFramebufferEXT(
395 0,
396 0,
397 width,
398 height,
399 0,
400 0,
401 width,
402 height,
403 COLOR_BUFFER_BIT,
404 NEAREST as GLenum,
405 );
406 glBindFramebufferEXT(FRAMEBUFFER, 0);
407 }
408 return;
409 }
410 let [width, height] = target.size().map(|side| side as f32);
411 let program = self.translucent;
412 unsafe {
413 glBindFramebufferEXT(FRAMEBUFFER, 0);
414 glDisable(FRAMEBUFFER_SRGB);
415 glDisable(BLEND);
416 glDisable(SCISSOR_TEST);
417 glViewport(0, 0, width as GLsizei, height as GLsizei);
418 glUseProgram(program);
419 glUniform1i(glGetUniformLocation(program, c"frame".as_ptr()), 0);
420 glUniform2f(
421 glGetUniformLocation(program, c"size".as_ptr()),
422 width,
423 height,
424 );
425 glActiveTexture(TEXTURE0);
426 glBindTexture(TEXTURE_2D, target.texture.name);
427 glBindBuffer(ARRAY_BUFFER, self.triangle);
428 for index in 1..ATTRIBUTES.len() {
429 glDisableVertexAttribArray(index as GLuint);
430 }
431 glEnableVertexAttribArray(0);
432 glVertexAttribPointer(0, 2, FLOAT, 0, 0, ptr::null());
433 glDrawArrays(TRIANGLES, 0, 3);
434 }
435 }
436
437 /// Blends in linear light, its targets being sRGB.
438 pub(super) fn blends_linear(&self) -> bool {
439 true
440 }
441
442 /// Offscreen pictures' rows run bottom first.
443 pub(super) fn flipped(&self) -> bool {
444 true
445 }
446
447 pub(super) fn max_texture_dimension(&self) -> u32 {
448 self.max_texture
449 }
450
451 pub(super) fn atlas_side(&self) -> u32 {
452 self.atlas.size[0]
453 }
454
455 pub(super) fn new_atlas(&mut self, side: u32) {
456 self.atlas = Image::new([side; 2], NEAREST, None);
457 }
458
459 pub(super) fn write_atlas(&self, origin: [u32; 2], size: [u32; 2], rgba: &[u8]) {
460 unsafe {
461 glBindTexture(TEXTURE_2D, self.atlas.name);
462 glTexSubImage2D(
463 TEXTURE_2D,
464 0,
465 origin[0] as GLint,
466 origin[1] as GLint,
467 size[0] as GLsizei,
468 size[1] as GLsizei,
469 RGBA,
470 UNSIGNED_BYTE,
471 rgba.as_ptr().cast(),
472 );
473 }
474 }
475
476 /// sRGB-encoded RGBA rows, top first.
477 pub(super) fn read_pixels(&self, target: &Target) -> Result<Vec<u8>, String> {
478 let [width, height] = target.size();
479 let mut pixels = vec![0; (width * height * 4) as usize];
480 unsafe {
481 glBindFramebufferEXT(FRAMEBUFFER, target.framebuffer);
482 glReadPixels(
483 0,
484 0,
485 width as GLsizei,
486 height as GLsizei,
487 RGBA,
488 UNSIGNED_BYTE,
489 pixels.as_mut_ptr().cast(),
490 );
491 glBindFramebufferEXT(FRAMEBUFFER, 0);
492 }
493 Ok(pixels
494 .chunks(width as usize * 4)
495 .rev()
496 .flatten()
497 .copied()
498 .collect())
499 }
500
501 pub(super) fn upload_image(&self, image: &RasterImage) -> Image {
502 Image::new(image.size, LINEAR, Some(image.pixels()))
503 }
504
505 /// Clears `target`, `size` device pixels, to linear `clear` and draws the prepared
506 /// batches, each group's offscreen first.
507 pub(super) fn submit(
508 &mut self,
509 frame: &Frame<'_>,
510 target: &Target,
511 size: [u32; 2],
512 clear: [f32; 4],
513 ) {
514 if self
515 .groups
516 .first()
517 .is_some_and(|picture| picture.size() != size)
518 {
519 self.groups.clear();
520 }
521 while self.groups.len() < frame.groups.len() {
522 // Filtered, so a picture leaning back stays smooth.
523 match Target::new(size, LINEAR) {
524 Ok(picture) => self.groups.push(picture),
525 Err(error) => {
526 eprintln!("{error}");
527 return;
528 }
529 }
530 }
531 let [width, height] = size.map(|side| side as GLsizei);
532 let gpu = &*self;
533 let paint = |target: &Target, clear: [f32; 4], batches: &[Batch]| unsafe {
534 glBindFramebufferEXT(FRAMEBUFFER, target.framebuffer);
535 glDisable(SCISSOR_TEST);
536 glClearColor(clear[0], clear[1], clear[2], clear[3]);
537 glClear(COLOR_BUFFER_BIT);
538 glEnable(SCISSOR_TEST);
539 for batch in batches {
540 let [x, y, w, h] = batch.scissor.map(|value| value as GLint);
541 glScissor(x, height - y - h, w, h);
542 let [src_rgb, dst_rgb, src_alpha, dst_alpha] = Blend::STATES[batch.blend.state()]
543 .map(|factor| match factor {
544 Factor::Zero => ZERO,
545 Factor::One => ONE,
546 Factor::SourceAlpha => SRC_ALPHA,
547 Factor::OneMinusSourceAlpha => ONE_MINUS_SRC_ALPHA,
548 Factor::Destination => DST_COLOR,
549 });
550 glBlendFuncSeparate(src_rgb, dst_rgb, src_alpha, dst_alpha);
551 let texture = match batch.blend {
552 Blend::Image(id) => frame.image::<Image>(id),
553 Blend::Group(index) => &gpu.groups[index].texture,
554 Blend::Over | Blend::Erase | Blend::Multiply => &gpu.atlas,
555 };
556 glBindTexture(TEXTURE_2D, texture.name);
557 glDrawArrays(
558 TRIANGLES,
559 batch.vertices.start as GLint,
560 batch.vertices.len() as GLsizei,
561 );
562 }
563 };
564 unsafe {
565 glEnable(FRAMEBUFFER_SRGB);
566 glViewport(0, 0, width, height);
567 glUseProgram(gpu.program);
568 glUniform1i(glGetUniformLocation(gpu.program, c"atlas".as_ptr()), 0);
569 glActiveTexture(TEXTURE0);
570 glBindBuffer(ARRAY_BUFFER, gpu.buffer);
571 glBufferData(
572 ARRAY_BUFFER,
573 size_of_val(frame.vertices) as isize,
574 frame.vertices.as_ptr().cast(),
575 STREAM_DRAW,
576 );
577 for (index, (_, floats, offset)) in ATTRIBUTES.into_iter().enumerate() {
578 glEnableVertexAttribArray(index as GLuint);
579 glVertexAttribPointer(
580 index as GLuint,
581 floats as GLint,
582 FLOAT,
583 0,
584 size_of::<Vertex>() as GLsizei,
585 ptr::without_provenance(offset),
586 );
587 }
588 glEnable(BLEND);
589 for (group, picture) in frame.groups.iter().zip(&gpu.groups) {
590 paint(picture, [0.0; 4], &group.batches);
591 }
592 paint(target, clear, frame.batches);
593 glDisable(SCISSOR_TEST);
594 glDisable(BLEND);
595 glBindFramebufferEXT(FRAMEBUFFER, 0);
596 }
597 }
598}
599
600/// A program from `source`'s two stages, its attributes bound in order.
601unsafe fn program(source: &str, attributes: &[&CStr]) -> Result<GLuint, String> {
602 unsafe {
603 let vertex = compile(
604 VERTEX_SHADER,
605 &format!("#version 120\n#define VERTEX\n{source}"),
606 )?;
607 let fragment = compile(FRAGMENT_SHADER, &format!("#version 120\n{source}"))?;
608 let program = glCreateProgram();
609 glAttachShader(program, vertex);
610 glAttachShader(program, fragment);
611 for (index, name) in attributes.iter().enumerate() {
612 glBindAttribLocation(program, index as GLuint, name.as_ptr());
613 }
614 glLinkProgram(program);
615 let mut linked = 0;
616 glGetProgramiv(program, LINK_STATUS, &mut linked);
617 if linked == 0 {
618 return Err(format!("Linking a shader failed: {}", log(program, false)));
619 }
620 Ok(program)
621 }
622}
623
624unsafe fn compile(kind: GLenum, source: &str) -> Result<GLuint, String> {
625 unsafe {
626 let shader = glCreateShader(kind);
627 let text = CString::new(source).expect("The shader has no NUL");
628 glShaderSource(shader, 1, &text.as_ptr(), ptr::null());
629 glCompileShader(shader);
630 let mut compiled = 0;
631 glGetShaderiv(shader, COMPILE_STATUS, &mut compiled);
632 if compiled == 0 {
633 return Err(format!("Compiling a shader failed: {}", log(shader, true)));
634 }
635 Ok(shader)
636 }
637}
638
639/// The info log of `object`, a shader or else a program.
640unsafe fn log(object: GLuint, shader: bool) -> String {
641 unsafe {
642 let mut length = 0;
643 if shader {
644 glGetShaderiv(object, INFO_LOG_LENGTH, &mut length);
645 } else {
646 glGetProgramiv(object, INFO_LOG_LENGTH, &mut length);
647 }
648 let mut text = vec![0u8; length.max(1) as usize];
649 let log = text.as_mut_ptr().cast();
650 if shader {
651 glGetShaderInfoLog(object, length, ptr::null_mut(), log);
652 } else {
653 glGetProgramInfoLog(object, length, ptr::null_mut(), log);
654 }
655 String::from_utf8_lossy(&text)
656 .trim_end_matches('\0')
657 .to_owned()
658 }
659}