#! /usr/bin/env python2 """generates tests from OpenGL ES 2.0 .run/.test files.""" import os import os.path import sys import re import json import shutil from optparse import OptionParser from xml.dom.minidom import parse if sys.version < '2.6': print 'Wrong Python Version !!!: Need >= 2.6' sys.exit(1) # each shader test generates up to 3 512x512 images. # a 512x512 image takes 1meg of memory so set this # number apporpriate for the platform with # the smallest memory issue. At 8 that means # at least 24 meg is needed to run the test. MAX_TESTS_PER_SET = 8 VERBOSE = False FILTERS = [ re.compile("GL/"), ] LICENSE = """ Copyright (c) 2019 The Khronos Group Inc. Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt file. """.strip() COMMENT_RE = re.compile("/\*\n\*\*\s+Copyright.*?\*/", re.IGNORECASE | re.DOTALL) REMOVE_COPYRIGHT_RE = re.compile("\/\/\s+Copyright.*?\n", re.IGNORECASE | re.DOTALL) MATRIX_RE = re.compile("Matrix(\\d)") VALID_UNIFORM_TYPES = [ "uniform1f", "uniform1fv", "uniform1fv", "uniform1i", "uniform1iv", "uniform1iv", "uniform2f", "uniform2fv", "uniform2fv", "uniform2i", "uniform2iv", "uniform2iv", "uniform3f", "uniform3fv", "uniform3fv", "uniform3i", "uniform3iv", "uniform3iv", "uniform4f", "uniform4fv", "uniform4fv", "uniform4i", "uniform4iv", "uniform4ivy", "uniformMatrix2fv", "uniformMatrix2fv", "uniformMatrix3fv", "uniformMatrix3fv", "uniformMatrix4fv", "uniformMatrix4fv", ] SUBSTITUTIONS = [ ("uniformmat3fv", "uniformMatrix3fv"), ("uniformmat4fv", "uniformMatrix4fv"), ] def Log(msg): global VERBOSE if VERBOSE: print msg def TransposeMatrix(values, dim): size = dim * dim count = len(values) / size for m in range(0, count): offset = m * size for i in range(0, dim): for j in range(i + 1, dim): t = values[offset + i * dim + j] values[offset + i * dim + j] = values[offset + j * dim + i] values[offset + j * dim + i] = t def GetValidTypeName(type_name): global VALID_UNIFORM_TYPES global SUBSTITUTIONS for subst in SUBSTITUTIONS: type_name = type_name.replace(subst[0], subst[1]) if not type_name in VALID_UNIFORM_TYPES: print "unknown type name: ", type_name raise SyntaxError return type_name def WriteOpen(filename): dirname = os.path.dirname(filename) if len(dirname) > 0 and not os.path.exists(dirname): os.makedirs(dirname) return open(filename, "wb") class TxtWriter(): def __init__(self, filename): self.filename = filename self.lines = [] def Write(self, line): self.lines.append(line) def Close(self): if len(self.lines) > 0: Log("Writing: %s" % self.filename) f = WriteOpen(self.filename) f.write("# this file is auto-generated. DO NOT EDIT.\n") f.write("".join(self.lines)) f.close() def ReadFileAsLines(filename): f = open(filename, "r") lines = f.readlines() f.close() return [line.strip() for line in lines] def ReadFile(filename): f = open(filename, "r") content = f.read() f.close() return content.replace("\r\n", "\n") def Chunkify(list, chunk_size): """divides an array into chunks of chunk_size""" return [list[i:i + chunk_size] for i in range(0, len(list), chunk_size)] def GetText(nodelist): """Gets the text of from a list of nodes""" rc = [] for node in nodelist: if node.nodeType == node.TEXT_NODE: rc.append(node.data) return ''.join(rc) def GetElementText(node, name): """Gets the text of an element""" elements = node.getElementsByTagName(name) if len(elements) > 0: return GetText(elements[0].childNodes) else: return None def GetBoolElement(node, name): text = GetElementText(node, name) return text.lower() == "true" def GetModel(node): """Gets the model""" model = GetElementText(node, "model") if model and len(model.strip()) == 0: elements = node.getElementsByTagName("model") if len(elements) > 0: model = GetElementText(elements[0], "filename") return model def RelativizePaths(base, paths, template): """converts paths to relative paths""" rels = [] for p in paths: #print "---" #print "base: ", os.path.abspath(base) #print "path: ", os.path.abspath(p) relpath = os.path.relpath(os.path.abspath(p), os.path.dirname(os.path.abspath(base))).replace("\\", "/") #print "rel : ", relpath rels.append(template % relpath) return "\n".join(rels) def CopyFile(filename, src, dst): s = os.path.abspath(os.path.join(os.path.dirname(src), filename)) d = os.path.abspath(os.path.join(os.path.dirname(dst), filename)) dst_dir = os.path.dirname(d) if not os.path.exists(dst_dir): os.makedirs(dst_dir) shutil.copyfile(s, d) def CopyShader(filename, src, dst): s = os.path.abspath(os.path.join(os.path.dirname(src), filename)) d = os.path.abspath(os.path.join(os.path.dirname(dst), filename)) text = ReadFile(s) # By agreement with the Khronos OpenGL working group we are allowed # to open source only the .vert and .frag files from the OpenGL ES 2.0 # conformance tests. All other files from the OpenGL ES 2.0 conformance # tests are not included. marker = "insert-copyright-here" new_text = COMMENT_RE.sub(marker, text) if new_text == text: print "no matching license found:", s raise RuntimeError new_text = REMOVE_COPYRIGHT_RE.sub("", new_text) glsl_license = '/*\n' + LICENSE + '\n*/' new_text = new_text.replace(marker, glsl_license) f = WriteOpen(d) f.write(new_text) f.close() def IsOneOf(string, regexs): for regex in regexs: if re.match(regex, string): return True return False def CheckForUnknownTags(valid_tags, node, depth=1): """do a hacky check to make sure we're not missing something.""" for child in node.childNodes: if child.localName and not IsOneOf(child.localName, valid_tags[0]): print "unsupported tag:", child.localName print "depth:", depth raise SyntaxError else: if len(valid_tags) > 1: CheckForUnknownTags(valid_tags[1:], child, depth + 1) def IsFileWeWant(filename): for f in FILTERS: if f.search(filename): return True return False class TestReader(): """class to read and parse tests""" def __init__(self, basepath): self.tests = [] self.modes = {} self.patterns = {} self.basepath = basepath def Print(self, msg): if self.verbose: print msg def MakeOutPath(self, filename): relpath = os.path.relpath(os.path.abspath(filename), os.path.dirname(os.path.abspath(self.basepath))) return relpath def ReadTests(self, filename): """reads a .run file and parses.""" Log("reading %s" % filename) outname = self.MakeOutPath(filename + ".txt") f = TxtWriter(outname) dirname = os.path.dirname(filename) lines = ReadFileAsLines(filename) count = 0 tests_data = [] for line in lines: if len(line) > 0 and not line.startswith("#"): fname = os.path.join(dirname, line) if line.endswith(".run"): if self.ReadTests(fname): f.Write(line + ".txt\n") count += 1 elif line.endswith(".test"): tests_data.extend(self.ReadTest(fname)) else: print "Error in %s:%d:%s" % (filename, count, line) raise SyntaxError() if len(tests_data): global MAX_TESTS_PER_SET sets = Chunkify(tests_data, MAX_TESTS_PER_SET) id = 1 for set in sets: suffix = "_%03d_to_%03d" % (id, id + len(set) - 1) test_outname = self.MakeOutPath(filename + suffix + ".html") if os.path.basename(test_outname).startswith("input.run"): dname = os.path.dirname(test_outname) folder_name = os.path.basename(dname) test_outname = os.path.join(dname, folder_name + suffix + ".html") self.WriteTests(filename, test_outname, {"tests":set}) f.Write(os.path.basename(test_outname) + "\n") id += len(set) count += 1 f.Close() return count def ReadTest(self, filename): """reads a .test file and parses.""" Log("reading %s" % filename) dom = parse(filename) tests = dom.getElementsByTagName("test") tests_data = [] outname = self.MakeOutPath(filename + ".html") for test in tests: if not IsFileWeWant(filename): self.CopyShaders(test, filename, outname) else: test_data = self.ProcessTest(test, filename, outname, len(tests_data)) if test_data: tests_data.append(test_data) return tests_data def ProcessTest(self, test, filename, outname, id): """Process a test""" mode = test.getAttribute("mode") pattern = test.getAttribute("pattern") self.modes[mode] = 1 self.patterns[pattern] = 1 Log ("%d: mode: %s pattern: %s" % (id, mode, pattern)) method = getattr(self, 'Process_' + pattern) test_data = method(test, filename, outname) if test_data: test_data["pattern"] = pattern return test_data def WriteTests(self, filename, outname, tests_data): Log("Writing %s" % outname) template = """ %(license)s