pan/decode: Validate and simplify FRAGMENT payloads
[mesa.git] / src / panfrost / pandecode / decode.c
index e4c1f8f7bc21f916df0f2cf6deca218e355d1458..7c8c5ca95c095ab225e5a2c89aba64960c37d0d4 100644 (file)
@@ -29,6 +29,7 @@
 #include <memory.h>
 #include <stdbool.h>
 #include <stdarg.h>
+#include <ctype.h>
 #include "decode.h"
 #include "util/macros.h"
 #include "util/u_math.h"
@@ -413,16 +414,21 @@ pandecode_stencil_op(enum mali_stencil_op op)
 #undef DEFINE_CASE
 
 #define DEFINE_CASE(name) case MALI_ATTR_ ## name: return "MALI_ATTR_" #name
-static char *pandecode_attr_mode(enum mali_attr_mode mode)
+static char *pandecode_attr_mode_short(enum mali_attr_mode mode)
 {
         switch(mode) {
-                DEFINE_CASE(UNUSED);
-                DEFINE_CASE(LINEAR);
-                DEFINE_CASE(POT_DIVIDE);
-                DEFINE_CASE(MODULO);
-                DEFINE_CASE(NPOT_DIVIDE);
-                DEFINE_CASE(IMAGE);
-                DEFINE_CASE(INTERNAL);
+                /* TODO: Combine to just "instanced" once this can be done
+                 * unambiguously in all known cases */
+        case MALI_ATTR_POT_DIVIDE:
+                return "instanced_pot";
+        case MALI_ATTR_MODULO:
+                return "instanced_mod";
+        case MALI_ATTR_NPOT_DIVIDE:
+                return "instanced_npot";
+        case MALI_ATTR_IMAGE:
+                return "image";
+        case MALI_ATTR_INTERNAL:
+                return "internal";
         default:
                 pandecode_msg("XXX: invalid attribute mode %X\n", mode);
                 return "";
@@ -431,25 +437,6 @@ static char *pandecode_attr_mode(enum mali_attr_mode mode)
 
 #undef DEFINE_CASE
 
-#define DEFINE_CASE(name) case MALI_CHANNEL_## name: return "MALI_CHANNEL_" #name
-static char *
-pandecode_channel(enum mali_channel channel)
-{
-        switch (channel) {
-                DEFINE_CASE(RED);
-                DEFINE_CASE(GREEN);
-                DEFINE_CASE(BLUE);
-                DEFINE_CASE(ALPHA);
-                DEFINE_CASE(ZERO);
-                DEFINE_CASE(ONE);
-
-        default:
-                pandecode_msg("XXX: invalid channel %X\n", channel);
-                return "";
-        }
-}
-#undef DEFINE_CASE
-
 #define DEFINE_CASE(name) case MALI_WRAP_## name: return "MALI_WRAP_" #name
 static char *
 pandecode_wrap_mode(enum mali_wrap_mode op)
@@ -467,22 +454,6 @@ pandecode_wrap_mode(enum mali_wrap_mode op)
 }
 #undef DEFINE_CASE
 
-#define DEFINE_CASE(name) case MALI_TEX_## name: return "MALI_TEX_" #name
-static char *
-pandecode_texture_type(enum mali_texture_type type)
-{
-        switch (type) {
-                DEFINE_CASE(1D);
-                DEFINE_CASE(2D);
-                DEFINE_CASE(3D);
-                DEFINE_CASE(CUBE);
-
-        default:
-                unreachable("Unknown case");
-        }
-}
-#undef DEFINE_CASE
-
 #define DEFINE_CASE(name) case MALI_MFBD_BLOCK_## name: return "MALI_MFBD_BLOCK_" #name
 static char *
 pandecode_mfbd_block_format(enum mali_mfbd_block_format fmt)
@@ -640,12 +611,24 @@ pandecode_midgard_tiler_descriptor(
         pandecode_log("}\n");
 }
 
-static void
+/* Information about the framebuffer passed back for
+ * additional analysis */
+
+struct pandecode_fbd {
+        unsigned width;
+        unsigned height;
+        unsigned rt_count;
+        bool has_extra;
+};
+
+static struct pandecode_fbd
 pandecode_sfbd(uint64_t gpu_va, int job_no, bool is_fragment)
 {
         struct pandecode_mapped_memory *mem = pandecode_find_mapped_gpu_mem_containing(gpu_va);
         const struct mali_single_framebuffer *PANDECODE_PTR_VAR(s, mem, (mali_ptr) gpu_va);
 
+        struct pandecode_fbd info;
+
         pandecode_log("struct mali_single_framebuffer framebuffer_%"PRIx64"_%d = {\n", gpu_va, job_no);
         pandecode_indent++;
 
@@ -656,8 +639,12 @@ pandecode_sfbd(uint64_t gpu_va, int job_no, bool is_fragment)
         pandecode_log_decoded_flags(fb_fmt_flag_info, s->format);
         pandecode_log_cont(",\n");
 
-        pandecode_prop("width = MALI_POSITIVE(%" PRId16 ")", s->width + 1);
-        pandecode_prop("height = MALI_POSITIVE(%" PRId16 ")", s->height + 1);
+        info.width = s->width + 1;
+        info.height = s->height + 1;
+        info.rt_count = 1;
+
+        pandecode_prop("width = MALI_POSITIVE(%" PRId16 ")", info.width);
+        pandecode_prop("height = MALI_POSITIVE(%" PRId16 ")", info.height);
 
         MEMORY_PROP(s, framebuffer);
         pandecode_prop("stride = %d", s->stride);
@@ -722,6 +709,8 @@ pandecode_sfbd(uint64_t gpu_va, int job_no, bool is_fragment)
                 printf("%X, ", s->zero6[i]);
 
         printf("},\n");
+
+        return info;
 }
 
 static void
@@ -753,14 +742,126 @@ pandecode_compute_fbd(uint64_t gpu_va, int job_no)
         printf("},\n");
 }
 
+/* Extracts the number of components associated with a Mali format */
+
+static unsigned
+pandecode_format_component_count(enum mali_format fmt)
+{
+        /* Mask out the format class */
+        unsigned top = fmt & 0b11100000;
+
+        switch (top) {
+        case MALI_FORMAT_SNORM:
+        case MALI_FORMAT_UINT:
+        case MALI_FORMAT_UNORM:
+        case MALI_FORMAT_SINT:
+                return ((fmt >> 3) & 3) + 1;
+        default:
+                /* TODO: Validate */
+                return 4;
+        }
+}
+
+/* Extracts a mask of accessed components from a 12-bit Mali swizzle */
+
+static unsigned
+pandecode_access_mask_from_channel_swizzle(unsigned swizzle)
+{
+        unsigned mask = 0;
+        assert(MALI_CHANNEL_RED == 0);
+
+        for (unsigned c = 0; c < 4; ++c) {
+                enum mali_channel chan = (swizzle >> (3*c)) & 0x7;
+
+                if (chan <= MALI_CHANNEL_ALPHA)
+                        mask |= (1 << chan);
+        }
+
+        return mask;
+}
+
+/* Validates that a (format, swizzle) pair is valid, in the sense that the
+ * swizzle doesn't access any components that are undefined in the format.
+ * Returns whether the swizzle is trivial (doesn't do any swizzling) and can be
+ * omitted */
+
+static bool
+pandecode_validate_format_swizzle(enum mali_format fmt, unsigned swizzle)
+{
+        unsigned nr_comp = pandecode_format_component_count(fmt);
+        unsigned access_mask = pandecode_access_mask_from_channel_swizzle(swizzle);
+        unsigned valid_mask = (1 << nr_comp) - 1;
+        unsigned invalid_mask = ~valid_mask;
+
+        if (access_mask & invalid_mask) {
+                pandecode_msg("XXX: invalid components accessed\n");
+                return false;
+        }
+
+        /* Check for the default non-swizzling swizzle so we can suppress
+         * useless printing for the defaults */
+
+        unsigned default_swizzles[4] = {
+                MALI_CHANNEL_RED | (MALI_CHANNEL_ZERO  << 3) | (MALI_CHANNEL_ZERO << 6) | (MALI_CHANNEL_ONE   << 9),
+                MALI_CHANNEL_RED | (MALI_CHANNEL_GREEN << 3) | (MALI_CHANNEL_ZERO << 6) | (MALI_CHANNEL_ONE   << 9),
+                MALI_CHANNEL_RED | (MALI_CHANNEL_GREEN << 3) | (MALI_CHANNEL_BLUE << 6) | (MALI_CHANNEL_ONE   << 9),
+                MALI_CHANNEL_RED | (MALI_CHANNEL_GREEN << 3) | (MALI_CHANNEL_BLUE << 6) | (MALI_CHANNEL_ALPHA << 9)
+        };
+
+        return (swizzle == default_swizzles[nr_comp - 1]);
+}
+
+/* Maps MALI_RGBA32F to rgba32f, etc */
+
+static void
+pandecode_format_short(enum mali_format fmt, bool srgb)
+{
+        /* We want a type-like format, so cut off the initial MALI_ */
+        char *format = pandecode_format(fmt);
+        format += strlen("MALI_");
+
+        unsigned len = strlen(format);
+        char *lower_format = calloc(1, len + 1);
+
+        for (unsigned i = 0; i < len; ++i)
+                lower_format[i] = tolower(format[i]);
+
+        /* Sanity check sRGB flag is applied to RGB, per the name */
+        if (srgb && lower_format[0] != 'r')
+                pandecode_msg("XXX: sRGB applied to non-colour format\n");
+
+        /* Just prefix with an s, so you get formats like srgba8_unorm */
+        if (srgb)
+                pandecode_log_cont("s");
+
+        pandecode_log_cont("%s", lower_format);
+        free(lower_format);
+}
+
 static void
-pandecode_swizzle(unsigned swizzle)
+pandecode_swizzle(unsigned swizzle, enum mali_format format)
 {
-        pandecode_prop("swizzle = %s | (%s << 3) | (%s << 6) | (%s << 9)",
-                       pandecode_channel((swizzle >> 0) & 0x7),
-                       pandecode_channel((swizzle >> 3) & 0x7),
-                       pandecode_channel((swizzle >> 6) & 0x7),
-                       pandecode_channel((swizzle >> 9) & 0x7));
+        /* First, do some validation */
+        bool trivial_swizzle = pandecode_validate_format_swizzle(
+                        format, swizzle);
+
+        if (trivial_swizzle)
+                return;
+
+        /* Next, print the swizzle */
+        pandecode_log_cont(".");
+
+        static const char components[] = "rgba01";
+
+        for (unsigned c = 0; c < 4; ++c) {
+                enum mali_channel chan = (swizzle >> (3 * c)) & 0x7;
+
+                if (chan >= MALI_CHANNEL_RESERVED_0) {
+                        pandecode_log("XXX: invalid swizzle channel %d\n", chan);
+                        continue;
+                }
+                pandecode_log_cont("%c", components[chan]);
+        }
 }
 
 static void
@@ -776,6 +877,11 @@ pandecode_rt_format(struct mali_rt_format format)
         pandecode_prop("block = %s",
                        pandecode_mfbd_block_format(format.block));
 
+        /* TODO: Map formats so we can check swizzles and print nicely */
+        pandecode_log("swizzle");
+        pandecode_swizzle(format.swizzle, MALI_RGBA8_UNORM);
+        pandecode_log_cont(",\n");
+
         pandecode_prop("nr_channels = MALI_POSITIVE(%d)",
                        MALI_NEGATIVE(format.nr_channels));
 
@@ -783,8 +889,6 @@ pandecode_rt_format(struct mali_rt_format format)
         pandecode_log_decoded_flags(mfbd_fmt_flag_info, format.flags);
         pandecode_log_cont(",\n");
 
-        pandecode_swizzle(format.swizzle);
-
         /* In theory, the no_preload bit can be cleared to enable MFBD preload,
          * which is a faster hardware-based alternative to the wallpaper method
          * to preserve framebuffer contents across frames. In practice, MFBD
@@ -865,12 +969,14 @@ pandecode_render_target(uint64_t gpu_va, unsigned job_no, const struct bifrost_f
         pandecode_log("};\n");
 }
 
-static unsigned
+static struct pandecode_fbd
 pandecode_mfbd_bfr(uint64_t gpu_va, int job_no, bool is_fragment)
 {
         struct pandecode_mapped_memory *mem = pandecode_find_mapped_gpu_mem_containing(gpu_va);
         const struct bifrost_framebuffer *PANDECODE_PTR_VAR(fb, mem, (mali_ptr) gpu_va);
 
+        struct pandecode_fbd info;
         if (fb->sample_locations) {
                 /* The blob stores all possible sample locations in a single buffer
                  * allocated on startup, and just switches the pointer when switching
@@ -910,6 +1016,10 @@ pandecode_mfbd_bfr(uint64_t gpu_va, int job_no, bool is_fragment)
          * now */
         MEMORY_PROP(fb, unknown1);
 
+        info.width = fb->width1 + 1;
+        info.height = fb->height1 + 1;
+        info.rt_count = fb->rt_count_1 + 1;
+
         pandecode_prop("width1 = MALI_POSITIVE(%d)", fb->width1 + 1);
         pandecode_prop("height1 = MALI_POSITIVE(%d)", fb->height1 + 1);
         pandecode_prop("width2 = MALI_POSITIVE(%d)", fb->width2 + 1);
@@ -950,7 +1060,9 @@ pandecode_mfbd_bfr(uint64_t gpu_va, int job_no, bool is_fragment)
 
         gpu_va += sizeof(struct bifrost_framebuffer);
 
-        if ((fb->mfbd_flags & MALI_MFBD_EXTRA) && is_fragment) {
+        info.has_extra = (fb->mfbd_flags & MALI_MFBD_EXTRA) && is_fragment;
+
+        if (info.has_extra) {
                 mem = pandecode_find_mapped_gpu_mem_containing(gpu_va);
                 const struct bifrost_fb_extra *PANDECODE_PTR_VAR(fbx, mem, (mali_ptr) gpu_va);
 
@@ -1034,8 +1146,7 @@ pandecode_mfbd_bfr(uint64_t gpu_va, int job_no, bool is_fragment)
         if (is_fragment)
                 pandecode_render_target(gpu_va, job_no, fb);
 
-        /* Passback the render target count */
-        return MALI_NEGATIVE(fb->rt_count_1);
+        return info;
 }
 
 /* Just add a comment decoding the shift/odd fields forming the padded vertices
@@ -1139,7 +1250,7 @@ pandecode_attributes(const struct pandecode_mapped_memory *mem,
                             mali_ptr addr, int job_no, char *suffix,
                             int count, bool varying)
 {
-        char *prefix = varying ? "varyings" : "attributes";
+        char *prefix = varying ? "varying" : "attribute";
 
         if (!addr) {
                 pandecode_msg("no %s\n", prefix);
@@ -1148,39 +1259,47 @@ pandecode_attributes(const struct pandecode_mapped_memory *mem,
 
         union mali_attr *attr = pandecode_fetch_gpu_mem(mem, addr, sizeof(union mali_attr) * count);
 
-        char base[128];
-        snprintf(base, sizeof(base), "%s_data_%d%s", prefix, job_no, suffix);
-
         for (int i = 0; i < count; ++i) {
                 enum mali_attr_mode mode = attr[i].elements & 7;
 
                 if (mode == MALI_ATTR_UNUSED)
-                        continue;
+                        pandecode_msg("XXX: unused attribute record\n");
 
-                mali_ptr raw_elements = attr[i].elements & ~7;
+                /* For non-linear records, we need to print the type of record */
+                if (mode != MALI_ATTR_LINEAR)
+                        pandecode_log_cont("%s ", pandecode_attr_mode_short(mode));
+
+                /* Print the name to link with attr_meta */
+                pandecode_log_cont("%s_%d", prefix, i);
 
-                /* TODO: Do we maybe want to dump the attribute values
-                 * themselves given the specified format? Or is that too hard?
-                 * */
+                /* Print the stride and size */
+                pandecode_log_cont("<%u>[%u]", attr[i].stride, attr[i].size);
 
+                /* Check: the size must be divisible by the stride */
+                if (attr[i].size % attr[i].stride)
+                        pandecode_msg("XXX: size not divisible by stride\n");
+
+                /* TODO: Sanity check the quotient itself -- it should equal
+                 * vertex count (or something computed from it for instanced)
+                 * which means we can check and elide */
+
+                /* Finally, print the pointer */
+                mali_ptr raw_elements = attr[i].elements & ~7;
                 char *a = pointer_as_memory_reference(raw_elements);
-                pandecode_log("mali_ptr %s_%d_p = %s;\n", base, i, a);
+                pandecode_log_cont(" = (%s);\n", a);
                 free(a);
-        }
 
-        pandecode_log("union mali_attr %s_%d[] = {\n", prefix, job_no);
-        pandecode_indent++;
+                /* Check the pointer */
+                pandecode_validate_buffer(raw_elements, attr[i].size);
 
-        for (int i = 0; i < count; ++i) {
-                pandecode_log("{\n");
-                pandecode_indent++;
+                /* shift/extra_flags exist only for instanced */
+                if (attr[i].shift | attr[i].extra_flags) {
+                        if (mode == MALI_ATTR_LINEAR)
+                                pandecode_msg("XXX: instancing fields set for linear\n");
 
-                unsigned mode = attr[i].elements & 7;
-                pandecode_prop("elements = (%s_%d_p) | %s", base, i, pandecode_attr_mode(mode));
-                pandecode_prop("shift = %d", attr[i].shift);
-                pandecode_prop("extra_flags = %d", attr[i].extra_flags);
-                pandecode_prop("stride = 0x%" PRIx32, attr[i].stride);
-                pandecode_prop("size = 0x%" PRIx32, attr[i].size);
+                        pandecode_prop("shift = %d", attr[i].shift);
+                        pandecode_prop("extra_flags = %d", attr[i].extra_flags);
+                }
 
                 /* Decode further where possible */
 
@@ -1190,9 +1309,6 @@ pandecode_attributes(const struct pandecode_mapped_memory *mem,
                                 attr[i].extra_flags);
                 }
 
-                pandecode_indent--;
-                pandecode_log("}, \n");
-
                 if (mode == MALI_ATTR_NPOT_DIVIDE) {
                         i++;
                         pandecode_log("{\n");
@@ -1209,8 +1325,7 @@ pandecode_attributes(const struct pandecode_mapped_memory *mem,
 
         }
 
-        pandecode_indent--;
-        pandecode_log("};\n");
+        pandecode_log("\n");
 }
 
 static mali_ptr
@@ -1226,21 +1341,14 @@ pandecode_shader_address(const char *name, mali_ptr ptr)
         return shader_ptr;
 }
 
-static bool
-all_zero(unsigned *buffer, unsigned count)
-{
-        for (unsigned i = 0; i < count; ++i) {
-                if (buffer[i])
-                        return false;
-        }
-
-        return true;
-}
-
 static void
 pandecode_stencil(const char *name, const struct mali_stencil_test *stencil)
 {
-        if (all_zero((unsigned *) stencil, sizeof(stencil) / sizeof(unsigned)))
+        unsigned any_nonzero =
+                stencil->ref | stencil->mask | stencil->func |
+                stencil->sfail | stencil->dpfail | stencil->dppass;
+
+        if (any_nonzero == 0)
                 return;
 
         const char *func = pandecode_func(stencil->func);
@@ -1322,7 +1430,8 @@ pandecode_bifrost_blend(void *descs, int job_no, int rt_no)
 static mali_ptr
 pandecode_midgard_blend(union midgard_blend *blend, bool is_shader)
 {
-        if (all_zero((unsigned *) blend, sizeof(blend) / sizeof(unsigned)))
+        /* constant/equation is in a union */
+        if (!blend->shader)
                 return 0;
 
         pandecode_log(".blend = {\n");
@@ -1370,66 +1479,6 @@ pandecode_midgard_blend_mrt(void *descs, int job_no, int rt_no)
  * want to validate that the combinations specified are self-consistent.
  */
 
-/* Extracts the number of components associated with a Mali format */
-
-static unsigned
-pandecode_format_component_count(enum mali_format fmt)
-{
-        /* Mask out the format class */
-        unsigned top = fmt & 0b11100000;
-
-        switch (top) {
-        case MALI_FORMAT_SNORM:
-        case MALI_FORMAT_UINT:
-        case MALI_FORMAT_UNORM:
-        case MALI_FORMAT_SINT:
-                return ((fmt >> 3) & 3) + 1;
-        default:
-                /* TODO: Validate */
-                return 4;
-        }
-}
-
-/* Extracts a mask of accessed components from a 12-bit Mali swizzle */
-
-static unsigned
-pandecode_access_mask_from_channel_swizzle(unsigned swizzle)
-{
-        unsigned mask = 0;
-        assert(MALI_CHANNEL_RED == 0);
-
-        for (unsigned c = 0; c < 4; ++c) {
-                enum mali_channel chan = (swizzle >> (3*c)) & 0x7;
-
-                if (chan <= MALI_CHANNEL_ALPHA)
-                        mask |= (1 << chan);
-        }
-
-        return mask;
-}
-
-/* Validates that a (format, swizzle) pair is valid, in the sense that the
- * swizzle doesn't access any components that are undefined in the format.
- * Returns whether the swizzle is trivial (doesn't do any swizzling) and can be
- * omitted */
-
-static bool
-pandecode_validate_format_swizzle(enum mali_format fmt, unsigned swizzle)
-{
-        unsigned nr_comp = pandecode_format_component_count(fmt);
-        unsigned access_mask = pandecode_access_mask_from_channel_swizzle(swizzle);
-        unsigned valid_mask = (1 << nr_comp) - 1;
-        unsigned invalid_mask = ~valid_mask;
-
-        if (access_mask & invalid_mask) {
-                pandecode_msg("XXX: invalid components accessed\n");
-                return false;
-        }
-
-        /* TODO: Trivial swizzle check */
-        return false;
-}
-
 static int
 pandecode_attribute_meta(int job_no, int count, const struct mali_vertex_tiler_postfix *v, bool varying, char *suffix)
 {
@@ -1438,9 +1487,6 @@ pandecode_attribute_meta(int job_no, int count, const struct mali_vertex_tiler_p
         unsigned max_index = 0;
         snprintf(base, sizeof(base), "%s_meta", prefix);
 
-        pandecode_log("struct mali_attr_meta %s_%d%s[] = {\n", base, job_no, suffix);
-        pandecode_indent++;
-
         struct mali_attr_meta *attr_meta;
         mali_ptr p = varying ? (v->varying_meta & ~0xF) : v->attribute_meta;
 
@@ -1482,25 +1528,9 @@ pandecode_attribute_meta(int job_no, int count, const struct mali_vertex_tiler_p
                         }
                 }
 
-                pandecode_log("{\n");
-                pandecode_indent++;
-                pandecode_prop("index = %d", attr_meta->index);
-
                 if (attr_meta->index > max_index)
                         max_index = attr_meta->index;
 
-                /* Check the swizzle/format, and if they match and the swizzle
-                 * is simple enough, we can avoid printing the swizzle since
-                 * it's just noise */
-
-                bool trivial_swizzle = pandecode_validate_format_swizzle(
-                                attr_meta->format, attr_meta->swizzle);
-
-                if (!trivial_swizzle)
-                        pandecode_swizzle(attr_meta->swizzle);
-
-                pandecode_prop("format = %s", pandecode_format(attr_meta->format));
-
                 if (attr_meta->unknown1 != 0x2) {
                         pandecode_msg("XXX: expected unknown1 = 0x2\n");
                         pandecode_prop("unknown1 = 0x%" PRIx64, (u64) attr_meta->unknown1);
@@ -1511,14 +1541,18 @@ pandecode_attribute_meta(int job_no, int count, const struct mali_vertex_tiler_p
                         pandecode_prop("unknown3 = 0x%" PRIx64, (u64) attr_meta->unknown3);
                 }
 
-                pandecode_prop("src_offset = %d", attr_meta->src_offset);
-                pandecode_indent--;
-                pandecode_log("},\n");
+                pandecode_format_short(attr_meta->format, false);
+                pandecode_log_cont(" %s_%u", prefix, attr_meta->index);
 
+                if (attr_meta->src_offset)
+                        pandecode_log_cont("[%u]", attr_meta->src_offset);
+
+                pandecode_swizzle(attr_meta->swizzle, attr_meta->format);
+
+                pandecode_log_cont(";\n");
         }
 
-        pandecode_indent--;
-        pandecode_log("};\n");
+        pandecode_log("\n");
 
         return count ? (max_index + 1) : 0;
 }
@@ -1650,29 +1684,18 @@ pandecode_uniform_buffers(mali_ptr pubufs, int ubufs_count, int job_no)
         struct pandecode_mapped_memory *umem = pandecode_find_mapped_gpu_mem_containing(pubufs);
         struct mali_uniform_buffer_meta *PANDECODE_PTR_VAR(ubufs, umem, pubufs);
 
-        pandecode_log("struct mali_uniform_buffer_meta uniform_buffers_%"PRIx64"_%d[] = {\n",
-                      pubufs, job_no);
-        pandecode_indent++;
-
         for (int i = 0; i < ubufs_count; i++) {
-                pandecode_log("{\n");
-                pandecode_indent++;
-
                 unsigned size = (ubufs[i].size + 1) * 16;
                 mali_ptr addr = ubufs[i].ptr << 2;
 
                 pandecode_validate_buffer(addr, size);
 
                 char *ptr = pointer_as_memory_reference(ubufs[i].ptr << 2);
-                pandecode_prop("size = %u", size);
-                pandecode_prop("ptr = (%s) >> 2", ptr);
-                pandecode_indent--;
-                pandecode_log("},\n");
+                pandecode_log("ubuf_%d[%u] = %s;\n", i, size, ptr);
                 free(ptr);
         }
 
-        pandecode_indent--;
-        pandecode_log("};\n");
+        pandecode_log("\n");
 }
 
 static void
@@ -1700,7 +1723,7 @@ pandecode_scratchpad(uintptr_t pscratchpad, int job_no, char *suffix)
 
 static unsigned shader_id = 0;
 
-static void
+static struct midgard_disasm_stats
 pandecode_shader_disassemble(mali_ptr shader_ptr, int shader_no, int type,
                              bool is_bifrost, unsigned nr_regs)
 {
@@ -1715,44 +1738,228 @@ pandecode_shader_disassemble(mali_ptr shader_ptr, int shader_no, int type,
 
         printf("\n\n");
 
-        char prefix[512];
-
-        snprintf(prefix, sizeof(prefix) - 1, "shader%d - %s shader: ",
-                        shader_id++,
-                        (type == JOB_TYPE_TILER) ? "FRAGMENT" : "VERTEX");
+        struct midgard_disasm_stats stats;
 
         if (is_bifrost) {
                 disassemble_bifrost(code, sz, false);
+
+                /* TODO: Extend stats to Bifrost */
+                stats.texture_count = -1;
+                stats.sampler_count = -1;
+                stats.attribute_count = -1;
+                stats.varying_count = -1;
+                stats.uniform_count = -1;
+                stats.uniform_buffer_count = -1;
+                stats.work_count = -1;
+
+                stats.instruction_count = 0;
+                stats.bundle_count = 0;
+                stats.quadword_count = 0;
         } else {
-                disassemble_midgard(code, sz, true, nr_regs, prefix);
+                stats = disassemble_midgard(code, sz);
+                stats.work_count = nr_regs;
         }
 
-        printf("\n\n");
+        /* Print shader-db stats */
+
+        unsigned nr_threads =
+                (stats.work_count <= 4) ? 4 :
+                (stats.work_count <= 8) ? 2 :
+                1;
+
+        printf("shader%d - %s shader: "
+                "%u inst, %u bundles, %u quadwords, "
+                "%u registers, %u threads, 0 loops\n\n\n",
+                shader_id++,
+                (type == JOB_TYPE_TILER) ? "FRAGMENT" : "VERTEX",
+                stats.instruction_count, stats.bundle_count, stats.quadword_count,
+                stats.work_count, nr_threads);
+
+
+        return stats;
 }
 
 static void
-pandecode_vertex_tiler_postfix_pre(const struct mali_vertex_tiler_postfix *p,
+pandecode_texture(mali_ptr u,
+                struct pandecode_mapped_memory *tmem,
+                unsigned job_no, unsigned tex)
+{
+        struct mali_texture_descriptor *PANDECODE_PTR_VAR(t, tmem, u);
+
+        pandecode_log("struct mali_texture_descriptor texture_descriptor_%"PRIx64"_%d_%d = {\n", u, job_no, tex);
+        pandecode_indent++;
+
+        struct mali_texture_format f = t->format;
+
+        /* See the definiton of enum mali_texture_type */
+
+        bool is_cube = f.type == MALI_TEX_CUBE;
+        unsigned dimension = is_cube ? 2 : f.type;
+
+        pandecode_make_indent();
+
+        /* TODO: Are there others? */
+        bool is_zs = f.format == MALI_Z32_UNORM;
+
+        /* Recall Z/S switched the meaning of linear/tiled .. */
+        if (is_zs && f.layout == MALI_TEXTURE_LINEAR)
+                pandecode_msg("XXX: depth/stencil cannot be tiled\n");
+
+        /* Print the layout. Default is linear; a modifier can denote AFBC or
+         * u-interleaved/tiled modes */
+
+        if (f.layout == MALI_TEXTURE_AFBC)
+                pandecode_log_cont("afbc");
+        else if (f.layout == MALI_TEXTURE_TILED)
+                pandecode_log_cont(is_zs ? "linear" : "tiled");
+        else if (f.layout == MALI_TEXTURE_LINEAR)
+                pandecode_log_cont("linear");
+        else
+                pandecode_msg("XXX: invalid texture layout 0x%X\n", f.layout);
+
+        pandecode_swizzle(t->swizzle, f.format);
+        pandecode_log_cont(" ");
+
+        /* Distinguish cube/2D with modifier */
+
+        if (is_cube)
+                pandecode_log_cont("cube ");
+
+        pandecode_format_short(f.format, f.srgb);
+        pandecode_swizzle(f.swizzle, f.format);
+
+        /* All four width/height/depth/array_size dimensions are present
+         * regardless of the type of texture, but it is an error to have
+         * non-zero dimensions for unused dimensions. Verify this. array_size
+         * can always be set, as can width. */
+
+        if (t->height && dimension < 2)
+                pandecode_msg("XXX: nonzero height for <2D texture\n");
+
+        if (t->depth && dimension < 3)
+                pandecode_msg("XXX: nonzero depth for <2D texture\n");
+
+        /* Print only the dimensions that are actually there */
+
+        pandecode_log_cont(": %d", t->width + 1);
+
+        if (dimension >= 2)
+                pandecode_log_cont("x%u", t->height + 1);
+
+        if (dimension >= 3)
+                pandecode_log_cont("x%u", t->depth + 1);
+
+        if (t->array_size)
+                pandecode_log_cont("[%u]", t->array_size + 1);
+
+        if (t->levels)
+                pandecode_log_cont(" mip %u", t->levels);
+
+        pandecode_log_cont("\n");
+
+        if (f.unknown1 | f.zero) {
+                pandecode_msg("XXX: texture format zero tripped\n");
+                pandecode_prop("unknown1 = %" PRId32, f.unknown1);
+                pandecode_prop("zero = %" PRId32, f.zero);
+        }
+
+        if (!f.unknown2) {
+                pandecode_msg("XXX: expected unknown texture bit set\n");
+                pandecode_prop("unknown2 = %" PRId32, f.unknown1);
+        }
+
+        if (t->swizzle_zero) {
+                pandecode_msg("XXX: swizzle zero tripped\n");
+                pandecode_prop("swizzle_zero = %d", t->swizzle_zero);
+        }
+
+        if (t->unknown3 | t->unknown3A | t->unknown5 | t->unknown6 | t->unknown7) {
+                pandecode_msg("XXX: texture zero tripped\n");
+                pandecode_prop("unknown3 = %" PRId16, t->unknown3);
+                pandecode_prop("unknown3A = %" PRId8, t->unknown3A);
+                pandecode_prop("unknown5 = 0x%" PRIx32, t->unknown5);
+                pandecode_prop("unknown6 = 0x%" PRIx32, t->unknown6);
+                pandecode_prop("unknown7 = 0x%" PRIx32, t->unknown7);
+        }
+
+        pandecode_log(".payload = {\n");
+        pandecode_indent++;
+
+        /* A bunch of bitmap pointers follow.
+         * We work out the correct number,
+         * based on the mipmap/cubemap
+         * properties, but dump extra
+         * possibilities to futureproof */
+
+        int bitmap_count = MALI_NEGATIVE(t->levels);
+
+        /* Miptree for each face */
+        if (f.type == MALI_TEX_CUBE)
+                bitmap_count *= 6;
+
+        /* Array of textures */
+        bitmap_count *= MALI_NEGATIVE(t->array_size);
+
+        /* Stride for each element */
+        if (f.manual_stride)
+                bitmap_count *= 2;
+
+        /* Sanity check the size */
+        int max_count = sizeof(t->payload) / sizeof(t->payload[0]);
+        assert (bitmap_count <= max_count);
+
+        for (int i = 0; i < bitmap_count; ++i) {
+                /* How we dump depends if this is a stride or a pointer */
+
+                if (f.manual_stride && (i & 1)) {
+                        /* signed 32-bit snuck in as a 64-bit pointer */
+                        uint64_t stride_set = t->payload[i];
+                        uint32_t clamped_stride = stride_set;
+                        int32_t stride = clamped_stride;
+                        assert(stride_set == clamped_stride);
+                        pandecode_log("(mali_ptr) %d /* stride */, \n", stride);
+                } else {
+                        char *a = pointer_as_memory_reference(t->payload[i]);
+                        pandecode_log("%s, \n", a);
+                        free(a);
+                }
+        }
+
+        pandecode_indent--;
+        pandecode_log("},\n");
+
+        pandecode_indent--;
+        pandecode_log("};\n");
+}
+
+static void
+pandecode_vertex_tiler_postfix_pre(
+                const struct mali_vertex_tiler_postfix *p,
                 int job_no, enum mali_job_type job_type,
                 char *suffix, bool is_bifrost)
 {
         mali_ptr shader_meta_ptr = (u64) (uintptr_t) (p->_shader_upper << 4);
         struct pandecode_mapped_memory *attr_mem;
 
-        unsigned rt_count = 1;
-
         /* On Bifrost, since the tiler heap (for tiler jobs) and the scratchpad
          * are the only things actually needed from the FBD, vertex/tiler jobs
          * no longer reference the FBD -- instead, this field points to some
          * info about the scratchpad.
          */
+
+        struct pandecode_fbd fbd_info = {
+                /* Default for Bifrost */
+                .rt_count = 1
+        };
+
         if (is_bifrost)
                 pandecode_scratchpad(p->framebuffer & ~FBD_TYPE, job_no, suffix);
         else if (p->framebuffer & MALI_MFBD)
-                rt_count = pandecode_mfbd_bfr((u64) ((uintptr_t) p->framebuffer) & FBD_MASK, job_no, false);
+                fbd_info = pandecode_mfbd_bfr((u64) ((uintptr_t) p->framebuffer) & FBD_MASK, job_no, false);
         else if (job_type == JOB_TYPE_COMPUTE)
                 pandecode_compute_fbd((u64) (uintptr_t) p->framebuffer, job_no);
         else
-                pandecode_sfbd((u64) (uintptr_t) p->framebuffer, job_no, false);
+                fbd_info = pandecode_sfbd((u64) (uintptr_t) p->framebuffer, job_no, false);
 
         int varying_count = 0, attribute_count = 0, uniform_count = 0, uniform_buffer_count = 0;
         int texture_count = 0, sampler_count = 0;
@@ -1899,7 +2106,7 @@ pandecode_vertex_tiler_postfix_pre(const struct mali_vertex_tiler_postfix *p,
                 if (job_type == JOB_TYPE_TILER) {
                         void* blend_base = (void *) (s + 1);
 
-                        for (unsigned i = 0; i < rt_count; i++) {
+                        for (unsigned i = 0; i < fbd_info.rt_count; i++) {
                                 mali_ptr shader = 0;
 
                                 if (is_bifrost)
@@ -2006,103 +2213,11 @@ pandecode_vertex_tiler_postfix_pre(const struct mali_vertex_tiler_postfix *p,
                         pandecode_log("};\n");
 
                         /* Now, finally, descend down into the texture descriptor */
-                        for (int tex = 0; tex < texture_count; ++tex) {
+                        for (unsigned tex = 0; tex < texture_count; ++tex) {
                                 mali_ptr *PANDECODE_PTR_VAR(u, mmem, p->texture_trampoline + tex * sizeof(mali_ptr));
                                 struct pandecode_mapped_memory *tmem = pandecode_find_mapped_gpu_mem_containing(*u);
-
-                                if (tmem) {
-                                        struct mali_texture_descriptor *PANDECODE_PTR_VAR(t, tmem, *u);
-
-                                        pandecode_log("struct mali_texture_descriptor texture_descriptor_%"PRIx64"_%d_%d = {\n", *u, job_no, tex);
-                                        pandecode_indent++;
-
-                                        pandecode_prop("width = MALI_POSITIVE(%" PRId16 ")", t->width + 1);
-                                        pandecode_prop("height = MALI_POSITIVE(%" PRId16 ")", t->height + 1);
-                                        pandecode_prop("depth = MALI_POSITIVE(%" PRId16 ")", t->depth + 1);
-                                        pandecode_prop("array_size = MALI_POSITIVE(%" PRId16 ")", t->array_size + 1);
-                                        pandecode_prop("unknown3 = %" PRId16, t->unknown3);
-                                        pandecode_prop("unknown3A = %" PRId8, t->unknown3A);
-                                        pandecode_prop("nr_mipmap_levels = %" PRId8, t->nr_mipmap_levels);
-
-                                        struct mali_texture_format f = t->format;
-
-                                        pandecode_log(".format = {\n");
-                                        pandecode_indent++;
-
-                                        pandecode_swizzle(f.swizzle);
-                                        pandecode_prop("format = %s", pandecode_format(f.format));
-                                        pandecode_prop("type = %s", pandecode_texture_type(f.type));
-                                        pandecode_prop("srgb = %" PRId32, f.srgb);
-                                        pandecode_prop("unknown1 = %" PRId32, f.unknown1);
-                                        pandecode_prop("usage2 = 0x%" PRIx32, f.usage2);
-
-                                        pandecode_indent--;
-                                        pandecode_log("},\n");
-
-                                        pandecode_swizzle(t->swizzle);
-
-                                        if (t->swizzle_zero) {
-                                                /* Shouldn't happen */
-                                                pandecode_msg("XXX: swizzle zero tripped\n");
-                                                pandecode_prop("swizzle_zero = %d", t->swizzle_zero);
-                                        }
-
-                                        pandecode_prop("unknown3 = 0x%" PRIx32, t->unknown3);
-
-                                        pandecode_prop("unknown5 = 0x%" PRIx32, t->unknown5);
-                                        pandecode_prop("unknown6 = 0x%" PRIx32, t->unknown6);
-                                        pandecode_prop("unknown7 = 0x%" PRIx32, t->unknown7);
-
-                                        pandecode_log(".payload = {\n");
-                                        pandecode_indent++;
-
-                                        /* A bunch of bitmap pointers follow.
-                                         * We work out the correct number,
-                                         * based on the mipmap/cubemap
-                                         * properties, but dump extra
-                                         * possibilities to futureproof */
-
-                                        int bitmap_count = MALI_NEGATIVE(t->nr_mipmap_levels);
-                                        bool manual_stride = f.usage2 & MALI_TEX_MANUAL_STRIDE;
-
-                                        /* Miptree for each face */
-                                        if (f.type == MALI_TEX_CUBE)
-                                                bitmap_count *= 6;
-
-                                        /* Array of textures */
-                                        bitmap_count *= MALI_NEGATIVE(t->array_size);
-
-                                        /* Stride for each element */
-                                        if (manual_stride)
-                                                bitmap_count *= 2;
-
-                                        /* Sanity check the size */
-                                        int max_count = sizeof(t->payload) / sizeof(t->payload[0]);
-                                        assert (bitmap_count <= max_count);
-
-                                        for (int i = 0; i < bitmap_count; ++i) {
-                                                /* How we dump depends if this is a stride or a pointer */
-
-                                                if ((f.usage2 & MALI_TEX_MANUAL_STRIDE) && (i & 1)) {
-                                                        /* signed 32-bit snuck in as a 64-bit pointer */
-                                                        uint64_t stride_set = t->payload[i];
-                                                        uint32_t clamped_stride = stride_set;
-                                                        int32_t stride = clamped_stride;
-                                                        assert(stride_set == clamped_stride);
-                                                        pandecode_log("(mali_ptr) %d /* stride */, \n", stride);
-                                                } else {
-                                                        char *a = pointer_as_memory_reference(t->payload[i]);
-                                                        pandecode_log("%s, \n", a);
-                                                        free(a);
-                                                }
-                                        }
-
-                                        pandecode_indent--;
-                                        pandecode_log("},\n");
-
-                                        pandecode_indent--;
-                                        pandecode_log("};\n");
-                                }
+                                if (tmem)
+                                        pandecode_texture(*u, tmem, job_no, tex);
                         }
                 }
         }
@@ -2437,67 +2552,87 @@ pandecode_fragment_job(const struct pandecode_mapped_memory *mem,
 {
         const struct mali_payload_fragment *PANDECODE_PTR_VAR(s, mem, payload);
 
-        bool fbd_dumped = false;
-
-        if (!is_bifrost && (s->framebuffer & FBD_TYPE) == MALI_SFBD) {
-                /* Only SFBDs are understood, not MFBDs. We're speculating,
-                 * based on the versioning, kernel code, etc, that the
-                 * difference is between Single FrameBuffer Descriptor and
-                 * Multiple FrmaeBuffer Descriptor; the change apparently lines
-                 * up with multi-framebuffer support being added (T7xx onwards,
-                 * including Gxx). In any event, there's some field shuffling
-                 * that we haven't looked into yet. */
-
-                pandecode_sfbd(s->framebuffer & FBD_MASK, job_no, true);
-                fbd_dumped = true;
-        } else if ((s->framebuffer & FBD_TYPE) == MALI_MFBD) {
-                /* We don't know if Bifrost supports SFBD's at all, since the
-                 * driver never uses them. And the format is different from
-                 * Midgard anyways, due to the tiler heap and scratchpad being
-                 * moved out into separate structures, so it's not clear what a
-                 * Bifrost SFBD would even look like without getting an actual
-                 * trace, which appears impossible.
-                 */
+        bool is_mfbd = (s->framebuffer & FBD_TYPE) == MALI_MFBD;
+
+        /* Bifrost theoretically may retain support for SFBD on compute jobs,
+         * but for graphics workloads with a FRAGMENT payload, use MFBD */
+
+        if (!is_mfbd && is_bifrost)
+                pandecode_msg("XXX: Bifrost fragment must use MFBD\n");
 
-                pandecode_mfbd_bfr(s->framebuffer & FBD_MASK, job_no, true);
-                fbd_dumped = true;
+        struct pandecode_fbd info;
+
+        if (is_mfbd)
+                info = pandecode_mfbd_bfr(s->framebuffer & FBD_MASK, job_no, true);
+        else
+                info = pandecode_sfbd(s->framebuffer & FBD_MASK, job_no, true);
+
+        /* Compute the tag for the tagged pointer. This contains the type of
+         * FBD (MFBD/SFBD), and in the case of an MFBD, information about which
+         * additional structures follow the MFBD header (an extra payload or
+         * not, as well as a count of render targets) */
+
+        unsigned expected_tag = is_mfbd ? MALI_MFBD : MALI_SFBD;
+
+        if (is_mfbd) {
+                if (info.has_extra)
+                        expected_tag |= MALI_MFBD_TAG_EXTRA;
+
+                expected_tag |= (MALI_POSITIVE(info.rt_count) << 2);
         }
 
-        uintptr_t p = (uintptr_t) s->framebuffer & FBD_MASK;
-        pandecode_log("struct mali_payload_fragment payload_%"PRIx64"_%d = {\n", payload, job_no);
-        pandecode_indent++;
+        if ((s->min_tile_coord | s->max_tile_coord) & ~(MALI_X_COORD_MASK | MALI_Y_COORD_MASK)) {
+                pandecode_msg("XXX: unexpected tile coordinate bits\n");
+                pandecode_prop("min_tile_coord = 0x%X\n", s->min_tile_coord);
+                pandecode_prop("max_tile_coord = 0x%X\n", s->min_tile_coord);
+        }
 
-        /* See the comments by the macro definitions for mathematical context
-         * on why this is so weird */
+        /* Extract tile coordinates */
 
-        if (MALI_TILE_COORD_FLAGS(s->max_tile_coord) || MALI_TILE_COORD_FLAGS(s->min_tile_coord))
-                pandecode_msg("Tile coordinate flag missed, replay wrong\n");
+        unsigned min_x = MALI_TILE_COORD_X(s->min_tile_coord) << MALI_TILE_SHIFT;
+        unsigned min_y = MALI_TILE_COORD_Y(s->min_tile_coord) << MALI_TILE_SHIFT;
 
-        pandecode_prop("min_tile_coord = MALI_COORDINATE_TO_TILE_MIN(%d, %d)",
-                       MALI_TILE_COORD_X(s->min_tile_coord) << MALI_TILE_SHIFT,
-                       MALI_TILE_COORD_Y(s->min_tile_coord) << MALI_TILE_SHIFT);
+        unsigned max_x = (MALI_TILE_COORD_X(s->max_tile_coord) + 1) << MALI_TILE_SHIFT;
+        unsigned max_y = (MALI_TILE_COORD_Y(s->max_tile_coord) + 1) << MALI_TILE_SHIFT;
 
-        pandecode_prop("max_tile_coord = MALI_COORDINATE_TO_TILE_MAX(%d, %d)",
-                       (MALI_TILE_COORD_X(s->max_tile_coord) + 1) << MALI_TILE_SHIFT,
-                       (MALI_TILE_COORD_Y(s->max_tile_coord) + 1) << MALI_TILE_SHIFT);
+        /* For the max, we also want the floored (rather than ceiled) version for checking */
 
-        /* If the FBD was just decoded, we can refer to it by pointer. If not,
-         * we have to fallback on offsets. */
+        unsigned max_x_f = (MALI_TILE_COORD_X(s->max_tile_coord)) << MALI_TILE_SHIFT;
+        unsigned max_y_f = (MALI_TILE_COORD_Y(s->max_tile_coord)) << MALI_TILE_SHIFT;
 
-        const char *fbd_type = s->framebuffer & MALI_MFBD ? "MALI_MFBD" : "MALI_SFBD";
+        /* Validate the coordinates are well-ordered */
 
-        /* TODO: Decode */
-        unsigned extra_flags = (s->framebuffer & ~FBD_MASK) & ~MALI_MFBD;
+        if (min_x == max_x)
+                pandecode_msg("XXX: empty X coordinates (%u = %u)\n", min_x, max_x);
+        else if (min_x > max_x)
+                pandecode_msg("XXX: misordered X coordinates (%u > %u)\n", min_x, max_x);
 
-        if (fbd_dumped)
-                pandecode_prop("framebuffer = framebuffer_%d_p | %s | 0x%X", job_no,
-                                fbd_type, extra_flags);
-        else
-                pandecode_prop("framebuffer = %s | %s | 0x%X", pointer_as_memory_reference(p),
-                                fbd_type, extra_flags);
+        if (min_y == max_y)
+                pandecode_msg("XXX: empty X coordinates (%u = %u)\n", min_x, max_x);
+        else if (min_y > max_y)
+                pandecode_msg("XXX: misordered X coordinates (%u > %u)\n", min_x, max_x);
 
-        pandecode_indent--;
-        pandecode_log("};\n");
+        /* Validate the coordinates fit inside the framebuffer. We use floor,
+         * rather than ceil, for the max coordinates, since the tile
+         * coordinates for something like an 800x600 framebuffer will actually
+         * resolve to 800x608, which would otherwise trigger a Y-overflow */
+
+        if ((min_x > info.width) || (max_x_f > info.width))
+                pandecode_msg("XXX: tile coordinates overflow in X direction\n");
+
+        if ((min_y > info.height) || (max_y_f > info.height))
+                pandecode_msg("XXX: tile coordinates overflow in Y direction\n");
+
+        /* After validation, we print */
+
+        pandecode_log("fragment (%u, %u) ... (%u, %u)\n\n", min_x, min_y, max_x, max_y);
+
+        /* The FBD is a tagged pointer */
+
+        unsigned tag = (s->framebuffer & ~FBD_MASK);
+
+        if (tag != expected_tag)
+                pandecode_msg("XXX: expected FBD tag %X but got %X\n", expected_tag, tag);
 
         return sizeof(*s);
 }