1#[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))]
61mod egl;
62#[cfg(target_os = "emscripten")]
63mod emscripten;
64#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
65mod web;
66
67mod adapter;
68mod command;
69mod conv;
70mod device;
71mod queue;
72
73use crate::{CopyExtent, TextureDescriptor};
74
75#[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))]
76pub use self::egl::{AdapterContext, AdapterContextLock};
77#[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))]
78use self::egl::{Instance, Surface};
79
80#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
81pub use self::web::AdapterContext;
82#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
83use self::web::{Instance, Surface};
84
85use arrayvec::ArrayVec;
86
87use glow::HasContext;
88
89use naga::FastHashMap;
90use parking_lot::Mutex;
91use std::sync::atomic::AtomicU32;
92use std::{fmt, ops::Range, sync::Arc};
93
94#[derive(Clone)]
95pub struct Api;
96
97const MAX_TEXTURE_SLOTS: usize = 16;
100const MAX_SAMPLERS: usize = 16;
101const MAX_VERTEX_ATTRIBUTES: usize = 16;
102const ZERO_BUFFER_SIZE: usize = 256 << 10;
103const MAX_PUSH_CONSTANTS: usize = 64;
104
105impl crate::Api for Api {
106 type Instance = Instance;
107 type Surface = Surface;
108 type Adapter = Adapter;
109 type Device = Device;
110
111 type Queue = Queue;
112 type CommandEncoder = CommandEncoder;
113 type CommandBuffer = CommandBuffer;
114
115 type Buffer = Buffer;
116 type Texture = Texture;
117 type SurfaceTexture = Texture;
118 type TextureView = TextureView;
119 type Sampler = Sampler;
120 type QuerySet = QuerySet;
121 type Fence = Fence;
122
123 type BindGroupLayout = BindGroupLayout;
124 type BindGroup = BindGroup;
125 type PipelineLayout = PipelineLayout;
126 type ShaderModule = ShaderModule;
127 type RenderPipeline = RenderPipeline;
128 type ComputePipeline = ComputePipeline;
129}
130
131bitflags::bitflags! {
132 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
135 struct PrivateCapabilities: u32 {
136 const BUFFER_ALLOCATION = 1 << 0;
138 const SHADER_BINDING_LAYOUT = 1 << 1;
140 const SHADER_TEXTURE_SHADOW_LOD = 1 << 2;
142 const MEMORY_BARRIERS = 1 << 3;
144 const VERTEX_BUFFER_LAYOUT = 1 << 4;
146 const INDEX_BUFFER_ROLE_CHANGE = 1 << 5;
149 const CAN_DISABLE_DRAW_BUFFER = 1 << 6;
151 const GET_BUFFER_SUB_DATA = 1 << 7;
153 const COLOR_BUFFER_HALF_FLOAT = 1 << 8;
155 const COLOR_BUFFER_FLOAT = 1 << 9;
157 const TEXTURE_FLOAT_LINEAR = 1 << 10;
159 }
160}
161
162bitflags::bitflags! {
163 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
165 struct Workarounds: u32 {
166 const MESA_I915_SRGB_SHADER_CLEAR = 1 << 0;
173 const EMULATE_BUFFER_MAP = 1 << 1;
175 }
176}
177
178type BindTarget = u32;
179
180#[derive(Debug, Clone, Copy)]
181enum VertexAttribKind {
182 Float, Integer, }
186
187impl Default for VertexAttribKind {
188 fn default() -> Self {
189 Self::Float
190 }
191}
192
193#[derive(Clone, Debug)]
194pub struct TextureFormatDesc {
195 pub internal: u32,
196 pub external: u32,
197 pub data_type: u32,
198}
199
200struct AdapterShared {
201 context: AdapterContext,
202 private_caps: PrivateCapabilities,
203 features: wgt::Features,
204 workarounds: Workarounds,
205 shading_language_version: naga::back::glsl::Version,
206 max_texture_size: u32,
207 next_shader_id: AtomicU32,
208 program_cache: Mutex<ProgramCache>,
209}
210
211pub struct Adapter {
212 shared: Arc<AdapterShared>,
213}
214
215pub struct Device {
216 shared: Arc<AdapterShared>,
217 main_vao: glow::VertexArray,
218 #[cfg(all(not(target_arch = "wasm32"), feature = "renderdoc"))]
219 render_doc: crate::auxil::renderdoc::RenderDoc,
220}
221
222pub struct Queue {
223 shared: Arc<AdapterShared>,
224 features: wgt::Features,
225 draw_fbo: glow::Framebuffer,
226 copy_fbo: glow::Framebuffer,
227 shader_clear_program: glow::Program,
230 shader_clear_program_color_uniform_location: glow::UniformLocation,
232 zero_buffer: glow::Buffer,
235 temp_query_results: Vec<u64>,
236 draw_buffer_count: u8,
237 current_index_buffer: Option<glow::Buffer>,
238}
239
240#[derive(Clone, Debug)]
241pub struct Buffer {
242 raw: Option<glow::Buffer>,
243 target: BindTarget,
244 size: wgt::BufferAddress,
245 map_flags: u32,
246 data: Option<Arc<std::sync::Mutex<Vec<u8>>>>,
247}
248
249#[cfg(all(
250 target_arch = "wasm32",
251 feature = "fragile-send-sync-non-atomic-wasm",
252 not(target_feature = "atomics")
253))]
254unsafe impl Sync for Buffer {}
255#[cfg(all(
256 target_arch = "wasm32",
257 feature = "fragile-send-sync-non-atomic-wasm",
258 not(target_feature = "atomics")
259))]
260unsafe impl Send for Buffer {}
261
262#[derive(Clone, Debug)]
263pub enum TextureInner {
264 Renderbuffer {
265 raw: glow::Renderbuffer,
266 },
267 DefaultRenderbuffer,
268 Texture {
269 raw: glow::Texture,
270 target: BindTarget,
271 },
272 #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
273 ExternalFramebuffer {
274 inner: web_sys::WebGlFramebuffer,
275 },
276}
277
278#[cfg(all(
279 target_arch = "wasm32",
280 feature = "fragile-send-sync-non-atomic-wasm",
281 not(target_feature = "atomics")
282))]
283unsafe impl Sync for TextureInner {}
284#[cfg(all(
285 target_arch = "wasm32",
286 feature = "fragile-send-sync-non-atomic-wasm",
287 not(target_feature = "atomics")
288))]
289unsafe impl Send for TextureInner {}
290
291impl TextureInner {
292 fn as_native(&self) -> (glow::Texture, BindTarget) {
293 match *self {
294 Self::Renderbuffer { .. } | Self::DefaultRenderbuffer => {
295 panic!("Unexpected renderbuffer");
296 }
297 Self::Texture { raw, target } => (raw, target),
298 #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
299 Self::ExternalFramebuffer { .. } => panic!("Unexpected external framebuffer"),
300 }
301 }
302}
303
304#[derive(Debug)]
305pub struct Texture {
306 pub inner: TextureInner,
307 pub drop_guard: Option<crate::DropGuard>,
308 pub mip_level_count: u32,
309 pub array_layer_count: u32,
310 pub format: wgt::TextureFormat,
311 #[allow(unused)]
312 pub format_desc: TextureFormatDesc,
313 pub copy_size: CopyExtent,
314 pub is_cubemap: bool,
315}
316
317impl Texture {
318 pub fn default_framebuffer(format: wgt::TextureFormat) -> Self {
319 Self {
320 inner: TextureInner::DefaultRenderbuffer,
321 drop_guard: None,
322 mip_level_count: 1,
323 array_layer_count: 1,
324 format,
325 format_desc: TextureFormatDesc {
326 internal: 0,
327 external: 0,
328 data_type: 0,
329 },
330 copy_size: CopyExtent {
331 width: 0,
332 height: 0,
333 depth: 0,
334 },
335 is_cubemap: false,
336 }
337 }
338
339 fn get_info_from_desc(desc: &TextureDescriptor) -> (u32, bool, bool) {
341 match desc.dimension {
342 wgt::TextureDimension::D1 => (glow::TEXTURE_2D, false, false),
343 wgt::TextureDimension::D2 => {
344 match (desc.is_cube_compatible(), desc.size.depth_or_array_layers) {
346 (false, 1) => (glow::TEXTURE_2D, false, false),
347 (false, _) => (glow::TEXTURE_2D_ARRAY, true, false),
348 (true, 6) => (glow::TEXTURE_CUBE_MAP, false, true),
349 (true, _) => (glow::TEXTURE_CUBE_MAP_ARRAY, true, true),
350 }
351 }
352 wgt::TextureDimension::D3 => (glow::TEXTURE_3D, true, false),
353 }
354 }
355}
356
357#[derive(Clone, Debug)]
358pub struct TextureView {
359 inner: TextureInner,
360 aspects: crate::FormatAspects,
361 mip_levels: Range<u32>,
362 array_layers: Range<u32>,
363 format: wgt::TextureFormat,
364}
365
366#[derive(Debug)]
367pub struct Sampler {
368 raw: glow::Sampler,
369}
370
371pub struct BindGroupLayout {
372 entries: Arc<[wgt::BindGroupLayoutEntry]>,
373}
374
375struct BindGroupLayoutInfo {
376 entries: Arc<[wgt::BindGroupLayoutEntry]>,
377 binding_to_slot: Box<[u8]>,
383}
384
385pub struct PipelineLayout {
386 group_infos: Box<[BindGroupLayoutInfo]>,
387 naga_options: naga::back::glsl::Options,
388}
389
390impl PipelineLayout {
391 fn get_slot(&self, br: &naga::ResourceBinding) -> u8 {
392 let group_info = &self.group_infos[br.group as usize];
393 group_info.binding_to_slot[br.binding as usize]
394 }
395}
396
397#[derive(Debug)]
398enum BindingRegister {
399 UniformBuffers,
400 StorageBuffers,
401 Textures,
402 Images,
403}
404
405#[derive(Debug)]
406enum RawBinding {
407 Buffer {
408 raw: glow::Buffer,
409 offset: i32,
410 size: i32,
411 },
412 Texture {
413 raw: glow::Texture,
414 target: BindTarget,
415 aspects: crate::FormatAspects,
416 },
418 Image(ImageBinding),
419 Sampler(glow::Sampler),
420}
421
422#[derive(Debug)]
423pub struct BindGroup {
424 contents: Box<[RawBinding]>,
425}
426
427type ShaderId = u32;
428
429#[derive(Debug)]
430pub struct ShaderModule {
431 naga: crate::NagaShader,
432 label: Option<String>,
433 id: ShaderId,
434}
435
436#[derive(Clone, Debug, Default)]
437struct VertexFormatDesc {
438 element_count: i32,
439 element_format: u32,
440 attrib_kind: VertexAttribKind,
441}
442
443#[derive(Clone, Debug, Default)]
444struct AttributeDesc {
445 location: u32,
446 offset: u32,
447 buffer_index: u32,
448 format_desc: VertexFormatDesc,
449}
450
451#[derive(Clone, Debug)]
452struct BufferBinding {
453 raw: glow::Buffer,
454 offset: wgt::BufferAddress,
455}
456
457#[derive(Clone, Debug)]
458struct ImageBinding {
459 raw: glow::Texture,
460 mip_level: u32,
461 array_layer: Option<u32>,
462 access: u32,
463 format: u32,
464}
465
466#[derive(Clone, Debug, Default, PartialEq)]
467struct VertexBufferDesc {
468 step: wgt::VertexStepMode,
469 stride: u32,
470}
471
472#[derive(Clone, Debug, Default)]
473struct UniformDesc {
474 location: Option<glow::UniformLocation>,
475 size: u32,
476 utype: u32,
477}
478
479#[cfg(all(
480 target_arch = "wasm32",
481 feature = "fragile-send-sync-non-atomic-wasm",
482 not(target_feature = "atomics")
483))]
484unsafe impl Sync for UniformDesc {}
485#[cfg(all(
486 target_arch = "wasm32",
487 feature = "fragile-send-sync-non-atomic-wasm",
488 not(target_feature = "atomics")
489))]
490unsafe impl Send for UniformDesc {}
491
492type SamplerBindMap = [Option<u8>; MAX_TEXTURE_SLOTS];
495
496struct PipelineInner {
497 program: glow::Program,
498 sampler_map: SamplerBindMap,
499 uniforms: [UniformDesc; MAX_PUSH_CONSTANTS],
500}
501
502#[derive(Clone, Debug)]
503struct DepthState {
504 function: u32,
505 mask: bool,
506}
507
508#[derive(Clone, Debug, PartialEq)]
509struct BlendComponent {
510 src: u32,
511 dst: u32,
512 equation: u32,
513}
514
515#[derive(Clone, Debug, PartialEq)]
516struct BlendDesc {
517 alpha: BlendComponent,
518 color: BlendComponent,
519}
520
521#[derive(Clone, Debug, Default, PartialEq)]
522struct ColorTargetDesc {
523 mask: wgt::ColorWrites,
524 blend: Option<BlendDesc>,
525}
526
527#[derive(PartialEq, Eq, Hash)]
528struct ProgramStage {
529 naga_stage: naga::ShaderStage,
530 shader_id: ShaderId,
531 entry_point: String,
532}
533
534#[derive(PartialEq, Eq, Hash)]
535struct ProgramCacheKey {
536 stages: ArrayVec<ProgramStage, 3>,
537 group_to_binding_to_slot: Box<[Box<[u8]>]>,
538}
539
540type ProgramCache = FastHashMap<ProgramCacheKey, Result<Arc<PipelineInner>, crate::PipelineError>>;
541
542pub struct RenderPipeline {
543 inner: Arc<PipelineInner>,
544 primitive: wgt::PrimitiveState,
545 vertex_buffers: Box<[VertexBufferDesc]>,
546 vertex_attributes: Box<[AttributeDesc]>,
547 color_targets: Box<[ColorTargetDesc]>,
548 depth: Option<DepthState>,
549 depth_bias: wgt::DepthBiasState,
550 stencil: Option<StencilState>,
551 alpha_to_coverage_enabled: bool,
552}
553
554#[cfg(all(
555 target_arch = "wasm32",
556 feature = "fragile-send-sync-non-atomic-wasm",
557 not(target_feature = "atomics")
558))]
559unsafe impl Sync for RenderPipeline {}
560#[cfg(all(
561 target_arch = "wasm32",
562 feature = "fragile-send-sync-non-atomic-wasm",
563 not(target_feature = "atomics")
564))]
565unsafe impl Send for RenderPipeline {}
566
567pub struct ComputePipeline {
568 inner: Arc<PipelineInner>,
569}
570
571#[cfg(all(
572 target_arch = "wasm32",
573 feature = "fragile-send-sync-non-atomic-wasm",
574 not(target_feature = "atomics")
575))]
576unsafe impl Sync for ComputePipeline {}
577#[cfg(all(
578 target_arch = "wasm32",
579 feature = "fragile-send-sync-non-atomic-wasm",
580 not(target_feature = "atomics")
581))]
582unsafe impl Send for ComputePipeline {}
583
584#[derive(Debug)]
585pub struct QuerySet {
586 queries: Box<[glow::Query]>,
587 target: BindTarget,
588}
589
590#[derive(Debug)]
591pub struct Fence {
592 last_completed: crate::FenceValue,
593 pending: Vec<(crate::FenceValue, glow::Fence)>,
594}
595
596#[cfg(any(
597 not(target_arch = "wasm32"),
598 all(
599 feature = "fragile-send-sync-non-atomic-wasm",
600 not(target_feature = "atomics")
601 )
602))]
603unsafe impl Send for Fence {}
604#[cfg(any(
605 not(target_arch = "wasm32"),
606 all(
607 feature = "fragile-send-sync-non-atomic-wasm",
608 not(target_feature = "atomics")
609 )
610))]
611unsafe impl Sync for Fence {}
612
613impl Fence {
614 fn get_latest(&self, gl: &glow::Context) -> crate::FenceValue {
615 let mut max_value = self.last_completed;
616 for &(value, sync) in self.pending.iter() {
617 let status = unsafe { gl.get_sync_status(sync) };
618 if status == glow::SIGNALED {
619 max_value = value;
620 }
621 }
622 max_value
623 }
624
625 fn maintain(&mut self, gl: &glow::Context) {
626 let latest = self.get_latest(gl);
627 for &(value, sync) in self.pending.iter() {
628 if value <= latest {
629 unsafe {
630 gl.delete_sync(sync);
631 }
632 }
633 }
634 self.pending.retain(|&(value, _)| value > latest);
635 self.last_completed = latest;
636 }
637}
638
639#[derive(Clone, Debug, PartialEq)]
640struct StencilOps {
641 pass: u32,
642 fail: u32,
643 depth_fail: u32,
644}
645
646impl Default for StencilOps {
647 fn default() -> Self {
648 Self {
649 pass: glow::KEEP,
650 fail: glow::KEEP,
651 depth_fail: glow::KEEP,
652 }
653 }
654}
655
656#[derive(Clone, Debug, PartialEq)]
657struct StencilSide {
658 function: u32,
659 mask_read: u32,
660 mask_write: u32,
661 reference: u32,
662 ops: StencilOps,
663}
664
665impl Default for StencilSide {
666 fn default() -> Self {
667 Self {
668 function: glow::ALWAYS,
669 mask_read: 0xFF,
670 mask_write: 0xFF,
671 reference: 0,
672 ops: StencilOps::default(),
673 }
674 }
675}
676
677#[derive(Clone, Default)]
678struct StencilState {
679 front: StencilSide,
680 back: StencilSide,
681}
682
683#[derive(Clone, Debug, Default, PartialEq)]
684struct PrimitiveState {
685 front_face: u32,
686 cull_face: u32,
687 unclipped_depth: bool,
688}
689
690type InvalidatedAttachments = ArrayVec<u32, { crate::MAX_COLOR_ATTACHMENTS + 2 }>;
691
692#[derive(Debug)]
693enum Command {
694 Draw {
695 topology: u32,
696 start_vertex: u32,
697 vertex_count: u32,
698 instance_count: u32,
699 },
700 DrawIndexed {
701 topology: u32,
702 index_type: u32,
703 index_count: u32,
704 index_offset: wgt::BufferAddress,
705 base_vertex: i32,
706 instance_count: u32,
707 },
708 DrawIndirect {
709 topology: u32,
710 indirect_buf: glow::Buffer,
711 indirect_offset: wgt::BufferAddress,
712 },
713 DrawIndexedIndirect {
714 topology: u32,
715 index_type: u32,
716 indirect_buf: glow::Buffer,
717 indirect_offset: wgt::BufferAddress,
718 },
719 Dispatch([u32; 3]),
720 DispatchIndirect {
721 indirect_buf: glow::Buffer,
722 indirect_offset: wgt::BufferAddress,
723 },
724 ClearBuffer {
725 dst: Buffer,
726 dst_target: BindTarget,
727 range: crate::MemoryRange,
728 },
729 CopyBufferToBuffer {
730 src: Buffer,
731 src_target: BindTarget,
732 dst: Buffer,
733 dst_target: BindTarget,
734 copy: crate::BufferCopy,
735 },
736 #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
737 CopyExternalImageToTexture {
738 src: wgt::ImageCopyExternalImage,
739 dst: glow::Texture,
740 dst_target: BindTarget,
741 dst_format: wgt::TextureFormat,
742 dst_premultiplication: bool,
743 copy: crate::TextureCopy,
744 },
745 CopyTextureToTexture {
746 src: glow::Texture,
747 src_target: BindTarget,
748 dst: glow::Texture,
749 dst_target: BindTarget,
750 copy: crate::TextureCopy,
751 dst_is_cubemap: bool,
752 },
753 CopyBufferToTexture {
754 src: Buffer,
755 #[allow(unused)]
756 src_target: BindTarget,
757 dst: glow::Texture,
758 dst_target: BindTarget,
759 dst_format: wgt::TextureFormat,
760 copy: crate::BufferTextureCopy,
761 },
762 CopyTextureToBuffer {
763 src: glow::Texture,
764 src_target: BindTarget,
765 src_format: wgt::TextureFormat,
766 dst: Buffer,
767 #[allow(unused)]
768 dst_target: BindTarget,
769 copy: crate::BufferTextureCopy,
770 },
771 SetIndexBuffer(glow::Buffer),
772 BeginQuery(glow::Query, BindTarget),
773 EndQuery(BindTarget),
774 CopyQueryResults {
775 query_range: Range<u32>,
776 dst: Buffer,
777 dst_target: BindTarget,
778 dst_offset: wgt::BufferAddress,
779 },
780 ResetFramebuffer {
781 is_default: bool,
782 },
783 BindAttachment {
784 attachment: u32,
785 view: TextureView,
786 },
787 ResolveAttachment {
788 attachment: u32,
789 dst: TextureView,
790 size: wgt::Extent3d,
791 },
792 InvalidateAttachments(InvalidatedAttachments),
793 SetDrawColorBuffers(u8),
794 ClearColorF {
795 draw_buffer: u32,
796 color: [f32; 4],
797 is_srgb: bool,
798 },
799 ClearColorU(u32, [u32; 4]),
800 ClearColorI(u32, [i32; 4]),
801 ClearDepth(f32),
802 ClearStencil(u32),
803 ClearDepthAndStencil(f32, u32),
808 BufferBarrier(glow::Buffer, crate::BufferUses),
809 TextureBarrier(crate::TextureUses),
810 SetViewport {
811 rect: crate::Rect<i32>,
812 depth: Range<f32>,
813 },
814 SetScissor(crate::Rect<i32>),
815 SetStencilFunc {
816 face: u32,
817 function: u32,
818 reference: u32,
819 read_mask: u32,
820 },
821 SetStencilOps {
822 face: u32,
823 write_mask: u32,
824 ops: StencilOps,
825 },
826 SetDepth(DepthState),
827 SetDepthBias(wgt::DepthBiasState),
828 ConfigureDepthStencil(crate::FormatAspects),
829 SetAlphaToCoverage(bool),
830 SetVertexAttribute {
831 buffer: Option<glow::Buffer>,
832 buffer_desc: VertexBufferDesc,
833 attribute_desc: AttributeDesc,
834 },
835 UnsetVertexAttribute(u32),
836 SetVertexBuffer {
837 index: u32,
838 buffer: BufferBinding,
839 buffer_desc: VertexBufferDesc,
840 },
841 SetProgram(glow::Program),
842 SetPrimitive(PrimitiveState),
843 SetBlendConstant([f32; 4]),
844 SetColorTarget {
845 draw_buffer_index: Option<u32>,
846 desc: ColorTargetDesc,
847 },
848 BindBuffer {
849 target: BindTarget,
850 slot: u32,
851 buffer: glow::Buffer,
852 offset: i32,
853 size: i32,
854 },
855 BindSampler(u32, Option<glow::Sampler>),
856 BindTexture {
857 slot: u32,
858 texture: glow::Texture,
859 target: BindTarget,
860 aspects: crate::FormatAspects,
861 },
862 BindImage {
863 slot: u32,
864 binding: ImageBinding,
865 },
866 InsertDebugMarker(Range<u32>),
867 PushDebugGroup(Range<u32>),
868 PopDebugGroup,
869 SetPushConstants {
870 uniform: UniformDesc,
871 offset: u32,
873 },
874}
875
876#[derive(Default)]
877pub struct CommandBuffer {
878 label: Option<String>,
879 commands: Vec<Command>,
880 data_bytes: Vec<u8>,
881 queries: Vec<glow::Query>,
882}
883
884impl fmt::Debug for CommandBuffer {
885 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
886 let mut builder = f.debug_struct("CommandBuffer");
887 if let Some(ref label) = self.label {
888 builder.field("label", label);
889 }
890 builder.finish()
891 }
892}
893
894pub struct CommandEncoder {
899 cmd_buffer: CommandBuffer,
900 state: command::State,
901 private_caps: PrivateCapabilities,
902}
903
904impl fmt::Debug for CommandEncoder {
905 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
906 f.debug_struct("CommandEncoder")
907 .field("cmd_buffer", &self.cmd_buffer)
908 .finish()
909 }
910}