Bug 1646835 - allow specifying backing data and stride for SWGL default framebuffer. r?jimb draft
authorLee Salzman <lsalzman@mozilla.com>
Tue, 23 Jun 2020 23:39:52 +0000
changeset 2999964 b084cc87f277fe3b21629213363fa03ecf72d03c
parent 2995438 fa015682f65310416d2a40a146a7e8d004dd81d7
child 2999965 ddda93e7a01b106b40e9a7a06ca9d4ae40af0bdb
push id558591
push userreviewbot
push dateTue, 23 Jun 2020 23:40:35 +0000
treeherdertry@98603fceb73a [default view] [failures only]
reviewersjimb
bugs1646835
milestone79.0a1
Bug 1646835 - allow specifying backing data and stride for SWGL default framebuffer. r?jimb Summary: For performance reasons in SWGL software compositors. to avoid unnecessary full-screen copies of the framembuffer, we need to allow those compositors to map their underlying widget surfaces and pass that buffer to SWGL so that they can be directly rendered to. That also requires supporting custom strides, as we can't always enforce the particular layout of the buffers handed off to us. To that end, InitDefaultFramebuffer is generalized to take such information and then many places where we rely on a specific hard-coded SWGL-calculated stride have been altered to deal with a caller-supplied stride. Differential Revision: https://phabricator.services.mozilla.com/D80267 Test Plan: Reviewers: jimb Subscribers: Bug #: 1646835 Differential Diff: PHID-DIFF-lpuuvlesv5ekxvsspzw3
gfx/webrender_bindings/src/swgl_bindings.rs
gfx/wr/swgl/src/gl.cc
gfx/wr/swgl/src/swgl_fns.rs
--- a/gfx/webrender_bindings/src/swgl_bindings.rs
+++ b/gfx/webrender_bindings/src/swgl_bindings.rs
@@ -20,18 +20,19 @@ pub extern "C" fn wr_swgl_destroy_contex
 }
 
 #[no_mangle]
 pub extern "C" fn wr_swgl_make_current(ctx: *mut c_void) {
     swgl::Context::from(ctx).make_current();
 }
 
 #[no_mangle]
-pub extern "C" fn wr_swgl_init_default_framebuffer(ctx: *mut c_void, width: i32, height: i32) {
-    swgl::Context::from(ctx).init_default_framebuffer(width, height);
+pub extern "C" fn wr_swgl_init_default_framebuffer(ctx: *mut c_void, width: i32, height: i32,
+                                                   stride: i32, buf: *mut c_void) {
+    swgl::Context::from(ctx).init_default_framebuffer(width, height, stride, buf);
 }
 
 #[derive(Debug)]
 pub struct SwTile {
     x: i32,
     y: i32,
     fbo_id: u32,
     color_id: u32,
@@ -306,26 +307,28 @@ impl Compositor for SwCompositor {
         if let Some(surface) = self.surfaces.get_mut(&id.surface_id) {
             let texs = self.gl.gen_textures(2);
             let color_id = texs[0];
             self.gl.set_texture_buffer(
                 color_id,
                 gl::RGBA8,
                 surface.tile_size.width,
                 surface.tile_size.height,
+                0,
                 ptr::null_mut(),
                 0,
                 0,
             );
             let depth_id = texs[1];
             self.gl.set_texture_buffer(
                 depth_id,
                 gl::DEPTH_COMPONENT16,
                 surface.tile_size.width,
                 surface.tile_size.height,
+                0,
                 ptr::null_mut(),
                 0,
                 0,
             );
             let fbo_id = self.gl.gen_framebuffers(1)[0];
             self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, fbo_id);
             self.gl
                 .framebuffer_texture_2d(gl::DRAW_FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, color_id, 0);
@@ -425,25 +428,27 @@ impl Compositor for SwCompositor {
                         }
                     }
                 }
                 self.gl.set_texture_buffer(
                     tile.color_id,
                     gl::RGBA8,
                     valid_rect.size.width,
                     valid_rect.size.height,
+                    valid_rect.size.width * 4,
                     buf,
                     surface.tile_size.width,
                     surface.tile_size.height,
                 );
                 self.gl.set_texture_buffer(
                     tile.depth_id,
                     gl::DEPTH_COMPONENT16,
                     valid_rect.size.width,
                     valid_rect.size.height,
+                    0,
                     ptr::null_mut(),
                     surface.tile_size.width,
                     surface.tile_size.height,
                 );
                 surface_info.fbo_id = tile.fbo_id;
                 surface_info.origin -= valid_rect.origin.to_vector();
             }
         }
@@ -458,33 +463,34 @@ impl Compositor for SwCompositor {
         };
 
         let id = self.cur_tile;
         if let Some(surface) = self.surfaces.get_mut(&id.surface_id) {
             if let Some(tile) = surface.tiles.iter().find(|t| t.x == id.x && t.y == id.y) {
                 if tile.valid_rect.is_empty() {
                     return;
                 }
-                let (swbuf, w, _) = self.gl.get_color_buffer(tile.fbo_id, true);
+                let (swbuf, _, _, stride) = self.gl.get_color_buffer(tile.fbo_id, true);
+                assert!(stride % 4 == 0);
                 let buf = if tile.pbo_id != 0 {
                     native_gl.unmap_buffer(gl::PIXEL_UNPACK_BUFFER);
                     0 as *mut c_void
                 } else {
                     swbuf
                 };
                 let dirty = tile.dirty_rect;
                 let src = unsafe {
                     (buf as *mut u32).offset(
-                        (dirty.origin.y - tile.valid_rect.origin.y) as isize * w as isize
+                        (dirty.origin.y - tile.valid_rect.origin.y) as isize * (stride / 4) as isize
                             + (dirty.origin.x - tile.valid_rect.origin.x) as isize,
                     )
                 };
                 native_gl.active_texture(gl::TEXTURE0);
                 native_gl.bind_texture(gl::TEXTURE_2D, tile.tex_id);
-                native_gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, w);
+                native_gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, stride / 4);
                 native_gl.tex_sub_image_2d_pbo(
                     gl::TEXTURE_2D,
                     0,
                     dirty.origin.x,
                     dirty.origin.y,
                     dirty.size.width,
                     dirty.size.height,
                     gl::BGRA,
@@ -529,17 +535,17 @@ impl Compositor for SwCompositor {
         }
         self.frame_surfaces.push((id, position, clip_rect));
     }
 
     fn end_frame(&mut self) {
         if let Some(compositor) = &mut self.compositor {
             compositor.end_frame();
         } else if let Some(native_gl) = &self.native_gl {
-            let (_, fw, fh) = self.gl.get_color_buffer(0, false);
+            let (_, fw, fh, _) = self.gl.get_color_buffer(0, false);
             let viewport = DeviceIntRect::from_size(DeviceIntSize::new(fw, fh));
             let draw_tile = self.draw_tile.as_ref().unwrap();
             draw_tile.enable(&viewport);
             let mut blend = false;
             native_gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
             for &(ref id, position, ref clip_rect) in &self.frame_surfaces {
                 if let Some(surface) = self.surfaces.get(id) {
                     if surface.is_opaque {
--- a/gfx/wr/swgl/src/gl.cc
+++ b/gfx/wr/swgl/src/gl.cc
@@ -221,16 +221,18 @@ TextureFilter gl_filter_to_texture_filte
 
 struct Texture {
   GLenum internal_format = 0;
   int width = 0;
   int height = 0;
   int depth = 0;
   char* buf = nullptr;
   size_t buf_size = 0;
+  uint32_t buf_stride = 0;
+  uint8_t buf_bpp = 0;
   GLenum min_filter = GL_NEAREST;
   GLenum mag_filter = GL_LINEAR;
 
   enum FLAGS {
     SHOULD_FREE = 1 << 1,
   };
   int flags = SHOULD_FREE;
   bool should_free() const { return bool(flags & SHOULD_FREE); }
@@ -270,76 +272,92 @@ struct Texture {
   void disable_delayed_clear() {
     if (cleared_rows) {
       delete[] cleared_rows;
       cleared_rows = nullptr;
       delay_clear = 0;
     }
   }
 
-  int bpp() const { return bytes_for_internal_format(internal_format); }
-
-  size_t stride(int b = 0, int min_width = 0) const {
-    return aligned_stride((b ? b : bpp()) * max(width, min_width));
-  }
-
-  size_t layer_stride(int b = 0, int min_width = 0, int min_height = 0) const {
-    return stride(b ? b : bpp(), min_width) * max(height, min_height);
+  int bpp() const { return buf_bpp; }
+  void set_bpp() { buf_bpp = bytes_for_internal_format(internal_format); }
+
+  size_t stride() const { return buf_stride; }
+  void set_stride() { buf_stride = aligned_stride(buf_bpp * width); }
+
+  // Set an external backing buffer of this texture.
+  void set_buffer(void* new_buf, size_t new_stride) {
+    assert(!should_free());
+    // Ensure that the supplied stride is at least as big as the internally
+    // calculated aligned stride.
+    set_bpp();
+    set_stride();
+    assert(new_stride >= buf_stride);
+
+    buf = (char *)new_buf;
+    buf_size = 0;
+    buf_stride = new_stride;
   }
 
   bool allocate(bool force = false, int min_width = 0, int min_height = 0) {
+    // Check if there is either no buffer currently or if we forced validation
+    // of the buffer size because some dimension might have changed.
     if ((!buf || force) && should_free()) {
-      size_t size = layer_stride(bpp(), min_width, min_height) * max(depth, 1);
+      // Initialize the buffer's BPP and stride, since they may have changed.
+      set_bpp();
+      set_stride();
+      // Compute new size based on the maximum potential stride, rather than
+      // the current stride, to hopefully avoid reallocations when size would
+      // otherwise change too much...
+      size_t max_stride = max(buf_stride, aligned_stride(buf_bpp * min_width));
+      size_t size = max_stride * max(height, min_height) * max(depth, 1);
       if (!buf || size > buf_size) {
         // Allocate with a SIMD register-sized tail of padding at the end so we
         // can safely read or write past the end of the texture with SIMD ops.
         char* new_buf = (char*)realloc(buf, size + sizeof(Float));
         assert(new_buf);
         if (new_buf) {
+          // Successfully reallocated the buffer, so go ahead and set it.
           buf = new_buf;
           buf_size = size;
           return true;
         }
+        // Allocation failed, so ensure we don't leave stale buffer state.
         cleanup();
       }
     }
+    // Nothing changed...
     return false;
   }
 
   void cleanup() {
     if (buf && should_free()) {
       free(buf);
       buf = nullptr;
       buf_size = 0;
+      buf_bpp = 0;
+      buf_stride = 0;
     }
     disable_delayed_clear();
   }
 
   ~Texture() { cleanup(); }
 
   IntRect bounds() const { return IntRect{0, 0, width, height}; }
 
   // Find the valid sampling bounds relative to the requested region
   IntRect sample_bounds(const IntRect& req, bool invertY = false) const {
     IntRect bb = bounds().intersect(req).offset(-req.x0, -req.y0);
     if (invertY) bb.invert_y(req.height());
     return bb;
   }
 
   // Get a pointer for sampling at the given offset
-  char* sample_ptr(int x, int y, int z, int bpp, size_t stride) const {
-    return buf + (height * z + y) * stride + x * bpp;
-  }
-
-  char* sample_ptr(int x, int y, int z, int bpp) const {
-    return sample_ptr(x, y, z, bpp, stride(bpp));
-  }
-
-  char* sample_ptr(int x, int y, int z) const {
-    return sample_ptr(x, y, z, bpp());
+  char* sample_ptr(int x, int y, int z = 0) const {
+    return buf + (height * z + y) * stride() + x * bpp();
   }
 
   // Get a pointer for sampling the requested region and limit to the provided
   // sampling bounds
   char* sample_ptr(const IntRect& req, const IntRect& bounds, int z,
                    bool invertY = false) const {
     // Offset the sample pointer by the clamped bounds
     int x = req.x0 + bounds.x0;
@@ -372,29 +390,29 @@ struct Program {
   ~Program() {
     delete impl;
   }
 };
 
 // for GL defines to fully expand
 #define CONCAT_KEY(prefix, x, y, z, w, ...) prefix##x##y##z##w
 #define BLEND_KEY(...) CONCAT_KEY(BLEND_, __VA_ARGS__, 0, 0)
-#define FOR_EACH_BLEND_KEY(macro)                                              \
-  macro(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE)                  \
-      macro(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, 0, 0)                              \
-          macro(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, 0, 0)                         \
-              macro(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, GL_ZERO, GL_ONE)          \
-                  macro(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA, 0, 0) macro(          \
-                      GL_ZERO, GL_SRC_COLOR, 0, 0) macro(GL_ONE, GL_ONE, 0, 0) \
-                      macro(GL_ONE, GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_ALPHA)    \
-                          macro(GL_ONE, GL_ZERO, 0, 0) macro(                  \
-                              GL_ONE_MINUS_DST_ALPHA, GL_ONE, GL_ZERO, GL_ONE) \
-                              macro(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR, \
-                                    0, 0)                                      \
-                                  macro(GL_ONE, GL_ONE_MINUS_SRC1_COLOR, 0, 0)
+#define FOR_EACH_BLEND_KEY(macro)                             \
+  macro(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE) \
+  macro(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, 0, 0)                 \
+  macro(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, 0, 0)                \
+  macro(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, GL_ZERO, GL_ONE)     \
+  macro(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA, 0, 0)                \
+  macro(GL_ZERO, GL_SRC_COLOR, 0, 0)                          \
+  macro(GL_ONE, GL_ONE, 0, 0)                                 \
+  macro(GL_ONE, GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_ALPHA)       \
+  macro(GL_ONE, GL_ZERO, 0, 0)                                \
+  macro(GL_ONE_MINUS_DST_ALPHA, GL_ONE, GL_ZERO, GL_ONE)      \
+  macro(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR, 0, 0)      \
+  macro(GL_ONE, GL_ONE_MINUS_SRC1_COLOR, 0, 0)
 
 #define DEFINE_BLEND_KEY(...) BLEND_KEY(__VA_ARGS__),
 enum BlendKey : uint8_t {
   BLEND_KEY_NONE = 0,
   FOR_EACH_BLEND_KEY(DEFINE_BLEND_KEY)
 };
 
 const size_t MAX_TEXTURE_UNITS = 16;
@@ -620,19 +638,18 @@ static inline void init_filter(S* s, Tex
   s->filter = gl_filter_to_texture_filter(t.mag_filter);
 }
 
 template <typename S>
 static inline void init_sampler(S* s, Texture& t) {
   prepare_texture(t);
   s->width = t.width;
   s->height = t.height;
-  int bpp = t.bpp();
-  s->stride = t.stride(bpp);
-  if (bpp >= 4) s->stride /= 4;
+  s->stride = t.stride();
+  if (t.bpp() >= 4) s->stride /= 4;
   // Use uint32_t* for easier sampling, but need to cast to uint8_t* for formats
   // with bpp < 4.
   s->buf = (uint32_t*)t.buf;
   s->format = gl_format_to_texture_format(t.internal_format);
 }
 
 template <typename S>
 S* lookup_sampler(S* s, int texture) {
@@ -1279,35 +1296,39 @@ void TexStorage3D(GLenum target, GLint l
     t.depth = depth;
   }
   t.disable_delayed_clear();
   t.allocate(changed);
 }
 
 static void set_tex_storage(Texture& t, GLenum internal_format,
                             GLsizei width, GLsizei height,
-                            bool should_free = true, void* buf = nullptr,
+                            void* buf = nullptr, GLsizei stride = 0,
                             GLsizei min_width = 0, GLsizei min_height = 0) {
   internal_format = remap_internal_format(internal_format);
   bool changed = false;
   if (t.width != width || t.height != height || t.depth != 0 ||
       t.internal_format != internal_format) {
     changed = true;
     t.internal_format = internal_format;
     t.width = width;
     t.height = height;
     t.depth = 0;
   }
-  if (t.should_free() != should_free || buf != nullptr) {
-    if (t.should_free()) {
-      t.cleanup();
-    }
+  // If we are changed from an internally managed buffer to an externally
+  // supplied one or vice versa, ensure that we clean up old buffer state.
+  bool should_free = buf == nullptr;
+  if (t.should_free() != should_free) {
+    changed = true;
+    t.cleanup();
     t.set_should_free(should_free);
-    t.buf = (char*)buf;
-    t.buf_size = 0;
+  }
+  // If now an external buffer, explicitly set it...
+  if (!should_free) {
+    t.set_buffer(buf, stride);
   }
   t.disable_delayed_clear();
   t.allocate(changed, min_width, min_height);
 }
 
 void TexStorage2D(GLenum target, GLint levels, GLenum internal_format,
                   GLsizei width, GLsizei height) {
   assert(levels == 1);
@@ -1384,18 +1405,18 @@ void TexSubImage2D(GLenum target, GLint 
   assert(xoffset + width <= t.width);
   assert(yoffset + height <= t.height);
   assert(ctx->unpack_row_length == 0 || ctx->unpack_row_length >= width);
   GLsizei row_length =
       ctx->unpack_row_length != 0 ? ctx->unpack_row_length : width;
   assert(t.internal_format == internal_format_for_data(format, ty));
   int bpp = t.bpp();
   if (!bpp || !t.buf) return;
-  size_t dest_stride = t.stride(bpp);
-  char* dest = t.sample_ptr(xoffset, yoffset, 0, bpp, dest_stride);
+  size_t dest_stride = t.stride();
+  char* dest = t.sample_ptr(xoffset, yoffset);
   char* src = (char*)data;
   for (int y = 0; y < height; y++) {
     if (t.internal_format == GL_RGBA8 && format != GL_BGRA) {
       copy_bgra8_to_rgba8((uint32_t*)dest, (uint32_t*)src, width);
     } else {
       memcpy(dest, src, width * bpp);
     }
     dest += dest_stride;
@@ -1430,19 +1451,19 @@ void TexSubImage3D(GLenum target, GLint 
     assert(t.internal_format == internal_format_for_data(format, ty));
   }
   int bpp = t.bpp();
   if (!bpp || !t.buf) return;
   char* src = (char*)data;
   assert(xoffset + width <= t.width);
   assert(yoffset + height <= t.height);
   assert(zoffset + depth <= t.depth);
-  size_t dest_stride = t.stride(bpp);
+  size_t dest_stride = t.stride();
   for (int z = 0; z < depth; z++) {
-    char* dest = t.sample_ptr(xoffset, yoffset, zoffset + z, bpp, dest_stride);
+    char* dest = t.sample_ptr(xoffset, yoffset, zoffset + z);
     for (int y = 0; y < height; y++) {
       if (t.internal_format == GL_RGBA8 && format != GL_BGRA) {
         copy_bgra8_to_rgba8((uint32_t*)dest, (uint32_t*)src, width);
       } else {
         memcpy(dest, src, width * bpp);
       }
       dest += dest_stride;
       src += row_length * bpp;
@@ -1792,24 +1813,24 @@ static inline void clear_row(T* buf, siz
 
 template <typename T>
 static void clear_buffer(Texture& t, T value, int layer, IntRect bb,
                          int skip_start = 0, int skip_end = 0) {
   if (!t.buf) return;
   skip_start = max(skip_start, bb.x0);
   skip_end = max(skip_end, skip_start);
   assert(sizeof(T) == t.bpp());
-  size_t stride = t.stride(sizeof(T));
+  size_t stride = t.stride();
   // When clearing multiple full-width rows, collapse them into a single
   // large "row" to avoid redundant setup from clearing each row individually.
   if (bb.width() == t.width && bb.height() > 1 && skip_start >= skip_end) {
     bb.x1 += (stride / sizeof(T)) * (bb.height() - 1);
     bb.y1 = bb.y0 + 1;
   }
-  T* buf = (T*)t.sample_ptr(bb.x0, bb.y0, layer, sizeof(T), stride);
+  T* buf = (T*)t.sample_ptr(bb.x0, bb.y0, layer);
   uint32_t chunk = clear_chunk(value);
   for (int rows = bb.height(); rows > 0; rows--) {
     if (bb.x0 < skip_start) {
       clear_row(buf, skip_start - bb.x0, value, chunk);
     }
     if (skip_end < bb.x1) {
       clear_row(buf + (skip_end - bb.x0), bb.x1 - skip_end, value, chunk);
     }
@@ -1826,17 +1847,17 @@ static inline void clear_buffer(Texture&
 }
 
 template <typename T>
 static inline void force_clear_row(Texture& t, int y, int skip_start = 0,
                                    int skip_end = 0) {
   assert(t.buf != nullptr);
   assert(sizeof(T) == t.bpp());
   assert(skip_start <= skip_end);
-  T* buf = (T*)t.sample_ptr(0, y, 0, sizeof(T));
+  T* buf = (T*)t.sample_ptr(0, y);
   uint32_t chunk = clear_chunk((T)t.clear_val);
   if (skip_start > 0) {
     clear_row<T>(buf, skip_start, t.clear_val, chunk);
   }
   if (skip_end < t.width) {
     clear_row<T>(buf + skip_end, t.width - skip_end, t.clear_val, chunk);
   }
 }
@@ -1909,58 +1930,56 @@ static void prepare_texture(Texture& t, 
         assert(false);
         break;
     }
   }
 }
 
 extern "C" {
 
-void InitDefaultFramebuffer(int width, int height) {
+void InitDefaultFramebuffer(int width, int height, int stride, void* buf) {
   Framebuffer& fb = ctx->framebuffers[0];
   if (!fb.color_attachment) {
     GenTextures(1, &fb.color_attachment);
     fb.layer = 0;
   }
+  // If the dimensions or buffer properties changed, we need to reallocate
+  // the underlying storage for the color buffer texture.
   Texture& colortex = ctx->textures[fb.color_attachment];
-  if (colortex.width != width || colortex.height != height) {
-    colortex.cleanup();
-    set_tex_storage(colortex, GL_RGBA8, width, height);
-  }
+  set_tex_storage(colortex, GL_RGBA8, width, height, buf, stride);
   if (!fb.depth_attachment) {
     GenTextures(1, &fb.depth_attachment);
   }
+  // Ensure dimensions of the depth buffer match the color buffer.
   Texture& depthtex = ctx->textures[fb.depth_attachment];
-  if (depthtex.width != width || depthtex.height != height) {
-    depthtex.cleanup();
-    set_tex_storage(depthtex, GL_DEPTH_COMPONENT16, width, height);
-  }
+  set_tex_storage(depthtex, GL_DEPTH_COMPONENT16, width, height);
 }
 
 void* GetColorBuffer(GLuint fbo, GLboolean flush, int32_t* width,
-                     int32_t* height) {
+                     int32_t* height, int32_t* stride) {
   Framebuffer* fb = ctx->framebuffers.find(fbo);
   if (!fb || !fb->color_attachment) {
     return nullptr;
   }
   Texture& colortex = ctx->textures[fb->color_attachment];
   if (flush) {
     prepare_texture(colortex);
   }
   *width = colortex.width;
   *height = colortex.height;
+  *stride = colortex.stride();
   return colortex.buf ? colortex.sample_ptr(0, 0, fb->layer) : nullptr;
 }
 
 void SetTextureBuffer(GLuint texid, GLenum internal_format, GLsizei width,
-                      GLsizei height, void* buf, GLsizei min_width,
-                      GLsizei min_height) {
+                      GLsizei height, GLsizei stride, void* buf,
+                      GLsizei min_width, GLsizei min_height) {
   Texture& t = ctx->textures[texid];
-  set_tex_storage(t, internal_format, width, height, !buf, buf, min_width,
-                  min_height);
+  set_tex_storage(t, internal_format, width, height, buf, stride,
+                  min_width, min_height);
 }
 
 GLenum CheckFramebufferStatus(GLenum target) {
   Framebuffer* fb = get_framebuffer(target);
   if (!fb || !fb->color_attachment) {
     return GL_FRAMEBUFFER_UNSUPPORTED;
   }
   return GL_FRAMEBUFFER_COMPLETE;
@@ -2056,18 +2075,18 @@ void ReadPixels(GLint x, GLint y, GLsize
   assert(y + height <= t.height);
   if (internal_format_for_data(format, type) != t.internal_format) {
     debugf("mismatched format for read pixels: %x vs %x\n", t.internal_format,
            internal_format_for_data(format, type));
     assert(false);
   }
   int bpp = t.bpp();
   char* dest = (char*)data;
-  size_t src_stride = t.stride(bpp);
-  char* src = t.sample_ptr(x, y, fb->layer, bpp, src_stride);
+  size_t src_stride = t.stride();
+  char* src = t.sample_ptr(x, y, fb->layer);
   for (; height > 0; height--) {
     if (t.internal_format == GL_RGBA8 && format != GL_BGRA) {
       copy_bgra8_to_rgba8((uint32_t*)dest, (uint32_t*)src, width);
     } else {
       memcpy(dest, src, width * bpp);
     }
     dest += width * bpp;
     src += src_stride;
@@ -2101,21 +2120,21 @@ void CopyImageSubData(GLuint srcName, GL
   assert(srcDepth >= 0);
   assert(srcX + srcWidth <= srctex.width);
   assert(srcY + srcHeight <= srctex.height);
   assert(srcZ + srcDepth <= max(srctex.depth, 1));
   assert(dstX + srcWidth <= dsttex.width);
   assert(dstY + srcHeight <= dsttex.height);
   assert(dstZ + srcDepth <= max(dsttex.depth, 1));
   int bpp = srctex.bpp();
-  int src_stride = srctex.stride(bpp);
-  int dest_stride = dsttex.stride(bpp);
+  int src_stride = srctex.stride();
+  int dest_stride = dsttex.stride();
   for (int z = 0; z < srcDepth; z++) {
-    char* dest = dsttex.sample_ptr(dstX, dstY, dstZ + z, bpp, dest_stride);
-    char* src = srctex.sample_ptr(srcX, srcY, srcZ + z, bpp, src_stride);
+    char* dest = dsttex.sample_ptr(dstX, dstY, dstZ + z);
+    char* src = srctex.sample_ptr(srcX, srcY, srcZ + z);
     for (int y = 0; y < srcHeight; y++) {
       memcpy(dest, src, srcWidth * bpp);
       dest += dest_stride;
       src += src_stride;
     }
   }
 }
 
@@ -2911,19 +2930,18 @@ static inline void draw_quad_spans(int n
   // Vertex selection above should result in equal left and right start rows
   assert(l0.y == r0.y);
   // Find the start y, clip to within the clip rect, and round to row center.
   float y = floor(max(l0.y, clipRect.y0) + 0.5f) + 0.5f;
   // Initialize left and right edges from end points and start Y
   Edge left(y, l0, l1, interp_outs[l0i], interp_outs[l1i]);
   Edge right(y, r0, r1, interp_outs[r0i], interp_outs[r1i]);
   // Get pointer to color buffer and depth buffer at current Y
-  P* fbuf = (P*)colortex.sample_ptr(0, int(y), layer, sizeof(P));
-  uint16_t* fdepth =
-    (uint16_t*)depthtex.sample_ptr(0, int(y), 0, sizeof(uint16_t));
+  P* fbuf = (P*)colortex.sample_ptr(0, int(y), layer);
+  uint16_t* fdepth = (uint16_t*)depthtex.sample_ptr(0, int(y));
   // Loop along advancing Ys, rasterizing spans at each row
   float checkY = min(min(l1.y, r1.y), clipRect.y1);
   for (;;) {
     // Check if we maybe passed edge ends or outside clip rect...
     if (y > checkY) {
       // If we're outside the clip rect, we're done.
       if (y > clipRect.y1) break;
       // Helper to find the next non-duplicate vertex that doesn't loop back.
@@ -3095,18 +3113,18 @@ static inline void draw_quad_spans(int n
       }
     }
   next_span:
     // Advance Y and edge interpolants to next row.
     y++;
     left.nextRow();
     right.nextRow();
     // Advance buffers to next row.
-    fbuf += colortex.stride(sizeof(P)) / sizeof(P);
-    fdepth += depthtex.stride(sizeof(uint16_t)) / sizeof(uint16_t);
+    fbuf += colortex.stride() / sizeof(P);
+    fdepth += depthtex.stride() / sizeof(uint16_t);
   }
 }
 
 // Draw perspective-correct spans for a convex quad that has been clipped to
 // the near and far Z planes, possibly producing a clipped convex polygon with
 // more than 4 sides. This assumes the Z value will vary across the spans and
 // requires interpolants to factor in W values. This tends to be slower than
 // the simpler 2D draw_quad_spans above, especially since we can't optimize the
@@ -3203,19 +3221,18 @@ static inline void draw_perspective_span
   // Vertex selection above should result in equal left and right start rows
   assert(l0.y == r0.y);
   // Find the start y, clip to within the clip rect, and round to row center.
   float y = floor(max(l0.y, clipRect.y0) + 0.5f) + 0.5f;
   // Initialize left and right edges from end points and start Y
   Edge left(y, l0, l1, interp_outs[l0i], interp_outs[l1i]);
   Edge right(y, r0, r1, interp_outs[r0i], interp_outs[r1i]);
   // Get pointer to color buffer and depth buffer at current Y
-  P* fbuf = (P*)colortex.sample_ptr(0, int(y), layer, sizeof(P));
-  uint16_t* fdepth =
-    (uint16_t*)depthtex.sample_ptr(0, int(y), 0, sizeof(uint16_t));
+  P* fbuf = (P*)colortex.sample_ptr(0, int(y), layer);
+  uint16_t* fdepth = (uint16_t*)depthtex.sample_ptr(0, int(y));
   // Loop along advancing Ys, rasterizing spans at each row
   float checkY = min(min(l1.y, r1.y), clipRect.y1);
   for (;;) {
     // Check if we maybe passed edge ends or outside clip rect...
     if (y > checkY) {
       // If we're outside the clip rect, we're done.
       if (y > clipRect.y1) break;
       // Check if Y advanced past the end of the left edge
@@ -3314,18 +3331,18 @@ static inline void draw_perspective_span
         draw_span<true, true>(buf, depth, span, packDepth);
       }
     }
     // Advance Y and edge interpolants to next row.
     y++;
     left.nextRow();
     right.nextRow();
     // Advance buffers to next row.
-    fbuf += colortex.stride(sizeof(P)) / sizeof(P);
-    fdepth += depthtex.stride(sizeof(uint16_t)) / sizeof(uint16_t);
+    fbuf += colortex.stride() / sizeof(P);
+    fdepth += depthtex.stride() / sizeof(uint16_t);
   }
 }
 
 // Clip a primitive against both sides of a view-frustum axis, producing
 // intermediate vertexes with interpolated attributes that will no longer
 // intersect the selected axis planes. This assumes the primitive is convex
 // and should produce at most N+2 vertexes for each invocation (only in the
 // worst case where one point falls outside on each of the opposite sides
@@ -3732,18 +3749,18 @@ static void scale_blit(Texture& srctex, 
   if (dstBounds.is_empty()) {
     return;
   }
   // Compute final source bounds from clamped dest sampling bounds
   srcBounds = IntRect(dstBounds)
     .scale(dstWidth, dstHeight, srcWidth, srcHeight);
   // Calculate source and dest pointers from clamped offsets
   int bpp = srctex.bpp();
-  int srcStride = srctex.stride(bpp);
-  int destStride = dsttex.stride(bpp);
+  int srcStride = srctex.stride();
+  int destStride = dsttex.stride();
   char* dest = dsttex.sample_ptr(dstReq, dstBounds, dstZ, invertY);
   char* src = srctex.sample_ptr(srcReq, srcBounds, srcZ);
   // Inverted Y must step downward along dest rows
   if (invertY) {
     destStride = -destStride;
   }
   int span = dstBounds.width();
   int frac = 0;
@@ -3833,17 +3850,17 @@ static void linear_blit(Texture& srctex,
                      float(srcReq.height()) / dstReq.height());
   // Skip to clamped source start
   srcUV += srcDUV * vec2_scalar(dstBounds.x0, dstBounds.y0);
   // Offset source UVs to texel centers and scale by lerp precision
   srcUV = linearQuantize(srcUV + 0.5f, 128);
   srcDUV *= 128.0f;
   // Calculate dest pointer from clamped offsets
   int bpp = dsttex.bpp();
-  int destStride = dsttex.stride(bpp);
+  int destStride = dsttex.stride();
   char* dest = dsttex.sample_ptr(dstReq, dstBounds, dstZ, invertY);
   // Inverted Y must step downward along dest rows
   if (invertY) {
     destStride = -destStride;
   }
   int span = dstBounds.width();
   for (int rows = dstBounds.height(); rows > 0; rows--) {
     switch (bpp) {
@@ -3943,18 +3960,18 @@ void Composite(GLuint srcId, GLint srcX,
   }
   Texture& srctex = ctx->textures[srcId];
   if (!srctex.buf) return;
   prepare_texture(srctex);
   Texture& dsttex = ctx->textures[fb.color_attachment];
   if (!dsttex.buf) return;
   assert(srctex.bpp() == 4);
   const int bpp = 4;
-  size_t src_stride = srctex.stride(bpp);
-  size_t dest_stride = dsttex.stride(bpp);
+  size_t src_stride = srctex.stride();
+  size_t dest_stride = dsttex.stride();
   if (srcY < 0) {
     dstY -= srcY;
     srcHeight += srcY;
     srcY = 0;
   }
   if (dstY < 0) {
     srcY -= dstY;
     srcHeight += dstY;
@@ -3964,18 +3981,18 @@ void Composite(GLuint srcId, GLint srcX,
     srcHeight = srctex.height - srcY;
   }
   if (dstY + srcHeight > dsttex.height) {
     srcHeight = dsttex.height - dstY;
   }
   IntRect skip = {dstX, dstY, dstX + srcWidth, dstY + srcHeight};
   prepare_texture(dsttex, &skip);
   char* dest = dsttex.sample_ptr(dstX, flip ? dsttex.height - 1 - dstY : dstY,
-                                 fb.layer, bpp, dest_stride);
-  char* src = srctex.sample_ptr(srcX, srcY, 0, bpp, src_stride);
+                                 fb.layer);
+  char* src = srctex.sample_ptr(srcX, srcY);
   if (flip) {
     dest_stride = -dest_stride;
   }
   if (opaque) {
     for (int y = 0; y < srcHeight; y++) {
       memcpy(dest, src, srcWidth * bpp);
       dest += dest_stride;
       src += src_stride;
--- a/gfx/wr/swgl/src/swgl_fns.rs
+++ b/gfx/wr/swgl/src/swgl_fns.rs
@@ -248,28 +248,30 @@ extern "C" {
         mask: GLbitfield,
         filter: GLenum,
     );
     fn GetIntegerv(pname: GLenum, params: *mut GLint);
     fn GetBooleanv(pname: GLenum, params: *mut GLboolean);
     fn GetString(name: GLenum) -> *const c_char;
     fn GetStringi(name: GLenum, index: GLuint) -> *const c_char;
     fn GetError() -> GLenum;
-    fn InitDefaultFramebuffer(width: i32, height: i32);
+    fn InitDefaultFramebuffer(width: i32, height: i32, stride: i32, buf: *mut c_void);
     fn GetColorBuffer(
         fbo: GLuint,
         flush: GLboolean,
         width: *mut i32,
         height: *mut i32,
+        stride: *mut i32,
     ) -> *mut c_void;
     fn SetTextureBuffer(
         tex: GLuint,
         internal_format: GLenum,
         width: GLsizei,
         height: GLsizei,
+        stride: GLsizei,
         buf: *mut c_void,
         min_width: GLsizei,
         min_height: GLsizei,
     );
     fn DeleteTexture(n: GLuint);
     fn DeleteRenderbuffer(n: GLuint);
     fn DeleteFramebuffer(n: GLuint);
     fn DeleteBuffer(n: GLuint);
@@ -308,47 +310,50 @@ impl Context {
     }
 
     pub fn make_current(&self) {
         unsafe {
             MakeCurrent(self.0);
         }
     }
 
-    pub fn init_default_framebuffer(&self, width: i32, height: i32) {
+    pub fn init_default_framebuffer(&self, width: i32, height: i32, stride: i32, buf: *mut c_void) {
         unsafe {
-            InitDefaultFramebuffer(width, height);
+            InitDefaultFramebuffer(width, height, stride, buf);
         }
     }
 
-    pub fn get_color_buffer(&self, fbo: GLuint, flush: bool) -> (*mut c_void, i32, i32) {
+    pub fn get_color_buffer(&self, fbo: GLuint, flush: bool) -> (*mut c_void, i32, i32, i32) {
         unsafe {
             let mut width: i32 = 0;
             let mut height: i32 = 0;
-            let data_ptr = GetColorBuffer(fbo, flush as GLboolean, &mut width, &mut height);
-            (data_ptr, width, height)
+            let mut stride: i32 = 0;
+            let data_ptr = GetColorBuffer(fbo, flush as GLboolean, &mut width, &mut height, &mut stride);
+            (data_ptr, width, height, stride)
         }
     }
 
     pub fn set_texture_buffer(
         &self,
         tex: GLuint,
         internal_format: GLenum,
         width: GLsizei,
         height: GLsizei,
+        stride: GLsizei,
         buf: *mut c_void,
         min_width: GLsizei,
         min_height: GLsizei,
     ) {
         unsafe {
             SetTextureBuffer(
                 tex,
                 internal_format,
                 width,
                 height,
+                stride,
                 buf,
                 min_width,
                 min_height,
             );
         }
     }
 
     pub fn composite(