PageRenderTime 54ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/sdk/tests/conformance/ogles/process-ogles2-tests.py

https://github.com/gabadie/WebGL
Python | 587 lines | 559 code | 14 blank | 14 comment | 15 complexity | 8c7b6f94c1399c7d7cf88703e62608ab MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
  1. #!/usr/bin/python
  2. """generates tests from OpenGL ES 2.0 .run/.test files."""
  3. import os
  4. import os.path
  5. import sys
  6. import re
  7. import json
  8. import shutil
  9. from optparse import OptionParser
  10. from xml.dom.minidom import parse
  11. if sys.version < '2.6':
  12. print 'Wrong Python Version !!!: Need >= 2.6'
  13. sys.exit(1)
  14. # each shader test generates up to 3 512x512 images.
  15. # a 512x512 image takes 1meg of memory so set this
  16. # number apporpriate for the platform with
  17. # the smallest memory issue. At 8 that means
  18. # at least 24 meg is needed to run the test.
  19. MAX_TESTS_PER_SET = 8
  20. VERBOSE = False
  21. FILTERS = [
  22. re.compile("GL/"),
  23. ]
  24. LICENSE = """
  25. /*
  26. ** Copyright (c) 2012 The Khronos Group Inc.
  27. **
  28. ** Permission is hereby granted, free of charge, to any person obtaining a
  29. ** copy of this software and/or associated documentation files (the
  30. ** "Materials"), to deal in the Materials without restriction, including
  31. ** without limitation the rights to use, copy, modify, merge, publish,
  32. ** distribute, sublicense, and/or sell copies of the Materials, and to
  33. ** permit persons to whom the Materials are furnished to do so, subject to
  34. ** the following conditions:
  35. **
  36. ** The above copyright notice and this permission notice shall be included
  37. ** in all copies or substantial portions of the Materials.
  38. **
  39. ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  40. ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  41. ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  42. ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  43. ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  44. ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  45. ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
  46. */
  47. """
  48. COMMENT_RE = re.compile("/\*\n\*\*\s+Copyright.*?\*/",
  49. re.IGNORECASE | re.DOTALL)
  50. REMOVE_COPYRIGHT_RE = re.compile("\/\/\s+Copyright.*?\n",
  51. re.IGNORECASE | re.DOTALL)
  52. MATRIX_RE = re.compile("Matrix(\\d)")
  53. VALID_UNIFORM_TYPES = [
  54. "uniform1f",
  55. "uniform1fv",
  56. "uniform1fv",
  57. "uniform1i",
  58. "uniform1iv",
  59. "uniform1iv",
  60. "uniform2f",
  61. "uniform2fv",
  62. "uniform2fv",
  63. "uniform2i",
  64. "uniform2iv",
  65. "uniform2iv",
  66. "uniform3f",
  67. "uniform3fv",
  68. "uniform3fv",
  69. "uniform3i",
  70. "uniform3iv",
  71. "uniform3iv",
  72. "uniform4f",
  73. "uniform4fv",
  74. "uniform4fv",
  75. "uniform4i",
  76. "uniform4iv",
  77. "uniform4ivy",
  78. "uniformMatrix2fv",
  79. "uniformMatrix2fv",
  80. "uniformMatrix3fv",
  81. "uniformMatrix3fv",
  82. "uniformMatrix4fv",
  83. "uniformMatrix4fv",
  84. ]
  85. SUBSTITUTIONS = [
  86. ("uniformmat3fv", "uniformMatrix3fv"),
  87. ("uniformmat4fv", "uniformMatrix4fv"),
  88. ]
  89. def Log(msg):
  90. global VERBOSE
  91. if VERBOSE:
  92. print msg
  93. def TransposeMatrix(values, dim):
  94. size = dim * dim
  95. count = len(values) / size
  96. for m in range(0, count):
  97. offset = m * size
  98. for i in range(0, dim):
  99. for j in range(i + 1, dim):
  100. t = values[offset + i * dim + j]
  101. values[offset + i * dim + j] = values[offset + j * dim + i]
  102. values[offset + j * dim + i] = t
  103. def GetValidTypeName(type_name):
  104. global VALID_UNIFORM_TYPES
  105. global SUBSTITUTIONS
  106. for subst in SUBSTITUTIONS:
  107. type_name = type_name.replace(subst[0], subst[1])
  108. if not type_name in VALID_UNIFORM_TYPES:
  109. print "unknown type name: ", type_name
  110. raise SyntaxError
  111. return type_name
  112. def WriteOpen(filename):
  113. dirname = os.path.dirname(filename)
  114. if len(dirname) > 0 and not os.path.exists(dirname):
  115. os.makedirs(dirname)
  116. return open(filename, "wb")
  117. class TxtWriter():
  118. def __init__(self, filename):
  119. self.filename = filename
  120. self.lines = []
  121. def Write(self, line):
  122. self.lines.append(line)
  123. def Close(self):
  124. if len(self.lines) > 0:
  125. Log("Writing: %s" % self.filename)
  126. f = WriteOpen(self.filename)
  127. f.write("# this file is auto-generated. DO NOT EDIT.\n")
  128. f.write("".join(self.lines))
  129. f.close()
  130. def ReadFileAsLines(filename):
  131. f = open(filename, "r")
  132. lines = f.readlines()
  133. f.close()
  134. return [line.strip() for line in lines]
  135. def ReadFile(filename):
  136. f = open(filename, "r")
  137. content = f.read()
  138. f.close()
  139. return content.replace("\r\n", "\n")
  140. def Chunkify(list, chunk_size):
  141. """divides an array into chunks of chunk_size"""
  142. return [list[i:i + chunk_size] for i in range(0, len(list), chunk_size)]
  143. def GetText(nodelist):
  144. """Gets the text of from a list of nodes"""
  145. rc = []
  146. for node in nodelist:
  147. if node.nodeType == node.TEXT_NODE:
  148. rc.append(node.data)
  149. return ''.join(rc)
  150. def GetElementText(node, name):
  151. """Gets the text of an element"""
  152. elements = node.getElementsByTagName(name)
  153. if len(elements) > 0:
  154. return GetText(elements[0].childNodes)
  155. else:
  156. return None
  157. def GetBoolElement(node, name):
  158. text = GetElementText(node, name)
  159. return text.lower() == "true"
  160. def GetModel(node):
  161. """Gets the model"""
  162. model = GetElementText(node, "model")
  163. if model and len(model.strip()) == 0:
  164. elements = node.getElementsByTagName("model")
  165. if len(elements) > 0:
  166. model = GetElementText(elements[0], "filename")
  167. return model
  168. def RelativizePaths(base, paths, template):
  169. """converts paths to relative paths"""
  170. rels = []
  171. for p in paths:
  172. #print "---"
  173. #print "base: ", os.path.abspath(base)
  174. #print "path: ", os.path.abspath(p)
  175. relpath = os.path.relpath(os.path.abspath(p), os.path.dirname(os.path.abspath(base))).replace("\\", "/")
  176. #print "rel : ", relpath
  177. rels.append(template % relpath)
  178. return "\n".join(rels)
  179. def CopyFile(filename, src, dst):
  180. s = os.path.abspath(os.path.join(os.path.dirname(src), filename))
  181. d = os.path.abspath(os.path.join(os.path.dirname(dst), filename))
  182. dst_dir = os.path.dirname(d)
  183. if not os.path.exists(dst_dir):
  184. os.makedirs(dst_dir)
  185. shutil.copyfile(s, d)
  186. def CopyShader(filename, src, dst):
  187. s = os.path.abspath(os.path.join(os.path.dirname(src), filename))
  188. d = os.path.abspath(os.path.join(os.path.dirname(dst), filename))
  189. text = ReadFile(s)
  190. # By agreement with the Khronos OpenGL working group we are allowed
  191. # to open source only the .vert and .frag files from the OpenGL ES 2.0
  192. # conformance tests. All other files from the OpenGL ES 2.0 conformance
  193. # tests are not included.
  194. marker = "insert-copyright-here"
  195. new_text = COMMENT_RE.sub(marker, text)
  196. if new_text == text:
  197. print "no matching license found:", s
  198. raise RuntimeError
  199. new_text = REMOVE_COPYRIGHT_RE.sub("", new_text)
  200. new_text = new_text.replace(marker, LICENSE)
  201. f = WriteOpen(d)
  202. f.write(new_text)
  203. f.close()
  204. def IsOneOf(string, regexs):
  205. for regex in regexs:
  206. if re.match(regex, string):
  207. return True
  208. return False
  209. def CheckForUnknownTags(valid_tags, node, depth=1):
  210. """do a hacky check to make sure we're not missing something."""
  211. for child in node.childNodes:
  212. if child.localName and not IsOneOf(child.localName, valid_tags[0]):
  213. print "unsupported tag:", child.localName
  214. print "depth:", depth
  215. raise SyntaxError
  216. else:
  217. if len(valid_tags) > 1:
  218. CheckForUnknownTags(valid_tags[1:], child, depth + 1)
  219. def IsFileWeWant(filename):
  220. for f in FILTERS:
  221. if f.search(filename):
  222. return True
  223. return False
  224. class TestReader():
  225. """class to read and parse tests"""
  226. def __init__(self, basepath):
  227. self.tests = []
  228. self.modes = {}
  229. self.patterns = {}
  230. self.basepath = basepath
  231. def Print(self, msg):
  232. if self.verbose:
  233. print msg
  234. def MakeOutPath(self, filename):
  235. relpath = os.path.relpath(os.path.abspath(filename), os.path.dirname(os.path.abspath(self.basepath)))
  236. return relpath
  237. def ReadTests(self, filename):
  238. """reads a .run file and parses."""
  239. Log("reading %s" % filename)
  240. outname = self.MakeOutPath(filename + ".txt")
  241. f = TxtWriter(outname)
  242. dirname = os.path.dirname(filename)
  243. lines = ReadFileAsLines(filename)
  244. count = 0
  245. tests_data = []
  246. for line in lines:
  247. if len(line) > 0 and not line.startswith("#"):
  248. fname = os.path.join(dirname, line)
  249. if line.endswith(".run"):
  250. if self.ReadTests(fname):
  251. f.Write(line + ".txt\n")
  252. count += 1
  253. elif line.endswith(".test"):
  254. tests_data.extend(self.ReadTest(fname))
  255. else:
  256. print "Error in %s:%d:%s" % (filename, count, line)
  257. raise SyntaxError()
  258. if len(tests_data):
  259. global MAX_TESTS_PER_SET
  260. sets = Chunkify(tests_data, MAX_TESTS_PER_SET)
  261. id = 1
  262. for set in sets:
  263. suffix = "_%03d_to_%03d" % (id, id + len(set) - 1)
  264. test_outname = self.MakeOutPath(filename + suffix + ".html")
  265. if os.path.basename(test_outname).startswith("input.run"):
  266. dname = os.path.dirname(test_outname)
  267. folder_name = os.path.basename(dname)
  268. test_outname = os.path.join(dname, folder_name + suffix + ".html")
  269. self.WriteTests(filename, test_outname, {"tests":set})
  270. f.Write(os.path.basename(test_outname) + "\n")
  271. id += len(set)
  272. count += 1
  273. f.Close()
  274. return count
  275. def ReadTest(self, filename):
  276. """reads a .test file and parses."""
  277. Log("reading %s" % filename)
  278. dom = parse(filename)
  279. tests = dom.getElementsByTagName("test")
  280. tests_data = []
  281. outname = self.MakeOutPath(filename + ".html")
  282. for test in tests:
  283. if not IsFileWeWant(filename):
  284. self.CopyShaders(test, filename, outname)
  285. else:
  286. test_data = self.ProcessTest(test, filename, outname, len(tests_data))
  287. if test_data:
  288. tests_data.append(test_data)
  289. return tests_data
  290. def ProcessTest(self, test, filename, outname, id):
  291. """Process a test"""
  292. mode = test.getAttribute("mode")
  293. pattern = test.getAttribute("pattern")
  294. self.modes[mode] = 1
  295. self.patterns[pattern] = 1
  296. Log ("%d: mode: %s pattern: %s" % (id, mode, pattern))
  297. method = getattr(self, 'Process_' + pattern)
  298. test_data = method(test, filename, outname)
  299. if test_data:
  300. test_data["pattern"] = pattern
  301. return test_data
  302. def WriteTests(self, filename, outname, tests_data):
  303. Log("Writing %s" % outname)
  304. template = """<!DOCTYPE html>
  305. <!-- this file is auto-generated. DO NOT EDIT.
  306. %(license)s
  307. -->
  308. <html>
  309. <head>
  310. <meta charset="utf-8">
  311. <title>WebGL GLSL conformance test: %(title)s</title>
  312. %(css)s
  313. %(scripts)s
  314. </head>
  315. <body>
  316. <canvas id="example" width="500" height="500" style="width: 16px; height: 16px;"></canvas>
  317. <div id="description"></div>
  318. <div id="console"></div>
  319. </body>
  320. <script>
  321. "use strict";
  322. OpenGLESTestRunner.run(%(tests_data)s);
  323. var successfullyParsed = true;
  324. </script>
  325. </html>
  326. """
  327. css = [
  328. "../../resources/js-test-style.css",
  329. "../resources/ogles-tests.css",
  330. ]
  331. scripts = [
  332. "../../resources/js-test-pre.js",
  333. "../resources/webgl-test.js",
  334. "../resources/webgl-test-utils.js",
  335. "ogles-utils.js",
  336. ]
  337. css_html = RelativizePaths(outname, css, '<link rel="stylesheet" href="%s" />')
  338. scripts_html = RelativizePaths(outname, scripts, '<script src="%s"></script>')
  339. f = WriteOpen(outname)
  340. f.write(template % {
  341. "license": LICENSE,
  342. "css": css_html,
  343. "scripts": scripts_html,
  344. "title": os.path.basename(outname),
  345. "tests_data": json.dumps(tests_data, indent=2)
  346. })
  347. f.close()
  348. def CopyShaders(self, test, filename, outname):
  349. """For tests we don't actually support yet, at least copy the shaders"""
  350. shaders = test.getElementsByTagName("shader")
  351. for shader in shaders:
  352. for name in ["vertshader", "fragshader"]:
  353. s = GetElementText(shader, name)
  354. if s and s != "empty":
  355. CopyShader(s, filename, outname)
  356. #
  357. # pattern handlers.
  358. #
  359. def Process_compare(self, test, filename, outname):
  360. global MATRIX_RE
  361. valid_tags = [
  362. ["shader", "model", "glstate"],
  363. ["uniform", "vertshader", "fragshader", "filename", "depthrange"],
  364. ["name", "count", "transpose", "uniform*", "near", "far"],
  365. ]
  366. CheckForUnknownTags(valid_tags, test)
  367. # parse the test
  368. shaders = test.getElementsByTagName("shader")
  369. shaderInfos = []
  370. for shader in shaders:
  371. v = GetElementText(shader, "vertshader")
  372. f = GetElementText(shader, "fragshader")
  373. CopyShader(v, filename, outname)
  374. CopyShader(f, filename, outname)
  375. info = {
  376. "vertexShader": v,
  377. "fragmentShader": f,
  378. }
  379. shaderInfos.append(info)
  380. uniformElems = shader.getElementsByTagName("uniform")
  381. if len(uniformElems) > 0:
  382. uniforms = {}
  383. info["uniforms"] = uniforms
  384. for uniformElem in uniformElems:
  385. uniform = {"count": 1}
  386. for child in uniformElem.childNodes:
  387. if child.localName == None:
  388. pass
  389. elif child.localName == "name":
  390. uniforms[GetText(child.childNodes)] = uniform
  391. elif child.localName == "count":
  392. uniform["count"] = int(GetText(child.childNodes))
  393. elif child.localName == "transpose":
  394. uniform["transpose"] = (GetText(child.childNodes) == "true")
  395. else:
  396. if "type" in uniform:
  397. print "utype was:", uniform["type"], " found ", child.localName
  398. raise SyntaxError
  399. type_name = GetValidTypeName(child.localName)
  400. uniform["type"] = type_name
  401. valueText = GetText(child.childNodes).replace(",", " ")
  402. uniform["value"] = [float(t) for t in valueText.split()]
  403. m = MATRIX_RE.search(type_name)
  404. if m:
  405. # Why are these backward from the API?!?!?
  406. TransposeMatrix(uniform["value"], int(m.group(1)))
  407. data = {
  408. "name": os.path.basename(outname),
  409. "model": GetModel(test),
  410. "referenceProgram": shaderInfos[1],
  411. "testProgram": shaderInfos[0],
  412. }
  413. gl_states = test.getElementsByTagName("glstate")
  414. if len(gl_states) > 0:
  415. state = {}
  416. data["state"] = state
  417. for gl_state in gl_states:
  418. for state_name in gl_state.childNodes:
  419. if state_name.localName:
  420. values = {}
  421. for field in state_name.childNodes:
  422. if field.localName:
  423. values[field.localName] = GetText(field.childNodes)
  424. state[state_name.localName] = values
  425. return data
  426. def Process_shaderload(self, test, filename, outname):
  427. """no need for shaderload tests"""
  428. self.CopyShaders(test, filename, outname)
  429. def Process_extension(self, test, filename, outname):
  430. """no need for extension tests"""
  431. self.CopyShaders(test, filename, outname)
  432. def Process_createtests(self, test, filename, outname):
  433. Log("createtests Not implemented: %s" % filename)
  434. self.CopyShaders(test, filename, outname)
  435. def Process_GL2Test(self, test, filename, outname):
  436. Log("GL2Test Not implemented: %s" % filename)
  437. self.CopyShaders(test, filename, outname)
  438. def Process_uniformquery(self, test, filename, outname):
  439. Log("uniformquery Not implemented: %s" % filename)
  440. self.CopyShaders(test, filename, outname)
  441. def Process_egl_image_external(self, test, filename, outname):
  442. """no need for egl_image_external tests"""
  443. self.CopyShaders(test, filename, outname)
  444. def Process_dismount(self, test, filename, outname):
  445. Log("dismount Not implemented: %s" % filename)
  446. self.CopyShaders(test, filename, outname)
  447. def Process_build(self, test, filename, outname):
  448. """don't need build tests"""
  449. valid_tags = [
  450. ["shader", "compstat", "linkstat"],
  451. ["vertshader", "fragshader"],
  452. ]
  453. CheckForUnknownTags(valid_tags, test)
  454. shader = test.getElementsByTagName("shader")
  455. if not shader:
  456. return None
  457. vs = GetElementText(shader[0], "vertshader")
  458. fs = GetElementText(shader[0], "fragshader")
  459. if vs and vs != "empty":
  460. CopyShader(vs, filename, outname)
  461. if fs and fs != "empty":
  462. CopyShader(fs, filename, outname)
  463. data = {
  464. "name": os.path.basename(outname),
  465. "compstat": bool(GetBoolElement(test, "compstat")),
  466. "linkstat": bool(GetBoolElement(test, "linkstat")),
  467. "testProgram": {
  468. "vertexShader": vs,
  469. "fragmentShader": fs,
  470. },
  471. }
  472. attach = test.getElementsByTagName("attach")
  473. if len(attach) > 0:
  474. data["attachError"] = GetElementText(attach[0], "attacherror")
  475. return data
  476. def Process_coverage(self, test, filename, outname):
  477. Log("coverage Not implemented: %s" % filename)
  478. self.CopyShaders(test, filename, outname)
  479. def Process_attributes(self, test, filename, outname):
  480. Log("attributes Not implemented: %s" % filename)
  481. self.CopyShaders(test, filename, outname)
  482. def Process_fixed(self, test, filename, outname):
  483. """no need for fixed function tests"""
  484. self.CopyShaders(test, filename, outname)
  485. def main(argv):
  486. """This is the main function."""
  487. global VERBOSE
  488. parser = OptionParser()
  489. parser.add_option(
  490. "-v", "--verbose", action="store_true",
  491. help="prints more output.")
  492. (options, args) = parser.parse_args(args=argv)
  493. if len(args) < 1:
  494. pass # fix me
  495. os.chdir(os.path.dirname(__file__) or '.')
  496. VERBOSE = options.verbose
  497. filename = args[0]
  498. test_reader = TestReader(filename)
  499. test_reader.ReadTests(filename)
  500. if __name__ == '__main__':
  501. sys.exit(main(sys.argv[1:]))