1 #! /usr/bin/env python2
3 """generates tests from OpenGL ES 2.0 .run/.test files."""
11 from optparse
import OptionParser
12 from xml
.dom
.minidom
import parse
14 if sys
.version
< '2.6':
15 print 'Wrong Python Version !!!: Need >= 2.6'
18 # each shader test generates up to 3 512x512 images.
19 # a 512x512 image takes 1meg of memory so set this
20 # number apporpriate for the platform with
21 # the smallest memory issue. At 8 that means
22 # at least 24 meg is needed to run the test.
32 Copyright (c) 2019 The Khronos Group Inc.
33 Use of this source code is governed by an MIT-style license that can be
34 found in the LICENSE.txt file.
37 COMMENT_RE
= re
.compile("/\*\n\*\*\s+Copyright.*?\*/",
38 re
.IGNORECASE | re
.DOTALL
)
39 REMOVE_COPYRIGHT_RE
= re
.compile("\/\/\s+Copyright.*?\n",
40 re
.IGNORECASE | re
.DOTALL
)
41 MATRIX_RE
= re
.compile("Matrix(\\d)")
43 VALID_UNIFORM_TYPES
= [
77 ("uniformmat3fv", "uniformMatrix3fv"),
78 ("uniformmat4fv", "uniformMatrix4fv"),
88 def TransposeMatrix(values
, dim
):
90 count
= len(values
) / size
91 for m
in range(0, count
):
93 for i
in range(0, dim
):
94 for j
in range(i
+ 1, dim
):
95 t
= values
[offset
+ i
* dim
+ j
]
96 values
[offset
+ i
* dim
+ j
] = values
[offset
+ j
* dim
+ i
]
97 values
[offset
+ j
* dim
+ i
] = t
100 def GetValidTypeName(type_name
):
101 global VALID_UNIFORM_TYPES
103 for subst
in SUBSTITUTIONS
:
104 type_name
= type_name
.replace(subst
[0], subst
[1])
105 if not type_name
in VALID_UNIFORM_TYPES
:
106 print "unknown type name: ", type_name
111 def WriteOpen(filename
):
112 dirname
= os
.path
.dirname(filename
)
113 if len(dirname
) > 0 and not os
.path
.exists(dirname
):
115 return open(filename
, "wb")
119 def __init__(self
, filename
):
120 self
.filename
= filename
123 def Write(self
, line
):
124 self
.lines
.append(line
)
127 if len(self
.lines
) > 0:
128 Log("Writing: %s" % self
.filename
)
129 f
= WriteOpen(self
.filename
)
130 f
.write("# this file is auto-generated. DO NOT EDIT.\n")
131 f
.write("".join(self
.lines
))
135 def ReadFileAsLines(filename
):
136 f
= open(filename
, "r")
137 lines
= f
.readlines()
139 return [line
.strip() for line
in lines
]
142 def ReadFile(filename
):
143 f
= open(filename
, "r")
146 return content
.replace("\r\n", "\n")
149 def Chunkify(list, chunk_size
):
150 """divides an array into chunks of chunk_size"""
151 return [list[i
:i
+ chunk_size
] for i
in range(0, len(list), chunk_size
)]
154 def GetText(nodelist
):
155 """Gets the text of from a list of nodes"""
157 for node
in nodelist
:
158 if node
.nodeType
== node
.TEXT_NODE
:
163 def GetElementText(node
, name
):
164 """Gets the text of an element"""
165 elements
= node
.getElementsByTagName(name
)
166 if len(elements
) > 0:
167 return GetText(elements
[0].childNodes
)
172 def GetBoolElement(node
, name
):
173 text
= GetElementText(node
, name
)
174 return text
.lower() == "true"
179 model
= GetElementText(node
, "model")
180 if model
and len(model
.strip()) == 0:
181 elements
= node
.getElementsByTagName("model")
182 if len(elements
) > 0:
183 model
= GetElementText(elements
[0], "filename")
187 def RelativizePaths(base
, paths
, template
):
188 """converts paths to relative paths"""
192 #print "base: ", os.path.abspath(base)
193 #print "path: ", os.path.abspath(p)
194 relpath
= os
.path
.relpath(os
.path
.abspath(p
), os
.path
.dirname(os
.path
.abspath(base
))).replace("\\", "/")
195 #print "rel : ", relpath
196 rels
.append(template
% relpath
)
197 return "\n".join(rels
)
200 def CopyFile(filename
, src
, dst
):
201 s
= os
.path
.abspath(os
.path
.join(os
.path
.dirname(src
), filename
))
202 d
= os
.path
.abspath(os
.path
.join(os
.path
.dirname(dst
), filename
))
203 dst_dir
= os
.path
.dirname(d
)
204 if not os
.path
.exists(dst_dir
):
206 shutil
.copyfile(s
, d
)
209 def CopyShader(filename
, src
, dst
):
210 s
= os
.path
.abspath(os
.path
.join(os
.path
.dirname(src
), filename
))
211 d
= os
.path
.abspath(os
.path
.join(os
.path
.dirname(dst
), filename
))
213 # By agreement with the Khronos OpenGL working group we are allowed
214 # to open source only the .vert and .frag files from the OpenGL ES 2.0
215 # conformance tests. All other files from the OpenGL ES 2.0 conformance
216 # tests are not included.
217 marker
= "insert-copyright-here"
218 new_text
= COMMENT_RE
.sub(marker
, text
)
220 print "no matching license found:", s
222 new_text
= REMOVE_COPYRIGHT_RE
.sub("", new_text
)
223 glsl_license
= '/*\n' + LICENSE
+ '\n*/'
224 new_text
= new_text
.replace(marker
, glsl_license
)
230 def IsOneOf(string
, regexs
):
232 if re
.match(regex
, string
):
237 def CheckForUnknownTags(valid_tags
, node
, depth
=1):
238 """do a hacky check to make sure we're not missing something."""
239 for child
in node
.childNodes
:
240 if child
.localName
and not IsOneOf(child
.localName
, valid_tags
[0]):
241 print "unsupported tag:", child
.localName
242 print "depth:", depth
245 if len(valid_tags
) > 1:
246 CheckForUnknownTags(valid_tags
[1:], child
, depth
+ 1)
249 def IsFileWeWant(filename
):
251 if f
.search(filename
):
257 """class to read and parse tests"""
259 def __init__(self
, basepath
):
263 self
.basepath
= basepath
265 def Print(self
, msg
):
269 def MakeOutPath(self
, filename
):
270 relpath
= os
.path
.relpath(os
.path
.abspath(filename
), os
.path
.dirname(os
.path
.abspath(self
.basepath
)))
273 def ReadTests(self
, filename
):
274 """reads a .run file and parses."""
275 Log("reading %s" % filename
)
276 outname
= self
.MakeOutPath(filename
+ ".txt")
277 f
= TxtWriter(outname
)
278 dirname
= os
.path
.dirname(filename
)
279 lines
= ReadFileAsLines(filename
)
283 if len(line
) > 0 and not line
.startswith("#"):
284 fname
= os
.path
.join(dirname
, line
)
285 if line
.endswith(".run"):
286 if self
.ReadTests(fname
):
287 f
.Write(line
+ ".txt\n")
289 elif line
.endswith(".test"):
290 tests_data
.extend(self
.ReadTest(fname
))
292 print "Error in %s:%d:%s" % (filename
, count
, line
)
295 global MAX_TESTS_PER_SET
296 sets
= Chunkify(tests_data
, MAX_TESTS_PER_SET
)
299 suffix
= "_%03d_to_%03d" % (id, id + len(set) - 1)
300 test_outname
= self
.MakeOutPath(filename
+ suffix
+ ".html")
301 if os
.path
.basename(test_outname
).startswith("input.run"):
302 dname
= os
.path
.dirname(test_outname
)
303 folder_name
= os
.path
.basename(dname
)
304 test_outname
= os
.path
.join(dname
, folder_name
+ suffix
+ ".html")
305 self
.WriteTests(filename
, test_outname
, {"tests":set})
306 f
.Write(os
.path
.basename(test_outname
) + "\n")
312 def ReadTest(self
, filename
):
313 """reads a .test file and parses."""
314 Log("reading %s" % filename
)
315 dom
= parse(filename
)
316 tests
= dom
.getElementsByTagName("test")
318 outname
= self
.MakeOutPath(filename
+ ".html")
320 if not IsFileWeWant(filename
):
321 self
.CopyShaders(test
, filename
, outname
)
323 test_data
= self
.ProcessTest(test
, filename
, outname
, len(tests_data
))
325 tests_data
.append(test_data
)
328 def ProcessTest(self
, test
, filename
, outname
, id):
330 mode
= test
.getAttribute("mode")
331 pattern
= test
.getAttribute("pattern")
333 self
.patterns
[pattern
] = 1
334 Log ("%d: mode: %s pattern: %s" % (id, mode
, pattern
))
335 method
= getattr(self
, 'Process_' + pattern
)
336 test_data
= method(test
, filename
, outname
)
338 test_data
["pattern"] = pattern
341 def WriteTests(self
, filename
, outname
, tests_data
):
342 Log("Writing %s" % outname
)
343 template
= """<!DOCTYPE html>
344 <!-- this file is auto-generated. DO NOT EDIT. -->
348 <meta charset="utf-8">
349 <title>WebGL GLSL conformance test: %(title)s</title>
354 <canvas id="example" width="500" height="500" style="width: 16px; height: 16px;"></canvas>
355 <div id="description"></div>
356 <div id="console"></div>
360 OpenGLESTestRunner.run(%(tests_data)s);
361 var successfullyParsed = true;
366 "../../resources/js-test-style.css",
367 "../../resources/ogles-tests.css",
370 "../../resources/js-test-pre.js",
371 "../../resources/webgl-test-utils.js",
374 css_html
= RelativizePaths(outname
, css
, '<link rel="stylesheet" href="%s" />')
375 scripts_html
= RelativizePaths(outname
, scripts
, '<script src="%s"></script>')
377 html_license
= '<!--\n' + LICENSE
+ '\n-->'
378 f
= WriteOpen(outname
)
380 "license": html_license
,
382 "scripts": scripts_html
,
383 "title": os
.path
.basename(outname
),
384 "tests_data": json
.dumps(tests_data
, indent
=2)
389 def CopyShaders(self
, test
, filename
, outname
):
390 """For tests we don't actually support yet, at least copy the shaders"""
391 shaders
= test
.getElementsByTagName("shader")
392 for shader
in shaders
:
393 for name
in ["vertshader", "fragshader"]:
394 s
= GetElementText(shader
, name
)
395 if s
and s
!= "empty":
396 CopyShader(s
, filename
, outname
)
402 def Process_compare(self
, test
, filename
, outname
):
406 ["shader", "model", "glstate"],
407 ["uniform", "vertshader", "fragshader", "filename", "depthrange"],
408 ["name", "count", "transpose", "uniform*", "near", "far"],
410 CheckForUnknownTags(valid_tags
, test
)
413 shaders
= test
.getElementsByTagName("shader")
415 for shader
in shaders
:
416 v
= GetElementText(shader
, "vertshader")
417 f
= GetElementText(shader
, "fragshader")
418 CopyShader(v
, filename
, outname
)
419 CopyShader(f
, filename
, outname
)
424 shaderInfos
.append(info
)
425 uniformElems
= shader
.getElementsByTagName("uniform")
426 if len(uniformElems
) > 0:
428 info
["uniforms"] = uniforms
429 for uniformElem
in uniformElems
:
430 uniform
= {"count": 1}
431 for child
in uniformElem
.childNodes
:
432 if child
.localName
== None:
434 elif child
.localName
== "name":
435 uniforms
[GetText(child
.childNodes
)] = uniform
436 elif child
.localName
== "count":
437 uniform
["count"] = int(GetText(child
.childNodes
))
438 elif child
.localName
== "transpose":
439 uniform
["transpose"] = (GetText(child
.childNodes
) == "true")
441 if "type" in uniform
:
442 print "utype was:", uniform
["type"], " found ", child
.localName
444 type_name
= GetValidTypeName(child
.localName
)
445 uniform
["type"] = type_name
446 valueText
= GetText(child
.childNodes
).replace(",", " ")
447 uniform
["value"] = [float(t
) for t
in valueText
.split()]
448 m
= MATRIX_RE
.search(type_name
)
450 # Why are these backward from the API?!?!?
451 TransposeMatrix(uniform
["value"], int(m
.group(1)))
453 "name": os
.path
.basename(outname
),
454 "model": GetModel(test
),
455 "referenceProgram": shaderInfos
[1],
456 "testProgram": shaderInfos
[0],
458 gl_states
= test
.getElementsByTagName("glstate")
459 if len(gl_states
) > 0:
461 data
["state"] = state
462 for gl_state
in gl_states
:
463 for state_name
in gl_state
.childNodes
:
464 if state_name
.localName
:
466 for field
in state_name
.childNodes
:
468 values
[field
.localName
] = GetText(field
.childNodes
)
469 state
[state_name
.localName
] = values
472 def Process_shaderload(self
, test
, filename
, outname
):
473 """no need for shaderload tests"""
474 self
.CopyShaders(test
, filename
, outname
)
476 def Process_extension(self
, test
, filename
, outname
):
477 """no need for extension tests"""
478 self
.CopyShaders(test
, filename
, outname
)
480 def Process_createtests(self
, test
, filename
, outname
):
481 Log("createtests Not implemented: %s" % filename
)
482 self
.CopyShaders(test
, filename
, outname
)
484 def Process_GL2Test(self
, test
, filename
, outname
):
485 Log("GL2Test Not implemented: %s" % filename
)
486 self
.CopyShaders(test
, filename
, outname
)
488 def Process_uniformquery(self
, test
, filename
, outname
):
489 Log("uniformquery Not implemented: %s" % filename
)
490 self
.CopyShaders(test
, filename
, outname
)
492 def Process_egl_image_external(self
, test
, filename
, outname
):
493 """no need for egl_image_external tests"""
494 self
.CopyShaders(test
, filename
, outname
)
496 def Process_dismount(self
, test
, filename
, outname
):
497 Log("dismount Not implemented: %s" % filename
)
498 self
.CopyShaders(test
, filename
, outname
)
500 def Process_build(self
, test
, filename
, outname
):
501 """don't need build tests"""
503 ["shader", "compstat", "linkstat"],
504 ["vertshader", "fragshader"],
506 CheckForUnknownTags(valid_tags
, test
)
508 shader
= test
.getElementsByTagName("shader")
511 vs
= GetElementText(shader
[0], "vertshader")
512 fs
= GetElementText(shader
[0], "fragshader")
513 if vs
and vs
!= "empty":
514 CopyShader(vs
, filename
, outname
)
515 if fs
and fs
!= "empty":
516 CopyShader(fs
, filename
, outname
)
518 "name": os
.path
.basename(outname
),
519 "compstat": bool(GetBoolElement(test
, "compstat")),
520 "linkstat": bool(GetBoolElement(test
, "linkstat")),
523 "fragmentShader": fs
,
526 attach
= test
.getElementsByTagName("attach")
528 data
["attachError"] = GetElementText(attach
[0], "attacherror")
531 def Process_coverage(self
, test
, filename
, outname
):
532 Log("coverage Not implemented: %s" % filename
)
533 self
.CopyShaders(test
, filename
, outname
)
535 def Process_attributes(self
, test
, filename
, outname
):
536 Log("attributes Not implemented: %s" % filename
)
537 self
.CopyShaders(test
, filename
, outname
)
539 def Process_fixed(self
, test
, filename
, outname
):
540 """no need for fixed function tests"""
541 self
.CopyShaders(test
, filename
, outname
)
545 """This is the main function."""
548 parser
= OptionParser()
550 "-v", "--verbose", action
="store_true",
551 help="prints more output.")
553 (options
, args
) = parser
.parse_args(args
=argv
)
558 os
.chdir(os
.path
.dirname(__file__
) or '.')
560 VERBOSE
= options
.verbose
563 test_reader
= TestReader(filename
)
564 test_reader
.ReadTests(filename
)
567 if __name__
== '__main__':
568 sys
.exit(main(sys
.argv
[1:]))