Esempio n. 1
0
    def initializeGL(self, gls: 'GLShared') -> None:
        self.gls = gls

        assert self.gls is not None

        # Basic solid-color program
        self.prog = self.gls.shader_cache.get("vert2", "frag1")
        self.mat_loc = GL.glGetUniformLocation(self.prog.program, "mat")
        self.col_loc = GL.glGetUniformLocation(self.prog.program, "color")

        # Build a VBO for rendering square "drag-handles"
        self.vbo_handles_ar = numpy.ndarray((4, ), dtype=[("vertex", numpy.float32, 2)])
        self.vbo_handles_ar["vertex"] = numpy.array(corners) * HANDLE_HALF_SIZE

        self.vbo_handles = VBO(self.vbo_handles_ar, GL.GL_STATIC_DRAW, GL.GL_ARRAY_BUFFER)

        self.vao_handles = VAO()
        with self.vbo_handles, self.vao_handles:
            VBOBind(self.prog.program, self.vbo_handles_ar.dtype, "vertex").assign()

        # Build a VBO/VAO for the perimeter
        # We don't initialize it here because it is updated every render
        # 4 verticies for outside perimeter
        # 6 verticies for each dim
        self.vbo_per_dim_ar = numpy.zeros(16, dtype=[("vertex", numpy.float32, 2)])

        self.vbo_per_dim = VBO(self.vbo_per_dim_ar, GL.GL_DYNAMIC_DRAW, GL.GL_ARRAY_BUFFER)

        self.vao_per_dim = VAO()
        with self.vao_per_dim, self.vbo_per_dim:
            VBOBind(self.prog.program, self.vbo_per_dim_ar.dtype, "vertex").assign()
Esempio n. 2
0
    def _initializeGL(self):
        self.initialized = True

        self.__filled_vao = VAO()
        self.__outline_vao = VAO()

        with self.__filled_vao, self.parent._sq_vbo:
            vbobind(self.parent._filled_shader, self.parent._sq_vbo.data.dtype,
                    "vertex").assign()

        # Use a fake array to get a zero-length VBO for initial binding
        filled_instance_array = numpy.ndarray(
            0, dtype=self.parent._filled_instance_dtype)
        self.filled_instance_vbo = VBO(filled_instance_array)

        with self.__filled_vao, self.filled_instance_vbo:
            vbobind(self.parent._filled_shader,
                    self.parent._filled_instance_dtype,
                    "pos",
                    div=1).assign()
            vbobind(self.parent._filled_shader,
                    self.parent._filled_instance_dtype,
                    "r",
                    div=1).assign()
            vbobind(self.parent._filled_shader,
                    self.parent._filled_instance_dtype,
                    "r_inside_frac_sq",
                    div=1).assign()
            vbobind(self.parent._filled_shader,
                    self.parent._filled_instance_dtype,
                    "color",
                    div=1).assign()

        with self.__outline_vao, self.parent._outline_vbo:
            vbobind(self.parent._outline_shader,
                    self.parent._outline_vbo.data.dtype, "vertex").assign()

        # Build instance for outline rendering
        # We don't have an inner 'r' for this because we just do two instances per vertex

        # Use a fake array to get a zero-length VBO for initial binding
        outline_instance_array = numpy.ndarray(
            0, dtype=self.parent._outline_instance_dtype)
        self.outline_instance_vbo = VBO(outline_instance_array)

        with self.__outline_vao, self.outline_instance_vbo:
            vbobind(self.parent._outline_shader,
                    self.parent._outline_instance_dtype,
                    "pos",
                    div=1).assign()
            vbobind(self.parent._outline_shader,
                    self.parent._outline_instance_dtype,
                    "r",
                    div=1).assign()
            vbobind(self.parent._outline_shader,
                    self.parent._outline_instance_dtype,
                    "color",
                    div=1).assign()
Esempio n. 3
0
    def initializeGL(self, gls: 'GLShared', width: int, height: int) -> None:
        self.__width = width
        self.__height = height

        # Initialize (but don't fill) the Color LUT
        self.__texture_colors = Texture(debug_name="Layer Color LUT")
        with self.__texture_colors.on(GL.GL_TEXTURE_1D):
            GL.glTexParameteri(GL.GL_TEXTURE_1D, GL.GL_TEXTURE_MIN_FILTER,
                               GL.GL_NEAREST)
            GL.glTexParameteri(GL.GL_TEXTURE_1D, GL.GL_TEXTURE_MAG_FILTER,
                               GL.GL_NEAREST)
            GL.glTexParameteri(GL.GL_TEXTURE_1D, GL.GL_TEXTURE_WRAP_S,
                               GL.GL_CLAMP_TO_EDGE)
            GL.glTexParameteri(GL.GL_TEXTURE_1D, GL.GL_TEXTURE_WRAP_T,
                               GL.GL_CLAMP_TO_EDGE)

        # Compositing shader and geometry
        self.__composite_shader = gls.shader_cache.get(
            "layer_composite_vert",
            "layer_composite_frag",
            fragment_bindings={"final_color": 0})

        ar = self.__get_vbo_data()
        self.__composite_vao = VAO(debug_name="Compositor Quad VAO")
        self.__composite_vbo = VBO(ar, GL.GL_STATIC_DRAW)
        GL.glObjectLabel(GL.GL_BUFFER, int(self.__composite_vbo), -1,
                         "Compositor Quad VBO")

        with self.__composite_vao:
            self.__composite_vbo.bind()
            VBOBind(self.__composite_shader.program, ar.dtype,
                    "vertex").assign()
            VBOBind(self.__composite_shader.program, ar.dtype,
                    "texpos").assign()
Esempio n. 4
0
    def initializeGL(self):
        self.vbo = VBO(numpy.ndarray(0, dtype=self.__text_render.buffer_dtype),
                       GL.GL_STATIC_DRAW, GL.GL_ARRAY_BUFFER)
        self.vao = VAO()

        with self.vao, self.vbo:
            self.__text_render.b1.assign()
            self.__text_render.b2.assign()
Esempio n. 5
0
    def initializeGL(self):
        # Working VBO that will contain glyph data
        self.vbo = VBO(numpy.ndarray(0, dtype=self.text_render.buffer_dtype), GL.GL_DYNAMIC_DRAW, GL.GL_ARRAY_BUFFER)
        self.vao = VAO()

        with self.vao, self.vbo:
            self.text_render.b1.assign()
            self.text_render.b2.assign()
        self.__vbo_needs_update = True
Esempio n. 6
0
    def initializeGL(self, gls, width, height):
        # Get shader

        self.__shader = gls.shader_cache.get("basic_fill_vert", "basic_fill_frag")

        self.__vbo = VBO(numpy.ndarray(0, dtype=self.__vbo_dtype), GL.GL_STATIC_DRAW)
        self.__vao = VAO()

        # Create array of lines sufficient to fill screen
        self.resize(width, height)
Esempio n. 7
0
        def __init__(self, dtype, shader, glhint):
            self.__dtype = dtype

            self.vao = VAO()
            self.batch_vbo = VBO(numpy.array([], dtype=dtype), glhint)

            with self.vao, self.batch_vbo:
                vbobind(shader, dtype, "vertex").assign()

            self.clear()
Esempio n. 8
0
    def __initialize_uniform(self, gls):
        self.__uniform_shader_vao = VAO()
        self.__uniform_shader = gls.shader_cache.get(
            "line_vertex_shader", "frag1", defines={"INPUT_TYPE": "uniform"})

        with self.__uniform_shader_vao, self.trace_vbo:
            vbobind(self.__uniform_shader, self.trace_vbo.dtype,
                    "vertex").assign()
            vbobind(self.__uniform_shader, self.trace_vbo.dtype,
                    "ptid").assign()
            self.index_vbo.bind()
Esempio n. 9
0
    def initializeGL(self) -> None:
        self.__dtype = numpy.dtype([('vertex', numpy.float32, 2)])
        self.__shader = self.__view.gls.shader_cache.get(
            "basic_fill_vert", "basic_fill_frag")

        self._va_vao = VAO()
        self._va_batch_vbo = VBO(numpy.array([], dtype=self.__dtype),
                                 GL.GL_STREAM_DRAW)
        GL.glObjectLabel(GL.GL_BUFFER, int(self._va_batch_vbo), -1,
                         "Hairline VA batch VBO")

        with self._va_vao, self._va_batch_vbo:
            VBOBind(self.__shader.program, self.__dtype, "vertex").assign()
Esempio n. 10
0
    def initializeGL(self, gls: 'GLShared') -> None:
        self.gls = gls

        # zap the cached text on GL reinitialize (VBO handles / etc are likely invalid)
        self.textCached = {}

        # basic solid-color shader
        self.prog = gls.shader_cache.get("vert2", "frag1")

        # Construct a VBO containing all the points we need for rendering
        dtype = numpy.dtype([("vertex", numpy.float32, 2)])
        points = numpy.ndarray((16, ), dtype=dtype)

        # keypoint display: edge half-dimension in pixels
        self.d1 = d1 = 20

        # keypoint display: text-area "flag" height in pixels
        th = 16

        # keypoint display: right-edge offset of text flag in pixels
        tw = 6

        points["vertex"] = [
            # Lines making up keypoint cross (rendered with GL_LINES)
            (-d1, -d1),
            (-d1, d1),
            (-d1, d1),
            (d1, d1),
            (d1, d1),
            (d1, -d1),
            (d1, -d1),
            (-d1, -d1),
            (0, -d1),
            (0, d1),
            (-d1, 0),
            (d1, 0),

            # flag (rendered with GL_TRIANGLE_STRIP)
            (-d1, -d1),
            (-d1, -d1 - th),
            (-tw, -d1),
            (-tw, -d1 - th)
        ]

        # Pack it all into a VBO
        self.handle_vbo = VBO(points, GL.GL_STATIC_DRAW, GL.GL_ARRAY_BUFFER)

        # and bind the program for rendering
        self.handle_vao = VAO()
        with self.handle_vao, self.handle_vbo:
            VBOBind(self.prog.program, dtype, "vertex").assign()
Esempio n. 11
0
    def initGL(self, gls: 'GLShared') -> None:
        self._tex = Texture()

        # Setup the basic texture parameters
        with self._tex.on(GL.GL_TEXTURE_2D):
            GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER,
                               GL.GL_NEAREST)
            GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER,
                               GL.GL_LINEAR)
            GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S,
                               GL.GL_CLAMP_TO_EDGE)
            GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T,
                               GL.GL_CLAMP_TO_EDGE)

            # numpy packs data tightly, whereas the openGL default is 4-byte-aligned
            # fix line alignment to 1 byte so odd-sized textures load right
            GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1)

            # Download the data to the buffer. cv2 stores data in BGR format
            GL.glTexImage2D(
                GL.GL_TEXTURE_2D, 0, GL.GL_RGB, self.im.shape[1],
                self.im.shape[0], 0, GL.GL_BGR, GL.GL_UNSIGNED_BYTE,
                self.im.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)))

        self.prog = gls.shader_cache.get("image_vert", "image_frag")

        ar = numpy.ndarray(
            (4, ),
            dtype=[("vertex", numpy.float32, 2),
                   ("texpos", numpy.float32, 2)])  # type: ignore

        sca = max(self.im.shape[0], self.im.shape[1])
        x = self.im.shape[1] / float(sca)
        y = self.im.shape[0] / float(sca)
        ar["vertex"] = [(-x, -y), (-x, y), (x, -y), (x, y)]
        ar["texpos"] = [(0, 0), (0, 1), (1, 0), (1, 1)]

        self.b1 = VBOBind(self.prog.program, ar.dtype, "vertex")
        self.b2 = VBOBind(self.prog.program, ar.dtype, "texpos")

        self.vbo = VBO(ar, GL.GL_STATIC_DRAW, GL.GL_ARRAY_BUFFER)

        self.mat_loc = GL.glGetUniformLocation(self.prog.program, "mat")
        self.tex1_loc = GL.glGetUniformLocation(self.prog.program, "tex1")

        self.vao = VAO()
        with self.vbo, self.vao:
            self.b1.assign()
            self.b2.assign()
Esempio n. 12
0
    def initializeGL(self, gls: 'GLShared') -> None:
        self.shader = gls.shader_cache.get("vert2", "frag1")

        self.vao = VAO()

        self.vbo = VBO(bytes(), usage=GL.GL_STREAM_DRAW)

        # bind the shader
        with self.vao, self.vbo:
            loc = GL.glGetAttribLocation(self.shader.program, "vertex")
            assert loc != -1
            GL.glEnableVertexAttribArray(loc)

            # TODO - HACK. AttribPointer is dependent on ABI data layout.
            # Should be common on all sane archs, but this should be fetched on demand
            GL.glVertexAttribPointer(loc, 2, GL.GL_FLOAT, False, 8,
                                     ctypes.c_void_p(0))
            GL.glVertexAttribDivisor(loc, 0)
Esempio n. 13
0
    def initializeGL(self) -> None:
        self.__vao = VAO()

        # Lookup for vertex positions
        self.__vert_vbo_dtype = numpy.dtype([("vertex", numpy.float32, 2)])
        self.__vert_vbo = VBO(numpy.ndarray(0, dtype=self.__vert_vbo_dtype),
                              GL.GL_DYNAMIC_DRAW)
        self.__vert_vbo_current = False

        self.__index_vbo_dtype = numpy.uint32
        self.__index_vbo = VBO(numpy.ndarray(0, dtype=self.__index_vbo_dtype),
                               GL.GL_DYNAMIC_DRAW, GL.GL_ELEMENT_ARRAY_BUFFER)
        self.__index_vbo_current = False

        self.__shader = self.__gls.shader_cache.get("vert2", "frag1")

        with self.__vao, self.__vert_vbo:
            VBOBind(self.__shader.program, self.__vert_vbo_dtype,
                    "vertex").assign()
            self.__index_vbo.bind()
Esempio n. 14
0
    def initializeGL(self, glshared: 'GLShared') -> None:

        self._filled_shader = glshared.shader_cache.get(
            "via_filled_vertex_shader", "via_filled_fragment_shader")

        self._outline_shader = glshared.shader_cache.get(
            "via_outline_vertex_shader", "basic_fill_frag")

        # Build geometry for filled rendering using the frag shader for circle borders
        filled_points = [
            ((-1, -1), ),
            ((1, -1), ),
            ((-1, 1), ),
            ((1, 1), ),
        ]
        ar = numpy.array(filled_points, dtype=[("vertex", numpy.float32, 2)])

        self._sq_vbo = VBO(ar, GL.GL_STATIC_DRAW)
        GL.glObjectLabel(GL.GL_BUFFER, int(self._sq_vbo), -1, "Via SQ VBO")

        # Build geometry for outline rendering
        outline_points = []
        for i in numpy.linspace(0, math.pi * 2, N_OUTLINE_SEGMENTS, False):
            outline_points.append(((math.cos(i), math.sin(i)), ))

        outline_points_array = numpy.array(outline_points,
                                           dtype=[("vertex", numpy.float32, 2)
                                                  ])

        self._outline_vbo = VBO(outline_points_array, GL.GL_STATIC_DRAW)
        GL.glObjectLabel(GL.GL_BUFFER, int(self._sq_vbo), -1,
                         "Via Outline VBO")

        self.__dtype = numpy.dtype([
            ("pos", numpy.float32, 2),
            ("r", numpy.float32),
            ("r_inside_frac_sq", numpy.float32),
        ])

        self.__filled_vao = VAO()
        self.__outline_vao = VAO()

        with self.__filled_vao, self._sq_vbo:
            VBOBind(self._filled_shader.program, self._sq_vbo.data.dtype,
                    "vertex").assign()

        # Use a fake array to get a zero-length VBO for initial binding
        filled_instance_array = numpy.ndarray(0, dtype=self.__dtype)
        self.filled_instance_vbo = VBO(filled_instance_array)
        GL.glObjectLabel(GL.GL_BUFFER, int(self._sq_vbo), -1,
                         "Via Filled Instance VBO")

        with self.__filled_vao, self.filled_instance_vbo:
            VBOBind(self._filled_shader.program, self.__dtype, "pos",
                    div=1).assign()
            VBOBind(self._filled_shader.program, self.__dtype, "r",
                    div=1).assign()
            VBOBind(self._filled_shader.program,
                    self.__dtype,
                    "r_inside_frac_sq",
                    div=1).assign()

        with self.__outline_vao, self._outline_vbo:
            VBOBind(self._outline_shader.program, self._outline_vbo.data.dtype,
                    "vertex").assign()

        # Build instance for outline rendering
        # We don't have an inner 'r' for this because we just do two instances per vertex

        # Use a fake array to get a zero-length VBO for initial binding
        outline_instance_array = numpy.ndarray(0, dtype=self.__dtype)
        self.outline_instance_vbo = VBO(outline_instance_array)
        GL.glObjectLabel(GL.GL_BUFFER, int(self.outline_instance_vbo), -1,
                         "Via Outline Instance VBO")

        with self.__outline_vao, self.outline_instance_vbo:
            VBOBind(self._outline_shader.program, self.__dtype, "pos",
                    div=1).assign()
            VBOBind(self._outline_shader.program, self.__dtype, "r",
                    div=1).assign()
Esempio n. 15
0
    def initializeGL(self, gls):
        self.__uniform_shader_vao = VAO()
        self.__attribute_shader_vao = VAO()

        # Load two versions of the shader, one for rendering a single line through uniforms
        # (no additional bound instance info), and one for rendering instanced geometry

        self.__uniform_shader = gls.shader_cache.get(
            "line_vertex_shader", "frag1", defines={"INPUT_TYPE": "uniform"})
        self.__attribute_shader = gls.shader_cache.get(
            "line_vertex_shader", "frag1", defines={"INPUT_TYPE": "in"})

        # Generate geometry for trace and endcaps
        # ptid is a variable with value 0 or 1 that indicates which endpoint the geometry is associated with

        # Build trace vertex VBO and associated vertex data
        dtype = [("vertex", numpy.float32, 2), ("ptid", numpy.uint32)]
        self.working_array = numpy.zeros(NUM_ENDCAP_SEGMENTS * 2 + 2,
                                         dtype=dtype)
        self.trace_vbo = VBO(self.working_array, GL.GL_DYNAMIC_DRAW)
        self.__build_trace()

        # Now we build an index buffer that allows us to render filled geometry from the same
        # VBO.
        arr = []
        for i in range(NUM_ENDCAP_SEGMENTS - 1):
            arr.append(0)
            arr.append(i + 2)
            arr.append(i + 3)

        for i in range(NUM_ENDCAP_SEGMENTS - 1):
            arr.append(1)
            arr.append(i + NUM_ENDCAP_SEGMENTS + 2)
            arr.append(i + NUM_ENDCAP_SEGMENTS + 3)

        arr.append(2)
        arr.append(2 + NUM_ENDCAP_SEGMENTS - 1)
        arr.append(2 + NUM_ENDCAP_SEGMENTS)
        arr.append(2 + NUM_ENDCAP_SEGMENTS)
        arr.append(2 + NUM_ENDCAP_SEGMENTS * 2 - 1)
        arr.append(2)

        arr = numpy.array(arr, dtype=numpy.uint32)
        self.index_vbo = VBO(arr, target=GL.GL_ELEMENT_ARRAY_BUFFER)

        # And bind the entire state together
        with self.__uniform_shader_vao, self.trace_vbo:
            vbobind(self.__uniform_shader, self.trace_vbo.dtype,
                    "vertex").assign()
            vbobind(self.__uniform_shader, self.trace_vbo.dtype,
                    "ptid").assign()
            self.index_vbo.bind()

        self.instance_dtype = numpy.dtype([
            ("pos_a", numpy.float32, 2),
            ("pos_b", numpy.float32, 2),
            ("thickness", numpy.float32, 1),
            #("color", numpy.float32, 4)
        ])

        # Use a fake array to get a zero-length VBO for initial binding
        instance_array = numpy.ndarray(0, dtype=self.instance_dtype)
        self.instance_vbo = VBO(instance_array)

        with self.__attribute_shader_vao, self.trace_vbo:
            vbobind(self.__attribute_shader, self.trace_vbo.dtype,
                    "vertex").assign()
            vbobind(self.__attribute_shader, self.trace_vbo.dtype,
                    "ptid").assign()

        with self.__attribute_shader_vao, self.instance_vbo:
            self.__bind_pos_a = vbobind(self.__attribute_shader,
                                        self.instance_dtype,
                                        "pos_a",
                                        div=1)
            self.__bind_pos_b = vbobind(self.__attribute_shader,
                                        self.instance_dtype,
                                        "pos_b",
                                        div=1)
            self.__bind_thickness = vbobind(self.__attribute_shader,
                                            self.instance_dtype,
                                            "thickness",
                                            div=1)
            #vbobind(self.__attribute_shader, self.instance_dtype, "color", div=1).assign()
            self.__base_rebind(0)

            self.index_vbo.bind()

        self.__last_prepared = weakref.WeakKeyDictionary()
Esempio n. 16
0
    def initializeGL(self, glshared):

        self.__filled_shader = glshared.shader_cache.get(
            "via_filled_vertex_shader", "via_filled_fragment_shader")

        self.__outline_shader = glshared.shader_cache.get(
            "via_outline_vertex_shader", "frag1")

        self.__filled_vao = VAO()
        self.__outline_vao = VAO()

        # Build geometry for filled rendering using the frag shader for circle borders
        filled_points = [
            ((-1, -1), ),
            ((1, -1), ),
            ((-1, 1), ),
            ((1, 1), ),
        ]
        ar = numpy.array(filled_points, dtype=[("vertex", numpy.float32, 2)])

        self.__sq_vbo = VBO(ar, GL.GL_STATIC_DRAW)
        with self.__filled_vao, self.__sq_vbo:
            vbobind(self.__filled_shader, ar.dtype, "vertex").assign()

        # Build and bind an instance array for the "filled" geometry
        self.filled_instance_dtype = numpy.dtype([("pos", numpy.float32, 2),
                                                  ("r", numpy.float32, 1),
                                                  ("r_inside_frac_sq",
                                                   numpy.float32, 1),
                                                  ("color", numpy.float32, 4)])

        # Use a fake array to get a zero-length VBO for initial binding
        filled_instance_array = numpy.ndarray(0,
                                              dtype=self.filled_instance_dtype)
        self.filled_instance_vbo = VBO(filled_instance_array)

        with self.__filled_vao, self.filled_instance_vbo:
            vbobind(self.__filled_shader,
                    self.filled_instance_dtype,
                    "pos",
                    div=1).assign()
            vbobind(self.__filled_shader,
                    self.filled_instance_dtype,
                    "r",
                    div=1).assign()
            vbobind(self.__filled_shader,
                    self.filled_instance_dtype,
                    "r_inside_frac_sq",
                    div=1).assign()
            vbobind(self.__filled_shader,
                    self.filled_instance_dtype,
                    "color",
                    div=1).assign()

        # Build geometry for outline rendering
        outline_points = []
        for i in numpy.linspace(0, math.pi * 2, N_OUTLINE_SEGMENTS, False):
            outline_points.append(((math.cos(i), math.sin(i)), ))

        ar = numpy.array(outline_points, dtype=[("vertex", numpy.float32, 2)])

        self.__outline_vbo = VBO(ar, GL.GL_STATIC_DRAW)
        with self.__outline_vao, self.__outline_vbo:
            vbobind(self.__outline_shader, ar.dtype, "vertex").assign()

        # Build instance for outline rendering
        # We don't have an inner 'r' for this because we just do two instances per vertex
        self.outline_instance_dtype = numpy.dtype([("pos", numpy.float32, 2),
                                                   ("r", numpy.float32, 1),
                                                   ("color", numpy.float32, 4)
                                                   ])

        # Use a fake array to get a zero-length VBO for initial binding
        outline_instance_array = numpy.ndarray(
            0, dtype=self.outline_instance_dtype)
        self.outline_instance_vbo = VBO(outline_instance_array)

        with self.__outline_vao, self.outline_instance_vbo:
            vbobind(self.__outline_shader,
                    self.outline_instance_dtype,
                    "pos",
                    div=1).assign()
            vbobind(self.__outline_shader,
                    self.outline_instance_dtype,
                    "r",
                    div=1).assign()
            vbobind(self.__outline_shader,
                    self.outline_instance_dtype,
                    "color",
                    div=1).assign()
Esempio n. 17
0
    def initializeGL(self, gls: 'GLShared') -> None:
        # Build trace vertex VBO and associated vertex data
        dtype = [("vertex", numpy.float32, 2), ("ptid", numpy.uint32)]
        self.working_array = numpy.zeros(NUM_ENDCAP_SEGMENTS * 2 + 2,
                                         dtype=dtype)
        self.trace_vbo = VBO(self.working_array, GL.GL_DYNAMIC_DRAW)
        GL.glObjectLabel(GL.GL_BUFFER, int(self.trace_vbo), -1,
                         "Thickline Trace VBO")

        # Generate geometry for trace and endcaps
        # ptid is a variable with value 0 or 1 that indicates which endpoint the geometry is associated with
        self.__build_trace()

        self.__attribute_shader_vao = VAO(
            debug_name="Thickline attribute shader VAO")
        shader = gls.shader_cache.get("line_vertex_shader",
                                      "basic_fill_frag",
                                      defines={"INPUT_TYPE": "in"})
        assert shader is not None
        self.__attribute_shader: 'EnhShaderProgram' = shader

        # Now we build an index buffer that allows us to render filled geometry from the same
        # VBO.
        arr = []
        for i in range(NUM_ENDCAP_SEGMENTS - 1):
            arr.append(0)
            arr.append(i + 2)
            arr.append(i + 3)

        for i in range(NUM_ENDCAP_SEGMENTS - 1):
            arr.append(1)
            arr.append(i + NUM_ENDCAP_SEGMENTS + 2)
            arr.append(i + NUM_ENDCAP_SEGMENTS + 3)

        arr.append(2)
        arr.append(2 + NUM_ENDCAP_SEGMENTS - 1)
        arr.append(2 + NUM_ENDCAP_SEGMENTS)
        arr.append(2 + NUM_ENDCAP_SEGMENTS)
        arr.append(2 + NUM_ENDCAP_SEGMENTS * 2 - 1)
        arr.append(2)

        arr2 = numpy.array(arr, dtype=numpy.uint32)
        self.index_vbo = VBO(arr2, target=GL.GL_ELEMENT_ARRAY_BUFFER)
        GL.glObjectLabel(GL.GL_BUFFER, int(self.index_vbo), -1,
                         "Thickline Index VBO")

        self.instance_dtype = numpy.dtype([
            ("pos_a", numpy.float32, 2),
            ("pos_b", numpy.float32, 2),
            ("thickness", numpy.float32),
            # ("color", numpy.float32, 4)
        ])

        # Use a fake array to get a zero-length VBO for initial binding
        instance_array: 'npt.NDArray[Any]' = numpy.ndarray(
            0, dtype=self.instance_dtype)
        self.instance_vbo = VBO(instance_array)
        GL.glObjectLabel(GL.GL_BUFFER, int(self.instance_vbo), -1,
                         "Thickline Instance VBO")

        with self.__attribute_shader_vao, self.trace_vbo:
            VBOBind(self.__attribute_shader.program, self.trace_vbo.dtype,
                    "vertex").assign()
            VBOBind(self.__attribute_shader.program, self.trace_vbo.dtype,
                    "ptid").assign()

        with self.__attribute_shader_vao, self.instance_vbo:
            self.__bind_pos_a = VBOBind(self.__attribute_shader.program,
                                        self.instance_dtype,
                                        "pos_a",
                                        div=1)
            self.__bind_pos_b = VBOBind(self.__attribute_shader.program,
                                        self.instance_dtype,
                                        "pos_b",
                                        div=1)
            self.__bind_thickness = VBOBind(self.__attribute_shader.program,
                                            self.instance_dtype,
                                            "thickness",
                                            div=1)
            # vbobind(self.__attribute_shader, self.instance_dtype, "color", div=1).assign()
            self.__base_rebind(0)

            self.index_vbo.bind()