1use optic_core::{ImgFilter, ImgFormat, ImgWrap, OpticError, OpticErrorKind, OpticResult, Size2D};
2
3use crate::handles::texture::{Texture2D, delete_texture};
4
5#[derive(Clone, Debug)]
9pub struct CanvasDesc {
10 pub size: Size2D,
11 pub color_formats: Vec<ImgFormat>,
12 pub depth: bool,
13 pub depth_as_texture: bool,
14 pub depth_compare: bool,
15 pub stencil: bool,
16 pub samples: u32,
17 pub filter: ImgFilter,
18 pub wrap: ImgWrap,
19}
20
21impl Default for CanvasDesc {
22 fn default() -> Self {
23 Self {
24 size: Size2D::from(512, 512),
25 color_formats: vec![ImgFormat::RGBA(8)],
26 depth: true,
27 depth_as_texture: true,
28 depth_compare: false,
29 stencil: false,
30 samples: 0,
31 filter: ImgFilter::Linear,
32 wrap: ImgWrap::Extend,
33 }
34 }
35}
36
37pub struct Canvas {
50 pub(crate) fbo_id: u32,
51 pub(crate) resolve_fbo_id: u32,
52 pub(crate) msaa_rbos: Vec<u32>,
53 pub(crate) depth_stencil_rbo: u32,
54 pub(crate) color_texs: Vec<Texture2D>,
55 pub(crate) depth_tex: Option<Texture2D>,
56 pub(crate) size: Size2D,
57 #[allow(dead_code)]
58 pub(crate) samples: u32,
59 pub(crate) has_stencil: bool,
60 pub(crate) has_depth: bool,
61 #[allow(dead_code)]
62 pub(crate) depth_as_texture: bool,
63 pub(crate) desc: CanvasDesc,
64}
65
66impl Canvas {
67 pub fn new(desc: &CanvasDesc) -> OpticResult<Self> {
74 if desc.color_formats.is_empty() && !desc.depth {
75 return Err(OpticError::new(
76 OpticErrorKind::Custom,
77 "Canvas: at least one color format or depth must be specified",
78 ));
79 }
80 if desc.stencil && !desc.depth {
81 return Err(OpticError::new(
82 OpticErrorKind::Custom,
83 "Canvas: stencil requires depth to be enabled",
84 ));
85 }
86 if desc.depth_compare && !desc.depth_as_texture {
87 return Err(OpticError::new(
88 OpticErrorKind::Custom,
89 "Canvas: depth_compare requires depth_as_texture",
90 ));
91 }
92
93 let size = desc.size;
94 let has_msaa = desc.samples > 1;
95 let msaa_s = if has_msaa { desc.samples } else { 0 };
96
97 let fbo_id = unsafe {
98 let mut id = 0;
99 gl::GenFramebuffers(1, &mut id);
100 gl::BindFramebuffer(gl::FRAMEBUFFER, id);
101 id
102 };
103
104 let mut color_texs: Vec<Texture2D> = Vec::new();
105 let mut msaa_rbos: Vec<u32> = Vec::new();
106 let mut depth_stencil_rbo = 0u32;
107 let mut depth_tex: Option<Texture2D> = None;
108 let mut resolve_fbo_id = 0u32;
109
110 for (i, fmt) in desc.color_formats.iter().enumerate() {
111 let attachment = gl::COLOR_ATTACHMENT0 + i as u32;
112 if has_msaa {
113 let rbo = create_rbo_storage_msaa(size, msaa_s, fmt, desc.stencil);
114 unsafe {
115 gl::FramebufferRenderbuffer(gl::FRAMEBUFFER, attachment, gl::RENDERBUFFER, rbo);
116 }
117 msaa_rbos.push(rbo);
118 } else {
119 let tex_id = create_empty_tex(size, fmt, desc.filter, desc.wrap);
120 unsafe {
121 gl::FramebufferTexture2D(gl::FRAMEBUFFER, attachment, gl::TEXTURE_2D, tex_id, 0);
122 }
123 color_texs.push(Texture2D::new(tex_id, size, *fmt, desc.filter, desc.wrap));
124 }
125 }
126
127 if !desc.color_formats.is_empty() {
128 let attachments: Vec<u32> = (0..desc.color_formats.len() as u32)
129 .map(|i| gl::COLOR_ATTACHMENT0 + i)
130 .collect();
131 unsafe {
132 gl::DrawBuffers(desc.color_formats.len() as i32, attachments.as_ptr());
133 }
134 } else {
135 unsafe { gl::DrawBuffer(gl::NONE); }
136 }
137
138 if desc.depth {
139 if has_msaa {
140 let (internal, att) = depth_rbo_params(desc.stencil);
141 let rbo = unsafe {
142 let mut id = 0;
143 gl::GenRenderbuffers(1, &mut id);
144 gl::BindRenderbuffer(gl::RENDERBUFFER, id);
145 gl::RenderbufferStorageMultisample(
146 gl::RENDERBUFFER, msaa_s as i32, internal as u32, size.w as i32, size.h as i32,
147 );
148 gl::FramebufferRenderbuffer(gl::FRAMEBUFFER, att, gl::RENDERBUFFER, id);
149 gl::BindRenderbuffer(gl::RENDERBUFFER, 0);
150 id
151 };
152 depth_stencil_rbo = rbo;
153 } else if desc.depth_as_texture {
154 let tex_id = create_depth_tex(size, desc.stencil, desc.depth_compare);
155 let att = if desc.stencil {
156 gl::DEPTH_STENCIL_ATTACHMENT
157 } else {
158 gl::DEPTH_ATTACHMENT
159 };
160 unsafe {
161 gl::FramebufferTexture2D(gl::FRAMEBUFFER, att, gl::TEXTURE_2D, tex_id, 0);
162 }
163 depth_tex = Some(Texture2D::new(
164 tex_id, size, ImgFormat::R(32), ImgFilter::Closest, ImgWrap::Extend,
165 ));
166 } else {
167 let (internal, att) = depth_rbo_params(desc.stencil);
168 let rbo = unsafe {
169 let mut id = 0;
170 gl::GenRenderbuffers(1, &mut id);
171 gl::BindRenderbuffer(gl::RENDERBUFFER, id);
172 gl::RenderbufferStorage(gl::RENDERBUFFER, internal as u32, size.w as i32, size.h as i32);
173 gl::FramebufferRenderbuffer(gl::FRAMEBUFFER, att, gl::RENDERBUFFER, id);
174 gl::BindRenderbuffer(gl::RENDERBUFFER, 0);
175 id
176 };
177 depth_stencil_rbo = rbo;
178 }
179 }
180
181 if has_msaa {
182 resolve_fbo_id = unsafe {
183 let mut id = 0;
184 gl::GenFramebuffers(1, &mut id);
185 gl::BindFramebuffer(gl::FRAMEBUFFER, id);
186 id
187 };
188
189 for (i, fmt) in desc.color_formats.iter().enumerate() {
190 let tex_id = create_empty_tex(size, fmt, desc.filter, desc.wrap);
191 unsafe {
192 gl::FramebufferTexture2D(
193 gl::FRAMEBUFFER,
194 gl::COLOR_ATTACHMENT0 + i as u32,
195 gl::TEXTURE_2D, tex_id, 0,
196 );
197 }
198 color_texs.push(Texture2D::new(tex_id, size, *fmt, desc.filter, desc.wrap));
199 }
200
201 if !desc.color_formats.is_empty() {
202 let attachments: Vec<u32> = (0..desc.color_formats.len() as u32)
203 .map(|i| gl::COLOR_ATTACHMENT0 + i)
204 .collect();
205 unsafe {
206 gl::DrawBuffers(desc.color_formats.len() as i32, attachments.as_ptr());
207 }
208 } else {
209 unsafe { gl::DrawBuffer(gl::NONE); }
210 }
211
212 if desc.depth && desc.depth_as_texture {
213 let tex_id = create_depth_tex(size, desc.stencil, desc.depth_compare);
214 let att = if desc.stencil {
215 gl::DEPTH_STENCIL_ATTACHMENT
216 } else {
217 gl::DEPTH_ATTACHMENT
218 };
219 unsafe {
220 gl::FramebufferTexture2D(gl::FRAMEBUFFER, att, gl::TEXTURE_2D, tex_id, 0);
221 }
222 depth_tex = Some(Texture2D::new(
223 tex_id, size, ImgFormat::R(32), ImgFilter::Closest, ImgWrap::Extend,
224 ));
225 }
226
227 unsafe { gl::BindFramebuffer(gl::FRAMEBUFFER, fbo_id); }
228 }
229
230 let complete = unsafe { gl::CheckFramebufferStatus(gl::FRAMEBUFFER) };
231 unsafe { gl::BindFramebuffer(gl::FRAMEBUFFER, 0); }
232
233 if complete != gl::FRAMEBUFFER_COMPLETE {
234 unsafe {
235 gl::DeleteFramebuffers(1, &fbo_id);
236 if resolve_fbo_id != 0 {
237 gl::DeleteFramebuffers(1, &resolve_fbo_id);
238 }
239 for &rbo in &msaa_rbos {
240 gl::DeleteRenderbuffers(1, &rbo);
241 }
242 if depth_stencil_rbo != 0 {
243 gl::DeleteRenderbuffers(1, &depth_stencil_rbo);
244 }
245 }
246 for tex in &color_texs {
247 delete_texture(tex.id);
248 }
249 if let Some(ref tex) = depth_tex {
250 delete_texture(tex.id);
251 }
252 return Err(OpticError::new(
253 OpticErrorKind::Framebuffer,
254 &format!("framebuffer incomplete: status={complete:#x}"),
255 ));
256 }
257
258 Ok(Self {
259 fbo_id,
260 resolve_fbo_id,
261 msaa_rbos,
262 depth_stencil_rbo,
263 color_texs,
264 depth_tex,
265 size,
266 samples: desc.samples,
267 has_stencil: desc.stencil,
268 has_depth: desc.depth,
269 depth_as_texture: desc.depth_as_texture,
270 desc: desc.clone(),
271 })
272 }
273
274 pub fn size(&self) -> Size2D {
276 self.size
277 }
278
279 pub fn color_tex(&self, index: usize) -> OpticResult<&Texture2D> {
281 self.color_texs.get(index).ok_or_else(|| {
282 OpticError::new(
283 OpticErrorKind::Custom,
284 &format!("Canvas color attachment index {index} out of range ({} attachments)", self.color_texs.len()),
285 )
286 })
287 }
288
289 pub fn depth_tex(&self) -> Option<&Texture2D> {
291 self.depth_tex.as_ref()
292 }
293
294 pub fn set_size(&mut self, new_size: Size2D) -> OpticResult<()> {
296 let mut new_desc = self.desc.clone();
297 new_desc.size = new_size;
298 let new_canvas = Canvas::new(&new_desc)?;
299 *self = new_canvas;
300 Ok(())
301 }
302
303 pub fn resolve(&self) {
307 if self.resolve_fbo_id == 0 {
308 return;
309 }
310 unsafe {
311 gl::BindFramebuffer(gl::READ_FRAMEBUFFER, self.fbo_id);
312 gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, self.resolve_fbo_id);
313 let mut mask = gl::COLOR_BUFFER_BIT;
314 if self.has_depth {
315 mask |= gl::DEPTH_BUFFER_BIT;
316 }
317 if self.has_stencil {
318 mask |= gl::STENCIL_BUFFER_BIT;
319 }
320 gl::BlitFramebuffer(
321 0, 0, self.size.w as i32, self.size.h as i32,
322 0, 0, self.size.w as i32, self.size.h as i32,
323 mask, gl::NEAREST,
324 );
325 gl::BindFramebuffer(gl::READ_FRAMEBUFFER, 0);
326 gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, 0);
327 }
328 }
329
330 pub fn blit_to_screen(&self, window_size: Size2D) {
332 self.resolve_if_needed();
333 let src = if self.resolve_fbo_id != 0 {
334 self.resolve_fbo_id
335 } else {
336 self.fbo_id
337 };
338 unsafe {
339 gl::BindFramebuffer(gl::READ_FRAMEBUFFER, src);
340 gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, 0);
341 gl::BlitFramebuffer(
342 0, 0, self.size.w as i32, self.size.h as i32,
343 0, 0, window_size.w as i32, window_size.h as i32,
344 gl::COLOR_BUFFER_BIT, gl::LINEAR,
345 );
346 gl::BindFramebuffer(gl::READ_FRAMEBUFFER, 0);
347 }
348 }
349
350 pub fn set_renderable_area(&self, x: i32, y: i32, size: Size2D) -> OpticResult<()> {
354 if x < 0
355 || y < 0
356 || size.w <= 0
357 || size.h <= 0
358 || x + size.w as i32 > self.size.w as i32
359 || y + size.h as i32 > self.size.h as i32
360 {
361 return Err(OpticError::new(
362 OpticErrorKind::Custom,
363 &format!(
364 "Canvas::set_renderable_area rect ({},{},{},{}) exceeds canvas size ({},{})",
365 x, y, size.w, size.h, self.size.w, self.size.h,
366 ),
367 ));
368 }
369 unsafe {
370 gl::Viewport(x, y, size.w as i32, size.h as i32);
371 }
372 Ok(())
373 }
374
375 pub fn read_pixels(&self, index: usize) -> OpticResult<Vec<u8>> {
377 self.resolve_if_needed();
378 let tex = self.color_tex(index)?;
379 let src = if self.resolve_fbo_id != 0 {
380 self.resolve_fbo_id
381 } else {
382 self.fbo_id
383 };
384 let mut prev_fbo = 0i32;
385 unsafe {
386 gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut prev_fbo);
387 gl::BindFramebuffer(gl::FRAMEBUFFER, src);
388 }
389 let (format, pix_type, pixel_size) = fmt_gl_params(&tex.fmt);
390 let total = (self.size.w as usize) * (self.size.h as usize) * pixel_size;
391 let mut pixels = vec![0u8; total];
392 unsafe {
393 gl::ReadPixels(
394 0, 0, self.size.w as i32, self.size.h as i32,
395 format, pix_type, pixels.as_mut_ptr() as *mut _,
396 );
397 gl::BindFramebuffer(gl::FRAMEBUFFER, prev_fbo as u32);
398 }
399 Ok(pixels)
400 }
401
402 pub fn save_to_disk(&self, index: usize, path: &str) -> OpticResult<()> {
407 let data = self.read_pixels(index)?;
408 let tex = self.color_tex(index)?;
409 let channels = tex.fmt.channels() as u8;
410 let bit_depth = tex.fmt.bit_depth();
411 let ct = match (channels, bit_depth) {
412 (1, 8) => image::ColorType::L8,
413 (2, 8) => image::ColorType::La8,
414 (3, 8) => image::ColorType::Rgb8,
415 (4, 8) => image::ColorType::Rgba8,
416 (1, 16) => image::ColorType::L16,
417 (2, 16) => image::ColorType::La16,
418 (3, 16) => image::ColorType::Rgb16,
419 (4, 16) => image::ColorType::Rgba16,
420 (3, 32) => image::ColorType::Rgb32F,
421 (4, 32) => image::ColorType::Rgba32F,
422 _ => {
423 return Err(OpticError::new(
424 OpticErrorKind::Custom,
425 &format!("unsupported format for save_to_file: {}x{}bpp", channels, bit_depth),
426 ))
427 }
428 };
429 image::save_buffer(path, &data, self.size.w, self.size.h, ct).map_err(|e| {
430 OpticError::new(OpticErrorKind::File, &format!("failed to save image: {e}"))
431 })
432 }
433
434 pub fn delete(&mut self) {
436 unsafe {
437 gl::DeleteFramebuffers(1, &self.fbo_id);
438 if self.resolve_fbo_id != 0 {
439 gl::DeleteFramebuffers(1, &self.resolve_fbo_id);
440 }
441 for &rbo in &self.msaa_rbos {
442 gl::DeleteRenderbuffers(1, &rbo);
443 }
444 if self.depth_stencil_rbo != 0 {
445 gl::DeleteRenderbuffers(1, &self.depth_stencil_rbo);
446 }
447 }
448 for tex in std::mem::take(&mut self.color_texs) {
449 tex.delete();
450 }
451 if let Some(tex) = self.depth_tex.take() {
452 tex.delete();
453 }
454 }
455
456 fn resolve_if_needed(&self) {
457 if self.resolve_fbo_id != 0 {
458 self.resolve();
459 }
460 }
461}
462
463fn depth_rbo_params(stencil: bool) -> (i32, u32) {
464 if stencil {
465 (gl::DEPTH24_STENCIL8 as i32, gl::DEPTH_STENCIL_ATTACHMENT)
466 } else {
467 (gl::DEPTH_COMPONENT24 as i32, gl::DEPTH_ATTACHMENT)
468 }
469}
470
471fn create_empty_tex(size: Size2D, fmt: &ImgFormat, filter: ImgFilter, wrap: ImgWrap) -> u32 {
472 unsafe {
473 let mut id = 0;
474 gl::GenTextures(1, &mut id);
475 gl::BindTexture(gl::TEXTURE_2D, id);
476
477 let (min_fil, mag_fil) = match filter {
478 ImgFilter::Closest => (gl::NEAREST as i32, gl::NEAREST as i32),
479 ImgFilter::Linear => (gl::LINEAR as i32, gl::LINEAR as i32),
480 };
481 let wrap_gl = match wrap {
482 ImgWrap::Repeat => gl::REPEAT,
483 ImgWrap::Extend => gl::CLAMP_TO_EDGE,
484 ImgWrap::Clip => gl::CLAMP_TO_BORDER,
485 };
486 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, min_fil);
487 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, mag_fil);
488 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, wrap_gl as i32);
489 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, wrap_gl as i32);
490
491 let (base, sized, pix_type) = fmt_to_gl(fmt);
492 gl::TexImage2D(
493 gl::TEXTURE_2D, 0, sized as i32,
494 size.w as i32, size.h as i32, 0,
495 base, pix_type, std::ptr::null(),
496 );
497 gl::BindTexture(gl::TEXTURE_2D, 0);
498 id
499 }
500}
501
502fn create_rbo_storage_msaa(size: Size2D, samples: u32, fmt: &ImgFormat, _stencil: bool) -> u32 {
503 unsafe {
504 let mut id = 0;
505 gl::GenRenderbuffers(1, &mut id);
506 gl::BindRenderbuffer(gl::RENDERBUFFER, id);
507 let (_base, sized, _pix_type) = fmt_to_gl(fmt);
508 gl::RenderbufferStorageMultisample(
509 gl::RENDERBUFFER, samples as i32, sized as u32,
510 size.w as i32, size.h as i32,
511 );
512 gl::BindRenderbuffer(gl::RENDERBUFFER, 0);
513 id
514 }
515}
516
517fn create_depth_tex(size: Size2D, stencil: bool, compare: bool) -> u32 {
518 unsafe {
519 let mut id = 0;
520 gl::GenTextures(1, &mut id);
521 gl::BindTexture(gl::TEXTURE_2D, id);
522 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
523 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
524 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
525 gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
526
527 let (internal, format, pix_type) = if stencil {
528 (gl::DEPTH24_STENCIL8 as i32, gl::DEPTH_STENCIL, gl::UNSIGNED_INT_24_8)
529 } else {
530 (gl::DEPTH_COMPONENT24 as i32, gl::DEPTH_COMPONENT, gl::FLOAT)
531 };
532
533 gl::TexImage2D(
534 gl::TEXTURE_2D, 0, internal,
535 size.w as i32, size.h as i32, 0,
536 format, pix_type, std::ptr::null(),
537 );
538
539 if compare {
540 gl::TexParameteri(
541 gl::TEXTURE_2D, gl::TEXTURE_COMPARE_MODE,
542 gl::COMPARE_REF_TO_TEXTURE as i32,
543 );
544 gl::TexParameteri(
545 gl::TEXTURE_2D, gl::TEXTURE_COMPARE_FUNC,
546 gl::LEQUAL as i32,
547 );
548 }
549
550 gl::BindTexture(gl::TEXTURE_2D, 0);
551 id
552 }
553}
554
555fn fmt_to_gl(fmt: &ImgFormat) -> (u32, u32, u32) {
556 match fmt {
557 ImgFormat::R(bd) => match bd {
558 32 => (gl::RED, gl::R32F, gl::FLOAT),
559 16 => (gl::RED, gl::R16, gl::UNSIGNED_SHORT),
560 _ => (gl::RED, gl::R8, gl::UNSIGNED_BYTE),
561 },
562 ImgFormat::RG(bd) => match bd {
563 32 => (gl::RG, gl::RG32F, gl::FLOAT),
564 16 => (gl::RG, gl::RG16, gl::UNSIGNED_SHORT),
565 _ => (gl::RG, gl::RG8, gl::UNSIGNED_BYTE),
566 },
567 ImgFormat::RGB(bd) => match bd {
568 32 => (gl::RGB, gl::RGB32F, gl::FLOAT),
569 16 => (gl::RGB, gl::RGB16, gl::UNSIGNED_SHORT),
570 _ => (gl::RGB, gl::RGB8, gl::UNSIGNED_BYTE),
571 },
572 ImgFormat::RGBA(bd) => match bd {
573 32 => (gl::RGBA, gl::RGBA32F, gl::FLOAT),
574 16 => (gl::RGBA, gl::RGBA16, gl::UNSIGNED_SHORT),
575 _ => (gl::RGBA, gl::RGBA8, gl::UNSIGNED_BYTE),
576 },
577 }
578}
579
580fn fmt_gl_params(fmt: &ImgFormat) -> (u32, u32, usize) {
581 let (base, _, pix_type) = fmt_to_gl(fmt);
582 let channels = fmt.channels() as usize;
583 let bytes_per_channel = (fmt.bit_depth() / 8) as usize;
584 (base, pix_type, channels * bytes_per_channel)
585}
586
587pub enum RenderTarget<'a> {
589 Screen,
590 Canvas(&'a Canvas),
591}