Browse Source

shaderpipeline: Make testing suite cover all environments/back-ends

rdb 1 year ago
parent
commit
c450fc071e

+ 2 - 0
panda/src/display/graphicsStateGuardian.I

@@ -601,6 +601,7 @@ get_supports_compute_shaders() const {
 
 
 /**
 /**
  * Returns true if this particular GSG supports GLSL shaders.
  * Returns true if this particular GSG supports GLSL shaders.
+ * @deprecated This is now irrelevant, use get_supports_basic_shaders instead.
  */
  */
 INLINE bool GraphicsStateGuardian::
 INLINE bool GraphicsStateGuardian::
 get_supports_glsl() const {
 get_supports_glsl() const {
@@ -609,6 +610,7 @@ get_supports_glsl() const {
 
 
 /**
 /**
  * Returns true if this particular GSG supports HLSL shaders.
  * Returns true if this particular GSG supports HLSL shaders.
+ * @deprecated This is now irrelevant, use get_supports_basic_shaders instead.
  */
  */
 INLINE bool GraphicsStateGuardian::
 INLINE bool GraphicsStateGuardian::
 get_supports_hlsl() const {
 get_supports_hlsl() const {

+ 42 - 17
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -1756,7 +1756,7 @@ reset() {
       Shader::C_vertex_texture |
       Shader::C_vertex_texture |
       Shader::C_point_coord;
       Shader::C_point_coord;
 
 
-  if (is_at_least_gles_version(3, 0)) {
+  if (is_at_least_gles_version(3, 0) && _glsl_version >= 300) {
     _supported_shader_caps |=
     _supported_shader_caps |=
       Shader::C_standard_derivatives |
       Shader::C_standard_derivatives |
       Shader::C_shadow_samplers |
       Shader::C_shadow_samplers |
@@ -1803,7 +1803,7 @@ reset() {
     }
     }
   }
   }
 
 
-  if (is_at_least_gles_version(3, 1)) {
+  if (is_at_least_gles_version(3, 1) && _glsl_version >= 310) {
     _supported_shader_caps |=
     _supported_shader_caps |=
       Shader::C_texture_gather_red |
       Shader::C_texture_gather_red |
       Shader::C_texture_gather_any |
       Shader::C_texture_gather_any |
@@ -1830,7 +1830,7 @@ reset() {
     }
     }
   }
   }
 
 
-  if (is_at_least_gles_version(3, 2)) {
+  if (is_at_least_gles_version(3, 2) && _glsl_version >= 320) {
     _supported_shader_caps |=
     _supported_shader_caps |=
       Shader::C_texture_buffer |
       Shader::C_texture_buffer |
       Shader::C_geometry_shader |
       Shader::C_geometry_shader |
@@ -1929,7 +1929,7 @@ reset() {
     if (_glsl_version >= 140 || has_extension("GL_ARB_draw_instanced")) {
     if (_glsl_version >= 140 || has_extension("GL_ARB_draw_instanced")) {
       _supported_shader_caps |= Shader::C_instance_id;
       _supported_shader_caps |= Shader::C_instance_id;
     }
     }
-    if (_glsl_version >= 140 || has_extension("GL_ARB_texture_buffer_object")) {
+    if (_glsl_version >= 140 || has_extension("GL_EXT_texture_buffer_object")) {
       _supported_shader_caps |= Shader::C_texture_buffer;
       _supported_shader_caps |= Shader::C_texture_buffer;
     }
     }
 
 
@@ -1966,7 +1966,7 @@ reset() {
       if (has_extension("GL_ARB_texture_gather")) {
       if (has_extension("GL_ARB_texture_gather")) {
         _supported_shader_caps |= Shader::C_texture_gather_red;
         _supported_shader_caps |= Shader::C_texture_gather_red;
       }
       }
-      if (has_extension("GL_ARB_gpu_shader_fp64")) {
+      if (_glsl_version >= 150 && has_extension("GL_ARB_gpu_shader_fp64")) {
         _supported_shader_caps |= Shader::C_double;
         _supported_shader_caps |= Shader::C_double;
       }
       }
       if (has_extension("GL_ARB_gpu_shader5")) {
       if (has_extension("GL_ARB_gpu_shader5")) {
@@ -1994,7 +1994,8 @@ reset() {
       if (has_extension("GL_ARB_shader_atomic_counters")) {
       if (has_extension("GL_ARB_shader_atomic_counters")) {
         _supported_shader_caps |= Shader::C_atomic_counters;
         _supported_shader_caps |= Shader::C_atomic_counters;
       }
       }
-      if (has_extension("GL_ARB_shader_image_load_store")) {
+      // We require 330 here as minimum due to a spirv-cross bug.
+      if (_glsl_version >= 330 && has_extension("GL_ARB_shader_image_load_store")) {
         _supported_shader_caps |=
         _supported_shader_caps |=
           Shader::C_image_load_store |
           Shader::C_image_load_store |
           Shader::C_image_atomic;
           Shader::C_image_atomic;
@@ -2015,7 +2016,8 @@ reset() {
       if (has_extension("GL_ARB_texture_query_levels")) {
       if (has_extension("GL_ARB_texture_query_levels")) {
         _supported_shader_caps |= Shader::C_texture_query_levels;
         _supported_shader_caps |= Shader::C_texture_query_levels;
       }
       }
-      if (has_extension("GL_ARB_shader_storage_buffer_object")) {
+      if (_glsl_version >= 150 &&
+          has_extension("GL_ARB_shader_storage_buffer_object")) {
         _supported_shader_caps |= Shader::C_storage_buffer;
         _supported_shader_caps |= Shader::C_storage_buffer;
       }
       }
       if (has_extension("GL_ARB_compute_shader")) {
       if (has_extension("GL_ARB_compute_shader")) {
@@ -9864,15 +9866,25 @@ query_glsl_version() {
 #endif
 #endif
 
 
   if (gl_force_glsl_version.get_num_words() > 0 && gl_force_glsl_version > 0) {
   if (gl_force_glsl_version.get_num_words() > 0 && gl_force_glsl_version > 0) {
-    _glsl_version = gl_force_glsl_version;
-
-    if (GLCAT.is_debug()) {
-      GLCAT.debug()
-        << "Forced GLSL "
+    int requested_version = gl_force_glsl_version;
+    if (requested_version > _glsl_version) {
+      GLCAT.warning()
+        << "Cannot force GLSL "
 #ifdef OPENGLES
 #ifdef OPENGLES
            "ES "
            "ES "
 #endif
 #endif
-           "version: " << _glsl_version << "\n";
+           "version higher than supported version " << _glsl_version << "\n";
+    } else {
+      _glsl_version = requested_version;
+
+      if (GLCAT.is_debug()) {
+        GLCAT.debug()
+          << "Forced GLSL "
+#ifdef OPENGLES
+             "ES "
+#endif
+             "version: " << _glsl_version << "\n";
+      }
     }
     }
   } else {
   } else {
     if (GLCAT.is_debug()) {
     if (GLCAT.is_debug()) {
@@ -15879,8 +15891,23 @@ extract_texture_image(PTA_uchar &image, size_t &page_size,
                       Texture::ComponentType type,
                       Texture::ComponentType type,
                       Texture::CompressionMode compression, int n) {
                       Texture::CompressionMode compression, int n) {
 #ifdef OPENGLES  // Extracting texture data unsupported in OpenGL ES.
 #ifdef OPENGLES  // Extracting texture data unsupported in OpenGL ES.
-  nassert_raise("OpenGL ES does not support extracting texture data");
-  return false;
+#ifndef OPENGLES_1
+  if (target == GL_TEXTURE_BUFFER && _glMapBufferRange != nullptr) {
+    // In the case of a buffer texture, we need to get it from the buffer.
+    image = PTA_uchar::empty_array(tex->get_expected_ram_mipmap_view_size(n));
+    void *data = _glMapBufferRange(GL_TEXTURE_BUFFER, 0, image.size(), GL_MAP_READ_BIT);
+    if (data == nullptr) {
+      return false;
+    }
+    memcpy(image.p(), data, image.size());
+    _glUnmapBuffer(GL_TEXTURE_BUFFER);
+    return true;
+  } else
+#endif
+  {
+    nassert_raise("OpenGL ES does not support extracting texture data");
+    return false;
+  }
 #else
 #else
 
 
   // Make sure the GL driver does not align textures, otherwise we get corrupt
   // Make sure the GL driver does not align textures, otherwise we get corrupt
@@ -15923,12 +15950,10 @@ extract_texture_image(PTA_uchar &image, size_t &page_size,
       }
       }
     }
     }
 
 
-#ifndef OPENGLES_1
   } else if (target == GL_TEXTURE_BUFFER) {
   } else if (target == GL_TEXTURE_BUFFER) {
     // In the case of a buffer texture, we need to get it from the buffer.
     // In the case of a buffer texture, we need to get it from the buffer.
     image = PTA_uchar::empty_array(tex->get_expected_ram_mipmap_view_size(n));
     image = PTA_uchar::empty_array(tex->get_expected_ram_mipmap_view_size(n));
     _glGetBufferSubData(target, 0, image.size(), image.p());
     _glGetBufferSubData(target, 0, image.size(), image.p());
-#endif
 
 
   } else if (compression == Texture::CM_off) {
   } else if (compression == Texture::CM_off) {
     // An uncompressed 1-d, 2-d, or 3-d texture.
     // An uncompressed 1-d, 2-d, or 3-d texture.

+ 0 - 93
tests/conftest.py

@@ -23,96 +23,3 @@ def tk_toplevel():
         pytest.skip(str(e))
         pytest.skip(str(e))
     yield root
     yield root
     root.destroy()
     root.destroy()
-
-
[email protected](scope='session')
-def graphics_pipe():
-    from panda3d.core import GraphicsPipeSelection
-
-    pipe = GraphicsPipeSelection.get_global_ptr().make_default_pipe()
-
-    if pipe is None or not pipe.is_valid():
-        pytest.skip("GraphicsPipe is invalid")
-
-    yield pipe
-
-
[email protected](scope='session')
-def graphics_engine():
-    from panda3d.core import GraphicsEngine
-
-    engine = GraphicsEngine.get_global_ptr()
-    yield engine
-
-    # This causes GraphicsEngine to also terminate the render threads.
-    engine.remove_all_windows()
-
-
[email protected]
-def window(graphics_pipe, graphics_engine):
-    from panda3d.core import GraphicsPipe, FrameBufferProperties, WindowProperties
-
-    fbprops = FrameBufferProperties.get_default()
-    winprops = WindowProperties.get_default()
-
-    win = graphics_engine.make_output(
-        graphics_pipe,
-        'window',
-        0,
-        fbprops,
-        winprops,
-        GraphicsPipe.BF_require_window
-    )
-    graphics_engine.open_windows()
-
-    if win is None:
-        pytest.skip("GraphicsPipe cannot make windows")
-
-    yield win
-
-    if win is not None:
-        graphics_engine.remove_window(win)
-
-
[email protected](scope='module')
-def gsg(graphics_pipe, graphics_engine):
-    "Returns a windowless GSG that can be used for offscreen rendering."
-    from panda3d.core import GraphicsPipe, FrameBufferProperties, WindowProperties
-
-    fbprops = FrameBufferProperties()
-    fbprops.force_hardware = True
-
-    props = WindowProperties.size(32, 32)
-
-    buffer = graphics_engine.make_output(
-        graphics_pipe,
-        'buffer',
-        0,
-        fbprops,
-        props,
-        GraphicsPipe.BF_refuse_window
-    )
-    graphics_engine.open_windows()
-
-    if buffer is None:
-        # Try making a window instead, putting it in the background so it
-        # disrupts the desktop as little as possible
-        props.minimized = True
-        props.foreground = False
-        props.z_order = WindowProperties.Z_bottom
-        buffer = graphics_engine.make_output(
-            graphics_pipe,
-            'buffer',
-            0,
-            fbprops,
-            props,
-            GraphicsPipe.BF_require_window
-        )
-
-    if buffer is None:
-        pytest.skip("GraphicsPipe cannot make offscreen buffers or windows")
-
-    yield buffer.gsg
-
-    if buffer is not None:
-        graphics_engine.remove_window(buffer)

+ 621 - 0
tests/display/conftest.py

@@ -0,0 +1,621 @@
+from panda3d import core
+import pytest
+from _pytest.outcomes import Failed
+import sys
+
+
+# Instantiate all of the available graphics pipes.
+ALL_PIPES = []
+sel = core.GraphicsPipeSelection.get_global_ptr()
+
+pipe = sel.make_module_pipe('p3headlessgl')
+if pipe and pipe.is_valid():
+    ALL_PIPES.append(pipe)
+else:
+    pipe = sel.make_module_pipe('pandagl')
+    if pipe and pipe.is_valid():
+        ALL_PIPES.append(pipe)
+
+#pipe = sel.make_module_pipe('pandagles2')
+#if pipe and pipe.is_valid():
+#    ALL_PIPES.append(pipe)
+
+if sys.platform == 'win32':
+    pipe = sel.make_module_pipe('pandadx9')
+    if pipe and pipe.is_valid():
+        ALL_PIPES.append(pipe)
+
+
[email protected](scope='session')
+def graphics_pipes():
+    yield ALL_PIPES
+
+
[email protected](scope='session', params=ALL_PIPES)
+def graphics_pipe(request):
+    pipe = request.param
+    if pipe is None or not pipe.is_valid():
+        pytest.skip("GraphicsPipe is invalid")
+
+    yield pipe
+
+
[email protected](scope='session')
+def graphics_engine():
+    from panda3d.core import GraphicsEngine
+
+    engine = GraphicsEngine.get_global_ptr()
+    yield engine
+
+    # This causes GraphicsEngine to also terminate the render threads.
+    engine.remove_all_windows()
+
+
[email protected]
+def window(graphics_pipe, graphics_engine):
+    from panda3d.core import GraphicsPipe, FrameBufferProperties, WindowProperties
+
+    fbprops = FrameBufferProperties.get_default()
+    winprops = WindowProperties.get_default()
+
+    win = graphics_engine.make_output(
+        graphics_pipe,
+        'window',
+        0,
+        fbprops,
+        winprops,
+        GraphicsPipe.BF_require_window
+    )
+    graphics_engine.open_windows()
+
+    if win is None:
+        pytest.skip("GraphicsPipe cannot make windows")
+
+    yield win
+
+    if win is not None:
+        graphics_engine.remove_window(win)
+
+
+# This is the template for the compute shader that is used by run_glsl_test.
+# It defines an assert() macro that writes failures to a buffer, indexed by
+# line number.
+# The reset() function serves to prevent the _triggered variable from being
+# optimized out in the case that the assertions are being optimized out.
+GLSL_COMPUTE_TEMPLATE = """#version {version}
+{extensions}
+
+layout(local_size_x = 1, local_size_y = 1) in;
+
+{preamble}
+
+layout(r32ui) uniform writeonly uimageBuffer _triggered;
+
+void _reset() {{
+    imageStore(_triggered, 0, uvec4(1));
+    memoryBarrier();
+}}
+
+void _assert(bool cond, int line) {{
+    if (!cond) {{
+        imageStore(_triggered, line, uvec4(1));
+    }}
+}}
+
+#define assert(cond) _assert(cond, __LINE__ - line_offset)
+
+void main() {{
+    _reset();
+    const int line_offset = __LINE__;
+{body}
+}}
+"""
+
+# This is a version that uses a vertex and fragment shader instead.  This is
+# slower to set up, but it works even when compute shaders are not supported.
+# The shader is rendered on a fullscreen triangle to a texture, where each
+# pixel represents one line of the code.  The assert writes the result to the
+# output color if the current fragment matches the line number of that assert.
+# The first pixel is used as a control, to check that the shader has run.
+GLSL_VERTEX_TEMPLATE = """#version {version}
+
+in vec4 p3d_Vertex;
+
+void main() {{
+    gl_Position = p3d_Vertex;
+}}
+"""
+
+GLSL_FRAGMENT_TEMPLATE = """#version {version}
+{extensions}
+
+{preamble}
+
+layout(location = 0) out vec4 p3d_FragColor;
+
+void _reset() {{
+    p3d_FragColor = vec4(0, 0, 0, 0);
+
+    if (int(gl_FragCoord.x) == 0) {{
+        p3d_FragColor = vec4(1, 1, 1, 1);
+    }}
+}}
+
+void _assert(bool cond, int line) {{
+    if (int(gl_FragCoord.x) == line) {{
+        p3d_FragColor = vec4(!cond, !cond, !cond, !cond);
+    }}
+}}
+
+#define assert(cond) _assert(cond, __LINE__ - line_offset)
+
+void main() {{
+    _reset();
+    const int line_offset = __LINE__;
+{body}
+}}
+"""
+
+# This is the template for the shader that is used by run_cg_test.
+# We render this to an nx1 texture, where n is the number of lines in the body.
+# An assert
+CG_VERTEX_TEMPLATE = """//Cg
+
+void vshader(float4 vtx_position : POSITION, out float4 l_position : POSITION) {{
+    l_position = vtx_position;
+}}
+"""
+
+CG_FRAGMENT_TEMPLATE = """//Cg
+
+{preamble}
+
+float4 _assert(bool cond) {{
+    return float4(cond.x, 1, 1, 1);
+}}
+
+float4 _assert(bool2 cond) {{
+    return float4(cond.x, cond.y, 1, 1);
+}}
+
+float4 _assert(bool3 cond) {{
+    return float4(cond.x, cond.y, cond.z, 1);
+}}
+
+float4 _assert(bool4 cond) {{
+    return float4(cond.x, cond.y, cond.z, cond.w);
+}}
+
+#define assert(cond) {{ if ((int)l_vpos.x == __LINE__ - line_offset) o_color = _assert(cond); }}
+
+void fshader(in float2 l_vpos : VPOS, out float4 o_color : COLOR) {{
+    o_color = float4(1, 1, 1, 1);
+
+    if ((int)l_vpos.x == 0) {{
+        o_color = float4(0, 0, 0, 0);
+    }}
+    const int line_offset = __LINE__;
+{body}
+}}
+"""
+
+
+class ShaderEnvironment:
+    def __init__(self, name, gsg, allow_compute=True):
+        self.name = name
+        self.gsg = gsg
+        self.engine = gsg.get_engine()
+        self.allow_compute = allow_compute
+
+    def __repr__(self):
+        return f'<{self.name} vendor="{self.gsg.driver_vendor}" renderer="{self.gsg.driver_renderer}" version="{self.gsg.driver_version}">'
+
+    def extract_texture_data(self, tex):
+        __tracebackhide__ = True
+        result = self.engine.extract_texture_data(tex, self.gsg)
+        assert result
+
+    def run_glsl(self, body, preamble="", inputs={}, version=420, exts=set(),
+                 state=core.RenderState.make_empty()):
+        """ Runs a GLSL test on the given GSG.  The given body is executed in the
+        main function and should call assert().  The preamble should contain all
+        of the shader inputs. """
+
+        gsg = self.gsg
+        if not gsg.supports_basic_shaders:
+            pytest.skip("shaders not supported")
+
+        use_compute = self.allow_compute and gsg.supports_compute_shaders and \
+                      (gsg.supported_shader_capabilities & core.Shader.C_image_load_store) != 0
+
+        missing_exts = sorted(ext for ext in exts if not gsg.has_extension(ext))
+        if missing_exts:
+            pytest.skip("missing extensions: " + ' '.join(missing_exts))
+
+        version = version or 420
+        exts = exts | {'GL_ARB_compute_shader', 'GL_ARB_shader_image_load_store'}
+
+        extensions = ''
+        for ext in exts:
+            extensions += '#extension {ext} : require\n'.format(ext=ext)
+
+        __tracebackhide__ = True
+
+        preamble = preamble.strip()
+        body = body.rstrip().lstrip('\n')
+
+        if use_compute:
+            code = GLSL_COMPUTE_TEMPLATE.format(version=version, extensions=extensions, preamble=preamble, body=body)
+            shader = core.Shader.make_compute(core.Shader.SL_GLSL, code)
+        else:
+            vertex_code = GLSL_VERTEX_TEMPLATE.format(version=version, extensions=extensions, preamble=preamble, body=body)
+            code = GLSL_FRAGMENT_TEMPLATE.format(version=version, extensions=extensions, preamble=preamble, body=body)
+            shader = core.Shader.make(core.Shader.SL_GLSL, vertex_code, code)
+
+        if not shader:
+            pytest.fail("error compiling shader:\n" + code)
+
+        unsupported_caps = shader.get_used_capabilities() & ~gsg.supported_shader_capabilities
+        if unsupported_caps != 0:
+            stream = core.StringStream()
+            core.ShaderEnums.output_capabilities(stream, unsupported_caps)
+            pytest.skip("unsupported capabilities: " + stream.data.decode('ascii'))
+
+        num_lines = body.count('\n') + 1
+
+        # Create a buffer to hold the results of the assertion.  We use one texel
+        # per line of shader code, so we can show which lines triggered.
+        engine = gsg.get_engine()
+        result = core.Texture("")
+        if use_compute:
+            result.set_clear_color((0, 0, 0, 0))
+            result.setup_buffer_texture(num_lines + 1,
+                                        core.Texture.T_unsigned_int,
+                                        core.Texture.F_r32i,
+                                        core.GeomEnums.UH_static)
+        else:
+            fbprops = core.FrameBufferProperties()
+            fbprops.force_hardware = True
+            fbprops.set_rgba_bits(8, 8, 8, 8)
+            fbprops.srgb_color = False
+
+            buffer = engine.make_output(
+                gsg.pipe,
+                'buffer',
+                0,
+                fbprops,
+                core.WindowProperties.size(core.Texture.up_to_power_2(num_lines + 1), 1),
+                core.GraphicsPipe.BF_refuse_window,
+                gsg
+            )
+            buffer.add_render_texture(result, core.GraphicsOutput.RTM_copy_ram, core.GraphicsOutput.RTP_color)
+            buffer.set_clear_color_active(True)
+            buffer.set_clear_color((0, 0, 0, 0))
+            engine.open_windows()
+
+        # Build up the shader inputs
+        attrib = core.ShaderAttrib.make(shader)
+        for name, value in inputs.items():
+            attrib = attrib.set_shader_input(name, value)
+        if use_compute:
+            attrib = attrib.set_shader_input('_triggered', result)
+        state = state.set_attrib(attrib)
+
+        # Run the shader.
+        if use_compute:
+            try:
+                engine.dispatch_compute((1, 1, 1), state, gsg)
+            except AssertionError as exc:
+                assert False, "Error executing compute shader:\n" + code
+        else:
+            scene = core.NodePath("root")
+            scene.set_attrib(core.DepthTestAttrib.make(core.RenderAttrib.M_always))
+
+            format = core.GeomVertexFormat.get_v3()
+            vdata = core.GeomVertexData("tri", format, core.Geom.UH_static)
+            vdata.unclean_set_num_rows(3)
+
+            vertex = core.GeomVertexWriter(vdata, "vertex")
+            vertex.set_data3(-1, -1, 0)
+            vertex.set_data3(3, -1, 0)
+            vertex.set_data3(-1, 3, 0)
+
+            tris = core.GeomTriangles(core.Geom.UH_static)
+            tris.add_next_vertices(3)
+
+            geom = core.Geom(vdata)
+            geom.add_primitive(tris)
+
+            gnode = core.GeomNode("tri")
+            gnode.add_geom(geom, state)
+            scene.attach_new_node(gnode)
+            scene.set_two_sided(True)
+
+            camera = scene.attach_new_node(core.Camera("camera"))
+            camera.node().get_lens(0).set_near_far(-10, 10)
+            camera.node().set_cull_bounds(core.OmniBoundingVolume())
+
+            region = buffer.make_display_region()
+            region.active = True
+            region.camera = camera
+
+            try:
+                engine.render_frame()
+            except AssertionError as exc:
+                assert False, "Error executing shader:\n" + code
+            finally:
+                engine.remove_window(buffer)
+
+        # Download the texture to check whether the assertion triggered.
+        if use_compute:
+            success = engine.extract_texture_data(result, gsg)
+            assert success
+
+        triggered = result.get_ram_image()
+        triggered = tuple(memoryview(triggered).cast('I'))
+
+        if not triggered[0]:
+            pytest.fail("control check failed")
+
+        if any(triggered[1:]):
+            count = len(triggered) - triggered.count(0) - 1
+            lines = body.split('\n')
+            formatted = ''
+            for i, line in enumerate(lines):
+                if triggered[i + 1]:
+                    formatted += '=>  ' + line + '\n'
+                else:
+                    formatted += '    ' + line + '\n'
+            pytest.fail("{0} GLSL assertions triggered:\n{1}".format(count, formatted))
+
+    def run_cg(self, body, preamble="", inputs={}, state=core.RenderState.make_empty()):
+        """ Runs a Cg test on the given GSG.  The given body is executed in the
+        main function and should call assert().  The preamble should contain all
+        of the shader inputs. """
+
+        if self.name.endswith("-legacy"):
+            pytest.skip("no Cg support in legacy pipeline")
+
+        gsg = self.gsg
+        if not gsg.supports_basic_shaders:
+            pytest.skip("basic shaders not supported")
+
+        __tracebackhide__ = True
+
+        preamble = preamble.strip()
+        body = body.rstrip().lstrip('\n')
+        num_lines = body.count('\n') + 1
+
+        vertex_code = CG_VERTEX_TEMPLATE.format(preamble=preamble, body=body)
+        code = CG_FRAGMENT_TEMPLATE.format(preamble=preamble, body=body)
+        shader = core.Shader.make(core.Shader.SL_Cg, vertex_code, code)
+        if not shader:
+            pytest.fail("error compiling shader:\n" + code)
+
+        result = core.Texture("")
+        fbprops = core.FrameBufferProperties()
+        fbprops.force_hardware = True
+        fbprops.set_rgba_bits(8, 8, 8, 8)
+        fbprops.srgb_color = False
+
+        engine = gsg.get_engine()
+        buffer = engine.make_output(
+            gsg.pipe,
+            'buffer',
+            0,
+            fbprops,
+            core.WindowProperties.size(core.Texture.up_to_power_2(num_lines + 1), 1),
+            core.GraphicsPipe.BF_refuse_window,
+            gsg
+        )
+        buffer.add_render_texture(result, core.GraphicsOutput.RTM_copy_ram, core.GraphicsOutput.RTP_color)
+        buffer.set_clear_color_active(True)
+        buffer.set_clear_color((0, 0, 0, 0))
+        engine.open_windows()
+
+        # Build up the shader inputs
+        attrib = core.ShaderAttrib.make(shader)
+        for name, value in inputs.items():
+            attrib = attrib.set_shader_input(name, value)
+        state = state.set_attrib(attrib)
+
+        scene = core.NodePath("root")
+        scene.set_attrib(core.DepthTestAttrib.make(core.RenderAttrib.M_always))
+
+        format = core.GeomVertexFormat.get_v3()
+        vdata = core.GeomVertexData("tri", format, core.Geom.UH_static)
+        vdata.unclean_set_num_rows(3)
+
+        vertex = core.GeomVertexWriter(vdata, "vertex")
+        vertex.set_data3(-1, -1, 0)
+        vertex.set_data3(3, -1, 0)
+        vertex.set_data3(-1, 3, 0)
+
+        tris = core.GeomTriangles(core.Geom.UH_static)
+        tris.add_next_vertices(3)
+
+        geom = core.Geom(vdata)
+        geom.add_primitive(tris)
+
+        gnode = core.GeomNode("tri")
+        gnode.add_geom(geom, state)
+        scene.attach_new_node(gnode)
+        scene.set_two_sided(True)
+
+        camera = scene.attach_new_node(core.Camera("camera"))
+        camera.node().get_lens(0).set_near_far(-10, 10)
+        camera.node().set_cull_bounds(core.OmniBoundingVolume())
+
+        region = buffer.make_display_region()
+        region.active = True
+        region.camera = camera
+
+        try:
+            engine.render_frame()
+        except AssertionError as exc:
+            assert False, "Error executing shader:\n" + code
+        finally:
+            engine.remove_window(buffer)
+
+        # Download the texture to check whether the assertion triggered.
+        triggered = tuple(result.get_ram_image())
+        if triggered[0]:
+            pytest.fail("control check failed")
+
+        if not all(triggered[4:]):
+            count = 0
+            lines = body.split('\n')
+            formatted = ''
+            for i, line in enumerate(lines):
+                offset = (i + 1) * 4
+                x = triggered[offset + 2] == 0
+                y = triggered[offset + 1] == 0
+                z = triggered[offset] == 0
+                w = triggered[offset + 3] == 0
+                if x or y or z or w:
+                    count += 1
+                else:
+                    continue
+                formatted += '=>  ' + line
+                components = ''
+                if x:
+                    components += 'x'
+                if y:
+                    components += 'y'
+                if z:
+                    components += 'z'
+                if w:
+                    components += 'w'
+                formatted += f'      <= {components} components don\'t match'
+                formatted += '\n'
+            pytest.fail("{0} Cg assertions triggered:\n{1}".format(count, formatted))
+
+
+# Which environments should we test shaders in?
+ENVS = set()
+for pipe in ALL_PIPES:
+    if pipe.interface_name == 'OpenGL':
+        ENVS |= frozenset(("gl-legacy", "gl-spirv", "gl-cross-120", "gl-cross-130", "gl-cross-140", "gl-cross-150", "gl-cross-330", "gl-cross-400", "gl-cross-410", "gl-cross-420", "gl-cross-430"))
+    elif pipe.interface_name == 'OpenGL ES':
+        ENVS |= frozenset(("gles-cross-100", "gles-cross-300", "gles-cross-310", "gles-cross-320"))
+    elif pipe.interface_name == 'DirectX9':
+        ENVS |= frozenset(("dx9-cross", ))
+
+
[email protected](scope="module", params=sorted(ENVS))
+def env(request):
+    config = {}
+
+    if request.param.startswith("gl-"):
+        for pipe in ALL_PIPES:
+            if pipe.interface_name == 'OpenGL':
+                break
+        else:
+            pytest.skip("no OpenGL pipe found")
+
+    elif request.param.startswith("gles-"):
+        for pipe in ALL_PIPES:
+            if pipe.interface_name == 'OpenGL ES':
+                break
+        else:
+            pytest.skip("no OpenGL ES pipe found")
+
+    elif request.param.startswith("dx9-"):
+        for pipe in ALL_PIPES:
+            if pipe.interface_name == 'DirectX9':
+                break
+        else:
+            pytest.skip("no DirectX 9 pipe found")
+
+    words = request.param.split("-")
+    if words[0] == "gl" or words[0] == "gles":
+        if words[0] == "gles":
+            # Necessary for image load/store support
+            config["gl-immutable-texture-storage"] = "true"
+
+        if words[1] == "legacy":
+            config["glsl-force-legacy-pipeline"] = "true"
+            allow_compute = True
+
+        elif words[1] == "spirv":
+            config["glsl-force-legacy-pipeline"] = "false"
+            config["gl-support-spirv"] = "true"
+            allow_compute = True
+
+        elif words[1] == "cross":
+            config["glsl-force-legacy-pipeline"] = "false"
+            config["gl-support-spirv"] = "false"
+
+            version = int(words[2])
+            allow_compute = (version >= 330)
+
+            config["gl-force-glsl-version"] = str(version)
+
+    else:
+        allow_compute = False
+
+    prc = '\n'.join(f"{key} {value}" for key, value in config.items())
+    page = core.load_prc_file_data("", prc)
+
+    engine = core.GraphicsEngine()
+
+    fbprops = core.FrameBufferProperties()
+    fbprops.force_hardware = True
+
+    props = core.WindowProperties.size(32, 32)
+
+    buffer = engine.make_output(
+        pipe,
+        'buffer',
+        0,
+        fbprops,
+        props,
+        core.GraphicsPipe.BF_refuse_window
+    )
+    engine.open_windows()
+
+    if buffer is None:
+        # Try making a window instead, putting it in the background so it
+        # disrupts the desktop as little as possible
+        props.minimized = True
+        props.foreground = False
+        props.z_order = WindowProperties.Z_bottom
+        buffer = engine.make_output(
+            pipe,
+            'buffer',
+            0,
+            fbprops,
+            props,
+            core.GraphicsPipe.BF_require_window
+        )
+
+    if buffer is None:
+        pytest.skip("GraphicsPipe cannot make offscreen buffers or windows")
+
+    gsg = buffer.gsg
+
+    # Check if the environment is actually supported.
+    if words[0] == "gl" or words[0] == "gles":
+        if words[1] == "legacy":
+            if not gsg.supports_glsl:
+                pytest.skip("legacy GLSL shaders not supported")
+
+        elif words[1] == "spirv":
+            if (gsg.driver_version_major, gsg.driver_version_minor) < (4, 6) and \
+               not gsg.has_extension("GL_ARB_gl_spirv"):
+                pytest.skip("SPIR-V shaders not supported")
+
+        elif words[1] == "cross":
+            version = int(words[2])
+            if version > gsg.driver_shader_version_major * 100 + gsg.driver_shader_version_minor:
+                pytest.skip(f"GLSL {version} shaders not supported")
+
+    env = ShaderEnvironment(request.param, gsg, allow_compute)
+
+    try:
+        yield env
+    finally:
+        core.unload_prc_file(page)
+
+        if buffer is not None:
+            engine.remove_window(buffer)

+ 23 - 187
tests/display/test_cg_shader.py

@@ -9,171 +9,7 @@ from panda3d import core
 SHADERS_DIR = core.Filename.from_os_specific(os.path.dirname(__file__))
 SHADERS_DIR = core.Filename.from_os_specific(os.path.dirname(__file__))
 
 
 
 
-# This is the template for the shader that is used by run_cg_test.
-# We render this to an nx1 texture, where n is the number of lines in the body.
-# An assert
-CG_VERTEX_TEMPLATE = """//Cg
-
-void vshader(float4 vtx_position : POSITION, out float4 l_position : POSITION) {{
-    l_position = vtx_position;
-}}
-"""
-
-CG_FRAGMENT_TEMPLATE = """//Cg
-
-{preamble}
-
-float4 _assert(bool cond) {{
-    return float4(cond.x, 1, 1, 1);
-}}
-
-float4 _assert(bool2 cond) {{
-    return float4(cond.x, cond.y, 1, 1);
-}}
-
-float4 _assert(bool3 cond) {{
-    return float4(cond.x, cond.y, cond.z, 1);
-}}
-
-float4 _assert(bool4 cond) {{
-    return float4(cond.x, cond.y, cond.z, cond.w);
-}}
-
-#define assert(cond) {{ if ((int)l_vpos.x == __LINE__ - line_offset) o_color = _assert(cond); }}
-
-void fshader(in float2 l_vpos : VPOS, out float4 o_color : COLOR) {{
-    o_color = float4(1, 1, 1, 1);
-
-    if ((int)l_vpos.x == 0) {{
-        o_color = float4(0, 0, 0, 0);
-    }}
-    const int line_offset = __LINE__;
-{body}
-}}
-"""
-
-
-def run_cg_test(gsg, body, preamble="", inputs={},
-                state=core.RenderState.make_empty()):
-    """ Runs a Cg test on the given GSG.  The given body is executed in the
-    main function and should call assert().  The preamble should contain all
-    of the shader inputs. """
-
-    if not gsg.supports_basic_shaders:
-        pytest.skip("basic shaders not supported")
-
-    __tracebackhide__ = True
-
-    preamble = preamble.strip()
-    body = body.rstrip().lstrip('\n')
-    num_lines = body.count('\n') + 1
-
-    vertex_code = CG_VERTEX_TEMPLATE.format(preamble=preamble, body=body)
-    code = CG_FRAGMENT_TEMPLATE.format(preamble=preamble, body=body)
-    shader = core.Shader.make(core.Shader.SL_Cg, vertex_code, code)
-    if not shader:
-        pytest.fail("error compiling shader:\n" + code)
-
-    result = core.Texture("")
-    fbprops = core.FrameBufferProperties()
-    fbprops.force_hardware = True
-    fbprops.set_rgba_bits(8, 8, 8, 8)
-    fbprops.srgb_color = False
-
-    engine = gsg.get_engine()
-    buffer = engine.make_output(
-        gsg.pipe,
-        'buffer',
-        0,
-        fbprops,
-        core.WindowProperties.size(core.Texture.up_to_power_2(num_lines + 1), 1),
-        core.GraphicsPipe.BF_refuse_window,
-        gsg
-    )
-    buffer.add_render_texture(result, core.GraphicsOutput.RTM_copy_ram, core.GraphicsOutput.RTP_color)
-    buffer.set_clear_color_active(True)
-    buffer.set_clear_color((0, 0, 0, 0))
-    engine.open_windows()
-
-    # Build up the shader inputs
-    attrib = core.ShaderAttrib.make(shader)
-    for name, value in inputs.items():
-        attrib = attrib.set_shader_input(name, value)
-    state = state.set_attrib(attrib)
-
-    scene = core.NodePath("root")
-    scene.set_attrib(core.DepthTestAttrib.make(core.RenderAttrib.M_always))
-
-    format = core.GeomVertexFormat.get_v3()
-    vdata = core.GeomVertexData("tri", format, core.Geom.UH_static)
-    vdata.unclean_set_num_rows(3)
-
-    vertex = core.GeomVertexWriter(vdata, "vertex")
-    vertex.set_data3(-1, -1, 0)
-    vertex.set_data3(3, -1, 0)
-    vertex.set_data3(-1, 3, 0)
-
-    tris = core.GeomTriangles(core.Geom.UH_static)
-    tris.add_next_vertices(3)
-
-    geom = core.Geom(vdata)
-    geom.add_primitive(tris)
-
-    gnode = core.GeomNode("tri")
-    gnode.add_geom(geom, state)
-    scene.attach_new_node(gnode)
-    scene.set_two_sided(True)
-
-    camera = scene.attach_new_node(core.Camera("camera"))
-    camera.node().get_lens(0).set_near_far(-10, 10)
-    camera.node().set_cull_bounds(core.OmniBoundingVolume())
-
-    region = buffer.make_display_region()
-    region.active = True
-    region.camera = camera
-
-    try:
-        engine.render_frame()
-    except AssertionError as exc:
-        assert False, "Error executing shader:\n" + code
-    finally:
-        engine.remove_window(buffer)
-
-    # Download the texture to check whether the assertion triggered.
-    triggered = tuple(result.get_ram_image())
-    if triggered[0]:
-        pytest.fail("control check failed")
-
-    if not all(triggered[4:]):
-        count = 0
-        lines = body.split('\n')
-        formatted = ''
-        for i, line in enumerate(lines):
-            offset = (i + 1) * 4
-            x = triggered[offset + 2] == 0
-            y = triggered[offset + 1] == 0
-            z = triggered[offset] == 0
-            w = triggered[offset + 3] == 0
-            if x or y or z or w:
-                count += 1
-            else:
-                continue
-            formatted += '=>  ' + line
-            components = ''
-            if x:
-                components += 'x'
-            if y:
-                components += 'y'
-            if z:
-                components += 'z'
-            if w:
-                components += 'w'
-            formatted += f'      <= {components} components don\'t match'
-            formatted += '\n'
-        pytest.fail("{0} Cg assertions triggered:\n{1}".format(count, formatted))
-
-
-def run_cg_compile_check(gsg, shader_path, expect_fail=False):
+def run_cg_compile_check(shader_path, expect_fail=False):
     """Compile supplied Cg shader path and check for errors"""
     """Compile supplied Cg shader path and check for errors"""
     shader = core.Shader.load(shader_path, core.Shader.SL_Cg)
     shader = core.Shader.load(shader_path, core.Shader.SL_Cg)
     # assert shader.is_prepared(gsg.prepared_objects)
     # assert shader.is_prepared(gsg.prepared_objects)
@@ -183,32 +19,32 @@ def run_cg_compile_check(gsg, shader_path, expect_fail=False):
         assert shader is not None
         assert shader is not None
 
 
 
 
-def test_cg_compile_error(gsg):
+def test_cg_compile_error():
     """Test getting compile errors from bad Cg shaders"""
     """Test getting compile errors from bad Cg shaders"""
     shader_path = core.Filename(SHADERS_DIR, 'cg_bad.sha')
     shader_path = core.Filename(SHADERS_DIR, 'cg_bad.sha')
-    run_cg_compile_check(gsg, shader_path, expect_fail=True)
+    run_cg_compile_check(shader_path, expect_fail=True)
 
 
 
 
-def test_cg_from_file(gsg):
+def test_cg_from_file():
     """Test compiling Cg shaders from files"""
     """Test compiling Cg shaders from files"""
     shader_path = core.Filename(SHADERS_DIR, 'cg_simple.sha')
     shader_path = core.Filename(SHADERS_DIR, 'cg_simple.sha')
-    run_cg_compile_check(gsg, shader_path)
+    run_cg_compile_check(shader_path)
 
 
 
 
-def test_cg_test(gsg):
+def test_cg_test(env):
     "Test to make sure that the Cg tests work correctly."
     "Test to make sure that the Cg tests work correctly."
 
 
-    run_cg_test(gsg, "assert(true);")
+    env.run_cg("assert(true);")
 
 
 
 
-def test_cg_test_fail(gsg):
+def test_cg_test_fail(env):
     "Same as above, but making sure that the failure case works correctly."
     "Same as above, but making sure that the failure case works correctly."
 
 
     with pytest.raises(Failed):
     with pytest.raises(Failed):
-        run_cg_test(gsg, "assert(false);")
+        env.run_cg("assert(false);")
 
 
 
 
-def test_cg_sampler(gsg):
+def test_cg_sampler(env):
     tex1 = core.Texture("tex1-ubyte-rgba8")
     tex1 = core.Texture("tex1-ubyte-rgba8")
     tex1.setup_1d_texture(1, core.Texture.T_unsigned_byte, core.Texture.F_rgba8)
     tex1.setup_1d_texture(1, core.Texture.T_unsigned_byte, core.Texture.F_rgba8)
     tex1.set_clear_color((0, 2 / 255.0, 1, 1))
     tex1.set_clear_color((0, 2 / 255.0, 1, 1))
@@ -231,10 +67,10 @@ def test_cg_sampler(gsg):
     assert(tex2D(tex2, float2(0, 0)) == float4(1.0, 2.0, -3.14, 0.0));
     assert(tex2D(tex2, float2(0, 0)) == float4(1.0, 2.0, -3.14, 0.0));
     assert(abs(tex3D(tex3, float3(0, 0, 0)).r - 0.5) < 0.01);
     assert(abs(tex3D(tex3, float3(0, 0, 0)).r - 0.5) < 0.01);
     """
     """
-    run_cg_test(gsg, code, preamble, {'tex1': tex1, 'tex2': tex2, 'tex3': tex3})
+    env.run_cg(code, preamble, {'tex1': tex1, 'tex2': tex2, 'tex3': tex3})
 
 
 
 
-def test_cg_int(gsg):
+def test_cg_int(env):
     inputs = dict(
     inputs = dict(
         zero=0,
         zero=0,
         ten=10,
         ten=10,
@@ -251,10 +87,10 @@ def test_cg_int(gsg):
     assert(intmax == 0x7fffffff);
     assert(intmax == 0x7fffffff);
     assert(intmin == -0x7fffffff);
     assert(intmin == -0x7fffffff);
     """
     """
-    run_cg_test(gsg, code, preamble, inputs)
+    env.run_cg(code, preamble, inputs)
 
 
 
 
-def test_cg_state_material(gsg):
+def test_cg_state_material(env):
     mat = core.Material("mat")
     mat = core.Material("mat")
     mat.ambient = (1, 2, 3, 4)
     mat.ambient = (1, 2, 3, 4)
     mat.diffuse = (5, 6, 7, 8)
     mat.diffuse = (5, 6, 7, 8)
@@ -276,10 +112,10 @@ def test_cg_state_material(gsg):
     node = core.NodePath("state")
     node = core.NodePath("state")
     node.set_material(mat)
     node.set_material(mat)
 
 
-    run_cg_test(gsg, code, preamble, state=node.get_state())
+    env.run_cg(code, preamble, state=node.get_state())
 
 
 
 
-def test_cg_state_fog(gsg):
+def test_cg_state_fog(env):
     fog = core.Fog("fog")
     fog = core.Fog("fog")
     fog.color = (1, 2, 3, 4)
     fog.color = (1, 2, 3, 4)
     fog.exp_density = 0.5
     fog.exp_density = 0.5
@@ -300,10 +136,10 @@ def test_cg_state_fog(gsg):
     node = core.NodePath("state")
     node = core.NodePath("state")
     node.set_fog(fog)
     node.set_fog(fog)
 
 
-    run_cg_test(gsg, code, preamble, state=node.get_state())
+    env.run_cg(code, preamble, state=node.get_state())
 
 
 
 
-def test_cg_texpad_texpix(gsg):
+def test_cg_texpad_texpix(env):
     tex = core.Texture("test")
     tex = core.Texture("test")
     tex.setup_2d_texture(16, 32, core.Texture.T_unsigned_byte, core.Texture.F_rgba)
     tex.setup_2d_texture(16, 32, core.Texture.T_unsigned_byte, core.Texture.F_rgba)
     tex.auto_texture_scale = core.ATS_pad
     tex.auto_texture_scale = core.ATS_pad
@@ -318,10 +154,10 @@ def test_cg_texpad_texpix(gsg):
     assert(texpix_test == float2(1.0 / 16, 1.0 / 32));
     assert(texpix_test == float2(1.0 / 16, 1.0 / 32));
     """
     """
 
 
-    run_cg_test(gsg, code, preamble, inputs={"test": tex})
+    env.run_cg(code, preamble, inputs={"test": tex})
 
 
 
 
-def test_cg_alight(gsg):
+def test_cg_alight(env):
     alight = core.AmbientLight("alight")
     alight = core.AmbientLight("alight")
     alight.set_color((1, 2, 3, 4))
     alight.set_color((1, 2, 3, 4))
     np = core.NodePath(alight)
     np = core.NodePath(alight)
@@ -333,10 +169,10 @@ def test_cg_alight(gsg):
     assert(alight_test == float4(1, 2, 3, 4));
     assert(alight_test == float4(1, 2, 3, 4));
     """
     """
 
 
-    run_cg_test(gsg, code, preamble, inputs={"test": np})
+    env.run_cg(code, preamble, inputs={"test": np})
 
 
 
 
-def test_cg_satten(gsg):
+def test_cg_satten(env):
     spot = core.Spotlight("spot")
     spot = core.Spotlight("spot")
     spot.set_attenuation((1, 2, 3))
     spot.set_attenuation((1, 2, 3))
     spot.set_exponent(4)
     spot.set_exponent(4)
@@ -349,4 +185,4 @@ def test_cg_satten(gsg):
     assert(satten_test == float4(1, 2, 3, 4));
     assert(satten_test == float4(1, 2, 3, 4));
     """
     """
 
 
-    run_cg_test(gsg, code, preamble, inputs={"test": np})
+    env.run_cg(code, preamble, inputs={"test": np})

+ 146 - 385
tests/display/test_glsl_shader.py

@@ -8,275 +8,20 @@ from _pytest.outcomes import Failed
 SHADERS_DIR = core.Filename.from_os_specific(os.path.dirname(__file__))
 SHADERS_DIR = core.Filename.from_os_specific(os.path.dirname(__file__))
 
 
 
 
-# This is the template for the compute shader that is used by run_glsl_test.
-# It defines an assert() macro that writes failures to a buffer, indexed by
-# line number.
-# The reset() function serves to prevent the _triggered variable from being
-# optimized out in the case that the assertions are being optimized out.
-GLSL_COMPUTE_TEMPLATE = """#version {version}
-{extensions}
-
-layout(local_size_x = 1, local_size_y = 1) in;
-
-{preamble}
-
-layout(r8ui) uniform writeonly uimageBuffer _triggered;
-
-void _reset() {{
-    imageStore(_triggered, 0, uvec4(1));
-    memoryBarrier();
-}}
-
-void _assert(bool cond, int line) {{
-    if (!cond) {{
-        imageStore(_triggered, line, uvec4(1));
-    }}
-}}
-
-#define assert(cond) _assert(cond, __LINE__ - line_offset)
-
-void main() {{
-    _reset();
-    const int line_offset = __LINE__;
-{body}
-}}
-"""
-
-# This is a version that uses a vertex and fragment shader instead.  This is
-# slower to set up, but it works even when compute shaders are not supported.
-# The shader is rendered on a fullscreen triangle to a texture, where each
-# pixel represents one line of the code.  The assert writes the result to the
-# output color if the current fragment matches the line number of that assert.
-# The first pixel is used as a control, to check that the shader has run.
-GLSL_VERTEX_TEMPLATE = """#version {version}
-
-in vec4 p3d_Vertex;
-
-void main() {{
-    gl_Position = p3d_Vertex;
-}}
-"""
-
-GLSL_FRAGMENT_TEMPLATE = """#version {version}
-{extensions}
-
-{preamble}
-
-layout(location = 0) out vec4 p3d_FragColor;
-
-void _reset() {{
-    p3d_FragColor = vec4(0, 0, 0, 0);
-
-    if (int(gl_FragCoord.x) == 0) {{
-        p3d_FragColor = vec4(1, 1, 1, 1);
-    }}
-}}
-
-void _assert(bool cond, int line) {{
-    if (int(gl_FragCoord.x) == line) {{
-        p3d_FragColor = vec4(!cond, !cond, !cond, !cond);
-    }}
-}}
-
-#define assert(cond) _assert(cond, __LINE__ - line_offset)
-
-void main() {{
-    _reset();
-    const int line_offset = __LINE__;
-{body}
-}}
-"""
-
-
-def run_glsl_test(gsg, body, preamble="", inputs={}, version=420, exts=set(),
-                  state=core.RenderState.make_empty()):
-    """ Runs a GLSL test on the given GSG.  The given body is executed in the
-    main function and should call assert().  The preamble should contain all
-    of the shader inputs. """
-
-    if not gsg.supports_basic_shaders:
-        pytest.skip("shaders not supported")
-
-    use_compute = gsg.supports_compute_shaders and \
-                  gsg.supports_buffer_texture and \
-                  (gsg.supported_shader_capabilities & core.Shader.C_image_load_store) != 0
-
-    missing_exts = sorted(ext for ext in exts if not gsg.has_extension(ext))
-    if missing_exts:
-        pytest.skip("missing extensions: " + ' '.join(missing_exts))
-
-    if use_compute:
-        exts = exts | {'GL_ARB_compute_shader', 'GL_ARB_shader_image_load_store'}
-
-    extensions = ''
-    for ext in exts:
-        extensions += '#extension {ext} : require\n'.format(ext=ext)
-
-    __tracebackhide__ = True
-
-    preamble = preamble.strip()
-    body = body.rstrip().lstrip('\n')
-
-    if use_compute:
-        code = GLSL_COMPUTE_TEMPLATE.format(version=version, extensions=extensions, preamble=preamble, body=body)
-        shader = core.Shader.make_compute(core.Shader.SL_GLSL, code)
-    else:
-        vertex_code = GLSL_VERTEX_TEMPLATE.format(version=version, extensions=extensions, preamble=preamble, body=body)
-        code = GLSL_FRAGMENT_TEMPLATE.format(version=version, extensions=extensions, preamble=preamble, body=body)
-        shader = core.Shader.make(core.Shader.SL_GLSL, vertex_code, code)
-
-    if not shader:
-        pytest.fail("error compiling shader:\n" + code)
-
-    unsupported_caps = shader.get_used_capabilities() & ~gsg.supported_shader_capabilities
-    if unsupported_caps != 0:
-        stream = core.StringStream()
-        core.ShaderEnums.output_capabilities(stream, unsupported_caps)
-        pytest.skip("unsupported capabilities: " + stream.data.decode('ascii'))
-
-    num_lines = body.count('\n') + 1
-
-    # Create a buffer to hold the results of the assertion.  We use one texel
-    # per line of shader code, so we can show which lines triggered.
-    engine = gsg.get_engine()
-    result = core.Texture("")
-    if use_compute:
-        result.set_clear_color((0, 0, 0, 0))
-        result.setup_buffer_texture(num_lines + 1, core.Texture.T_unsigned_byte,
-                                    core.Texture.F_r8i, core.GeomEnums.UH_static)
-    else:
-        fbprops = core.FrameBufferProperties()
-        fbprops.force_hardware = True
-        fbprops.set_rgba_bits(8, 8, 8, 8)
-        fbprops.srgb_color = False
-
-        buffer = engine.make_output(
-            gsg.pipe,
-            'buffer',
-            0,
-            fbprops,
-            core.WindowProperties.size(core.Texture.up_to_power_2(num_lines + 1), 1),
-            core.GraphicsPipe.BF_refuse_window,
-            gsg
-        )
-        buffer.add_render_texture(result, core.GraphicsOutput.RTM_copy_ram, core.GraphicsOutput.RTP_color)
-        buffer.set_clear_color_active(True)
-        buffer.set_clear_color((0, 0, 0, 0))
-        engine.open_windows()
-
-    # Build up the shader inputs
-    attrib = core.ShaderAttrib.make(shader)
-    for name, value in inputs.items():
-        attrib = attrib.set_shader_input(name, value)
-    if use_compute:
-        attrib = attrib.set_shader_input('_triggered', result)
-    state = state.set_attrib(attrib)
-
-    # Run the shader.
-    if use_compute:
-        try:
-            engine.dispatch_compute((1, 1, 1), state, gsg)
-        except AssertionError as exc:
-            assert False, "Error executing compute shader:\n" + code
-    else:
-        scene = core.NodePath("root")
-        scene.set_attrib(core.DepthTestAttrib.make(core.RenderAttrib.M_always))
-
-        format = core.GeomVertexFormat.get_v3()
-        vdata = core.GeomVertexData("tri", format, core.Geom.UH_static)
-        vdata.unclean_set_num_rows(3)
-
-        vertex = core.GeomVertexWriter(vdata, "vertex")
-        vertex.set_data3(-1, -1, 0)
-        vertex.set_data3(3, -1, 0)
-        vertex.set_data3(-1, 3, 0)
-
-        tris = core.GeomTriangles(core.Geom.UH_static)
-        tris.add_next_vertices(3)
-
-        geom = core.Geom(vdata)
-        geom.add_primitive(tris)
-
-        gnode = core.GeomNode("tri")
-        gnode.add_geom(geom, state)
-        scene.attach_new_node(gnode)
-        scene.set_two_sided(True)
-
-        camera = scene.attach_new_node(core.Camera("camera"))
-        camera.node().get_lens(0).set_near_far(-10, 10)
-        camera.node().set_cull_bounds(core.OmniBoundingVolume())
-
-        region = buffer.make_display_region()
-        region.active = True
-        region.camera = camera
-
-        try:
-            engine.render_frame()
-        except AssertionError as exc:
-            assert False, "Error executing shader:\n" + code
-        finally:
-            engine.remove_window(buffer)
-
-    # Download the texture to check whether the assertion triggered.
-    if use_compute:
-        success = engine.extract_texture_data(result, gsg)
-        assert success
-
-    triggered = result.get_ram_image()
-    if use_compute:
-        triggered = tuple(triggered)
-    else:
-        triggered = tuple(memoryview(triggered).cast('I'))
-
-    if not triggered[0]:
-        pytest.fail("control check failed")
-
-    if any(triggered[1:]):
-        count = len(triggered) - triggered.count(0) - 1
-        lines = body.split('\n')
-        formatted = ''
-        for i, line in enumerate(lines):
-            if triggered[i + 1]:
-                formatted += '=>  ' + line + '\n'
-            else:
-                formatted += '    ' + line + '\n'
-        pytest.fail("{0} GLSL assertions triggered:\n{1}".format(count, formatted))
-
-
-def run_glsl_compile_check(gsg, vert_path, frag_path, expect_fail=False):
-    """Compile supplied GLSL shader paths and check for errors"""
-    shader = core.Shader.load(core.Shader.SL_GLSL, vert_path, frag_path)
-    if expect_fail:
-        assert shader is None
-        return
-
-    assert shader is not None
-
-    if not gsg.supports_glsl:
-        expect_fail = True
-
-    shader.prepare_now(gsg.prepared_objects, gsg)
-    assert shader.is_prepared(gsg.prepared_objects)
-    if expect_fail:
-        assert shader.get_error_flag()
-    else:
-        assert not shader.get_error_flag()
-
-
-def test_glsl_test(gsg):
+def test_glsl_test(env):
     "Test to make sure that the GLSL tests work correctly."
     "Test to make sure that the GLSL tests work correctly."
 
 
-    run_glsl_test(gsg, "assert(true);")
+    env.run_glsl("assert(true);")
 
 
 
 
-def test_glsl_test_fail(gsg):
+def test_glsl_test_fail(env):
     "Same as above, but making sure that the failure case works correctly."
     "Same as above, but making sure that the failure case works correctly."
 
 
     with pytest.raises(Failed):
     with pytest.raises(Failed):
-        run_glsl_test(gsg, "assert(false);")
+        env.run_glsl("assert(false);")
 
 
 
 
-def test_glsl_sampler(gsg):
+def test_glsl_sampler(env):
     tex1 = core.Texture("tex1-ubyte-rgba8")
     tex1 = core.Texture("tex1-ubyte-rgba8")
     tex1.setup_1d_texture(1, core.Texture.T_unsigned_byte, core.Texture.F_rgba8)
     tex1.setup_1d_texture(1, core.Texture.T_unsigned_byte, core.Texture.F_rgba8)
     tex1.set_clear_color((0, 2 / 255.0, 1, 1))
     tex1.set_clear_color((0, 2 / 255.0, 1, 1))
@@ -299,10 +44,10 @@ def test_glsl_sampler(gsg):
     assert(texture(tex2, vec2(0, 0)) == vec4(1.0, 2.0, -3.14, 0.0));
     assert(texture(tex2, vec2(0, 0)) == vec4(1.0, 2.0, -3.14, 0.0));
     assert(texture(tex3, vec3(0, 0, 0)).r == 0.5);
     assert(texture(tex3, vec3(0, 0, 0)).r == 0.5);
     """
     """
-    run_glsl_test(gsg, code, preamble, {'tex1': tex1, 'tex2': tex2, 'tex3': tex3})
+    env.run_glsl(code, preamble, {'tex1': tex1, 'tex2': tex2, 'tex3': tex3})
 
 
 
 
-def test_glsl_isampler(gsg):
+def test_glsl_isampler(env):
     from struct import pack
     from struct import pack
 
 
     tex1 = core.Texture("")
     tex1 = core.Texture("")
@@ -327,10 +72,10 @@ def test_glsl_isampler(gsg):
     assert(texelFetch(tex2, ivec2(0, 0), 0) == ivec4(4, 0, 0, 1));
     assert(texelFetch(tex2, ivec2(0, 0), 0) == ivec4(4, 0, 0, 1));
     assert(texelFetch(tex3, ivec3(0, 0, 0), 0) == ivec4(5, 0, 0, 1));
     assert(texelFetch(tex3, ivec3(0, 0, 0), 0) == ivec4(5, 0, 0, 1));
     """
     """
-    run_glsl_test(gsg, code, preamble, {'tex1': tex1, 'tex2': tex2, 'tex3': tex3})
+    env.run_glsl(code, preamble, {'tex1': tex1, 'tex2': tex2, 'tex3': tex3})
 
 
 
 
-def test_glsl_usampler(gsg):
+def test_glsl_usampler(env):
     from struct import pack
     from struct import pack
 
 
     tex1 = core.Texture("")
     tex1 = core.Texture("")
@@ -355,10 +100,10 @@ def test_glsl_usampler(gsg):
     assert(texelFetch(tex2, ivec2(0, 0), 0) == uvec4(4, 0, 0, 1));
     assert(texelFetch(tex2, ivec2(0, 0), 0) == uvec4(4, 0, 0, 1));
     assert(texelFetch(tex3, ivec3(0, 0, 0), 0) == uvec4(5, 0, 0, 1));
     assert(texelFetch(tex3, ivec3(0, 0, 0), 0) == uvec4(5, 0, 0, 1));
     """
     """
-    run_glsl_test(gsg, code, preamble, {'tex1': tex1, 'tex2': tex2, 'tex3': tex3})
+    env.run_glsl(code, preamble, {'tex1': tex1, 'tex2': tex2, 'tex3': tex3})
 
 
 
 
-def test_glsl_image(gsg):
+def test_glsl_image(env):
     tex1 = core.Texture("")
     tex1 = core.Texture("")
     tex1.setup_1d_texture(1, core.Texture.T_unsigned_byte, core.Texture.F_rgba8)
     tex1.setup_1d_texture(1, core.Texture.T_unsigned_byte, core.Texture.F_rgba8)
     tex1.set_clear_color((0, 2 / 255.0, 1, 1))
     tex1.set_clear_color((0, 2 / 255.0, 1, 1))
@@ -375,10 +120,10 @@ def test_glsl_image(gsg):
     assert(imageLoad(tex1, 0) == vec4(0, 2 / 255.0, 1, 1));
     assert(imageLoad(tex1, 0) == vec4(0, 2 / 255.0, 1, 1));
     assert(imageLoad(tex2, ivec2(0, 0)) == vec4(1.0, 2.0, -3.14, 0.0));
     assert(imageLoad(tex2, ivec2(0, 0)) == vec4(1.0, 2.0, -3.14, 0.0));
     """
     """
-    run_glsl_test(gsg, code, preamble, {'tex1': tex1, 'tex2': tex2})
+    env.run_glsl(code, preamble, {'tex1': tex1, 'tex2': tex2})
 
 
 
 
-def test_glsl_iimage(gsg):
+def test_glsl_iimage(env):
     from struct import pack
     from struct import pack
 
 
     tex1 = core.Texture("")
     tex1 = core.Texture("")
@@ -403,10 +148,10 @@ def test_glsl_iimage(gsg):
     assert(imageLoad(tex2, ivec2(0, 0)) == ivec4(4, 0, 0, 1));
     assert(imageLoad(tex2, ivec2(0, 0)) == ivec4(4, 0, 0, 1));
     assert(imageLoad(tex3, ivec3(0, 0, 0)) == ivec4(5, 0, 0, 1));
     assert(imageLoad(tex3, ivec3(0, 0, 0)) == ivec4(5, 0, 0, 1));
     """
     """
-    run_glsl_test(gsg, code, preamble, {'tex1': tex1, 'tex2': tex2, 'tex3': tex3})
+    env.run_glsl(code, preamble, {'tex1': tex1, 'tex2': tex2, 'tex3': tex3})
 
 
 
 
-def test_glsl_uimage(gsg):
+def test_glsl_uimage(env):
     from struct import pack
     from struct import pack
 
 
     tex1 = core.Texture("")
     tex1 = core.Texture("")
@@ -431,10 +176,10 @@ def test_glsl_uimage(gsg):
     assert(imageLoad(tex2, ivec2(0, 0)) == uvec4(4, 0, 0, 1));
     assert(imageLoad(tex2, ivec2(0, 0)) == uvec4(4, 0, 0, 1));
     assert(imageLoad(tex3, ivec3(0, 0, 0)) == uvec4(5, 0, 0, 1));
     assert(imageLoad(tex3, ivec3(0, 0, 0)) == uvec4(5, 0, 0, 1));
     """
     """
-    run_glsl_test(gsg, code, preamble, {'tex1': tex1, 'tex2': tex2, 'tex3': tex3})
+    env.run_glsl(code, preamble, {'tex1': tex1, 'tex2': tex2, 'tex3': tex3})
 
 
 
 
-def test_glsl_ssbo(gsg):
+def test_glsl_ssbo(env):
     from struct import pack
     from struct import pack
     num1 = pack('<i', 1234567)
     num1 = pack('<i', 1234567)
     num2 = pack('<i', -1234567)
     num2 = pack('<i', -1234567)
@@ -460,12 +205,12 @@ def test_glsl_ssbo(gsg):
     assert(value1 == 1234567);
     assert(value1 == 1234567);
     assert(value2 == -1234567);
     assert(value2 == -1234567);
     """
     """
-    run_glsl_test(gsg, code, preamble,
+    env.run_glsl(code, preamble,
                   {'buffer1': buffer1, 'buffer2': buffer2, 'buffer3': buffer3},
                   {'buffer1': buffer1, 'buffer2': buffer2, 'buffer3': buffer3},
                   version=430)
                   version=430)
 
 
 
 
-def test_glsl_ssbo_array(gsg):
+def test_glsl_ssbo_array(env):
     from struct import pack
     from struct import pack
     dummy = pack('<i', 999999)
     dummy = pack('<i', 999999)
     num1 = pack('<i', 1234567)
     num1 = pack('<i', 1234567)
@@ -486,12 +231,12 @@ def test_glsl_ssbo_array(gsg):
     assert(test_ns[1][0].inside[0].value == 1234567);
     assert(test_ns[1][0].inside[0].value == 1234567);
     assert(test_ns[2][0].inside[0].value == -1234567);
     assert(test_ns[2][0].inside[0].value == -1234567);
     """
     """
-    run_glsl_test(gsg, code, preamble,
+    env.run_glsl(code, preamble,
                   {'test[0][0]': unused, 'test[1][0]': buffer1, 'test[2][0]': buffer2},
                   {'test[0][0]': unused, 'test[1][0]': buffer1, 'test[2][0]': buffer2},
                   version=430)
                   version=430)
 
 
 
 
-def test_glsl_ssbo_runtime_length(gsg):
+def test_glsl_ssbo_runtime_length(env):
     from struct import pack
     from struct import pack
     nums = pack('<ii', 1234, 5678)
     nums = pack('<ii', 1234, 5678)
     ssbo = core.ShaderBuffer("ssbo", nums, core.GeomEnums.UH_static)
     ssbo = core.ShaderBuffer("ssbo", nums, core.GeomEnums.UH_static)
@@ -506,10 +251,10 @@ def test_glsl_ssbo_runtime_length(gsg):
     assert(values[0] == 1234);
     assert(values[0] == 1234);
     assert(values[1] == 5678);
     assert(values[1] == 5678);
     """
     """
-    run_glsl_test(gsg, code, preamble, {'ssbo': ssbo}, version=430)
+    env.run_glsl(code, preamble, {'ssbo': ssbo}, version=430)
 
 
 
 
-def test_glsl_float(gsg):
+def test_glsl_float(env):
     inputs = dict(
     inputs = dict(
         zero=0,
         zero=0,
         a=1.23,
         a=1.23,
@@ -525,10 +270,10 @@ def test_glsl_float(gsg):
     assert(abs(a - 1.23) < 0.001);
     assert(abs(a - 1.23) < 0.001);
     assert(abs(b - -829.123) < 0.001);
     assert(abs(b - -829.123) < 0.001);
     """
     """
-    run_glsl_test(gsg, code, preamble, inputs)
+    env.run_glsl(code, preamble, inputs)
 
 
 
 
-def test_glsl_int(gsg):
+def test_glsl_int(env):
     inputs = dict(
     inputs = dict(
         zero=0,
         zero=0,
         intmax=0x7fffffff,
         intmax=0x7fffffff,
@@ -544,10 +289,10 @@ def test_glsl_int(gsg):
     assert(intmax == 0x7fffffff);
     assert(intmax == 0x7fffffff);
     assert(intmin == -0x7fffffff);
     assert(intmin == -0x7fffffff);
     """
     """
-    run_glsl_test(gsg, code, preamble, inputs)
+    env.run_glsl(code, preamble, inputs)
 
 
 
 
-def test_glsl_uint(gsg):
+def test_glsl_uint(env):
     #TODO: fix passing uints greater than intmax
     #TODO: fix passing uints greater than intmax
     inputs = dict(
     inputs = dict(
         zero=0,
         zero=0,
@@ -561,11 +306,11 @@ def test_glsl_uint(gsg):
     assert(zero == 0u);
     assert(zero == 0u);
     assert(intmax == 0x7fffffffu);
     assert(intmax == 0x7fffffffu);
     """
     """
-    run_glsl_test(gsg, code, preamble, inputs)
+    env.run_glsl(code, preamble, inputs)
 
 
 
 
 #@pytest.mark.xfail(reason="https://github.com/KhronosGroup/SPIRV-Tools/issues/3387")
 #@pytest.mark.xfail(reason="https://github.com/KhronosGroup/SPIRV-Tools/issues/3387")
-def test_glsl_bool(gsg):
+def test_glsl_bool(env):
     flags = dict(
     flags = dict(
         flag1=False,
         flag1=False,
         flag2=0,
         flag2=0,
@@ -590,10 +335,10 @@ def test_glsl_bool(gsg):
     assert(flag5);
     assert(flag5);
     assert(flag6);
     assert(flag6);
     """
     """
-    run_glsl_test(gsg, code, preamble, flags)
+    env.run_glsl(code, preamble, flags)
 
 
 
 
-def test_glsl_mat3(gsg):
+def test_glsl_mat3(env):
     param1 = core.LMatrix4f(core.LMatrix3f(1, 2, 3, 4, 5, 6, 7, 8, 9))
     param1 = core.LMatrix4f(core.LMatrix3f(1, 2, 3, 4, 5, 6, 7, 8, 9))
     param2 = core.LMatrix4d(core.LMatrix3d(10, 11, 12, 13, 14, 15, 16, 17, 18))
     param2 = core.LMatrix4d(core.LMatrix3d(10, 11, 12, 13, 14, 15, 16, 17, 18))
 
 
@@ -616,11 +361,11 @@ def test_glsl_mat3(gsg):
     assert(param3[1] == vec3(22, 23, 24));
     assert(param3[1] == vec3(22, 23, 24));
     assert(param3[2] == vec3(25, 26, 27));
     assert(param3[2] == vec3(25, 26, 27));
     """
     """
-    run_glsl_test(gsg, code, preamble,
+    env.run_glsl(code, preamble,
         {'param1': param1, 'param2': param2, 'param3': param3})
         {'param1': param1, 'param2': param2, 'param3': param3})
 
 
 
 
-def test_glsl_mat4(gsg):
+def test_glsl_mat4(env):
     param1 = core.LMatrix4f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
     param1 = core.LMatrix4f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
     param2 = core.LMatrix4d(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)
     param2 = core.LMatrix4d(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)
 
 
@@ -650,11 +395,11 @@ def test_glsl_mat4(gsg):
     assert(param3[2] == vec4(41, 42, 43, 44));
     assert(param3[2] == vec4(41, 42, 43, 44));
     assert(param3[3] == vec4(45, 46, 47, 48));
     assert(param3[3] == vec4(45, 46, 47, 48));
     """
     """
-    run_glsl_test(gsg, code, preamble,
+    env.run_glsl(code, preamble,
         {'param1': param1, 'param2': param2, 'param3': param3})
         {'param1': param1, 'param2': param2, 'param3': param3})
 
 
 
 
-def test_glsl_mat3x4(gsg):
+def test_glsl_mat3x4(env):
     param1 = core.LMatrix4f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
     param1 = core.LMatrix4f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
     param2 = core.LMatrix4d(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)
     param2 = core.LMatrix4d(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)
 
 
@@ -670,11 +415,11 @@ def test_glsl_mat3x4(gsg):
     assert(param2[1] == vec4(21, 22, 23, 24));
     assert(param2[1] == vec4(21, 22, 23, 24));
     assert(param2[2] == vec4(25, 26, 27, 28));
     assert(param2[2] == vec4(25, 26, 27, 28));
     """
     """
-    run_glsl_test(gsg, code, preamble,
+    env.run_glsl(code, preamble,
         {'param1': param1, 'param2': param2})
         {'param1': param1, 'param2': param2})
 
 
 
 
-def test_glsl_mat4x3(gsg):
+def test_glsl_mat4x3(env):
     param1 = core.LMatrix4f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
     param1 = core.LMatrix4f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
     param2 = core.LMatrix4d(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)
     param2 = core.LMatrix4d(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)
 
 
@@ -693,11 +438,11 @@ def test_glsl_mat4x3(gsg):
     assert(param2[2] == vec3(25, 26, 27));
     assert(param2[2] == vec3(25, 26, 27));
     assert(param2[3] == vec3(29, 30, 31));
     assert(param2[3] == vec3(29, 30, 31));
     """
     """
-    run_glsl_test(gsg, code, preamble,
+    env.run_glsl(code, preamble,
         {'param1': param1, 'param2': param2})
         {'param1': param1, 'param2': param2})
 
 
 
 
-def test_glsl_pta_int(gsg):
+def test_glsl_pta_int(env):
     pta = core.PTA_int((0, 1, 2, 3))
     pta = core.PTA_int((0, 1, 2, 3))
 
 
     preamble = """
     preamble = """
@@ -709,10 +454,10 @@ def test_glsl_pta_int(gsg):
     assert(pta[2] == 2);
     assert(pta[2] == 2);
     assert(pta[3] == 3);
     assert(pta[3] == 3);
     """
     """
-    run_glsl_test(gsg, code, preamble, {'pta': pta})
+    env.run_glsl(code, preamble, {'pta': pta})
 
 
 
 
-def test_glsl_pta_ivec4(gsg):
+def test_glsl_pta_ivec4(env):
     pta = core.PTA_LVecBase4i(((0, 1, 2, 3), (4, 5, 6, 7)))
     pta = core.PTA_LVecBase4i(((0, 1, 2, 3), (4, 5, 6, 7)))
 
 
     preamble = """
     preamble = """
@@ -722,11 +467,11 @@ def test_glsl_pta_ivec4(gsg):
     assert(pta[0] == ivec4(0, 1, 2, 3));
     assert(pta[0] == ivec4(0, 1, 2, 3));
     assert(pta[1] == ivec4(4, 5, 6, 7));
     assert(pta[1] == ivec4(4, 5, 6, 7));
     """
     """
-    run_glsl_test(gsg, code, preamble, {'pta': pta})
+    env.run_glsl(code, preamble, {'pta': pta})
 
 
 
 
 @pytest.mark.parametrize("type", (core.PTA_LVecBase3f, core.PTA_LVecBase3d, core.PTA_LVecBase3i))
 @pytest.mark.parametrize("type", (core.PTA_LVecBase3f, core.PTA_LVecBase3d, core.PTA_LVecBase3i))
-def test_glsl_pta_vec3(gsg, type):
+def test_glsl_pta_vec3(env, type):
     pta = type((
     pta = type((
         (0, 1, 2),
         (0, 1, 2),
         (3, 4, 5),
         (3, 4, 5),
@@ -741,11 +486,11 @@ def test_glsl_pta_vec3(gsg, type):
     assert(pta[1] == vec3(3, 4, 5));
     assert(pta[1] == vec3(3, 4, 5));
     assert(pta[2] == vec3(6, 7, 8));
     assert(pta[2] == vec3(6, 7, 8));
     """
     """
-    run_glsl_test(gsg, code, preamble, {'pta': pta})
+    env.run_glsl(code, preamble, {'pta': pta})
 
 
 
 
 @pytest.mark.parametrize("type", (core.PTA_LVecBase3f, core.PTA_LVecBase3d, core.PTA_LVecBase3i))
 @pytest.mark.parametrize("type", (core.PTA_LVecBase3f, core.PTA_LVecBase3d, core.PTA_LVecBase3i))
-def test_glsl_pta_dvec3(gsg, type):
+def test_glsl_pta_dvec3(env, type):
     pta = type((
     pta = type((
         (0, 1, 2),
         (0, 1, 2),
         (3, 4, 5),
         (3, 4, 5),
@@ -760,11 +505,11 @@ def test_glsl_pta_dvec3(gsg, type):
     assert(pta[1] == vec3(3, 4, 5));
     assert(pta[1] == vec3(3, 4, 5));
     assert(pta[2] == vec3(6, 7, 8));
     assert(pta[2] == vec3(6, 7, 8));
     """
     """
-    run_glsl_test(gsg, code, preamble, {'pta': pta})
+    env.run_glsl(code, preamble, {'pta': pta})
 
 
 
 
 @pytest.mark.parametrize("type", (core.PTA_LVecBase4f, core.PTA_LVecBase4d, core.PTA_LVecBase4i))
 @pytest.mark.parametrize("type", (core.PTA_LVecBase4f, core.PTA_LVecBase4d, core.PTA_LVecBase4i))
-def test_glsl_pta_vec4(gsg, type):
+def test_glsl_pta_vec4(env, type):
     pta = type((
     pta = type((
         (0, 1, 2, 3),
         (0, 1, 2, 3),
         (4, 5, 6, 7),
         (4, 5, 6, 7),
@@ -779,11 +524,11 @@ def test_glsl_pta_vec4(gsg, type):
     assert(pta[1] == vec4(4, 5, 6, 7));
     assert(pta[1] == vec4(4, 5, 6, 7));
     assert(pta[2] == vec4(8, 9, 10, 11));
     assert(pta[2] == vec4(8, 9, 10, 11));
     """
     """
-    run_glsl_test(gsg, code, preamble, {'pta': pta})
+    env.run_glsl(code, preamble, {'pta': pta})
 
 
 
 
 @pytest.mark.parametrize("type", (core.PTA_LVecBase4f, core.PTA_LVecBase4d, core.PTA_LVecBase4i))
 @pytest.mark.parametrize("type", (core.PTA_LVecBase4f, core.PTA_LVecBase4d, core.PTA_LVecBase4i))
-def test_glsl_pta_dvec4(gsg, type):
+def test_glsl_pta_dvec4(env, type):
     pta = type((
     pta = type((
         (0, 1, 2, 3),
         (0, 1, 2, 3),
         (4, 5, 6, 7),
         (4, 5, 6, 7),
@@ -798,11 +543,11 @@ def test_glsl_pta_dvec4(gsg, type):
     assert(pta[1] == dvec4(4, 5, 6, 7));
     assert(pta[1] == dvec4(4, 5, 6, 7));
     assert(pta[2] == dvec4(8, 9, 10, 11));
     assert(pta[2] == dvec4(8, 9, 10, 11));
     """
     """
-    run_glsl_test(gsg, code, preamble, {'pta': pta})
+    env.run_glsl(code, preamble, {'pta': pta})
 
 
 
 
 @pytest.mark.parametrize("type", (core.PTA_LMatrix3f, core.PTA_LMatrix3d))
 @pytest.mark.parametrize("type", (core.PTA_LMatrix3f, core.PTA_LMatrix3d))
-def test_glsl_pta_mat3(gsg, type):
+def test_glsl_pta_mat3(env, type):
     pta = type((
     pta = type((
         (0, 1, 2, 3, 4, 5, 6, 7, 8),
         (0, 1, 2, 3, 4, 5, 6, 7, 8),
         (9, 10, 11, 12, 13, 14, 15, 16, 17),
         (9, 10, 11, 12, 13, 14, 15, 16, 17),
@@ -819,11 +564,11 @@ def test_glsl_pta_mat3(gsg, type):
     assert(pta[1][1] == vec3(12, 13, 14));
     assert(pta[1][1] == vec3(12, 13, 14));
     assert(pta[1][2] == vec3(15, 16, 17));
     assert(pta[1][2] == vec3(15, 16, 17));
     """
     """
-    run_glsl_test(gsg, code, preamble, {'pta': pta})
+    env.run_glsl(code, preamble, {'pta': pta})
 
 
 
 
 @pytest.mark.parametrize("type", (core.PTA_LMatrix4f, core.PTA_LMatrix4d))
 @pytest.mark.parametrize("type", (core.PTA_LMatrix4f, core.PTA_LMatrix4d))
-def test_glsl_pta_mat4(gsg, type):
+def test_glsl_pta_mat4(env, type):
     pta = type((
     pta = type((
         (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15),
         (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15),
         (16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31),
         (16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31),
@@ -842,10 +587,10 @@ def test_glsl_pta_mat4(gsg, type):
     assert(pta[1][2] == vec4(24, 25, 26, 27));
     assert(pta[1][2] == vec4(24, 25, 26, 27));
     assert(pta[1][3] == vec4(28, 29, 30, 31));
     assert(pta[1][3] == vec4(28, 29, 30, 31));
     """
     """
-    run_glsl_test(gsg, code, preamble, {'pta': pta})
+    env.run_glsl(code, preamble, {'pta': pta})
 
 
 
 
-def test_glsl_param_vec4(gsg):
+def test_glsl_param_vec4(env):
     param = core.ParamVecBase4((0, 1, 2, 3))
     param = core.ParamVecBase4((0, 1, 2, 3))
 
 
     preamble = """
     preamble = """
@@ -857,10 +602,10 @@ def test_glsl_param_vec4(gsg):
     assert(param.z == 2.0);
     assert(param.z == 2.0);
     assert(param.w == 3.0);
     assert(param.w == 3.0);
     """
     """
-    run_glsl_test(gsg, code, preamble, {'param': param})
+    env.run_glsl(code, preamble, {'param': param})
 
 
 
 
-def test_glsl_param_ivec4(gsg):
+def test_glsl_param_ivec4(env):
     param = core.ParamVecBase4i((0, 1, 2, 3))
     param = core.ParamVecBase4i((0, 1, 2, 3))
 
 
     preamble = """
     preamble = """
@@ -872,10 +617,10 @@ def test_glsl_param_ivec4(gsg):
     assert(param.z == 2);
     assert(param.z == 2);
     assert(param.w == 3);
     assert(param.w == 3);
     """
     """
-    run_glsl_test(gsg, code, preamble, {'param': param})
+    env.run_glsl(code, preamble, {'param': param})
 
 
 
 
-def test_glsl_struct(gsg):
+def test_glsl_struct(env):
     preamble = """
     preamble = """
     uniform struct TestStruct {
     uniform struct TestStruct {
         vec3 a;
         vec3 a;
@@ -902,7 +647,7 @@ def test_glsl_struct(gsg):
     tex_f = core.Texture('f')
     tex_f = core.Texture('f')
     tex_f.setup_2d_texture(1, 1, core.Texture.T_float, core.Texture.F_r32)
     tex_f.setup_2d_texture(1, 1, core.Texture.T_float, core.Texture.F_r32)
     tex_f.set_clear_color((14, 0, 0, 0))
     tex_f.set_clear_color((14, 0, 0, 0))
-    run_glsl_test(gsg, code, preamble, {
+    env.run_glsl(code, preamble, {
         'test.unused': 0,
         'test.unused': 0,
         'test.a': (1, 2, 3),
         'test.a': (1, 2, 3),
         'test.b': 4,
         'test.b': 4,
@@ -913,7 +658,7 @@ def test_glsl_struct(gsg):
     })
     })
 
 
 
 
-def test_glsl_struct_nested(gsg):
+def test_glsl_struct_nested(env):
     preamble = """
     preamble = """
     struct TestSubStruct1 {
     struct TestSubStruct1 {
         float a;
         float a;
@@ -942,7 +687,7 @@ def test_glsl_struct_nested(gsg):
     tex_c_a = core.Texture()
     tex_c_a = core.Texture()
     tex_c_a.setup_2d_texture(1, 1, core.Texture.T_float, core.Texture.F_r32)
     tex_c_a.setup_2d_texture(1, 1, core.Texture.T_float, core.Texture.F_r32)
     tex_c_a.set_clear_color((6, 0, 0, 0))
     tex_c_a.set_clear_color((6, 0, 0, 0))
-    run_glsl_test(gsg, code, preamble, {
+    env.run_glsl(code, preamble, {
         'test.unused': 0,
         'test.unused': 0,
         'test.a': (1, 2, 3),
         'test.a': (1, 2, 3),
         'test.b.a': 4,
         'test.b.a': 4,
@@ -954,7 +699,7 @@ def test_glsl_struct_nested(gsg):
     })
     })
 
 
 
 
-def test_glsl_struct_array(gsg):
+def test_glsl_struct_array(env):
     preamble = """
     preamble = """
     uniform struct TestStruct {
     uniform struct TestStruct {
         vec3 a;
         vec3 a;
@@ -977,7 +722,7 @@ def test_glsl_struct_array(gsg):
     tex_1_b = core.Texture()
     tex_1_b = core.Texture()
     tex_1_b.setup_2d_texture(1, 1, core.Texture.T_float, core.Texture.F_r32)
     tex_1_b.setup_2d_texture(1, 1, core.Texture.T_float, core.Texture.F_r32)
     tex_1_b.set_clear_color((9, 0, 0, 0))
     tex_1_b.set_clear_color((9, 0, 0, 0))
-    run_glsl_test(gsg, code, preamble, {
+    env.run_glsl(code, preamble, {
         'test[0].unused': 0,
         'test[0].unused': 0,
         'test[0].a': (1, 2, 3),
         'test[0].a': (1, 2, 3),
         'test[0].b': tex_0_b,
         'test[0].b': tex_0_b,
@@ -989,7 +734,7 @@ def test_glsl_struct_array(gsg):
     })
     })
 
 
 
 
-def test_glsl_struct_pseudo_light(gsg):
+def test_glsl_struct_pseudo_light(env):
     # Something that looks like a named light source, but isn't one at all
     # Something that looks like a named light source, but isn't one at all
     preamble = """
     preamble = """
     struct FakeLightParameters {
     struct FakeLightParameters {
@@ -1008,7 +753,7 @@ def test_glsl_struct_pseudo_light(gsg):
     assert(test.constantAttenuation == 12);
     assert(test.constantAttenuation == 12);
     assert(test.radius == 13);
     assert(test.radius == 13);
     """
     """
-    run_glsl_test(gsg, code, preamble, {
+    env.run_glsl(code, preamble, {
         'test.specular': (1, 2, 3, 4),
         'test.specular': (1, 2, 3, 4),
         'test.position': (5, 6, 7, 8),
         'test.position': (5, 6, 7, 8),
         'test.attenuation': (9, 10, 11),
         'test.attenuation': (9, 10, 11),
@@ -1017,7 +762,7 @@ def test_glsl_struct_pseudo_light(gsg):
     })
     })
 
 
 
 
-def test_glsl_light(gsg):
+def test_glsl_light(env):
     preamble = """
     preamble = """
     uniform struct p3d_LightSourceParameters {
     uniform struct p3d_LightSourceParameters {
         vec4 color;
         vec4 color;
@@ -1056,12 +801,12 @@ def test_glsl_light(gsg):
     plight.transform = core.TransformState.make_pos((9, 10, 11))
     plight.transform = core.TransformState.make_pos((9, 10, 11))
     plight.attenuation = (12, 13, 14)
     plight.attenuation = (12, 13, 14)
 
 
-    run_glsl_test(gsg, code, preamble, {
+    env.run_glsl(code, preamble, {
         'plight': core.NodePath(plight),
         'plight': core.NodePath(plight),
     })
     })
 
 
 
 
-def test_glsl_named_light_source(gsg):
+def test_glsl_named_light_source(env):
     spot = core.Spotlight("spot")
     spot = core.Spotlight("spot")
     spot.get_lens().set_fov(90, 90)
     spot.get_lens().set_fov(90, 90)
     spot.set_color((1, 2, 3, 4))
     spot.set_color((1, 2, 3, 4))
@@ -1078,10 +823,10 @@ def test_glsl_named_light_source(gsg):
     assert(spot.color == vec4(1, 2, 3, 4));
     assert(spot.color == vec4(1, 2, 3, 4));
     assert(spot.specular == vec4(5, 6, 7, 8));
     assert(spot.specular == vec4(5, 6, 7, 8));
     """
     """
-    run_glsl_test(gsg, code, preamble, {'spot': core.NodePath(spot)})
+    env.run_glsl(code, preamble, {'spot': core.NodePath(spot)})
 
 
 
 
-def test_glsl_state_light(gsg):
+def test_glsl_state_light(env):
     preamble = """
     preamble = """
     uniform struct p3d_LightSourceParameters {
     uniform struct p3d_LightSourceParameters {
         vec4 color;
         vec4 color;
@@ -1147,10 +892,10 @@ def test_glsl_state_light(gsg):
     lattr = lattr.add_on_light(dlight_path)
     lattr = lattr.add_on_light(dlight_path)
     state = core.RenderState.make(lattr)
     state = core.RenderState.make(lattr)
 
 
-    run_glsl_test(gsg, code, preamble, state=state)
+    env.run_glsl(code, preamble, state=state)
 
 
 
 
-def test_glsl_state_light_source(gsg):
+def test_glsl_state_light_source(env):
     spot = core.Spotlight("spot")
     spot = core.Spotlight("spot")
     spot.priority = 3
     spot.priority = 3
     spot.get_lens().set_fov(120, 120)
     spot.get_lens().set_fov(120, 120)
@@ -1252,10 +997,10 @@ def test_glsl_state_light_source(gsg):
     dire_path = node.attach_new_node(dire)
     dire_path = node.attach_new_node(dire)
     node.set_light(dire_path)
     node.set_light(dire_path)
 
 
-    run_glsl_test(gsg, code, preamble, state=node.get_state())
+    env.run_glsl(code, preamble, state=node.get_state())
 
 
 
 
-def test_glsl_state_material(gsg):
+def test_glsl_state_material(env):
     mat = core.Material("mat")
     mat = core.Material("mat")
     mat.ambient = (1, 2, 3, 4)
     mat.ambient = (1, 2, 3, 4)
     mat.diffuse = (5, 6, 7, 8)
     mat.diffuse = (5, 6, 7, 8)
@@ -1290,10 +1035,10 @@ def test_glsl_state_material(gsg):
     node = core.NodePath("state")
     node = core.NodePath("state")
     node.set_material(mat)
     node.set_material(mat)
 
 
-    run_glsl_test(gsg, code, preamble, state=node.get_state())
+    env.run_glsl(code, preamble, state=node.get_state())
 
 
 
 
-def test_glsl_state_material_pbr(gsg):
+def test_glsl_state_material_pbr(env):
     mat = core.Material("mat")
     mat = core.Material("mat")
     mat.base_color = (1, 2, 3, 4)
     mat.base_color = (1, 2, 3, 4)
     mat.emission = (9, 10, 11, 12)
     mat.emission = (9, 10, 11, 12)
@@ -1322,10 +1067,10 @@ def test_glsl_state_material_pbr(gsg):
     node = core.NodePath("state")
     node = core.NodePath("state")
     node.set_material(mat)
     node.set_material(mat)
 
 
-    run_glsl_test(gsg, code, preamble, state=node.get_state())
+    env.run_glsl(code, preamble, state=node.get_state())
 
 
 
 
-def test_glsl_state_fog(gsg):
+def test_glsl_state_fog(env):
     fog = core.Fog("fog")
     fog = core.Fog("fog")
     fog.color = (1, 2, 3, 4)
     fog.color = (1, 2, 3, 4)
     fog.exp_density = 0.5
     fog.exp_density = 0.5
@@ -1352,10 +1097,10 @@ def test_glsl_state_fog(gsg):
     node = core.NodePath("state")
     node = core.NodePath("state")
     node.set_fog(fog)
     node.set_fog(fog)
 
 
-    run_glsl_test(gsg, code, preamble, state=node.get_state())
+    env.run_glsl(code, preamble, state=node.get_state())
 
 
 
 
-def test_glsl_state_texture(gsg):
+def test_glsl_state_texture(env):
     def gen_texture(v):
     def gen_texture(v):
         tex = core.Texture(f"tex{v}")
         tex = core.Texture(f"tex{v}")
         tex.setup_2d_texture(1, 1, core.Texture.T_unsigned_byte, core.Texture.F_red)
         tex.setup_2d_texture(1, 1, core.Texture.T_unsigned_byte, core.Texture.F_red)
@@ -1423,7 +1168,7 @@ def test_glsl_state_texture(gsg):
     assert(texture(p3d_Texture[6], coord).r == 1.0);
     assert(texture(p3d_Texture[6], coord).r == 1.0);
     """
     """
 
 
-    run_glsl_test(gsg, code, preamble, state=np.get_state())
+    env.run_glsl(code, preamble, state=np.get_state())
 
 
     preamble = """
     preamble = """
     uniform sampler2D p3d_TextureFF[5];
     uniform sampler2D p3d_TextureFF[5];
@@ -1452,10 +1197,10 @@ def test_glsl_state_texture(gsg):
     assert(all(lessThan(abs(texture(p3d_TextureHeight[1], coord) - vec4(127 / 255.0, 127 / 255.0, 1.0, 0.0)), vec4(0.004))));
     assert(all(lessThan(abs(texture(p3d_TextureHeight[1], coord) - vec4(127 / 255.0, 127 / 255.0, 1.0, 0.0)), vec4(0.004))));
     """
     """
 
 
-    run_glsl_test(gsg, code, preamble, state=np.get_state())
+    env.run_glsl(code, preamble, state=np.get_state())
 
 
 
 
-def test_glsl_frame_number(gsg):
+def test_glsl_frame_number(env):
     clock = core.ClockObject.get_global_clock()
     clock = core.ClockObject.get_global_clock()
     old_frame_count = clock.get_frame_count()
     old_frame_count = clock.get_frame_count()
     try:
     try:
@@ -1468,12 +1213,12 @@ def test_glsl_frame_number(gsg):
         assert(osg_FrameNumber == 123);
         assert(osg_FrameNumber == 123);
         """
         """
 
 
-        run_glsl_test(gsg, code, preamble)
+        env.run_glsl(code, preamble)
     finally:
     finally:
         clock.set_frame_count(old_frame_count)
         clock.set_frame_count(old_frame_count)
 
 
 
 
-def test_glsl_write_extract_image_buffer(gsg):
+def test_glsl_write_extract_image_buffer(env):
     # Tests that we can write to a buffer texture on the GPU, and then extract
     # Tests that we can write to a buffer texture on the GPU, and then extract
     # the data on the CPU.  We test two textures since there was in the past a
     # the data on the CPU.  We test two textures since there was in the past a
     # where it would only work correctly for one texture.
     # where it would only work correctly for one texture.
@@ -1500,58 +1245,69 @@ def test_glsl_write_extract_image_buffer(gsg):
     assert(imageLoad(tex2, 0).r == -456);
     assert(imageLoad(tex2, 0).r == -456);
     """
     """
 
 
-    run_glsl_test(gsg, code, preamble, {'tex1': tex1, 'tex2': tex2})
+    env.run_glsl(code, preamble, {'tex1': tex1, 'tex2': tex2})
 
 
-    engine = core.GraphicsEngine.get_global_ptr()
-    assert engine.extract_texture_data(tex1, gsg)
-    assert engine.extract_texture_data(tex2, gsg)
+    env.extract_texture_data(tex1)
+    env.extract_texture_data(tex2)
 
 
     assert struct.unpack('I', tex1.get_ram_image()) == (123,)
     assert struct.unpack('I', tex1.get_ram_image()) == (123,)
     assert struct.unpack('i', tex2.get_ram_image()) == (-456,)
     assert struct.unpack('i', tex2.get_ram_image()) == (-456,)
 
 
 
 
-def test_glsl_compile_error(gsg):
+def test_glsl_compile_error():
     """Test getting compile errors from bad shaders"""
     """Test getting compile errors from bad shaders"""
-    suffix = ''
-    if gsg.pipe.interface_name == "OpenGL" and \
-        (gsg.driver_shader_version_major, gsg.driver_shader_version_minor) < (1, 50):
-        suffix = '_legacy'
-    vert_path = core.Filename(SHADERS_DIR, 'glsl_bad' + suffix + '.vert')
-    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple' + suffix + '.frag')
-    run_glsl_compile_check(gsg, vert_path, frag_path, expect_fail=True)
+    vert_path = core.Filename(SHADERS_DIR, 'glsl_bad.vert')
+    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple.frag')
+    shader = core.Shader.load(core.Shader.SL_GLSL, vert_path, frag_path)
+    assert shader is None
 
 
 
 
-def test_glsl_from_file(gsg):
+def test_glsl_from_file():
     """Test compiling GLSL shaders from files"""
     """Test compiling GLSL shaders from files"""
-    suffix = ''
-    if gsg.pipe.interface_name == "OpenGL" and \
-        (gsg.driver_shader_version_major, gsg.driver_shader_version_minor) < (1, 50):
-        suffix = '_legacy'
-    vert_path = core.Filename(SHADERS_DIR, 'glsl_simple' + suffix + '.vert')
-    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple' + suffix + '.frag')
-    run_glsl_compile_check(gsg, vert_path, frag_path)
+    vert_path = core.Filename(SHADERS_DIR, 'glsl_simple.vert')
+    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple.frag')
+    shader = core.Shader.load(core.Shader.SL_GLSL, vert_path, frag_path)
+    assert shader is not None
+
 
 
+def test_glsl_from_file_legacy():
+    """Test compiling GLSL shaders from files"""
+    vert_path = core.Filename(SHADERS_DIR, 'glsl_simple_legacy.vert')
+    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple_legacy.frag')
+    shader = core.Shader.load(core.Shader.SL_GLSL, vert_path, frag_path)
+    assert shader is not None
 
 
-def test_glsl_includes(gsg):
+
+def test_glsl_includes():
+    """Test preprocessing includes in GLSL shaders"""
+    vert_path = core.Filename(SHADERS_DIR, 'glsl_include.vert')
+    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple.frag')
+    shader = core.Shader.load(core.Shader.SL_GLSL, vert_path, frag_path)
+    assert shader is not None
+
+
+def test_glsl_includes_legacy():
     """Test preprocessing includes in GLSL shaders"""
     """Test preprocessing includes in GLSL shaders"""
-    suffix = ''
-    if gsg.pipe.interface_name == "OpenGL" and \
-        (gsg.driver_shader_version_major, gsg.driver_shader_version_minor) < (1, 50):
-        suffix = '_legacy'
-    vert_path = core.Filename(SHADERS_DIR, 'glsl_include' + suffix + '.vert')
-    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple' + suffix + '.frag')
-    run_glsl_compile_check(gsg, vert_path, frag_path)
+    vert_path = core.Filename(SHADERS_DIR, 'glsl_include_legacy.vert')
+    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple_legacy.frag')
+    shader = core.Shader.load(core.Shader.SL_GLSL, vert_path, frag_path)
+    assert shader is not None
+
+
+def test_glsl_includes_angle_nodir():
+    """Test preprocessing includes with angle includes without model-path"""
+    vert_path = core.Filename(SHADERS_DIR, 'glsl_include_angle.vert')
+    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple.frag')
+    shader = core.Shader.load(core.Shader.SL_GLSL, vert_path, frag_path)
+    assert shader is None
 
 
 
 
-def test_glsl_includes_angle_nodir(gsg):
+def test_glsl_includes_angle_nodir_legacy():
     """Test preprocessing includes with angle includes without model-path"""
     """Test preprocessing includes with angle includes without model-path"""
-    suffix = ''
-    if gsg.pipe.interface_name == "OpenGL" and \
-        (gsg.driver_shader_version_major, gsg.driver_shader_version_minor) < (1, 50):
-        suffix = '_legacy'
-    vert_path = core.Filename(SHADERS_DIR, 'glsl_include_angle' + suffix + '.vert')
-    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple' + suffix + '.frag')
-    assert core.Shader.load(core.Shader.SL_GLSL, vert_path, frag_path) is None
+    vert_path = core.Filename(SHADERS_DIR, 'glsl_include_angle_legacy.vert')
+    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple_legacy.frag')
+    shader = core.Shader.load(core.Shader.SL_GLSL, vert_path, frag_path)
+    assert shader is None
 
 
 
 
 @pytest.fixture
 @pytest.fixture
@@ -1562,12 +1318,17 @@ def with_current_dir_on_model_path():
     model_path.clear_local_value()
     model_path.clear_local_value()
 
 
 
 
-def test_glsl_includes_angle_withdir(gsg, with_current_dir_on_model_path):
+def test_glsl_includes_angle_withdir(with_current_dir_on_model_path):
+    """Test preprocessing includes with angle includes with model-path"""
+    vert_path = core.Filename(SHADERS_DIR, 'glsl_include_angle.vert')
+    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple.frag')
+    shader = core.Shader.load(core.Shader.SL_GLSL, vert_path, frag_path)
+    assert shader is not None
+
+
+def test_glsl_includes_angle_withdir_legacy(with_current_dir_on_model_path):
     """Test preprocessing includes with angle includes with model-path"""
     """Test preprocessing includes with angle includes with model-path"""
-    suffix = ''
-    if gsg.pipe.interface_name == "OpenGL" and \
-        (gsg.driver_shader_version_major, gsg.driver_shader_version_minor) < (1, 50):
-        suffix = '_legacy'
-    vert_path = core.Filename(SHADERS_DIR, 'glsl_include_angle' + suffix + '.vert')
-    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple' + suffix + '.frag')
-    run_glsl_compile_check(gsg, vert_path, frag_path)
+    vert_path = core.Filename(SHADERS_DIR, 'glsl_include_angle_legacy.vert')
+    frag_path = core.Filename(SHADERS_DIR, 'glsl_simple_legacy.frag')
+    shader = core.Shader.load(core.Shader.SL_GLSL, vert_path, frag_path)
+    assert shader is not None

+ 15 - 1
tests/shaderpipeline/test_glsl_caps.py

@@ -558,6 +558,20 @@ def test_glsl_caps_image_load_store():
     """) == Shader.C_image_load_store
     """) == Shader.C_image_load_store
 
 
 
 
+def test_glsl_caps_image_buffer():
+    assert compile_and_get_caps(Stage.FRAGMENT, """
+    #version 420
+
+    layout(rgba8) uniform readonly imageBuffer a;
+
+    out vec4 p3d_FragColor;
+
+    void main() {
+        p3d_FragColor = imageLoad(a, 0);
+    }
+    """) == Shader.C_image_load_store | Shader.C_texture_buffer
+
+
 def test_glsl_caps_image_atomic():
 def test_glsl_caps_image_atomic():
     assert compile_and_get_caps(Stage.FRAGMENT, """
     assert compile_and_get_caps(Stage.FRAGMENT, """
     #version 420
     #version 420
@@ -603,7 +617,7 @@ def test_glsl_caps_texture_query_levels():
     """) == Shader.C_texture_query_levels
     """) == Shader.C_texture_query_levels
 
 
 
 
-def test_glsl_caps_texture_storage_buffer():
+def test_glsl_caps_storage_buffer():
     assert compile_and_get_caps(Stage.VERTEX, """
     assert compile_and_get_caps(Stage.VERTEX, """
     #version 430
     #version 430
 
 

+ 1 - 1
tests/tkpanels/test_Placer.py

@@ -5,7 +5,7 @@ from direct.showbase.ShowBase import ShowBase
 from direct.tkpanels.Placer import Placer
 from direct.tkpanels.Placer import Placer
 
 
 
 
-def test_Placer(window, tk_toplevel):
+def test_Placer(tk_toplevel):
     base = ShowBase()
     base = ShowBase()
     base.start_direct()
     base.start_direct()
     root = Pmw.initialise()
     root = Pmw.initialise()