Repository: pygfx/pygfx (python)

Issue:
dealing with non-contiguous arrays
I got this issue when dealing when setting data for Geometry to create a line using output from sklearn's `PCA.transform()`. It can be fixed using `np.ascontiguousarray(data)` but I'm not sure if this could effect other use cases too. The exception I receive is:

```
ERROR:wgpu:Draw error: memoryview: underlying buffer is not C-contiguous
```

I'll try to get a minimal code example that produces this, I was using some of my own data.


Pull request description:
Buffer and Texture test for c_contiguous
Closes #307

Somewhat relates to #19

Patch:
diff --git a/pygfx/resources/_buffer.py b/pygfx/resources/_buffer.py
index 3de29f0..af91cbc 100644
--- a/pygfx/resources/_buffer.py
+++ b/pygfx/resources/_buffer.py
@@ -201,8 +201,8 @@ class Buffer(Resource):
 
     def _get_subdata(self, offset, size):
         """Return subdata as a contiguous array."""
-        # If this is a full range, this is easy
-        if offset == 0 and size == self.nitems and self.mem.contiguous:
+        # If this is a full range, this is easy (and fast)
+        if offset == 0 and size == self.nitems and self.mem.c_contiguous:
             return self.mem
         # Get a numpy array, because memoryviews do not support nd slicing
         if isinstance(self.data, np.ndarray):
diff --git a/pygfx/resources/_texture.py b/pygfx/resources/_texture.py
index 8dc9ce5..56ca315 100644
--- a/pygfx/resources/_texture.py
+++ b/pygfx/resources/_texture.py
@@ -13,7 +13,7 @@ class Texture(Resource):
     Parameters:
         data : array, optional
             Array data of any type that supports the buffer-protocol, (e.g. a
-            bytes or numpy array). If None, nbytes and nitems must be provided.
+            bytes or numpy array). If None, nbytes and size must be provided.
             The dtype must be compatible with the rendering backend.
         dim : int
             The dimensionality of the array (1, 2 or 3).
@@ -229,8 +229,13 @@ class Texture(Resource):
 
     def _get_subdata(self, offset, size, pixel_padding=None):
         """Return subdata as a contiguous array."""
-        # If this is a full range, this is easy
-        if offset == 0 and size == self.nitems and self.mem.contiguous:
+        # If this is a full range, this is easy (and fast)
+        if (
+            offset == (0, 0, 0)
+            and size == self.size
+            and self.mem.c_contiguous
+            and pixel_padding is None
+        ):
             return self.mem
         # Get a numpy array, because memoryviews do not support nd slicing
         if isinstance(self.data, np.ndarray):


Test patch:
diff --git a/tests/resources/test_buffer.py b/tests/resources/test_buffer.py
index 593ed86..1f84883 100644
--- a/tests/resources/test_buffer.py
+++ b/tests/resources/test_buffer.py
@@ -83,3 +83,22 @@ def test_special_data():
     a = np.zeros((), dtype=[("a", "<i4"), ("b", "<f4")])
     b = gfx.Buffer(a)
     assert b.format is None
+
+
+def test_contiguous():
+    xs = np.linspace(0, 20 * np.pi, 500)
+    ys = np.sin(xs) * 10
+    zs = np.zeros(xs.size)
+
+    # This works, because at upload time the data is copied if necessary
+    positions1 = np.vstack([xs, ys, zs]).astype(np.float32).T
+    buf = gfx.Buffer(positions1)
+    mem = buf._get_subdata(0, buf.nitems)
+    assert mem.c_contiguous
+
+    # This work, and avoids the aforementioned copy
+    positions2 = np.ascontiguousarray(positions1)
+    buf = gfx.Buffer(positions2)
+    mem = buf._get_subdata(0, buf.nitems)
+    assert mem.c_contiguous
+    assert mem is buf.mem
diff --git a/tests/resources/test_texture.py b/tests/resources/test_texture.py
index d298135..0a1ad54 100644
--- a/tests/resources/test_texture.py
+++ b/tests/resources/test_texture.py
@@ -109,3 +109,20 @@ def test_unsupported_shapes():
         a = np.zeros((10, 10, 10, i), np.float32)
         with pytest.raises(ValueError):
             gfx.Texture(a, dim=3)
+
+
+def test_contiguous():
+
+    im1 = np.zeros((100, 100), np.float32)[10:-10, 10:-10]
+
+    # This works, because at upload time the data is copied if necessary
+    tex = gfx.Texture(im1, dim=2)
+    mem = tex._get_subdata((0, 0, 0), im1.shape + (1,))
+    assert mem.c_contiguous
+
+    # This works, and avoids the aforementioned copy
+    im2 = np.ascontiguousarray(im1)
+    tex = gfx.Texture(im2, dim=2)
+    mem = tex._get_subdata((0, 0, 0), im2.shape + (1,))
+    assert mem.c_contiguous
+    assert mem is tex.mem