diff --git a/src/nouveau/nil/cbindgen.toml b/src/nouveau/nil/cbindgen.toml
index 3556bb872edf052697b5bd6938056a4874504651..8e5eb5c2bf195b0c29316434c9b3788e09b80296 100644
--- a/src/nouveau/nil/cbindgen.toml
+++ b/src/nouveau/nil/cbindgen.toml
@@ -23,6 +23,7 @@ renaming_overrides_prefixing = true
 
 # This is annoying. rename_types doesn't seem to work
 "Format" = "nil_format"
+"GOBType" = "nil_gob_type"
 "Image" = "nil_image"
 "ImageDim" = "nil_image_dim"
 "ImageInitInfo" = "nil_image_init_info"
diff --git a/src/nouveau/nil/extent.rs b/src/nouveau/nil/extent.rs
index b29cfd8f288aee35ec61fddd0b22364f776c1ccb..91a8ca2b6483c5edff186675b115fd42d6c2b9be 100644
--- a/src/nouveau/nil/extent.rs
+++ b/src/nouveau/nil/extent.rs
@@ -3,7 +3,7 @@
 
 use crate::format::Format;
 use crate::image::SampleLayout;
-use crate::tiling::{gob_height, Tiling, GOB_DEPTH, GOB_WIDTH_B};
+use crate::tiling::{GOBType, Tiling};
 use crate::Minify;
 
 pub mod units {
@@ -174,16 +174,8 @@ impl Extent4D<units::Bytes> {
         u64::from(self.width) * u64::from(self.height) * u64::from(self.depth)
     }
 
-    pub fn to_GOB(self, gob_height_is_8: bool) -> Extent4D<units::GOBs> {
-        let gob_extent_B = Extent4D {
-            width: GOB_WIDTH_B,
-            height: gob_height(gob_height_is_8),
-            depth: GOB_DEPTH,
-            array_len: 1,
-            phantom: std::marker::PhantomData,
-        };
-
-        self.div_ceil(gob_extent_B)
+    pub fn to_GOB(self, gob_type: GOBType) -> Extent4D<units::GOBs> {
+        self.div_ceil(gob_type.extent_B())
     }
 }
 
diff --git a/src/nouveau/nil/image.rs b/src/nouveau/nil/image.rs
index e124616e72182ad93c2f77e80e4d52206fd4b679..d9ddfbaf8a186fcc8e2d1dd6deda0ceb728ce308 100644
--- a/src/nouveau/nil/image.rs
+++ b/src/nouveau/nil/image.rs
@@ -199,7 +199,7 @@ impl Image {
             // the size of a miplevel, we don't care about arrays.
             lvl_ext_B.array_len = 1;
 
-            if tiling.is_tiled {
+            if tiling.is_tiled() {
                 let lvl_tiling = tiling.clamp(lvl_ext_B);
 
                 if tiling != lvl_tiling {
@@ -275,7 +275,7 @@ impl Image {
             image.align_B = std::cmp::max(image.align_B, 1 << 16);
         }
 
-        if image.levels[0].tiling.is_tiled {
+        if image.levels[0].tiling.is_tiled() {
             image.pte_kind = Self::choose_pte_kind(
                 dev,
                 info.format,
@@ -290,7 +290,7 @@ impl Image {
             }
         }
 
-        if image.levels[0].tiling.is_tiled {
+        if image.levels[0].tiling.is_tiled() {
             image.tile_mode = u16::from(image.levels[0].tiling.y_log2) << 4
                 | u16::from(image.levels[0].tiling.z_log2) << 8;
 
@@ -386,7 +386,7 @@ impl Image {
         let lvl_ext_B = self.level_extent_B(level);
         let level = &self.levels[level as usize];
 
-        if level.tiling.is_tiled {
+        if level.tiling.is_tiled() {
             let lvl_tiling_ext_B = level.tiling.extent_B();
             let mut lvl_ext_B = lvl_ext_B.align(&lvl_tiling_ext_B);
 
@@ -527,7 +527,7 @@ impl Image {
         let lvl0 = &image_2d_out.levels[0];
 
         assert!(image_2d_out.num_levels == 1);
-        assert!(!lvl0.tiling.is_tiled || lvl0.tiling.z_log2 == 0);
+        assert!(!lvl0.tiling.is_tiled() || lvl0.tiling.z_log2 == 0);
 
         let lvl_tiling_ext_B = lvl0.tiling.extent_B();
         let lvl_ext_B = image_2d_out.level_extent_B(0);
diff --git a/src/nouveau/nil/modifiers.rs b/src/nouveau/nil/modifiers.rs
index 4e1b6f289380f6b99d2ada17cb8a004faa41458d..c6b1843c431d34753d1d39637d51808a7ec8223b 100644
--- a/src/nouveau/nil/modifiers.rs
+++ b/src/nouveau/nil/modifiers.rs
@@ -3,7 +3,7 @@
 
 use crate::format::Format;
 use crate::image::Image;
-use crate::tiling::Tiling;
+use crate::tiling::{GOBType, Tiling};
 
 use bitview::*;
 use nvidia_headers::classes::{cl9097, clc597};
@@ -170,9 +170,9 @@ impl BlockLinearModifier {
     }
 
     pub fn tiling(&self) -> Tiling {
+        assert!(self.gob_kind_version() != GOBKindVersion::G80);
         Tiling {
-            is_tiled: true,
-            gob_height_is_8: self.gob_kind_version() != GOBKindVersion::G80,
+            gob_type: GOBType::Fermi8,
             x_log2: 0,
             y_log2: self.height_log2(),
             z_log2: 0,
diff --git a/src/nouveau/nil/tic.rs b/src/nouveau/nil/tic.rs
index 7ea5d76b8aeaf31f798a0709a17e3b10f1372acc..c594f51edb800fbb2e0f323fc33168a5c8e26637 100644
--- a/src/nouveau/nil/tic.rs
+++ b/src/nouveau/nil/tic.rs
@@ -25,6 +25,7 @@ use crate::image::ImageDim;
 use crate::image::SampleLayout;
 use crate::image::View;
 use crate::image::ViewType;
+use crate::tiling::GOBType;
 
 macro_rules! set_enum {
     ($th:expr, $cls:ident, $field:ident, $enum:ident) => {
@@ -269,10 +270,10 @@ fn nv9097_fill_tic(
 
     let tiling = &image.levels[0].tiling;
 
-    if tiling.is_tiled {
+    if tiling.is_tiled() {
         set_enum!(th, cl9097, TEXHEADV2_MEMORY_LAYOUT, BLOCKLINEAR);
 
-        assert!(tiling.gob_height_is_8);
+        assert!(tiling.gob_type == GOBType::Fermi8);
         assert!(tiling.x_log2 == 0);
         set_enum!(th, cl9097, TEXHEADV2_GOBS_PER_BLOCK_WIDTH, ONE_GOB);
         th.set_field(cl9097::TEXHEADV2_GOBS_PER_BLOCK_HEIGHT, tiling.y_log2);
@@ -386,7 +387,7 @@ fn nvb097_fill_tic(
             u64::from(view.base_array_layer) * u64::from(image.array_stride_B);
     }
 
-    if tiling.is_tiled {
+    if tiling.is_tiled() {
         set_enum!(th, clb097, TEXHEAD_BL_HEADER_VERSION, SELECT_BLOCKLINEAR);
 
         let addr = BitView::new(&layer_address);
@@ -401,7 +402,7 @@ fn nvb097_fill_tic(
         );
         assert!(addr.get_bit_range_u64(48..64) == 0);
 
-        assert!(tiling.gob_height_is_8);
+        assert!(tiling.gob_type == GOBType::Fermi8);
 
         set_enum!(th, clb097, TEXHEAD_BL_GOBS_PER_BLOCK_WIDTH, ONE_GOB);
         th.set_field(clb097::TEXHEAD_BL_GOBS_PER_BLOCK_HEIGHT, tiling.y_log2);
diff --git a/src/nouveau/nil/tiling.rs b/src/nouveau/nil/tiling.rs
index a5198982ce625e3a9523b15666369e856f16ce34..8e2e300bb9705a7eb19304c1283908c5f3261157 100644
--- a/src/nouveau/nil/tiling.rs
+++ b/src/nouveau/nil/tiling.rs
@@ -9,23 +9,33 @@ use crate::image::{
 };
 use crate::ILog2Ceil;
 
-pub const GOB_WIDTH_B: u32 = 64;
-pub const GOB_DEPTH: u32 = 1;
-
-pub fn gob_height(gob_height_is_8: bool) -> u32 {
-    if gob_height_is_8 {
-        8
-    } else {
-        4
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+pub enum GOBType {
+    #[default]
+    Linear,
+    Fermi8,
+}
+
+impl GOBType {
+    pub fn extent_B(&self) -> Extent4D<units::Bytes> {
+        match self {
+            GOBType::Linear => Extent4D::new(1, 1, 1, 1),
+            GOBType::Fermi8 => Extent4D::new(64, 8, 1, 1),
+        }
+    }
+
+    #[no_mangle]
+    pub extern "C" fn nil_gob_type_height(self) -> u32 {
+        self.extent_B().height
     }
 }
 
 #[derive(Clone, Debug, Default, Copy, PartialEq)]
 #[repr(C)]
 pub struct Tiling {
-    pub is_tiled: bool,
-    /// Whether the GOB height is 4 or 8
-    pub gob_height_is_8: bool,
+    /// GOB type
+    pub gob_type: GOBType,
     /// log2 of the X tile dimension in GOBs
     pub x_log2: u8,
     /// log2 of the Y tile dimension in GOBs
@@ -41,7 +51,7 @@ impl Tiling {
     pub fn clamp(&self, extent_B: Extent4D<units::Bytes>) -> Self {
         let mut tiling = *self;
 
-        if !self.is_tiled {
+        if !self.is_tiled() {
             return tiling;
         }
 
@@ -54,7 +64,7 @@ impl Tiling {
             tiling.x_log2 = 0;
         }
 
-        let extent_GOB = extent_B.to_GOB(tiling.gob_height_is_8);
+        let extent_GOB = extent_B.to_GOB(tiling.gob_type);
 
         let ceil_h = extent_GOB.height.ilog2_ceil() as u8;
         let ceil_d = extent_GOB.depth.ilog2_ceil() as u8;
@@ -75,17 +85,14 @@ impl Tiling {
     }
 
     pub fn extent_B(&self) -> Extent4D<units::Bytes> {
-        if self.is_tiled {
-            Extent4D::new(
-                GOB_WIDTH_B << self.x_log2,
-                gob_height(self.gob_height_is_8) << self.y_log2,
-                GOB_DEPTH << self.z_log2,
-                1,
-            )
-        } else {
-            // We handle linear images in Image::new()
-            Extent4D::new(1, 1, 1, 1)
-        }
+        let gob_extent_B = self.gob_type.extent_B();
+        debug_assert!(gob_extent_B.array_len == 1);
+        Extent4D::new(
+            gob_extent_B.width << self.x_log2,
+            gob_extent_B.height << self.y_log2,
+            gob_extent_B.depth << self.z_log2,
+            1,
+        )
     }
 }
 
@@ -152,13 +159,11 @@ impl Tiling {
         assert!(sparse_block_extent_B.height.is_power_of_two());
         assert!(sparse_block_extent_B.depth.is_power_of_two());
 
-        let gob_height_is_8 = true;
-        let sparse_block_extent_gob =
-            sparse_block_extent_B.to_GOB(gob_height_is_8);
+        let gob_type = GOBType::Fermi8;
+        let sparse_block_extent_gob = sparse_block_extent_B.to_GOB(gob_type);
 
         Self {
-            is_tiled: true,
-            gob_height_is_8,
+            gob_type,
             x_log2: sparse_block_extent_gob.width.ilog2().try_into().unwrap(),
             y_log2: sparse_block_extent_gob.height.ilog2().try_into().unwrap(),
             z_log2: sparse_block_extent_gob.depth.ilog2().try_into().unwrap(),
@@ -176,8 +181,7 @@ impl Tiling {
         }
 
         let mut tiling = Tiling {
-            is_tiled: true,
-            gob_height_is_8: true,
+            gob_type: GOBType::Fermi8,
             x_log2: 0,
             y_log2: 5,
             z_log2: 5,
@@ -189,4 +193,15 @@ impl Tiling {
 
         tiling.clamp(extent_px.to_B(format, sample_layout))
     }
+
+    pub fn is_tiled(&self) -> bool {
+        if self.gob_type == GOBType::Linear {
+            debug_assert!(self.x_log2 == 0);
+            debug_assert!(self.y_log2 == 0);
+            debug_assert!(self.z_log2 == 0);
+            false
+        } else {
+            true
+        }
+    }
 }
diff --git a/src/nouveau/vulkan/nvk_cmd_copy.c b/src/nouveau/vulkan/nvk_cmd_copy.c
index d6bd4ca93e73e5d52965ec15fb66d54599f0daa1..37c092e7add0c1020db286b24e5480d43a298bf4 100644
--- a/src/nouveau/vulkan/nvk_cmd_copy.c
+++ b/src/nouveau/vulkan/nvk_cmd_copy.c
@@ -216,12 +216,12 @@ nouveau_copy_rect(struct nvk_cmd_buffer *cmd, struct nouveau_copy *copy)
       if (copy->dst.image_type != VK_IMAGE_TYPE_3D)
          dst_addr += (z + copy->dst.offset_el.a) * copy->dst.array_stride;
 
-      if (!copy->src.tiling.is_tiled) {
+      if (copy->src.tiling.gob_type == NIL_GOB_TYPE_LINEAR) {
          src_addr += copy->src.offset_el.x * copy->src.bpp +
                      copy->src.offset_el.y * copy->src.row_stride;
       }
 
-      if (!copy->dst.tiling.is_tiled) {
+      if (copy->dst.tiling.gob_type == NIL_GOB_TYPE_LINEAR) {
          dst_addr += copy->dst.offset_el.x * copy->dst.bpp +
                      copy->dst.offset_el.y * copy->dst.row_stride;
       }
@@ -239,9 +239,9 @@ nouveau_copy_rect(struct nvk_cmd_buffer *cmd, struct nouveau_copy *copy)
       P_NV90B5_LINE_COUNT(p, copy->extent_el.height);
 
       uint32_t src_layout = 0, dst_layout = 0;
-      if (copy->src.tiling.is_tiled) {
+      if (copy->src.tiling.gob_type != NIL_GOB_TYPE_LINEAR) {
          P_MTHD(p, NV90B5, SET_SRC_BLOCK_SIZE);
-         assert(copy->src.tiling.gob_height_is_8);
+         assert(nil_gob_type_height(copy->src.tiling.gob_type) == 8);
          P_NV90B5_SET_SRC_BLOCK_SIZE(p, {
             .width = 0, /* Tiles are always 1 GOB wide */
             .height = copy->src.tiling.y_log2,
@@ -279,9 +279,9 @@ nouveau_copy_rect(struct nvk_cmd_buffer *cmd, struct nouveau_copy *copy)
          src_layout = NV90B5_LAUNCH_DMA_SRC_MEMORY_LAYOUT_PITCH;
       }
 
-      if (copy->dst.tiling.is_tiled) {
+      if (copy->dst.tiling.gob_type != NIL_GOB_TYPE_LINEAR) {
          P_MTHD(p, NV90B5, SET_DST_BLOCK_SIZE);
-         assert(copy->dst.tiling.gob_height_is_8);
+         assert(nil_gob_type_height(copy->dst.tiling.gob_type) == 8);
          P_NV90B5_SET_DST_BLOCK_SIZE(p, {
             .width = 0, /* Tiles are always 1 GOB wide */
             .height = copy->dst.tiling.y_log2,
diff --git a/src/nouveau/vulkan/nvk_cmd_draw.c b/src/nouveau/vulkan/nvk_cmd_draw.c
index e20f34ef2403abe4f9a683d11e872348dec0bb42..28df936d59afd846b6ea54ff32d3c935d17b2563 100644
--- a/src/nouveau/vulkan/nvk_cmd_draw.c
+++ b/src/nouveau/vulkan/nvk_cmd_draw.c
@@ -760,7 +760,7 @@ nvk_rendering_all_linear(const struct nvk_rendering_state *render)
       const struct nil_image_level *level =
          &image->planes[ip].nil.levels[iview->vk.base_mip_level];
 
-      if (level->tiling.is_tiled)
+      if (level->tiling.gob_type != NIL_GOB_TYPE_LINEAR)
          return false;
    }
 
@@ -842,7 +842,8 @@ nvk_CmdBeginRendering(VkCommandBuffer commandBuffer,
          const uint8_t ip = iview->planes[0].image_plane;
          const struct nvk_image_plane *plane = &image->planes[ip];
 
-         if (!render->all_linear && !plane->nil.levels[0].tiling.is_tiled)
+         if (!render->all_linear &&
+             plane->nil.levels[0].tiling.gob_type == NIL_GOB_TYPE_LINEAR)
             plane = &image->linear_tiled_shadow;
 
          const struct nil_image *nil_image = &plane->nil;
@@ -873,7 +874,7 @@ nvk_CmdBeginRendering(VkCommandBuffer commandBuffer,
          P_NV9097_SET_COLOR_TARGET_A(p, i, addr >> 32);
          P_NV9097_SET_COLOR_TARGET_B(p, i, addr);
 
-         if (level->tiling.is_tiled) {
+         if (level->tiling.gob_type != NIL_GOB_TYPE_LINEAR) {
             const enum pipe_format p_format =
                vk_format_to_pipe_format(iview->vk.format);
 
@@ -993,7 +994,7 @@ nvk_CmdBeginRendering(VkCommandBuffer commandBuffer,
          vk_format_to_pipe_format(iview->vk.format);
       const uint8_t zs_format = nil_format_to_depth_stencil(p_format);
       P_NV9097_SET_ZT_FORMAT(p, zs_format);
-      assert(level->tiling.is_tiled);
+      assert(level->tiling.gob_type != NIL_GOB_TYPE_LINEAR);
       assert(level->tiling.z_log2 == 0);
       P_NV9097_SET_ZT_BLOCK_SIZE(p, {
          .width = WIDTH_ONE_GOB,
@@ -1073,7 +1074,8 @@ nvk_CmdBeginRendering(VkCommandBuffer commandBuffer,
 
       const VkAttachmentLoadOp load_op =
          pRenderingInfo->pColorAttachments[i].loadOp;
-      if (!render->all_linear && !plane->nil.levels[0].tiling.is_tiled &&
+      if (!render->all_linear &&
+          plane->nil.levels[0].tiling.gob_type == NIL_GOB_TYPE_LINEAR &&
           load_op == VK_ATTACHMENT_LOAD_OP_LOAD)
          nvk_linear_render_copy(cmd, iview, render->area, true);
    }
@@ -1147,7 +1149,8 @@ nvk_CmdEndRendering(VkCommandBuffer commandBuffer)
          struct nvk_image *image = (struct nvk_image *)iview->vk.image;
          const uint8_t ip = iview->planes[0].image_plane;
          const struct nvk_image_plane *plane = &image->planes[ip];
-         if (!render->all_linear && !plane->nil.levels[0].tiling.is_tiled &&
+         if (!render->all_linear &&
+             plane->nil.levels[0].tiling.gob_type == NIL_GOB_TYPE_LINEAR &&
              render->color_att[i].store_op == VK_ATTACHMENT_STORE_OP_STORE)
             nvk_linear_render_copy(cmd, iview, render->area, false);
       }