Bug 1620076 - Partial compositing (damage) with EGL_EXT_buffer_age in WebRender r=jgilbert
authorGreg V <greg@unrelenting.technology>
Fri, 22 May 2020 18:15:13 +0000
changeset 531754 90998e8e1bdd4046fb5ff04e622a840dfc531c5c
parent 531753 54e8c686651391619c633efe527c4a2d23333db4
child 531755 bbcc193fe0f0389a417ab4e047dc62b4837bf6b1
push id37442
push userncsoregi@mozilla.com
push dateSat, 23 May 2020 09:21:24 +0000
treeherdermozilla-central@bbcc193fe0f0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjgilbert
bugs1620076
milestone78.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1620076 - Partial compositing (damage) with EGL_EXT_buffer_age in WebRender r=jgilbert EGL with buffer-age requires the application to keep the front buffer fully consistent. This means we have to draw the previous frame's damage as well. (But we don't need to include it in the hint we're sending to the system compositor via SwapBuffersWithDamage.) Differential Revision: https://phabricator.services.mozilla.com/D61062
gfx/gl/GLContextEGL.h
gfx/gl/GLContextProviderEGL.cpp
gfx/gl/GLLibraryEGL.cpp
gfx/gl/GLLibraryEGL.h
gfx/webrender_bindings/RenderCompositor.h
gfx/webrender_bindings/RenderCompositorEGL.cpp
gfx/webrender_bindings/RenderCompositorEGL.h
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/src/bindings.rs
gfx/wr/webrender/src/composite.rs
gfx/wr/webrender/src/render_backend.rs
gfx/wr/webrender/src/renderer.rs
--- a/gfx/gl/GLContextEGL.h
+++ b/gfx/gl/GLContextEGL.h
@@ -76,16 +76,19 @@ class GLContextEGL : public GLContext {
   virtual void GetWSIInfo(nsCString* const out) const override;
 
   // hold a reference to the given surface
   // for the lifetime of this context.
   void HoldSurface(gfxASurface* aSurf);
 
   EGLSurface GetEGLSurface() const { return mSurface; }
 
+  bool HasBufferAge() const;
+  EGLint GetBufferAge() const;
+
   bool BindTex2DOffscreen(GLContext* aOffscreen);
   void UnbindTex2DOffscreen(GLContext* aOffscreen);
   void BindOffscreenFramebuffer();
 
   void Destroy();
 
   static already_AddRefed<GLContextEGL> CreateEGLPBufferOffscreenContext(
       CreateContextFlags flags, const gfx::IntSize& size,
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -564,16 +564,34 @@ void GLContextEGL::GetWSIInfo(nsCString*
   out->Append((const char*)mEgl->fQueryString(nullptr, LOCAL_EGL_EXTENSIONS));
 #endif
 }
 
 // hold a reference to the given surface
 // for the lifetime of this context.
 void GLContextEGL::HoldSurface(gfxASurface* aSurf) { mThebesSurface = aSurf; }
 
+bool GLContextEGL::HasBufferAge() const {
+  return mEgl->IsExtensionSupported(GLLibraryEGL::EXT_buffer_age);
+}
+
+EGLint GLContextEGL::GetBufferAge() const {
+  EGLSurface surface =
+      mSurfaceOverride != EGL_NO_SURFACE ? mSurfaceOverride : mSurface;
+
+  if (surface && HasBufferAge()) {
+    EGLint result;
+    mEgl->fQuerySurface(mEgl->Display(), surface, LOCAL_EGL_BUFFER_AGE_EXT,
+                        &result);
+    return result;
+  }
+
+  return 0;
+}
+
 #define LOCAL_EGL_CONTEXT_PROVOKING_VERTEX_DONT_CARE_MOZ 0x6000
 
 already_AddRefed<GLContextEGL> GLContextEGL::CreateGLContext(
     GLLibraryEGL* const egl, CreateContextFlags flags, const SurfaceCaps& caps,
     bool isOffscreen, EGLConfig config, EGLSurface surface, const bool useGles,
     nsACString* const out_failureId) {
   std::vector<EGLint> required_attribs;
 
--- a/gfx/gl/GLLibraryEGL.cpp
+++ b/gfx/gl/GLLibraryEGL.cpp
@@ -71,17 +71,18 @@ static const char* sEGLExtensionNames[] 
     "EGL_NV_stream_consumer_gltexture_yuv",
     "EGL_ANGLE_stream_producer_d3d_texture",
     "EGL_ANGLE_device_creation",
     "EGL_ANGLE_device_creation_d3d11",
     "EGL_KHR_surfaceless_context",
     "EGL_KHR_create_context_no_error",
     "EGL_MOZ_create_context_provoking_vertex_dont_care",
     "EGL_EXT_swap_buffers_with_damage",
-    "EGL_KHR_swap_buffers_with_damage"};
+    "EGL_KHR_swap_buffers_with_damage",
+    "EGL_EXT_buffer_age"};
 
 PRLibrary* LoadApitraceLibrary() {
   const char* path = nullptr;
 
 #ifdef ANDROID
   // We only need to explicitly dlopen egltrace
   // on android as we can use LD_PRELOAD or other tricks
   // on other platforms. We look for it in /data/local
--- a/gfx/gl/GLLibraryEGL.h
+++ b/gfx/gl/GLLibraryEGL.h
@@ -86,16 +86,17 @@ class GLLibraryEGL final {
     ANGLE_stream_producer_d3d_texture,
     ANGLE_device_creation,
     ANGLE_device_creation_d3d11,
     KHR_surfaceless_context,
     KHR_create_context_no_error,
     MOZ_create_context_provoking_vertex_dont_care,
     EXT_swap_buffers_with_damage,
     KHR_swap_buffers_with_damage,
+    EXT_buffer_age,
     Extensions_Max
   };
 
   bool IsExtensionSupported(EGLExtensions aKnownExtension) const {
     return mAvailableExtensions[aKnownExtension];
   }
 
   void MarkExtensionUnsupported(EGLExtensions aKnownExtension) {
--- a/gfx/webrender_bindings/RenderCompositor.h
+++ b/gfx/webrender_bindings/RenderCompositor.h
@@ -102,16 +102,17 @@ class RenderCompositor {
   virtual void EnableNativeCompositor(bool aEnable) {}
   virtual void DeInit() {}
   virtual CompositorCapabilities GetCompositorCapabilities() = 0;
 
   // Interface for partial present
   virtual bool UsePartialPresent() { return false; }
   virtual bool RequestFullRender() { return false; }
   virtual uint32_t GetMaxPartialPresentRects() { return 0; }
+  virtual bool ShouldDrawPreviousPartialPresentRegions() { return false; }
 
   // Whether the surface origin is top-left.
   virtual bool SurfaceOriginIsTopLeft() { return false; }
 
   // Does readback if wr_renderer_readback() could not get correct WR rendered
   // result. It could happen when WebRender renders to multiple overlay layers.
   virtual bool MaybeReadback(const gfx::IntSize& aReadbackSize,
                              const wr::ImageFormat& aReadbackFormat,
--- a/gfx/webrender_bindings/RenderCompositorEGL.cpp
+++ b/gfx/webrender_bindings/RenderCompositorEGL.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "RenderCompositorEGL.h"
 
 #include "GLContext.h"
 #include "GLContextEGL.h"
 #include "GLContextProvider.h"
 #include "GLLibraryEGL.h"
+#include "mozilla/gfx/gfxVars.h"
 #include "mozilla/webrender/RenderThread.h"
 #include "mozilla/widget/CompositorWidget.h"
 
 #ifdef MOZ_WAYLAND
 #  include "mozilla/widget/GtkCompositorWidget.h"
 #  include <gdk/gdk.h>
 #  include <gdk/gdkx.h>
 #endif
@@ -81,31 +82,44 @@ bool RenderCompositorEGL::BeginFrame() {
     return false;
   }
 
 #ifdef MOZ_WIDGET_ANDROID
   java::GeckoSurfaceTexture::DestroyUnused((int64_t)gl());
   gl()->MakeCurrent();  // DestroyUnused can change the current context!
 #endif
 
+  // sets 0 if buffer_age is not supported
+  mBufferAge = gl::GLContextEGL::Cast(gl())->GetBufferAge();
+
   return true;
 }
 
 RenderedFrameId RenderCompositorEGL::EndFrame(
     const nsTArray<DeviceIntRect>& aDirtyRects) {
   RenderedFrameId frameId = GetNextRenderFrameId();
-  if (mEGLSurface != EGL_NO_SURFACE) {
-    gl()->SwapBuffers();
+  if (mEGLSurface != EGL_NO_SURFACE && aDirtyRects.Length() > 0) {
+    gfx::IntRegion bufferInvalid;
+    for (const DeviceIntRect& rect : aDirtyRects) {
+      const auto width = std::min(rect.size.width, GetBufferSize().width);
+      const auto height = std::min(rect.size.height, GetBufferSize().height);
+      const auto left =
+          std::max(0, std::min(rect.origin.x, GetBufferSize().width));
+      const auto bottom =
+          std::max(0, std::min(rect.origin.y + height, GetBufferSize().height));
+      bufferInvalid.OrWith(
+          gfx::IntRect(left, (GetBufferSize().height - bottom), width, height));
+    }
+    gl()->SetDamage(bufferInvalid);
   }
+  gl()->SwapBuffers();
   return frameId;
 }
 
-void RenderCompositorEGL::Pause() {
-  DestroyEGLSurface();
-}
+void RenderCompositorEGL::Pause() { DestroyEGLSurface(); }
 
 bool RenderCompositorEGL::Resume() {
 #ifdef MOZ_WIDGET_ANDROID
   // Destroy EGLSurface if it exists.
   DestroyEGLSurface();
   mEGLSurface = CreateEGLSurface();
   gl::GLContextEGL::Cast(gl())->SetEGLSurfaceOverride(mEGLSurface);
 
@@ -174,9 +188,23 @@ LayoutDeviceIntSize RenderCompositorEGL:
 CompositorCapabilities RenderCompositorEGL::GetCompositorCapabilities() {
   CompositorCapabilities caps;
 
   caps.virtual_surface_size = 0;
 
   return caps;
 }
 
+bool RenderCompositorEGL::UsePartialPresent() {
+  return gfx::gfxVars::WebRenderMaxPartialPresentRects() > 0;
+}
+
+bool RenderCompositorEGL::RequestFullRender() { return mBufferAge != 2; }
+
+uint32_t RenderCompositorEGL::GetMaxPartialPresentRects() {
+  return gfx::gfxVars::WebRenderMaxPartialPresentRects();
+}
+
+bool RenderCompositorEGL::ShouldDrawPreviousPartialPresentRegions() {
+  return gl::GLContextEGL::Cast(gl())->HasBufferAge();
+}
+
 }  // namespace mozilla::wr
--- a/gfx/webrender_bindings/RenderCompositorEGL.h
+++ b/gfx/webrender_bindings/RenderCompositorEGL.h
@@ -32,24 +32,32 @@ class RenderCompositorEGL : public Rende
   bool MakeCurrent() override;
 
   bool UseANGLE() const override { return false; }
 
   LayoutDeviceIntSize GetBufferSize() override;
 
   CompositorCapabilities GetCompositorCapabilities() override;
 
+  // Interface for partial present
+  bool UsePartialPresent() override;
+  bool RequestFullRender() override;
+  uint32_t GetMaxPartialPresentRects() override;
+  bool ShouldDrawPreviousPartialPresentRegions() override;
+
  protected:
   EGLSurface CreateEGLSurface();
 
   void DestroyEGLSurface();
 
   EGLSurface mEGLSurface;
 #ifdef MOZ_WIDGET_ANDROID
   // On android we must track our own surface size.
   LayoutDeviceIntSize mEGLSurfaceSize;
 #endif
+
+  EGLint mBufferAge;
 };
 
 }  // namespace wr
 }  // namespace mozilla
 
 #endif  // MOZILLA_GFX_RENDERCOMPOSITOR_EGL_H
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -111,18 +111,19 @@ class NewRenderer : public RendererEvent
                 ? aRenderThread.GetShaders()->RawShaders()
                 : nullptr,
             aRenderThread.ThreadPool().Raw(),
             aRenderThread.ThreadPoolLP().Raw(), &WebRenderMallocSizeOf,
             &WebRenderMallocEnclosingSizeOf, 0,
             compositor->ShouldUseNativeCompositor() ? compositor.get()
                                                     : nullptr,
             compositor->GetMaxUpdateRects(),
-            compositor->GetMaxPartialPresentRects(), mDocHandle, &wrRenderer,
-            mMaxTextureSize,
+            compositor->GetMaxPartialPresentRects(),
+            compositor->ShouldDrawPreviousPartialPresentRegions(), mDocHandle,
+            &wrRenderer, mMaxTextureSize,
             StaticPrefs::gfx_webrender_enable_gpu_markers_AtStartup(),
             panic_on_gl_error)) {
       // wr_window_new puts a message into gfxCriticalNote if it returns false
       if (swCtx) {
         wr_swgl_destroy_context(swCtx);
       }
       return;
     }
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1313,16 +1313,17 @@ pub extern "C" fn wr_window_new(
     thread_pool: *mut WrThreadPool,
     thread_pool_low_priority: *mut WrThreadPool,
     size_of_op: VoidPtrToSizeFn,
     enclosing_size_of_op: VoidPtrToSizeFn,
     document_id: u32,
     compositor: *mut c_void,
     max_update_rects: usize,
     max_partial_present_rects: usize,
+    draw_previous_partial_present_regions: bool,
     out_handle: &mut *mut DocumentHandle,
     out_renderer: &mut *mut Renderer,
     out_max_texture_size: *mut i32,
     enable_gpu_markers: bool,
     panic_on_gl_error: bool,
 ) -> bool {
     assert!(unsafe { is_in_render_thread() });
 
@@ -1391,16 +1392,17 @@ pub extern "C" fn wr_window_new(
     } else if compositor != ptr::null_mut() {
         CompositorConfig::Native {
             max_update_rects,
             compositor: Box::new(WrCompositor(compositor)),
         }
     } else {
         CompositorConfig::Draw {
             max_partial_present_rects,
+            draw_previous_partial_present_regions,
         }
     };
 
     let opts = RendererOptions {
         enable_aa: true,
         force_subpixel_aa: false,
         enable_subpixel_aa: cfg!(not(target_os = "android")),
         support_low_priority_transactions,
--- a/gfx/wr/webrender/src/composite.rs
+++ b/gfx/wr/webrender/src/composite.rs
@@ -181,16 +181,20 @@ pub struct ResolvedExternalSurface {
 pub enum CompositorConfig {
     /// Let WR draw tiles via normal batching. This requires no special OS support.
     Draw {
         /// If this is zero, a full screen present occurs at the end of the
         /// frame. This is the simplest and default mode. If this is non-zero,
         /// then the operating system supports a form of 'partial present' where
         /// only dirty regions of the framebuffer need to be updated.
         max_partial_present_rects: usize,
+        /// If this is true, WR would draw the previous frame's dirty region when
+        /// doing a partial present. This is used for EGL which requires the front
+        /// buffer to always be fully consistent.
+        draw_previous_partial_present_regions: bool,
     },
     /// Use a native OS compositor to draw tiles. This requires clients to implement
     /// the Compositor trait, but can be significantly more power efficient on operating
     /// systems that support it.
     Native {
         /// The maximum number of dirty rects that can be provided per compositor
         /// surface update. If this is zero, the entire compositor surface for
         /// a given tile will be drawn if it's dirty.
@@ -213,46 +217,50 @@ impl CompositorConfig {
     }
 }
 
 impl Default for CompositorConfig {
     /// Default compositor config is full present without partial present.
     fn default() -> Self {
         CompositorConfig::Draw {
             max_partial_present_rects: 0,
+            draw_previous_partial_present_regions: false,
         }
     }
 }
 
 /// This is a representation of `CompositorConfig` without the `Compositor` trait
 /// present. This allows it to be freely copied to other threads, such as the render
 /// backend where the frame builder can access it.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum CompositorKind {
     /// WR handles compositing via drawing.
     Draw {
         /// Partial present support.
         max_partial_present_rects: usize,
+        /// Draw previous regions when doing partial present.
+        draw_previous_partial_present_regions: bool,
     },
     /// Native OS compositor.
     Native {
         /// Maximum dirty rects per compositor surface.
         max_update_rects: usize,
         /// The virtual surface size used by underlying platform.
         virtual_surface_size: i32,
     },
 }
 
 impl Default for CompositorKind {
     /// Default compositor config is full present without partial present.
     fn default() -> Self {
         CompositorKind::Draw {
             max_partial_present_rects: 0,
+            draw_previous_partial_present_regions: false,
         }
     }
 }
 
 impl CompositorKind {
     pub fn get_virtual_surface_size(&self) -> i32 {
         match self {
             CompositorKind::Draw { .. } => 0,
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -1483,17 +1483,18 @@ impl RenderBackend {
         let build_frame = (render_frame && !doc.frame_is_valid && doc.has_pixels()) ||
             (requires_frame_build && doc.can_render());
 
         // Request composite is true when we want to composite frame even when
         // there is no frame update. This happens when video frame is updated under
         // external image with NativeTexture or when platform requested to composite frame.
         if invalidate_rendered_frame {
             doc.rendered_frame_is_valid = false;
-            if let CompositorKind::Draw { max_partial_present_rects } = doc.scene.config.compositor_kind {
+            if let CompositorKind::Draw { max_partial_present_rects, .. } = doc.scene.config.compositor_kind {
+
               // When partial present is enabled, we need to force redraw.
               if max_partial_present_rects > 0 {
                   let msg = ResultMsg::ForceRedraw;
                   self.result_tx.send(msg).unwrap();
               }
             }
         }
 
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -2109,16 +2109,21 @@ pub struct Renderer {
     allocated_native_surfaces: FastHashSet<NativeSurfaceId>,
 
     /// If true, partial present state has been reset and everything needs to
     /// be drawn on the next render.
     force_redraw: bool,
 
     /// State related to the debug / profiling overlays
     debug_overlay_state: DebugOverlayState,
+
+    /// The dirty rectangle from the previous frame, used on platforms that
+    /// require keeping the front buffer fully correct when doing
+    /// partial present (e.g. unix desktop with EGL_EXT_buffer_age).
+    prev_dirty_rect: DeviceRect,
 }
 
 #[derive(Debug)]
 pub enum RendererError {
     Shader(ShaderError),
     Thread(std::io::Error),
     Resource(ResourceCacheError),
     MaxTextureSize,
@@ -2386,18 +2391,18 @@ impl Renderer {
         let prefer_subpixel_aa = options.force_subpixel_aa || (options.enable_subpixel_aa && use_dual_source_blending);
         let default_font_render_mode = match (options.enable_aa, prefer_subpixel_aa) {
             (true, true) => FontRenderMode::Subpixel,
             (true, false) => FontRenderMode::Alpha,
             (false, _) => FontRenderMode::Mono,
         };
 
         let compositor_kind = match options.compositor_config {
-            CompositorConfig::Draw { max_partial_present_rects } => {
-                CompositorKind::Draw { max_partial_present_rects }
+            CompositorConfig::Draw { max_partial_present_rects, draw_previous_partial_present_regions } => {
+                CompositorKind::Draw { max_partial_present_rects, draw_previous_partial_present_regions }
             }
             CompositorConfig::Native { ref compositor, max_update_rects, .. } => {
                 let capabilities = compositor.get_capabilities();
 
                 CompositorKind::Native {
                     max_update_rects,
                     virtual_surface_size: capabilities.virtual_surface_size,
                 }
@@ -2663,16 +2668,17 @@ impl Renderer {
             cursor_position: DeviceIntPoint::zero(),
             shared_texture_cache_cleared: false,
             documents_seen: FastHashSet::default(),
             force_redraw: true,
             compositor_config: options.compositor_config,
             current_compositor_kind: compositor_kind,
             allocated_native_surfaces: FastHashSet::default(),
             debug_overlay_state: DebugOverlayState::new(),
+            prev_dirty_rect: DeviceRect::zero(),
         };
 
         // We initially set the flags to default and then now call set_debug_flags
         // to ensure any potential transition when enabling a flag is run.
         renderer.set_debug_flags(debug_flags);
 
         let sender = RenderApiSender::new(api_tx, blob_image_handler, font_instances);
         Ok((renderer, sender))
@@ -4871,16 +4877,17 @@ impl Renderer {
     fn composite_simple(
         &mut self,
         composite_state: &CompositeState,
         clear_framebuffer: bool,
         draw_target: DrawTarget,
         projection: &default::Transform3D<f32>,
         results: &mut RenderResults,
         max_partial_present_rects: usize,
+        draw_previous_partial_present_regions: bool,
     ) {
         let _gm = self.gpu_profile.start_marker("framebuffer");
         let _timer = self.gpu_profile.start_timer(GPU_TAG_COMPOSITE);
 
         self.device.bind_draw_target(draw_target);
         self.device.enable_depth();
         self.device.enable_depth_write();
 
@@ -4904,27 +4911,40 @@ impl Renderer {
                 let combined_dirty_rect = combined_dirty_rect.round();
                 let combined_dirty_rect_i32 = combined_dirty_rect.to_i32();
                 // If nothing has changed, don't return any dirty rects at all (the client
                 // can use this as a signal to skip present completely).
                 if !combined_dirty_rect.is_empty() {
                     results.dirty_rects.push(combined_dirty_rect_i32);
                 }
 
+                // If the implementation requires manually keeping the buffer consistent,
+                // combine the previous frame's damage for tile clipping.
+                // (Not for the returned region though, that should be from this frame only)
                 partial_present_mode = Some(PartialPresentMode::Single {
-                    dirty_rect: combined_dirty_rect,
+                    dirty_rect: if draw_previous_partial_present_regions {
+                        combined_dirty_rect.union(&self.prev_dirty_rect)
+                    } else { combined_dirty_rect },
                 });
+
+                if draw_previous_partial_present_regions {
+                    self.prev_dirty_rect = combined_dirty_rect;
+                }
             } else {
                 // If we don't have a valid partial present scenario, return a single
                 // dirty rect to the client that covers the entire framebuffer.
                 let fb_rect = DeviceIntRect::new(
                     DeviceIntPoint::zero(),
                     draw_target.dimensions(),
                 );
                 results.dirty_rects.push(fb_rect);
+
+                if draw_previous_partial_present_regions {
+                    self.prev_dirty_rect = fb_rect.to_f32();
+                }
             }
 
             self.force_redraw = false;
         }
 
         // Clear the framebuffer, if required
         if clear_framebuffer {
             let clear_color = self.clear_color.map(|color| color.to_array());
@@ -5870,24 +5890,25 @@ impl Renderer {
                                 CompositorKind::Native { .. } => {
                                     self.update_external_native_surfaces(
                                         &frame.composite_state.external_surfaces,
                                         results,
                                     );
                                     let compositor = self.compositor_config.compositor().unwrap();
                                     frame.composite_state.composite_native(&mut **compositor);
                                 }
-                                CompositorKind::Draw { max_partial_present_rects, .. } => {
+                                CompositorKind::Draw { max_partial_present_rects, draw_previous_partial_present_regions, .. } => {
                                     self.composite_simple(
                                         &frame.composite_state,
                                         clear_framebuffer,
                                         draw_target,
                                         &projection,
                                         results,
                                         max_partial_present_rects,
+                                        draw_previous_partial_present_regions,
                                     );
                                 }
                             }
                         } else {
                             if clear_framebuffer {
                                 let clear_color = self.clear_color.map(|color| color.to_array());
                                 self.device.bind_draw_target(draw_target);
                                 self.device.enable_depth_write();