Hi,

On 11/09/2015 08:47 PM, Matt Turner wrote:
On Mon, Nov 9, 2015 at 10:46 AM, Ilia Mirkin <imir...@alum.mit.edu> wrote:
I used this script in conjunction with ST_DUMP_SHADERS. What other way is there?

Some local hack and we should probably finish and upstream.

Did anything happen with this?

I had to rewrite split-to-files because it didn't output all (ARB) shaders and it picks the wrong one [1] when application re-uses same program numbers.

[1] It picked first one, although I think almost always the last one will be most interesting. Last one will also allow easily dumping different shader sets (that re-use same program numbers) from the same application.

RFC patches for my changes are attached.


        - Eero


>From c52f02ff664af269fa5268627624fe94c647ad37 Mon Sep 17 00:00:00 2001
From: Eero Tamminen <eero.t.tammi...@intel.com>
Date: Wed, 9 Dec 2015 16:29:43 +0200
Subject: [PATCH 1/2] Rewrite split-to-files.py to fix it

This rewrite improves on previous version in following ways:

* Improve recognization of shader end.

* Remove extra lines after shader ends (in normal shaders anything
  after last line with '}' that closes main(), and in ARB shaders, lines
  after END).

* Optimize parsing by using compiled regexps.

* Calculate (md5) hashes for normalized (single line comments and white
  space removed) shader contents and identify duplicate shaders with
  those

* If program gets a new shader, output the latest one.  It should be
  more relevant one.  It also allows dumping different shader sets
  e.g. shaders for startup / game menu vs. actual game play, just by
  running application further before killing it.

* When application replaces ARB shaders, continue instead of claiming
  to be done & exiting.  Same program numbers can be used if
  application removes previous programs.

* Tell user which shaders were duplicates and which were replaced by
  which shaders.

* Remove duplicate programs based on shader stage hashes (of their
  normalized sources) and tell user about this.

* Output shader stage sources in 3D pipeline order.

* Give ARB shaders different file name from normal shaders.
---
 split-to-files.py |  409 +++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 306 insertions(+), 103 deletions(-)

diff --git a/split-to-files.py b/split-to-files.py
index 151681e..7150622 100755
--- a/split-to-files.py
+++ b/split-to-files.py
@@ -2,122 +2,300 @@
 
 import re
 import os
+import hashlib
 import argparse
 
 
+class ShaderBase:
+    def __init__(self, prog, stage):
+        self.lines = []
+        self.progid = prog # latest
+        self.programs = {self.progid: True} # all
+        self.stage = stage
+        self.hash = None
+        self.hashed_len = 0
+        self.done = False
+        self.replaced = False
+        # filled by subclasses
+        self.shadernum = 0
+        self.req_start = None
+        self.req_end = None
+        self.warn = None
+
+    def append_line(self, line):
+        assert not self.done
+        self.lines.append(line)
+
+    def is_finished(self, line):
+        return False
+
+    def get_source(self):
+        assert self.done
+        return "\n".join(self.lines) + "\n"
+
+    def add_program(self, dup):
+        self.programs[dup.progid] = True
+
+    def del_program(self, dup):
+        del(self.programs[dup.progid])
+
+    def get_hash(self):
+        if self.hash:
+            return self.hash
+        assert self.done
+
+        # source without single line comments & whitespace
+        normalized = []
+        for line in self.lines:
+            offset = line.find("//")
+            if offset >= 0:
+                line = line[:offset]
+            # Python2: line = line.translate(None, " \t")
+            line = line.translate({' ': None, '\t': None})
+            if line:
+                normalized.append(line)
+        normalized = "".join(normalized).encode()
+
+        # create hash for normalized source
+        md5 = hashlib.md5()
+        self.hashed_len = len(normalized)
+        md5.update(normalized)
+        self.hash = md5.hexdigest()
+        return self.hash
+
+    def check_conflict(self, dup):
+        assert self.done and dup.done
+        if self.hash == dup.hash and self.hashed_len != dup.hashed_len:
+            print("ERROR: hash collision with %s" % dup.get_info())
+            exit(-1)
+        if dup.stage != dup.stage:
+            # same shader for different stage, this isn't handled correctly at the moment
+            # all code assumes that each hash/shader represents just one shader stage
+            print("ERROR: duplicate is for different shader stage (%s)" % dup.stage)
+            exit(-1)
+
+    def get_info(self):
+        assert None # must be subclassed
+
+    def show_info(self):
+        print(self.get_info())
+
+
+class ShaderARB(ShaderBase):
+    def __init__(self, prog, stage):
+        ShaderBase.__init__(self, prog, stage)
+        self.progid = "%s-ARB_%s" % (prog, stage)
+
+        self.req_start = "GL_ARB_{0}_program".format(self.stage)
+        # INTEL_DEBUG won't output anything for ARB programs unless you draw
+        self.req_end = "[test]\ndraw rect -1 -1 1 2"
+
+    def is_finished(self, line):
+        if line == "END":
+            self.lines.append(line)
+            self.done = True
+            return True
+        return False
+
+    def do_cleanup(self):
+        if self.done:
+            return
+        self.done = True
+        idx = len(self.lines)
+        while idx > 0:
+            idx -= 1
+            if "END" in self.lines[idx]:
+                # shader end
+                return
+            del(self.lines[idx])
+        print("- ERROR: empty shader!")
+
+    def get_info(self):
+        return "program %s shader" % self.progid
+
+    def get_version(self):
+        return 0
+
+    def write_stage(self, out):
+        out.write("[{0} program]\n".format(self.stage))
+        out.write(self.get_source())
+
+
+class Shader(ShaderBase):
+    map = {
+     "vertex": "[vertex shader]",
+     "fragment": "[fragment shader]",
+     "geometry": "[geometry shader]",
+     "tess ctrl": "[tessellation control shader]",
+     "tess eval": "[tessellation evaluation shader]"
+    }
+    r_version = re.compile(r"^#version (\d\d\d)")
+
+    def __init__(self, prog, stage, shadernum):
+        ShaderBase.__init__(self, prog, stage)
+        self.shadernum = shadernum
+
+    def get_info(self):
+        return "program %s %s shader %d" % (self.progid, self.stage, self.shadernum)
+
+    def do_cleanup(self):
+        if self.done:
+            return
+        self.done = True
+        idx = len(self.lines)
+        while idx > 0:
+            idx -= 1
+            if "}" in self.lines[idx]:
+                # main() end
+                return
+            del(self.lines[idx])
+        print("- ERROR: empty shader!")
+
+    def get_version(self):
+        source = self.get_source()
+        match = self.r_version.match(source)
+        if match:
+            return int(match.group(1), 10)
+        return 110
+
+    def write_stage(self, out):
+        out.write("%s\n" % self.map[self.stage])
+        out.write(self.get_source())
+
+
+def finish_shader(shader, programs, shaders):
+    shader.do_cleanup()
+
+    hashed = shader.get_hash()
+    #print("- hash %s" % hashed)
+
+    # link shader hash to a program stage
+    if not shader.progid in programs:
+        programs[shader.progid] = {}
+    prog_dict = programs[shader.progid]
+    if shader.stage in prog_dict and hashed != prog_dict[shader.stage]:
+        dup = shaders[prog_dict[shader.stage]]
+        assert shader.progid in dup.programs
+        print("- replacing earlier (different) shader")
+        dup.del_program(shader)
+        dup.replaced = True
+    prog_dict[shader.stage] = hashed
+
+    # add shader to shader dict, keyed by hash, check for duplicates
+    if hashed in shaders:
+        dup = shaders[hashed]
+        if shader.progid in dup.programs:
+            print("- recompiled earlier one")
+        else:
+            replaced = ""
+            if dup.replaced:
+                replaced = "(replaced) "
+            print("- duplicate of %s%s" % (replaced, dup.get_info()))
+            dup.add_program(shader)
+        shader.check_conflict(dup)
+    else:
+        shaders[hashed] = shader
+
+
 def parse_input(infile):
-    shaders = dict()
-    programs = dict()
-    shadertuple = ("bad", 0)
-    prognum = ""
+    r_decl = re.compile(r"GLSL (.*) shader (.*) source for linked program (.*):")
+    r_arb = re.compile(r"ARB_([^_]*)_program source for program (.*):")
+    r_end = re.compile(r"(GLSL IR|Mesa IR|GLSL source) for")
+    shaders = {}
+    programs = {}
     reading = False
-    is_glsl = True
 
     for line in infile.splitlines():
-        declmatch = re.match(
-            r"GLSL (.*) shader (.*) source for linked program (.*):", line)
-        arbmatch = re.match(
-            r"ARB_([^_]*)_program source for program (.*):", line)
-        if declmatch:
-            shadertype = declmatch.group(1)
-            shadernum = declmatch.group(2)
-            prognum = declmatch.group(3)
-            shadertuple = (shadertype, shadernum)
 
+        match_decl = r_decl.match(line)
+        match_arb = r_arb.match(line)
+
+        if reading:
+            if match_decl or match_arb or r_end.match(line) or shader.is_finished(line):
+                finish_shader(shader, programs, shaders)
+                reading = False
+            else:
+                shader.append_line(line)
+                continue
+
+        if match_decl:
+            prognum = match_decl.group(3)
             # don't save driver-internal shaders.
             if prognum == "0":
                 continue
 
-            if prognum not in shaders:
-                shaders[prognum] = dict()
-            if shadertuple in shaders[prognum]:
-                print("Warning: duplicate", shadertype, " shader ", shadernum,
-                      "in program", prognum, "...tossing old shader.")
-            shaders[prognum][shadertuple] = ''
+            shadernum = int(match_decl.group(2))
+            shadertype = match_decl.group(1)
+            shader = Shader(prognum, shadertype, shadernum)
+            shader.show_info()
             reading = True
-            is_glsl = True
-            print("Reading program {0} {1} shader {2}".format(
-                prognum, shadertype, shadernum))
-        elif arbmatch:
-            shadertype = arbmatch.group(1)
-            prognum = arbmatch.group(2)
-            if prognum in programs:
-                print("dupe!")
-                exit(1)
-            programs[prognum] = (shadertype, '')
+            continue
+
+        if match_arb:
+            shadertype = match_arb.group(1)
+            prognum = match_arb.group(2)
+            shader = ShaderARB(prognum, shadertype)
+            shader.show_info()
             reading = True
-            is_glsl = False
-            print("Reading program {0} {1} shader".format(prognum, shadertype))
-        elif re.match("GLSL IR for ", line):
-            reading = False
-        elif re.match("Mesa IR for ", line):
-            reading = False
-        elif re.match("GLSL source for ", line):
-            reading = False
-        elif reading:
-            if is_glsl:
-                shaders[prognum][shadertuple] += line + '\n'
-            else:
-                type, source = programs[prognum]
-                programs[prognum] = (type, ''.join([source, line, '\n']))
-
-    return (shaders, programs)
-
-
-def write_shader_test(filename, shaders):
-    print("Writing {0}".format(filename))
-    out = open(filename, 'w')
-
-    min_version = 110
-    for stage, num in shaders:
-        shader = shaders[(stage, num)]
-        m = re.match(r"^#version (\d\d\d)", shader)
-        if m:
-            version = int(m.group(1), 10)
-            if version > min_version:
-                min_version = version
-
-    out.write("[require]\n")
-    out.write("GLSL >= %.2f\n" % (min_version / 100.))
-    out.write("\n")
-
-    for stage, num in shaders:
-        if stage == "vertex":
-            out.write("[vertex shader]\n")
-        elif stage == "fragment":
-            out.write("[fragment shader]\n")
-        elif stage == "geometry":
-            out.write("[geometry shader]\n")
-        elif stage == "tess ctrl":
-            out.write("[tessellation control shader]\n")
-        elif stage == "tess eval":
-            out.write("[tessellation evaluation shader]\n")
+            continue
+
+    return programs, shaders
+
+
+def remove_duplicates(programs):
+    "parse_input() just identifies duplicate shaders, this removes programs which all stages are duplicate"
+    toremove = []
+    hash2prog = {}
+    for program, stages in programs.items():
+        # tuple of hashes for all stages in a program
+        hashes = tuple(sorted(stages.values()))
+        if hashes in hash2prog:
+            toremove.append(program)
+            prevprog = hash2prog[hashes]
+            print("removing program %s which is duplicate of program %s" % (program, prevprog))
         else:
-            assert False, stage
-        out.write(shaders[(stage, num)])
-
-    out.close()
-
-def write_arb_shader_test(filename, type, source):
-    print("Writing {0}".format(filename))
-    out = open(filename, 'w')
-    out.write("[require]\n")
-    out.write("GL_ARB_{0}_program\n".format(type))
-    out.write("\n")
-    out.write("[{0} program]\n".format(type))
-    out.write(source)
-    # INTEL_DEBUG won't output anything for ARB programs unless you draw
-    out.write("\n[test]\ndraw rect -1 -1 1 2\n");
-    out.close()
-
-def write_files(directory, shaders, programs):
-    for prog in shaders:
-        write_shader_test("{0}/{1}.shader_test".format(directory, prog),
-                          shaders[prog])
-    for prognum in programs:
-        prog = programs[prognum]
-        write_arb_shader_test("{0}/{1}p-{2}.shader_test".format(directory,
-            prog[0][0], prognum), prog[0], prog[1])
+            hash2prog[hashes] = program
+
+    if toremove:
+        for program in toremove:
+            del(programs[program])
+    else:
+        print("- No duplicate programs")
+
+
+def get_req_check(shaders, hashes):
+    "do some extra program checks and return appropriate require section content"
+    types = {}
+    versions = []
+    for stage_hash in hashes:
+        shader = shaders[stage_hash]
+        versions.append(shader.get_version())
+        types[shader.__class__] = True
+        if shader.warn:
+            print("WARNING: %s" % shader.warn)
+
+    if len(types) > 1:
+        print("WARNING: program %s mixes different shader types in its stages" % shader.progid)
+
+    min_version = max(versions)
+    if min_version:
+        return "GLSL >= %.2f" % (min_version / 100.)
+    else:
+        # ARB
+        first_stage = shaders[list(hashes)[0]]
+        return first_stage.req_start
+
+
+def stage_order(stage):
+    order = {
+     "vertex": 1, 
+     "tess ctrl": 2,
+     "tess eval": 3,
+     "geometry": 4,
+     "fragment": 5
+    }
+    return order[stage]
+
 
 def main():
     parser = argparse.ArgumentParser()
@@ -125,14 +303,39 @@ def main():
     parser.add_argument('mesadebug', help='MESA_GLSL=dump output file')
     args = parser.parse_args()
 
+    print("Parsing...")
     dirname = "shaders/{0}".format(args.appname)
     if not os.path.isdir(dirname):
         os.mkdir(dirname)
 
     with open(args.mesadebug, 'r') as infile:
-        shaders, programs = parse_input(infile.read())
+        programs, shaders = parse_input(infile.read())
+
+    print("\nRemoving duplicate programs...")
+    remove_duplicates(programs)
+
+    print("\nDumping...")
+    for program, stage_dict in programs.items():
+        filename = "%s/%s.shader_test" % (dirname, program)
+        print("Writing {0}".format(filename))
+        out = open(filename, 'w')
+
+        hashes = stage_dict.values()
+        out.write("[require]\n")
+        out.write("%s\n" % get_req_check(shaders, hashes))
+
+        stages = sorted(stage_dict.keys(), key=stage_order)
+        for stage in stages:
+            out.write("\n")
+            hashed = stage_dict[stage]
+            shader = shaders[hashed]
+            shader.write_stage(out)
+
+        if shader.req_end:
+            out.write("\n%s\n" % shader.req_end)
+
+        out.close()
 
-    write_files(dirname, shaders, programs)
 
 if __name__ == "__main__":
     main()
-- 
1.7.10.4

>From d0657bed5a7592bf4bd428775129dec3376f1b6d Mon Sep 17 00:00:00 2001
From: Eero Tamminen <eero.t.tammi...@intel.com>
Date: Wed, 9 Dec 2015 16:50:22 +0200
Subject: [PATCH 2/2] Update README

* Note how to get different shader sets from same program with
  the rewritten split-to-files.py script
* Update list of supported drivers
* remove "env" (unecessary)
* More white space for readability
---
 README |   23 +++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/README b/README
index 06294c9..31355c2 100644
--- a/README
+++ b/README
@@ -4,18 +4,26 @@ A giant pile of shaders from various apps, for whatever purpose.  In
 particular, we use it to capture assembly output of the shader
 compiler for analysis of regressions in compiler behavior.
 
-Currently it supports Mesa's i965 and radeonsi drivers.
+Currently it supports Mesa's i965, radeonsi, freedreno drivers.
+
 
 === Capturing shaders ===
-env MESA_GLSL=dump appname |& tee log
+
+MESA_GLSL=dump appname |& tee log
 ./split-to-files.py appname log
-# clean up resulting files, as the parsing is just an assist, not actually
-# complete.
+# check & clean up resulting files in case log file had extra content
 $EDITOR shaders/appname/*
 
+If application replaces a lot of shaders without them being duplicate,
+collect logs from different application stages (e.g. startup and
+gameplay) by killing it at suitable places.  ./split-to-files.py will
+save the latest version of each shader program sources in each case.
+
+
 === i965 Usage ===
 
 === Running shaders ===
+
 ./run shaders 2> err | tee new-run
 
 # To run just a subset:
@@ -30,6 +38,7 @@ To compile shaders for an i965 PCI ID different from your system, pass
 to run.
 
 === Analysis ===
+
 ./report.py old-run new-run
 
 
@@ -44,8 +53,10 @@ Note that a debug mesa build required (ie. --enable-debug)
 -1 option for disabling multi-threading is required to avoid garbled shader dumps.
 
 === Analysis ===
+
 ./si-report.py old-run new-run
 
+
 === freedreno Usage ===
 
 === Running shaders ===
@@ -57,13 +68,17 @@ Note that a debug mesa build required (ie. --enable-debug)
 -1 option for disabling multi-threading is required to avoid garbled shader dumps.
 
 === Analysis ===
+
 ./fd-report.py old-run new-run
 
+
 === Dependencies ===
+
 run requires some GNU C extensions, render nodes (/dev/dri/renderD128),
 libepoxy, OpenMP, and Mesa configured with --with-egl-platforms=x11,drm
 
 === jemalloc ===
+
 Since run compiles shaders in different threads, malloc/free locking overhead
 from inside Mesa can be expensive. Preloading jemalloc can cut significant
 amounts of time:
-- 
1.7.10.4

_______________________________________________
mesa-dev mailing list
mesa-dev@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/mesa-dev

Reply via email to