| 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)] |
| 7 | use super::*; |
| 8 | use std::{ |
| 9 | ffi::{CStr, CString, c_char, c_void}, |
| 10 | ptr, |
| 11 | }; |
| 12 | |
| 13 | type GLenum = u32; |
| 14 | type GLuint = u32; |
| 15 | type GLint = i32; |
| 16 | type GLsizei = i32; |
| 17 | |
| 18 | const TRIANGLES: GLenum = 0x0004; |
| 19 | const COLOR_BUFFER_BIT: GLenum = 0x4000; |
| 20 | const ZERO: GLenum = 0; |
| 21 | const ONE: GLenum = 1; |
| 22 | const SRC_ALPHA: GLenum = 0x0302; |
| 23 | const ONE_MINUS_SRC_ALPHA: GLenum = 0x0303; |
| 24 | const DST_COLOR: GLenum = 0x0306; |
| 25 | const BLEND: GLenum = 0x0BE2; |
| 26 | const SCISSOR_TEST: GLenum = 0x0C11; |
| 27 | const MAX_TEXTURE_SIZE: GLenum = 0x0D33; |
| 28 | const TEXTURE_2D: GLenum = 0x0DE1; |
| 29 | const UNSIGNED_BYTE: GLenum = 0x1401; |
| 30 | const FLOAT: GLenum = 0x1406; |
| 31 | const RGBA: GLenum = 0x1908; |
| 32 | const NEAREST: GLint = 0x2600; |
| 33 | const LINEAR: GLint = 0x2601; |
| 34 | const TEXTURE_MAG_FILTER: GLenum = 0x2800; |
| 35 | const TEXTURE_MIN_FILTER: GLenum = 0x2801; |
| 36 | const TEXTURE_WRAP_S: GLenum = 0x2802; |
| 37 | const TEXTURE_WRAP_T: GLenum = 0x2803; |
| 38 | const CLAMP_TO_EDGE: GLint = 0x812F; |
| 39 | const TEXTURE0: GLenum = 0x84C0; |
| 40 | const ARRAY_BUFFER: GLenum = 0x8892; |
| 41 | const STREAM_DRAW: GLenum = 0x88E0; |
| 42 | const STATIC_DRAW: GLenum = 0x88E4; |
| 43 | const FRAGMENT_SHADER: GLenum = 0x8B30; |
| 44 | const VERTEX_SHADER: GLenum = 0x8B31; |
| 45 | const COMPILE_STATUS: GLenum = 0x8B81; |
| 46 | const LINK_STATUS: GLenum = 0x8B82; |
| 47 | const INFO_LOG_LENGTH: GLenum = 0x8B84; |
| 48 | const SRGB8_ALPHA8: GLint = 0x8C43; |
| 49 | const READ_FRAMEBUFFER: GLenum = 0x8CA8; |
| 50 | const DRAW_FRAMEBUFFER: GLenum = 0x8CA9; |
| 51 | const FRAMEBUFFER_COMPLETE: GLenum = 0x8CD5; |
| 52 | const COLOR_ATTACHMENT0: GLenum = 0x8CE0; |
| 53 | const FRAMEBUFFER: GLenum = 0x8D40; |
| 54 | const 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`. |
| 58 | macro_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))] |
| 102 | fn load() -> Result<(), String> { |
| 103 | Ok(()) |
| 104 | } |
| 105 | |
| 106 | #[cfg(windows)] |
| 107 | mod 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 | |
| 144 | functions! { |
| 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. |
| 253 | pub(super) struct Image { |
| 254 | name: GLuint, |
| 255 | size: [u32; 2], |
| 256 | } |
| 257 | |
| 258 | impl 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 | |
| 284 | impl Drop for Image { |
| 285 | fn drop(&mut self) { |
| 286 | unsafe { glDeleteTextures(1, &self.name) }; |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | impl 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. |
| 298 | pub struct Target { |
| 299 | framebuffer: GLuint, |
| 300 | texture: Image, |
| 301 | } |
| 302 | |
| 303 | impl 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 | |
| 330 | impl Drop for Target { |
| 331 | fn drop(&mut self) { |
| 332 | unsafe { glDeleteFramebuffersEXT(1, &self.framebuffer) }; |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | pub(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 | |
| 348 | impl 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. |
| 601 | unsafe 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 | |
| 624 | unsafe 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. |
| 640 | unsafe 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 | } |