blob: 9cb0aa6f070d758b30b84a5093217554efe87712 [file] [log] [blame]
#!/usr/bin/python
"""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) 2012 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
"""
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)
new_text = new_text.replace(marker, 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 = """<!DOCTYPE html>
<!-- this file is auto-generated. DO NOT EDIT.
%(license)s
-->
<html>
<head>
<meta charset="utf-8">
<title>WebGL GLSL conformance test: %(title)s</title>
%(css)s
%(scripts)s
</head>
<body>
<canvas id="example" width="500" height="500" style="width: 16px; height: 16px;"></canvas>
<div id="description"></div>
<div id="console"></div>
</body>
<script>
"use strict";
OpenGLESTestRunner.run(%(tests_data)s);
var successfullyParsed = true;
</script>
</html>
"""
css = [
"../../resources/js-test-style.css",
"../resources/ogles-tests.css",
]
scripts = [
"../../resources/js-test-pre.js",
"../resources/webgl-test.js",
"../resources/webgl-test-utils.js",
"ogles-utils.js",
]
css_html = RelativizePaths(outname, css, '<link rel="stylesheet" href="%s" />')
scripts_html = RelativizePaths(outname, scripts, '<script src="%s"></script>')
f = WriteOpen(outname)
f.write(template % {
"license": LICENSE,
"css": css_html,
"scripts": scripts_html,
"title": os.path.basename(outname),
"tests_data": json.dumps(tests_data, indent=2)
})
f.close()
def CopyShaders(self, test, filename, outname):
"""For tests we don't actually support yet, at least copy the shaders"""
shaders = test.getElementsByTagName("shader")
for shader in shaders:
for name in ["vertshader", "fragshader"]:
s = GetElementText(shader, name)
if s and s != "empty":
CopyShader(s, filename, outname)
#
# pattern handlers.
#
def Process_compare(self, test, filename, outname):
global MATRIX_RE
valid_tags = [
["shader", "model", "glstate"],
["uniform", "vertshader", "fragshader", "filename", "depthrange"],
["name", "count", "transpose", "uniform*", "near", "far"],
]
CheckForUnknownTags(valid_tags, test)
# parse the test
shaders = test.getElementsByTagName("shader")
shaderInfos = []
for shader in shaders:
v = GetElementText(shader, "vertshader")
f = GetElementText(shader, "fragshader")
CopyShader(v, filename, outname)
CopyShader(f, filename, outname)
info = {
"vertexShader": v,
"fragmentShader": f,
}
shaderInfos.append(info)
uniformElems = shader.getElementsByTagName("uniform")
if len(uniformElems) > 0:
uniforms = {}
info["uniforms"] = uniforms
for uniformElem in uniformElems:
uniform = {"count": 1}
for child in uniformElem.childNodes:
if child.localName == None:
pass
elif child.localName == "name":
uniforms[GetText(child.childNodes)] = uniform
elif child.localName == "count":
uniform["count"] = int(GetText(child.childNodes))
elif child.localName == "transpose":
uniform["transpose"] = (GetText(child.childNodes) == "true")
else:
if "type" in uniform:
print "utype was:", uniform["type"], " found ", child.localName
raise SyntaxError
type_name = GetValidTypeName(child.localName)
uniform["type"] = type_name
valueText = GetText(child.childNodes).replace(",", " ")
uniform["value"] = [float(t) for t in valueText.split()]
m = MATRIX_RE.search(type_name)
if m:
# Why are these backward from the API?!?!?
TransposeMatrix(uniform["value"], int(m.group(1)))
data = {
"name": os.path.basename(outname),
"model": GetModel(test),
"referenceProgram": shaderInfos[1],
"testProgram": shaderInfos[0],
}
gl_states = test.getElementsByTagName("glstate")
if len(gl_states) > 0:
state = {}
data["state"] = state
for gl_state in gl_states:
for state_name in gl_state.childNodes:
if state_name.localName:
values = {}
for field in state_name.childNodes:
if field.localName:
values[field.localName] = GetText(field.childNodes)
state[state_name.localName] = values
return data
def Process_shaderload(self, test, filename, outname):
"""no need for shaderload tests"""
self.CopyShaders(test, filename, outname)
def Process_extension(self, test, filename, outname):
"""no need for extension tests"""
self.CopyShaders(test, filename, outname)
def Process_createtests(self, test, filename, outname):
Log("createtests Not implemented: %s" % filename)
self.CopyShaders(test, filename, outname)
def Process_GL2Test(self, test, filename, outname):
Log("GL2Test Not implemented: %s" % filename)
self.CopyShaders(test, filename, outname)
def Process_uniformquery(self, test, filename, outname):
Log("uniformquery Not implemented: %s" % filename)
self.CopyShaders(test, filename, outname)
def Process_egl_image_external(self, test, filename, outname):
"""no need for egl_image_external tests"""
self.CopyShaders(test, filename, outname)
def Process_dismount(self, test, filename, outname):
Log("dismount Not implemented: %s" % filename)
self.CopyShaders(test, filename, outname)
def Process_build(self, test, filename, outname):
"""don't need build tests"""
valid_tags = [
["shader", "compstat", "linkstat"],
["vertshader", "fragshader"],
]
CheckForUnknownTags(valid_tags, test)
shader = test.getElementsByTagName("shader")
if not shader:
return None
vs = GetElementText(shader[0], "vertshader")
fs = GetElementText(shader[0], "fragshader")
if vs and vs != "empty":
CopyShader(vs, filename, outname)
if fs and fs != "empty":
CopyShader(fs, filename, outname)
data = {
"name": os.path.basename(outname),
"compstat": bool(GetBoolElement(test, "compstat")),
"linkstat": bool(GetBoolElement(test, "linkstat")),
"testProgram": {
"vertexShader": vs,
"fragmentShader": fs,
},
}
attach = test.getElementsByTagName("attach")
if len(attach) > 0:
data["attachError"] = GetElementText(attach[0], "attacherror")
return data
def Process_coverage(self, test, filename, outname):
Log("coverage Not implemented: %s" % filename)
self.CopyShaders(test, filename, outname)
def Process_attributes(self, test, filename, outname):
Log("attributes Not implemented: %s" % filename)
self.CopyShaders(test, filename, outname)
def Process_fixed(self, test, filename, outname):
"""no need for fixed function tests"""
self.CopyShaders(test, filename, outname)
def main(argv):
"""This is the main function."""
global VERBOSE
parser = OptionParser()
parser.add_option(
"-v", "--verbose", action="store_true",
help="prints more output.")
(options, args) = parser.parse_args(args=argv)
if len(args) < 1:
pass # fix me
os.chdir(os.path.dirname(__file__) or '.')
VERBOSE = options.verbose
filename = args[0]
test_reader = TestReader(filename)
test_reader.ReadTests(filename)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))