1use crate::wrappers::mj_visualization::MjvScene;
3use crate::wrappers::mj_rendering::MjrContext;
4
5#[cfg(target_os = "linux")]
6use crate::renderer::egl::GlStateEgl;
7
8use crate::vis_common::sync_geoms;
9use crate::builder_setters;
10use crate::prelude::*;
11
12use bitflags::bitflags;
13use png::Encoder;
14
15use std::io::{self, BufWriter, ErrorKind, Write};
16use std::fmt::Display;
17use std::error::Error;
18use std::marker::PhantomData;
19use std::num::NonZero;
20use std::ops::Deref;
21use std::path::Path;
22use std::fs::File;
23
24#[cfg(feature = "renderer-winit-fallback")]
25mod universal;
26
27#[cfg(feature = "renderer-winit-fallback")]
28use universal::GlStateWinit;
29
30#[cfg(target_os = "linux")]
31mod egl;
32
33
34const RGB_NOT_FOUND_ERR_STR: &str = "RGB rendering is not enabled (renderer.with_rgb_rendering(true))";
35const DEPTH_NOT_FOUND_ERR_STR: &str = "depth rendering is not enabled (renderer.with_depth_rendering(true))";
36const INVALID_INPUT_SIZE: &str = "the input width and height don't match the renderer's configuration";
37const EXTRA_INTERNAL_VISUAL_GEOMS: u32 = 100;
38
39
40pub(crate) enum GlState {
43 #[cfg(feature = "renderer-winit-fallback")] Winit(GlStateWinit),
44 #[cfg(target_os = "linux")] Egl(egl::GlStateEgl),
45}
46
47impl GlState {
48 pub(crate) fn new(width: NonZero<u32>, height: NonZero<u32>) -> Result<Self, RendererError> {
51 #[cfg(target_os = "linux")]
52 #[allow(unused_variables)]
53 let egl_err = match GlStateEgl::new(width, height) {
54 Ok(egl_state) => return Ok(Self::Egl(egl_state)),
55 Err(e) => e,
56 };
57
58 #[cfg(feature = "renderer-winit-fallback")]
59 match GlStateWinit::new(width, height) {
60 Ok(winit_state) => return Ok(Self::Winit(winit_state)),
61 #[cfg(not(target_os = "linux"))]
62 Err(e) => {
63 return Err(e);
64 },
65
66 #[cfg(target_os = "linux")]
67 _ => {}
68 }
69
70 #[cfg(target_os = "linux")]
71 Err(RendererError::GlutinError(egl_err))
72 }
73
74 pub(crate) fn make_current(&self) -> glutin::error::Result<()> {
75 match self {
76 #[cfg(target_os = "linux")]
77 Self::Egl(egl_state) => egl_state.make_current(),
78 #[cfg(feature = "renderer-winit-fallback")]
79 Self::Winit(winit_state) => winit_state.make_current()
80 }
81 }
82}
83
84
85#[derive(Debug)]
87pub struct MjRendererBuilder<M: Deref<Target = MjModel> + Clone> {
88 width: u32,
89 height: u32,
90 num_visual_internal_geom: u32,
91 num_visual_user_geom: u32,
92 rgb: bool,
93 depth: bool,
94 font_scale: MjtFontScale,
95 camera: MjvCamera,
96 opts: MjvOption,
97 model_type: PhantomData<M>
98}
99
100impl<M: Deref<Target = MjModel> + Clone> MjRendererBuilder<M> {
101 pub fn new() -> Self {
109 Self {
110 width: 0, height: 0,
111 num_visual_internal_geom: EXTRA_INTERNAL_VISUAL_GEOMS, num_visual_user_geom: 0,
112 rgb: true, depth: false, font_scale: MjtFontScale::mjFONTSCALE_100,
113 camera: MjvCamera::default(), opts: MjvOption::default(),
114 model_type: PhantomData
115 }
116 }
117
118 builder_setters! {
119 width: u32; "
120image width.
121
122<div class=\"warning\">
123
124The width must be less or equal to the offscreen buffer width,
125which can be configured at the top of the model's XML like so:
126
127```xml
128<visual>
129 <global offwidth=\"1920\" .../>
130</visual>
131```
132
133</div>";
134
135 height: u32; "\
136image height.
137
138<div class=\"warning\">
139
140The height must be less or equal to the offscreen buffer height,
141which can be configured at the top of the model's XML like so:
142
143```xml
144<visual>
145 <global offheight=\"1080\" .../>
146</visual>
147```
148
149</div>";
150
151 num_visual_internal_geom: u32; "\
152 maximum number of additional visual-only internal geoms to allocate for.
153 Note that the total number of geoms in the internal scene will be
154 `num_visual_internal_geom` + `num_visual_user_geom`.";
155
156 num_visual_user_geom: u32; "maximum number of additional visual-only user geoms (drawn by the user).";
157 rgb: bool; "RGB rendering enabled (true) or disabled (false).";
158 depth: bool; "depth rendering enabled (true) or disabled (false).";
159 font_scale: MjtFontScale; "font scale of drawn text (with [MjrContext]).";
160 camera: MjvCamera; "camera used for drawing.";
161 opts: MjvOption; "visualization options.";
162 }
163
164 pub fn build(self, model: M) -> Result<MjRenderer<M>, RendererError> {
166 let mut height = self.height;
168 let mut width = self.width;
169 if width == 0 && height == 0 {
170 let global = &model.vis().global;
171 height = global.offheight as u32;
172 width = global.offwidth as u32;
173 }
174
175 let gl_state = GlState::new(width.try_into().unwrap(), height.try_into().unwrap())?;
176
177 let mut context = MjrContext::new(&model);
179 context.offscreen();
180
181 let scene = MjvScene::new(
183 model.clone(),
184 model.ffi().ngeom as usize + self.num_visual_internal_geom as usize
185 + self.num_visual_user_geom as usize
186 );
187
188 let user_scene = MjvScene::new(
189 model.clone(),
190 self.num_visual_user_geom as usize
191 );
192
193 let renderer = MjRenderer {
195 scene, user_scene, context, model, camera: self.camera, option: self.opts,
196 flags: RendererFlags::empty(), rgb: None, depth: None,
197 width: width as usize, height: height as usize, gl_state
198 } .with_rgb_rendering(self.rgb)
200 .with_depth_rendering(self.depth);
201
202 Ok(renderer)
203 }
204}
205
206
207impl<M: Deref<Target = MjModel> + Clone> Default for MjRendererBuilder<M> {
208 fn default() -> Self {
209 Self::new()
210 }
211}
212
213pub struct MjRenderer<M: Deref<Target = MjModel> + Clone> {
216 scene: MjvScene<M>,
217 user_scene: MjvScene<M>,
218 context: MjrContext,
219 model: M,
220
221 gl_state: GlState,
223
224 camera: MjvCamera,
226 option: MjvOption,
227 flags: RendererFlags,
228
229 rgb: Option<Box<[u8]>>,
233 depth: Option<Box<[f32]>>,
234
235 width: usize,
236 height: usize,
237}
238
239impl<M: Deref<Target = MjModel> + Clone> MjRenderer<M> {
240 pub fn new(model: M, width: usize, height: usize, max_user_geom: usize) -> Result<Self, RendererError> {
268 let builder = Self::builder()
269 .width(width as u32).height(height as u32).num_visual_user_geom(max_user_geom as u32);
270 builder.build(model)
271 }
272
273 pub fn builder() -> MjRendererBuilder<M> {
275 MjRendererBuilder::new()
276 }
277
278 pub fn scene(&self) -> &MjvScene<M>{
280 &self.scene
281 }
282
283 pub fn user_scene(&self) -> &MjvScene<M>{
285 &self.user_scene
286 }
287
288 pub fn user_scene_mut(&mut self) -> &mut MjvScene<M>{
290 &mut self.user_scene
291 }
292
293 pub fn opts(&self) -> &MjvOption {
295 &self.option
296 }
297
298 pub fn opts_mut(&mut self) -> &mut MjvOption {
300 &mut self.option
301 }
302
303 pub fn camera(&self) -> &MjvCamera {
305 &self.camera
306 }
307
308 pub fn camera_mut(&mut self) -> &mut MjvCamera {
310 &mut self.camera
311 }
312
313 pub fn rgb_enabled(&self) -> bool {
315 self.flags.contains(RendererFlags::RENDER_RGB)
316 }
317
318 pub fn depth_enabled(&self) -> bool {
320 self.flags.contains(RendererFlags::RENDER_DEPTH)
321 }
322
323 pub fn set_font_scale(&mut self, font_scale: MjtFontScale) {
325 self.context.change_font(font_scale);
326 }
327
328 pub fn set_opts(&mut self, options: MjvOption) {
330 self.option = options;
331 }
332
333 pub fn set_camera(&mut self, camera: MjvCamera) {
335 self.camera = camera;
336 }
337
338 pub fn set_rgb_rendering(&mut self, enable: bool) {
340 self.flags.set(RendererFlags::RENDER_RGB, enable);
341 self.rgb = if enable { Some(vec![0; 3 * self.width * self.height].into_boxed_slice()) } else { None } ;
342 }
343
344 pub fn set_depth_rendering(&mut self, enable: bool) {
346 self.flags.set(RendererFlags::RENDER_DEPTH, enable);
347 self.depth = if enable { Some(vec![0.0; self.width * self.height].into_boxed_slice()) } else { None } ;
348 }
349
350 pub fn with_font_scale(mut self, font_scale: MjtFontScale) -> Self {
352 self.set_font_scale(font_scale);
353 self
354 }
355
356 pub fn with_opts(mut self, options: MjvOption) -> Self {
358 self.set_opts(options);
359 self
360 }
361
362 pub fn with_camera(mut self, camera: MjvCamera) -> Self {
364 self.set_camera(camera);
365 self
366 }
367
368 pub fn with_rgb_rendering(mut self, enable: bool) -> Self {
370 self.set_rgb_rendering(enable);
371 self
372 }
373
374 pub fn with_depth_rendering(mut self, enable: bool) -> Self {
376 self.set_depth_rendering(enable);
377 self
378 }
379
380 pub fn sync(&mut self, data: &mut MjData<M>) {
382 assert_eq!(data.model().signature(), self.model.signature(), "'data' must be created from the same model as the renderer.");
383
384 self.scene.update(data, &self.option, &MjvPerturb::default(), &mut self.camera);
385
386 sync_geoms(&self.user_scene, &mut self.scene)
388 .expect("could not sync the user scene with the internal scene; this is a bug, please report it.");
389
390 self.render();
391 }
392
393 pub fn rgb_flat(&self) -> Option<&[u8]> {
395 self.rgb.as_deref()
396 }
397
398 pub fn rgb<const WIDTH: usize, const HEIGHT: usize>(&self) -> io::Result<&[[[u8; 3]; WIDTH]; HEIGHT]> {
401 if let Some(flat) = self.rgb_flat() {
402 if flat.len() == WIDTH * HEIGHT * 3 {
403 let p_shaped = flat.as_ptr() as *const [[[u8; 3]; WIDTH]; HEIGHT];
404
405 Ok(unsafe { p_shaped.as_ref().unwrap() })
409 }
410 else {
411 Err(io::Error::new(io::ErrorKind::InvalidInput, INVALID_INPUT_SIZE))
412 }
413 }
414 else {
415 Err(io::Error::new(io::ErrorKind::NotFound, RGB_NOT_FOUND_ERR_STR))
416 }
417 }
418
419 pub fn depth_flat(&self) -> Option<&[f32]> {
421 self.depth.as_deref()
422 }
423
424 pub fn depth<const WIDTH: usize, const HEIGHT: usize>(&self) -> io::Result<&[[f32; WIDTH]; HEIGHT]> {
427 if let Some(flat) = self.depth_flat() {
428 if flat.len() == WIDTH * HEIGHT {
429 let p_shaped = flat.as_ptr() as *const [[f32; WIDTH]; HEIGHT];
430
431 Ok(unsafe { p_shaped.as_ref().unwrap() })
435 }
436 else {
437 Err(io::Error::new(io::ErrorKind::InvalidInput, INVALID_INPUT_SIZE))
438 }
439 }
440 else {
441 Err(io::Error::new(io::ErrorKind::NotFound, DEPTH_NOT_FOUND_ERR_STR))
442 }
443 }
444
445 pub fn save_rgb<T: AsRef<Path>>(&self, path: T) -> io::Result<()> {
450 if let Some(rgb) = &self.rgb {
451 let file = File::create(path.as_ref())?;
452 let w = BufWriter::new(file);
453
454 let mut encoder = Encoder::new(w, self.width as u32, self.height as u32);
455 encoder.set_color(png::ColorType::Rgb);
456 encoder.set_depth(png::BitDepth::Eight);
457 encoder.set_compression(png::Compression::NoCompression);
458
459 let mut writer = encoder.write_header()?;
460 writer.write_image_data(rgb)?;
461 Ok(())
462 }
463 else {
464 Err(io::Error::new(ErrorKind::NotFound, RGB_NOT_FOUND_ERR_STR))
465 }
466 }
467
468 pub fn save_depth<T: AsRef<Path>>(&self, path: T, normalize: bool) -> io::Result<(f32, f32)> {
480 if let Some(depth) = &self.depth {
481 let file = File::create(path.as_ref())?;
482 let w = BufWriter::new(file);
483
484 let mut encoder = Encoder::new(w, self.width as u32, self.height as u32);
485 encoder.set_color(png::ColorType::Grayscale);
486 encoder.set_depth(png::BitDepth::Sixteen);
487 encoder.set_compression(png::Compression::NoCompression);
488
489 let (norm, min, max) =
490 if normalize {
491 let max = depth.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
492 let min = depth.iter().cloned().fold(f32::INFINITY, f32::min);
493 (depth.iter().flat_map(|&x| (((x - min) / (max - min) * 65535.0).min(65535.0) as u16).to_be_bytes()).collect::<Box<_>>(), min, max)
494 }
495 else {
496 (depth.iter().flat_map(|&x| ((x * 65535.0).min(65535.0) as u16).to_be_bytes()).collect::<Box<_>>(), 0.0, 1.0)
497 };
498
499 let mut writer = encoder.write_header()?;
500 writer.write_image_data(&norm)?;
501 Ok((min, max))
502 }
503 else {
504 Err(io::Error::new(ErrorKind::NotFound, DEPTH_NOT_FOUND_ERR_STR))
505 }
506 }
507
508 pub fn save_depth_raw<T: AsRef<Path>>(&self, path: T) -> io::Result<()> {
515 if let Some(depth) = &self.depth {
516 let file = File::create(path.as_ref())?;
517 let mut writer = BufWriter::new(file);
518
519 let p = unsafe { std::slice::from_raw_parts(
521 depth.as_ptr() as *const u8,
522 std::mem::size_of::<f32>() * depth.len()
523 ) };
524
525 writer.write_all(p)?;
526 Ok(())
527 }
528 else {
529 Err(io::Error::new(ErrorKind::NotFound, DEPTH_NOT_FOUND_ERR_STR))
530 }
531 }
532
533 fn render(&mut self) {
536 self.gl_state.make_current().expect("failed to make OpenGL context current");
537 let vp = MjrRectangle::new(0, 0, self.width as i32, self.height as i32);
538 self.scene.render(&vp, &self.context);
539
540 let flat_rgb = self.rgb.as_deref_mut();
542 let flat_depth = self.depth.as_deref_mut();
543
544 self.context.read_pixels(
546 flat_rgb,
547 flat_depth,
548 &vp
549 );
550
551 if let Some(depth) = self.depth.as_deref_mut() {
553 let map = &self.model.vis().map;
554 let stat = &self.model.stat();
555
556 let extent = stat.extent as f32;
557 let near = map.znear * extent;
558 let far = map.zfar * extent;
559 for value in depth {
560 *value = near / (1.0 - *value * (1.0 - near / far));
561 }
562 }
563 }
564}
565
566
567#[derive(Debug)]
568pub enum RendererError {
569 #[cfg(feature = "renderer-winit-fallback")]
570 EventLoopError(winit::error::EventLoopError),
571 GlutinError(glutin::error::Error)
572}
573
574impl Display for RendererError {
575 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
576 match self {
577 #[cfg(feature = "renderer-winit-fallback")]
578 Self::EventLoopError(e) => write!(f, "event loop failed to initialize: {}", e),
579 Self::GlutinError(e) => write!(f, "glutin failed to initialize: {}", e)
580 }
581 }
582}
583
584impl Error for RendererError {
585 fn source(&self) -> Option<&(dyn Error + 'static)> {
586 match self {
587 #[cfg(feature = "renderer-winit-fallback")]
588 Self::EventLoopError(e) => Some(e),
589 Self::GlutinError(e) => Some(e)
590 }
591 }
592}
593
594bitflags! {
595 struct RendererFlags: u8 {
597 const RENDER_RGB = 1 << 0;
598 const RENDER_DEPTH = 1 << 1;
599 }
600}
601
602
603
604#[cfg(test)]
610mod test {
611 use crate::assert_relative_eq;
612
613 use super::*;
614
615 const MODEL: &str = stringify!(
616 <mujoco>
617
618 <visual>
619 <global offwidth="1280" offheight="720"/>
620 </visual>
621
622 <worldbody>
623 <geom name="floor" type="plane" size="10 10 1" euler="0 0 0"/>
624 <geom type="box" size="1 10 10" pos="-1 0 0" euler="0 0 0"/>
625
626 <camera name="depth_test" euler="90 90 0" pos="2.25 0 1"/>
627
628 </worldbody>
629 </mujoco>
630 );
631
632 #[test]
635 #[cfg(target_os = "linux")]
636 fn test_depth() {
637 let model = MjModel::from_xml_string(MODEL).expect("could not load the model");
638 let mut data = MjData::new(&model);
639 data.step();
640
641 let mut renderer = MjRenderer::builder()
642 .rgb(false)
643 .depth(true)
644 .camera(MjvCamera::new_fixed(model.name_to_id(MjtObj::mjOBJ_CAMERA, "depth_test") as u32))
645 .build(&model)
646 .unwrap();
647
648 renderer.sync(&mut data);
649 let min = renderer.depth_flat().unwrap().iter().fold(f32::INFINITY, |a , &b| a.min(b));
650 let max = renderer.depth_flat().unwrap().iter().fold(f32::NEG_INFINITY, |a , &b| a.max(b));
651
652 assert_relative_eq!(min, max, epsilon = 1e-4);
653 assert_relative_eq!(min, 2.25, epsilon = 1e-4);
654 }
655}