glutin/
context.rs

1//! OpenGL context creation and initialization.
2
3#![allow(unreachable_patterns)]
4use std::ffi;
5
6use raw_window_handle::RawWindowHandle;
7
8use crate::config::{Config, GetGlConfig};
9use crate::display::{Display, GetGlDisplay};
10use crate::error::Result;
11use crate::private::{gl_api_dispatch, Sealed};
12use crate::surface::{GlSurface, Surface, SurfaceTypeTrait};
13
14#[cfg(cgl_backend)]
15use crate::api::cgl::context::{
16    NotCurrentContext as NotCurrentCglContext, PossiblyCurrentContext as PossiblyCurrentCglContext,
17};
18#[cfg(egl_backend)]
19use crate::api::egl::context::{
20    NotCurrentContext as NotCurrentEglContext, PossiblyCurrentContext as PossiblyCurrentEglContext,
21};
22#[cfg(glx_backend)]
23use crate::api::glx::context::{
24    NotCurrentContext as NotCurrentGlxContext, PossiblyCurrentContext as PossiblyCurrentGlxContext,
25};
26#[cfg(wgl_backend)]
27use crate::api::wgl::context::{
28    NotCurrentContext as NotCurrentWglContext, PossiblyCurrentContext as PossiblyCurrentWglContext,
29};
30
31/// A trait to group common context operations.
32pub trait GlContext: Sealed {
33    /// Get the [`ContextApi`] used by the context.
34    ///
35    /// The returned value's [`Version`] will always be `None`.
36    fn context_api(&self) -> ContextApi;
37
38    /// Get the [`Priority`] used by the context.
39    fn priority(&self) -> Priority;
40}
41
42/// A trait to group common not current operations.
43pub trait NotCurrentGlContext: Sealed {
44    /// The type of possibly current context.
45    type PossiblyCurrentContext: PossiblyCurrentGlContext;
46
47    /// The surface supported by the context.
48    type Surface<T: SurfaceTypeTrait>: GlSurface<T>;
49
50    /// Treat the not current context as possibly current. The operation is safe
51    /// because the possibly current context is more restricted and not
52    /// guaranteed to be current.
53    fn treat_as_possibly_current(self) -> Self::PossiblyCurrentContext;
54
55    /// Make [`Self::Surface`] on the calling thread producing the
56    /// [`Self::PossiblyCurrentContext`] indicating that the context could
57    /// be current on the thread.
58    ///
59    /// # Platform specific
60    ///
61    /// - **macOS: this will block if your main thread is blocked**;
62    /// - **Wayland:** this call may latch the underlying back buffer (will do
63    ///   with mesa drivers), meaning that all resize operations will apply
64    ///   after the next [`GlSurface::swap_buffers`].
65    fn make_current<T: SurfaceTypeTrait>(
66        self,
67        surface: &Self::Surface<T>,
68    ) -> Result<Self::PossiblyCurrentContext>;
69
70    /// The same as [`Self::make_current`], but provides a way to set read and
71    /// draw surfaces.
72    ///
73    /// # Api-specific:
74    ///
75    /// - **WGL/CGL:** not supported.
76    fn make_current_draw_read<T: SurfaceTypeTrait>(
77        self,
78        surface_draw: &Self::Surface<T>,
79        surface_read: &Self::Surface<T>,
80    ) -> Result<Self::PossiblyCurrentContext>;
81}
82
83/// A trait to group common context operations.
84pub trait PossiblyCurrentGlContext: Sealed {
85    /// The not current context type.
86    type NotCurrentContext: NotCurrentGlContext;
87
88    /// The surface supported by the context.
89    type Surface<T: SurfaceTypeTrait>: GlSurface<T>;
90
91    /// Returns `true` if this context is the current one in this thread.
92    fn is_current(&self) -> bool;
93
94    /// Make the context not current to the current thread and returns a
95    /// [`Self::NotCurrentContext`] to indicate that the context is a not
96    /// current to allow sending it to the different thread.
97    ///
98    /// # Platform specific
99    ///
100    /// - **macOS: this will block if your main thread is blocked.**
101    fn make_not_current(self) -> Result<Self::NotCurrentContext>;
102
103    /// Make the context not current to the current thread. If you need to
104    /// send the context to another thread, use [`Self::make_not_current`]
105    /// instead.
106    fn make_not_current_in_place(&self) -> Result<()>;
107
108    /// Make [`Self::Surface`] current on the calling thread.
109    ///
110    /// # Platform specific
111    ///
112    /// - **macOS: this will block if your main thread is blocked.**
113    fn make_current<T: SurfaceTypeTrait>(&self, surface: &Self::Surface<T>) -> Result<()>;
114
115    /// The same as [`Self::make_current`] but provides a way to set read and
116    /// draw surfaces explicitly.
117    ///
118    /// # Api-specific:
119    ///
120    /// - **CGL/WGL:** not supported.
121    fn make_current_draw_read<T: SurfaceTypeTrait>(
122        &self,
123        surface_draw: &Self::Surface<T>,
124        surface_read: &Self::Surface<T>,
125    ) -> Result<()>;
126}
127
128/// A trait that provides raw context.
129pub trait AsRawContext {
130    /// Get the raw context handle.
131    fn raw_context(&self) -> RawContext;
132}
133
134/// The builder to help customizing context
135#[derive(Default, Debug, Clone)]
136pub struct ContextAttributesBuilder {
137    attributes: ContextAttributes,
138}
139
140impl ContextAttributesBuilder {
141    /// Create new builder.
142    pub fn new() -> Self {
143        Default::default()
144    }
145
146    /// Sets the *debug* flag for the OpenGL context.
147    ///
148    /// Debug contexts are usually slower, but give better error reporting.
149    /// This option is ignored when using [`Robustness::NoError`].
150    ///
151    /// The default value for this flag is `false`.
152    pub fn with_debug(mut self, debug: bool) -> Self {
153        self.attributes.debug = debug;
154        self
155    }
156
157    /// Share the display lists with the given context.
158    ///
159    /// To get sharing working it's recommended to use the same [`Config`] when
160    /// creating contexts that are going to be shared.
161    ///
162    /// # Platform-specific
163    ///
164    /// - **Wayland:** both contexts must use the same Wayland connection.
165    ///
166    /// [`Config`]: crate::config::Config
167    pub fn with_sharing(mut self, context: &impl AsRawContext) -> Self {
168        self.attributes.shared_context = Some(context.raw_context());
169        self
170    }
171
172    /// Sets the robustness of the OpenGL context. See the docs of
173    /// [`Robustness`].
174    ///
175    /// The default is [`Robustness::NotRobust`], because this is what typically
176    /// expected when you create an OpenGL context.  However for safety you
177    /// should consider [`Robustness::RobustLoseContextOnReset`].
178    pub fn with_robustness(mut self, robustness: Robustness) -> Self {
179        self.attributes.robustness = robustness;
180        self
181    }
182
183    /// The behavior when changing the current context. See the docs of
184    /// [`ReleaseBehavior`].
185    ///
186    /// The default is [`ReleaseBehavior::Flush`].
187    pub fn with_release_behavior(mut self, release_behavior: ReleaseBehavior) -> Self {
188        self.attributes.release_behavior = release_behavior;
189        self
190    }
191
192    /// Set the desired OpenGL context profile. See the docs of [`GlProfile`].
193    ///
194    /// By default the profile is unspecified.
195    ///
196    /// # Api-specific
197    ///
198    /// - **macOS:** not supported, the latest is picked automatically.
199    pub fn with_profile(mut self, profile: GlProfile) -> Self {
200        self.attributes.profile = Some(profile);
201        self
202    }
203
204    /// Set the desired OpenGL context api. See the docs of [`ContextApi`].
205    ///
206    /// By default the supported api will be picked.
207    pub fn with_context_api(mut self, api: ContextApi) -> Self {
208        self.attributes.api = Some(api);
209        self
210    }
211
212    /// Set the priority hint, which might not be honored if the API does not
213    /// support it, if there are constraints on the number of high priority
214    /// contexts available in the system, or system policy limits access to
215    /// high priority contexts to appropriate system privilege level the
216    /// context creation may fail.
217    ///
218    /// By default no priority is specified, which corresponds to
219    /// [`Priority::Medium`].
220    ///
221    /// # Api specific
222    ///
223    /// - **WGL/GLX:** not implemented.
224    /// - **CGL:** not supported.
225    pub fn with_priority(mut self, priority: Priority) -> Self {
226        self.attributes.priority = Some(priority);
227        self
228    }
229
230    /// Build the context attributes.
231    ///
232    /// The `raw_window_handle` isn't required and here for WGL compatibility.
233    ///
234    /// # Api-specific
235    ///
236    /// - **WGL:** you **must** pass a `raw_window_handle` if you plan to use
237    ///   this context with that window.
238    pub fn build(mut self, raw_window_handle: Option<RawWindowHandle>) -> ContextAttributes {
239        self.attributes.raw_window_handle = raw_window_handle;
240        self.attributes
241    }
242}
243
244/// The attributes that are used to create a graphics context.
245#[derive(Default, Debug, Clone)]
246pub struct ContextAttributes {
247    pub(crate) release_behavior: ReleaseBehavior,
248
249    pub(crate) debug: bool,
250
251    pub(crate) robustness: Robustness,
252
253    pub(crate) profile: Option<GlProfile>,
254
255    pub(crate) api: Option<ContextApi>,
256
257    pub(crate) priority: Option<Priority>,
258
259    pub(crate) shared_context: Option<RawContext>,
260
261    pub(crate) raw_window_handle: Option<RawWindowHandle>,
262}
263
264/// Specifies the tolerance of the OpenGL context to faults. If you accept
265/// raw OpenGL commands and/or raw shader code from an untrusted source, you
266/// should definitely care about this.
267#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
268pub enum Robustness {
269    /// Not everything is checked. Your application can crash if you do
270    /// something wrong with your shaders.
271    #[default]
272    NotRobust,
273
274    /// The driver doesn't check anything. This option is very dangerous.
275    /// Please know what you're doing before using it. See the
276    /// `GL_KHR_no_error` extension.
277    ///
278    /// Since this option is purely an optimization, no error will be returned
279    /// if the backend doesn't support it. Instead it will automatically
280    /// fall back to [`Robustness::NotRobust`].
281    NoError,
282
283    /// Everything is checked to avoid any crash. The driver will attempt to
284    /// avoid any problem, but if a problem occurs the behavior is
285    /// implementation-defined. You are just guaranteed not to get a crash.
286    RobustNoResetNotification,
287
288    /// Everything is checked to avoid any crash. If a problem occurs, the
289    /// context will enter a "context lost" state. It must then be
290    /// recreated.
291    RobustLoseContextOnReset,
292}
293
294/// Describes the requested OpenGL context profiles.
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub enum GlProfile {
297    /// Include all the future-compatible functions and definitions.
298    ///
299    /// The requested OpenGL version with [`ContextApi`] should be at least 3.3.
300    Core,
301    /// Include all the immediate more functions and definitions.
302    ///
303    /// Use it only when it's really needed, otherwise use [`Self::Core`].
304    Compatibility,
305}
306
307/// The rendering Api context should support.
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub enum ContextApi {
310    /// OpenGL Api version that should be used by the context.
311    ///
312    /// When using `None` as `Version` any OpenGL context will be picked,
313    /// however when the [`GlProfile::Core`] is used at least 3.3 will be
314    /// requested.
315    OpenGl(Option<Version>),
316
317    /// OpenGL Api version that should be used by the context.
318    ///
319    /// When using `None` as `Version` the latest **known** major version is
320    /// picked. Versions that are higher than what was picked automatically
321    /// could still be supported.
322    Gles(Option<Version>),
323}
324
325#[cfg(any(egl_backend, glx_backend, wgl_backend))]
326impl ContextApi {
327    pub(crate) fn version(&self) -> Option<Version> {
328        match self {
329            Self::OpenGl(version) => *version,
330            Self::Gles(version) => *version,
331            _ => None,
332        }
333    }
334}
335
336impl Default for ContextApi {
337    fn default() -> Self {
338        Self::OpenGl(None)
339    }
340}
341
342/// The version used to index the Api.
343#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
344pub struct Version {
345    /// Major version of the Api.
346    pub major: u8,
347    /// Minor version of the Api.
348    pub minor: u8,
349}
350
351impl Version {
352    /// Create new version with the given `major` and `minor` values.
353    pub const fn new(major: u8, minor: u8) -> Self {
354        Self { major, minor }
355    }
356}
357
358/// The behavior of the driver when you change the current context.
359#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
360pub enum ReleaseBehavior {
361    /// Doesn't do anything. Most notably doesn't flush. Not supported by all
362    /// drivers.
363    ///
364    /// # Api-specific
365    ///
366    /// - **macOS:** not supported, [`Self::Flush`] is always used.
367    None,
368
369    /// Flushes the context that was previously current as if `glFlush` was
370    /// called. This is the default behavior.
371    #[default]
372    Flush,
373}
374
375/// A context that is known to be not current on the current thread.
376///
377/// This type is a safe wrapper around the context to indicate that it could be
378/// `Send` to the different thread, since the context must be not current before
379/// doing so.
380///
381/// ```no_run
382/// fn test_send<T: Send>() {}
383/// test_send::<glutin::context::NotCurrentContext>();
384/// ```
385/// However it's not `Sync`.
386/// ```compile_fail
387/// fn test_sync<T: Sync>() {}
388/// test_sync::<glutin::context::NotCurrentContext>();
389/// ```
390#[derive(Debug)]
391pub enum NotCurrentContext {
392    /// The EGL context.
393    #[cfg(egl_backend)]
394    Egl(NotCurrentEglContext),
395
396    /// The GLX context.
397    #[cfg(glx_backend)]
398    Glx(NotCurrentGlxContext),
399
400    /// The WGL context.
401    #[cfg(wgl_backend)]
402    Wgl(NotCurrentWglContext),
403
404    /// The CGL context.
405    #[cfg(cgl_backend)]
406    Cgl(NotCurrentCglContext),
407}
408
409impl NotCurrentGlContext for NotCurrentContext {
410    type PossiblyCurrentContext = PossiblyCurrentContext;
411    type Surface<T: SurfaceTypeTrait> = Surface<T>;
412
413    fn treat_as_possibly_current(self) -> Self::PossiblyCurrentContext {
414        gl_api_dispatch!(self; Self(context) => context.treat_as_possibly_current(); as PossiblyCurrentContext)
415    }
416
417    fn make_current<T: SurfaceTypeTrait>(
418        self,
419        surface: &Self::Surface<T>,
420    ) -> Result<Self::PossiblyCurrentContext> {
421        match (self, surface) {
422            #[cfg(egl_backend)]
423            (Self::Egl(context), Surface::Egl(surface)) => {
424                Ok(PossiblyCurrentContext::Egl(context.make_current(surface)?))
425            },
426            #[cfg(glx_backend)]
427            (Self::Glx(context), Surface::Glx(surface)) => {
428                Ok(PossiblyCurrentContext::Glx(context.make_current(surface)?))
429            },
430            #[cfg(wgl_backend)]
431            (Self::Wgl(context), Surface::Wgl(surface)) => {
432                Ok(PossiblyCurrentContext::Wgl(context.make_current(surface)?))
433            },
434            #[cfg(cgl_backend)]
435            (Self::Cgl(context), Surface::Cgl(surface)) => {
436                Ok(PossiblyCurrentContext::Cgl(context.make_current(surface)?))
437            },
438            _ => unreachable!(),
439        }
440    }
441
442    fn make_current_draw_read<T: SurfaceTypeTrait>(
443        self,
444        surface_draw: &Self::Surface<T>,
445        surface_read: &Self::Surface<T>,
446    ) -> Result<Self::PossiblyCurrentContext> {
447        match (self, surface_draw, surface_read) {
448            #[cfg(egl_backend)]
449            (Self::Egl(context), Surface::Egl(draw), Surface::Egl(read)) => {
450                Ok(PossiblyCurrentContext::Egl(context.make_current_draw_read(draw, read)?))
451            },
452            #[cfg(glx_backend)]
453            (Self::Glx(context), Surface::Glx(draw), Surface::Glx(read)) => {
454                Ok(PossiblyCurrentContext::Glx(context.make_current_draw_read(draw, read)?))
455            },
456            #[cfg(wgl_backend)]
457            (Self::Wgl(context), Surface::Wgl(draw), Surface::Wgl(read)) => {
458                Ok(PossiblyCurrentContext::Wgl(context.make_current_draw_read(draw, read)?))
459            },
460            #[cfg(cgl_backend)]
461            (Self::Cgl(context), Surface::Cgl(draw), Surface::Cgl(read)) => {
462                Ok(PossiblyCurrentContext::Cgl(context.make_current_draw_read(draw, read)?))
463            },
464            _ => unreachable!(),
465        }
466    }
467}
468
469impl GlContext for NotCurrentContext {
470    fn context_api(&self) -> ContextApi {
471        gl_api_dispatch!(self; Self(context) => context.context_api())
472    }
473
474    fn priority(&self) -> Priority {
475        gl_api_dispatch!(self; Self(context) => context.priority())
476    }
477}
478
479impl GetGlConfig for NotCurrentContext {
480    type Target = Config;
481
482    fn config(&self) -> Self::Target {
483        gl_api_dispatch!(self; Self(context) => context.config(); as Config)
484    }
485}
486
487impl GetGlDisplay for NotCurrentContext {
488    type Target = Display;
489
490    fn display(&self) -> Self::Target {
491        gl_api_dispatch!(self; Self(context) => context.display(); as Display)
492    }
493}
494
495impl AsRawContext for NotCurrentContext {
496    fn raw_context(&self) -> RawContext {
497        gl_api_dispatch!(self; Self(context) => context.raw_context())
498    }
499}
500
501impl Sealed for NotCurrentContext {}
502
503/// A context that is possibly current on the current thread.
504///
505/// The context that could be current on the current thread can neither be
506/// [`Send`] nor [`Sync`]. In case you need to use it on a different thread
507/// [make it not current].
508/// ```compile_fail
509/// fn test_send<T: Send>() {}
510/// test_send::<glutin::context::PossiblyCurrentContext>();
511/// ```
512///
513/// ```compile_fail
514/// fn test_sync<T: Sync>() {}
515/// test_sync::<glutin::context::PossiblyCurrentContext>();
516/// ```
517///
518/// [make it not current]: crate::context::PossiblyCurrentGlContext::make_not_current
519#[derive(Debug)]
520pub enum PossiblyCurrentContext {
521    /// The EGL context.
522    #[cfg(egl_backend)]
523    Egl(PossiblyCurrentEglContext),
524
525    /// The GLX context.
526    #[cfg(glx_backend)]
527    Glx(PossiblyCurrentGlxContext),
528
529    /// The WGL context.
530    #[cfg(wgl_backend)]
531    Wgl(PossiblyCurrentWglContext),
532
533    /// The CGL context.
534    #[cfg(cgl_backend)]
535    Cgl(PossiblyCurrentCglContext),
536}
537
538impl PossiblyCurrentGlContext for PossiblyCurrentContext {
539    type NotCurrentContext = NotCurrentContext;
540    type Surface<T: SurfaceTypeTrait> = Surface<T>;
541
542    fn is_current(&self) -> bool {
543        gl_api_dispatch!(self; Self(context) => context.is_current())
544    }
545
546    fn make_not_current(self) -> Result<Self::NotCurrentContext> {
547        Ok(
548            gl_api_dispatch!(self; Self(context) => context.make_not_current()?; as NotCurrentContext),
549        )
550    }
551
552    fn make_not_current_in_place(&self) -> Result<()> {
553        Ok(gl_api_dispatch!(self; Self(context) => context.make_not_current_in_place()?))
554    }
555
556    fn make_current<T: SurfaceTypeTrait>(&self, surface: &Self::Surface<T>) -> Result<()> {
557        match (self, surface) {
558            #[cfg(egl_backend)]
559            (Self::Egl(context), Surface::Egl(surface)) => context.make_current(surface),
560            #[cfg(glx_backend)]
561            (Self::Glx(context), Surface::Glx(surface)) => context.make_current(surface),
562            #[cfg(wgl_backend)]
563            (Self::Wgl(context), Surface::Wgl(surface)) => context.make_current(surface),
564            #[cfg(cgl_backend)]
565            (Self::Cgl(context), Surface::Cgl(surface)) => context.make_current(surface),
566            _ => unreachable!(),
567        }
568    }
569
570    fn make_current_draw_read<T: SurfaceTypeTrait>(
571        &self,
572        surface_draw: &Self::Surface<T>,
573        surface_read: &Self::Surface<T>,
574    ) -> Result<()> {
575        match (self, surface_draw, surface_read) {
576            #[cfg(egl_backend)]
577            (Self::Egl(context), Surface::Egl(draw), Surface::Egl(read)) => {
578                context.make_current_draw_read(draw, read)
579            },
580            #[cfg(glx_backend)]
581            (Self::Glx(context), Surface::Glx(draw), Surface::Glx(read)) => {
582                context.make_current_draw_read(draw, read)
583            },
584            #[cfg(wgl_backend)]
585            (Self::Wgl(context), Surface::Wgl(draw), Surface::Wgl(read)) => {
586                context.make_current_draw_read(draw, read)
587            },
588            #[cfg(cgl_backend)]
589            (Self::Cgl(context), Surface::Cgl(draw), Surface::Cgl(read)) => {
590                context.make_current_draw_read(draw, read)
591            },
592            _ => unreachable!(),
593        }
594    }
595}
596
597impl GlContext for PossiblyCurrentContext {
598    fn context_api(&self) -> ContextApi {
599        gl_api_dispatch!(self; Self(context) => context.context_api())
600    }
601
602    fn priority(&self) -> Priority {
603        gl_api_dispatch!(self; Self(context) => context.priority())
604    }
605}
606
607impl GetGlConfig for PossiblyCurrentContext {
608    type Target = Config;
609
610    fn config(&self) -> Self::Target {
611        gl_api_dispatch!(self; Self(context) => context.config(); as Config)
612    }
613}
614
615impl GetGlDisplay for PossiblyCurrentContext {
616    type Target = Display;
617
618    fn display(&self) -> Self::Target {
619        gl_api_dispatch!(self; Self(context) => context.display(); as Display)
620    }
621}
622
623impl AsRawContext for PossiblyCurrentContext {
624    fn raw_context(&self) -> RawContext {
625        gl_api_dispatch!(self; Self(context) => context.raw_context())
626    }
627}
628
629impl Sealed for PossiblyCurrentContext {}
630
631/// Raw context.
632#[derive(Debug, Clone, Copy, PartialEq, Eq)]
633pub enum RawContext {
634    /// Raw EGL context.
635    #[cfg(egl_backend)]
636    Egl(*const ffi::c_void),
637
638    /// Raw GLX context.
639    #[cfg(glx_backend)]
640    Glx(*const ffi::c_void),
641
642    /// HGLRC pointer.
643    #[cfg(wgl_backend)]
644    Wgl(*const ffi::c_void),
645
646    /// Pointer to NSOpenGLContext.
647    #[cfg(cgl_backend)]
648    Cgl(*const ffi::c_void),
649}
650
651/// Priority hint
652#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
653pub enum Priority {
654    /// Lowest priority, contexts using this priority give way for most other
655    /// contexts.
656    Low,
657    /// Default priority.
658    #[default]
659    Medium,
660    /// High priority is usually required for VR applications.
661    High,
662    /// Realtime priority contexts are executed immediately and preempt any
663    /// current context running.
664    ///
665    /// When such context is not supported, [`Priority::High`] will be requested
666    /// instead.
667    Realtime,
668}
669
670/// Pick `GlProfile` and `Version` based on the provided params.
671#[cfg(any(egl_backend, glx_backend, wgl_backend))]
672pub(crate) fn pick_profile(
673    profile: Option<GlProfile>,
674    version: Option<Version>,
675) -> (GlProfile, Version) {
676    match (profile, version) {
677        (Some(GlProfile::Core), Some(version)) => (GlProfile::Core, version),
678        (Some(GlProfile::Compatibility), Some(version)) => (GlProfile::Compatibility, version),
679        (None, Some(version)) if version >= Version::new(3, 3) => (GlProfile::Core, version),
680        (None, Some(version)) => (GlProfile::Compatibility, version),
681        (Some(GlProfile::Core), None) => (GlProfile::Core, Version::new(3, 3)),
682        (Some(GlProfile::Compatibility), None) => (GlProfile::Compatibility, Version::new(2, 1)),
683        (None, None) => (GlProfile::Core, Version::new(3, 3)),
684    }
685}