1 # Copyright (c) 2015-2016 Intel Corporation
3 # Permission is hereby granted, free of charge, to any person obtaining a copy
4 # of this software and associated documentation files (the "Software"), to deal
5 # in the Software without restriction, including without limitation the rights
6 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 # copies of the Software, and to permit persons to whom the Software is
8 # furnished to do so, subject to the following conditions:
10 # The above copyright notice and this permission notice shall be included in
11 # all copies or substantial portions of the Software.
13 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 from __future__
import (
22 absolute_import
, division
, print_function
, unicode_literals
31 from framework
import exceptions
, core
32 from framework
.options
import OPTIONS
33 from framework
.test
import piglit_test
36 class StopWflinfo(exceptions
.PiglitException
):
37 """Exception called when wlfinfo getter should stop."""
38 def __init__(self
, reason
):
39 super(StopWflinfo
, self
).__init
__()
43 class WflInfo(object):
44 """Class representing platform information as provided by wflinfo.
46 The design of this is odd to say the least, it's basically a bag with some
47 lazy property evaluators in it, used to avoid calculating the values
48 provided by wflinfo more than once.
51 - Needs to be shared with all subclasses
52 - Needs to evaluate only once
53 - cannot evaluate until user sets OPTIONS.env['PIGLIT_PLATFORM']
55 This solves all of that.
59 def __new__(cls
, *args
, **kwargs
):
60 # Implement the borg pattern:
61 # https://code.activestate.com/recipes/66531-singleton-we-dont-need-no-stinkin-singleton-the-bo/
63 # This is something like a singleton, but much easier to implement
64 self
= super(WflInfo
, cls
).__new
__(cls
, *args
, **kwargs
)
65 self
.__dict
__ = cls
.__shared
_state
69 def __call_wflinfo(opts
):
70 """Helper to call wflinfo and reduce code duplication.
72 This catches and handles CalledProcessError and OSError.ernno == 2
73 gracefully: it passes them to allow platforms without a particular
74 gl/gles version or wflinfo (resepctively) to work.
77 opts -- arguments to pass to wflinfo other than verbose and platform
80 with
open(os
.devnull
, 'w') as d
:
82 # Get the piglit platform string and, if needed, convert it
83 # to something that wflinfo understands.
84 platform
= OPTIONS
.env
['PIGLIT_PLATFORM']
85 if platform
== "mixed_glx_egl":
88 if sys
.platform
in ['windows', 'cygwin']:
93 cmd
= [bin
, '--platform', platform
] + opts
95 # setup execution environment where we extend the PATH env var
96 # to include the piglit TEST_BIN_DIR
98 new_env
['PATH'] = ':'.join([piglit_test
.TEST_BIN_DIR
,
101 raw
= subprocess
.check_output(cmd
, env
=new_env
, stderr
=d
)
103 except subprocess
.CalledProcessError
:
104 # When we hit this error it usually going to be because we have
105 # an incompatible platform/profile combination
106 raise StopWflinfo('Called')
108 # If we get a 'no wflinfo' warning then just return
109 print("wflinfo utility not found.", file=sys
.stderr
)
110 if e
.errno
== errno
.ENOENT
:
111 raise StopWflinfo('OSError')
113 return raw
.decode('utf-8')
116 def __getline(lines
, name
):
117 """Find a line in a list return it."""
119 if line
.startswith(name
):
121 raise Exception('Unreachable')
124 def gl_extensions(self
):
125 """Call wflinfo to get opengl extensions.
127 This provides a very conservative set of extensions, it provides every
128 extension from gles1, 2 and 3 and from GL both core and compat profile
129 as a single set. This may let a few tests execute that will still skip
130 manually, but it helps to ensure that this method never skips when it
134 _trim
= len('OpenGL extensions: ')
137 def helper(const
, vars_
):
138 """Helper function to reduce code duplication."""
139 # This is a pretty fragile function but it really does help with
143 ret
= self
.__call
_wflinfo
(const
+ [var
])
144 except StopWflinfo
as e
:
145 # This means the particular api or profile is unsupported
146 if e
.reason
== 'Called':
150 all_
.update(set(self
.__getline
(
151 ret
.split('\n'), 'OpenGL extensions')[_trim
:].split()))
154 helper(['--verbose', '--api'], ['gles1', 'gles2', 'gles3'])
155 helper(['--verbose', '--api', 'gl', '--profile'],
156 ['core', 'compat', 'none'])
157 except StopWflinfo
as e
:
158 # Handle wflinfo not being installed by returning an empty set. This
159 # will essentially make FastSkipMixin a no-op.
160 if e
.reason
== 'OSError':
164 # Don't return a set with only WFLINFO_GL_ERROR.
165 ret
= {e
.strip() for e
in all_
}
166 if ret
== {'WFLINFO_GL_ERROR'}:
171 def gl_version(self
):
172 """Calculate the maximum opengl version.
174 This will try (in order): core, compat, and finally no profile,
175 stopping when it finds a profile. It assumes that most implementations
176 will have core and compat as equals, or core as superior to compat in
181 for profile
in ['core', 'compat', 'none']:
183 raw
= self
.__call
_wflinfo
(['--api', 'gl', '--profile', profile
])
184 except StopWflinfo
as e
:
185 if e
.reason
== 'Called':
187 elif e
.reason
== 'OSError':
192 # Grab the GL version string, trim any release_number values
193 ret
= float(self
.__getline
(
195 'OpenGL version string').split()[3][:3])
196 except (IndexError, ValueError):
197 # This is caused by wlfinfo returning an error
203 def gles_version(self
):
204 """Calculate the maximum opengl es version.
206 The design of this function isn't 100% correct. GLES1 and GLES2+ behave
207 differently, since 2+ can be silently promoted, but 1 cannot. This
208 means that a driver can implement 2, 3, 3.1, etc, but never have 1
211 I don't think this is a big deal for a couple of reasons. First, piglit
212 has a very small set of GLES1 tests, so they shouldn't have big impact
213 on runtime, and second, the design of the FastSkipMixin is
214 conservative: it would rather run a few tests that should be skipped
215 than skip a few tests that should be run.
219 for api
in ['gles3', 'gles2', 'gles1']:
221 raw
= self
.__call
_wflinfo
(['--api', api
])
222 except StopWflinfo
as e
:
223 if e
.reason
== 'Called':
225 elif e
.reason
== 'OSError':
230 # Yes, search for "OpenGL version string" in GLES
231 # GLES doesn't support patch versions.
232 ret
= float(self
.__getline
(
234 'OpenGL version string').split()[5])
235 except (IndexError, ValueError):
236 # This is caused by wlfinfo returning an error
242 def glsl_version(self
):
243 """Calculate the maximum OpenGL Shader Language version."""
245 for profile
in ['core', 'compat', 'none']:
247 raw
= self
.__call
_wflinfo
(
248 ['--verbose', '--api', 'gl', '--profile', profile
])
249 except StopWflinfo
as e
:
250 if e
.reason
== 'Called':
252 elif e
.reason
== 'OSError':
257 # GLSL versions are M.mm formatted
258 line
= self
.__getline
(raw
.split('\n'), 'OpenGL shading language')
259 ret
= float(line
.split(":")[1][:5])
260 except (IndexError, ValueError):
261 # This is caused by wflinfo returning an error
267 def glsl_es_version(self
):
268 """Calculate the maximum OpenGL ES Shader Language version."""
270 for api
in ['gles3', 'gles2']:
272 raw
= self
.__call
_wflinfo
(['--verbose', '--api', api
])
273 except StopWflinfo
as e
:
274 if e
.reason
== 'Called':
276 elif e
.reason
== 'OSError':
281 # GLSL ES version numbering is insane.
282 # For version >= 3 the numbers are 3.00, 3.10, etc.
283 # For version 2, they are 1.0.xx
284 ret
= float(self
.__getline
(
286 'OpenGL shading language').split()[-1][:3])
287 except (IndexError, ValueError):
288 # Handle wflinfo internal errors