def __setup(self, width: int, height: int) -> None: if self.__is_setup: self.__teardown() self.__is_setup = True self.__fbo = GL.glGenFramebuffers(1) if self.__debug_name is not None: GL.glObjectLabel(GL.GL_FRAMEBUFFER, self.__fbo, -1, self.__debug_name) self.__info_tex = Texture(debug_name=self.__debug_name) self.__i8_texture(self.__info_tex, GL.GL_RG8UI, GL.GL_RG_INTEGER, width, height) with self: GL.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D, self.__info_tex.v, 0) GL.glDrawBuffers([GL.GL_COLOR_ATTACHMENT0]) status = GL.glCheckFramebufferStatus(GL.GL_FRAMEBUFFER) if status != GL.GL_FRAMEBUFFER_COMPLETE: lut = [ GL.GL_FRAMEBUFFER_UNDEFINED, GL.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, GL.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT, GL.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER, GL.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER, GL.GL_FRAMEBUFFER_UNSUPPORTED, GL.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, GL.GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS ] # Find the magic constant name for i in lut: if status == int(i): result = i break else: result = "unknown %d" % status print("Error, could not create framebuffer. Status: %s" % str(result)) assert False
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()
def initializeGL(self) -> None: self.sdf_shader = self.gls.shader_cache.get("image_vert", "tex_frag") self.buffer_dtype = numpy.dtype([("vertex", numpy.float32, 2), ("texpos", numpy.float32, 2)]) self.b1 = VBOBind(self.sdf_shader.program, self.buffer_dtype, "vertex") self.b2 = VBOBind(self.sdf_shader.program, self.buffer_dtype, "texpos") self.tex = Texture()
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()
def initGL(self, gls): 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) ]) 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, ar.dtype, "vertex") self.b2 = vbobind(self.prog, ar.dtype,"texpos") self.vbo = VBO(ar, GL.GL_STATIC_DRAW, GL.GL_ARRAY_BUFFER) self.mat_loc = GL.glGetUniformLocation(self.prog, "mat") self.tex1_loc = GL.glGetUniformLocation(self.prog, "tex1") self.vao = VAO() with self.vbo, self.vao: self.b1.assign() self.b2.assign()
def initializeGL(self): self.sdf_shader = self.gls.shader_cache.get("image_vert", "tex_frag") self.buffer_dtype = numpy.dtype([ ("vertex", numpy.float32, 2), ("texpos", numpy.float32, 2) ]) self.b1 = vbobind(self.sdf_shader, self.buffer_dtype, "vertex") self.b2 = vbobind(self.sdf_shader, self.buffer_dtype, "texpos") self.tex = Texture()
class ImageView: def __init__(self, il: 'ImageLayer') -> None: """ :param il: :type il: pcbre.model.imagelayer.ImageLayer :return: """ self.il = il self.im = il.decoded_image self.mat = None 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() def render(self, viewPort: 'npt.NDArray[numpy.float64]') -> None: m_pre = self.mat if self.mat is None: m_pre = self.il.transform_matrix mat = viewPort.dot(m_pre) GL.glActiveTexture(GL.GL_TEXTURE0) with self.prog.program, self._tex.on(GL.GL_TEXTURE_2D), self.vao: GL.glUniformMatrix3fv(self.mat_loc, 1, True, mat.astype(numpy.float32)) GL.glUniform1i(self.tex1_loc, 0) GL.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, 4)
class TextRender: def __init__(self, gls: Any, sdf_atlas: SDFTextAtlas) -> None: self.gls = gls self.sdf_atlas = sdf_atlas self.last_glyph_count = 0 self.__cached_metrics: Dict[str, _StringMetrics] = {} def initializeGL(self) -> None: self.sdf_shader = self.gls.shader_cache.get("image_vert", "tex_frag") self.buffer_dtype = numpy.dtype([("vertex", numpy.float32, 2), ("texpos", numpy.float32, 2)]) self.b1 = VBOBind(self.sdf_shader.program, self.buffer_dtype, "vertex") self.b2 = VBOBind(self.sdf_shader.program, self.buffer_dtype, "texpos") self.tex = Texture() # TODO: implement short-int caching # build a De-bruijn sequence (shorted substring containing all substrings) # self.int_seq = de_bruijn(4) # self.int_seq += self.int_seq[:3] def getStringMetrics(self, text: str) -> _StringMetrics: """ create an array of coord data for rendering :return: """ try: return self.__cached_metrics[text] except KeyError: pass # Starting pen X coordinate va = VA_tex(1024) pen_x = 0 left, right, top, bottom = 0.0, 0.0, 0.0, 0.0 for ch in text: # Fetch the glyph from the atlas gp = self.sdf_atlas.getGlyph(ch) # width and height of the rendered quad is proportional to the glpyh size margin = self.sdf_atlas.margin w = (gp.w + margin * 2) h = (gp.h + margin * 2) # Calculate the offset to the corner of the character. c_off_x = pen_x + gp.l - margin # Y position is a bit tricky. the "top" of the glyph is whats specified, but we care about the bottom-left # so subtract the height c_off_y = gp.t - gp.h - margin # Update the bounding rect left = min(left, (c_off_x + margin) / BASE_FONT) right = max(right, (c_off_x + w - margin) / BASE_FONT) bottom = min(bottom, (c_off_y + margin) / BASE_FONT) top = max(top, (c_off_y + h - margin) / BASE_FONT) # Calculate the draw rect positions x0 = (c_off_x) / BASE_FONT y0 = (c_off_y) / BASE_FONT x1 = (c_off_x + w) / BASE_FONT y1 = (c_off_y + h) / BASE_FONT # Add two triangles for the rect va.add_tex(x0, y0, gp.sx, gp.sy) va.add_tex(x0, y1, gp.sx, gp.ty) va.add_tex(x1, y0, gp.tx, gp.sy) va.add_tex(x1, y0, gp.tx, gp.sy) va.add_tex(x0, y1, gp.sx, gp.ty) va.add_tex(x1, y1, gp.tx, gp.ty) # And increment to the next character pen_x += gp.hb cm = _StringMetrics(va, (left, right, bottom, top)) self.__cached_metrics[text] = cm return cm def updateTexture(self) -> None: # Don't update the texture if its up-to-date if len(self.sdf_atlas.atlas) == self.last_glyph_count: return self.last_glyph_count = len(self.sdf_atlas.atlas) # 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_LINEAR) 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. GL.glTexImage2D( GL.GL_TEXTURE_2D, 0, GL.GL_RED, self.sdf_atlas.image.shape[1], self.sdf_atlas.image.shape[0], 0, GL.GL_RED, GL.GL_UNSIGNED_BYTE, self.sdf_atlas.image.ctypes.data_as( ctypes.POINTER(ctypes.c_uint8)))
class TextRender(object): def __init__(self, gls, sdf_atlas): self.gls = gls self.sdf_atlas = sdf_atlas self.last_glyph_count = 0 def initializeGL(self): self.sdf_shader = self.gls.shader_cache.get("image_vert", "tex_frag") self.buffer_dtype = numpy.dtype([("vertex", numpy.float32, 2), ("texpos", numpy.float32, 2)]) self.b1 = vbobind(self.sdf_shader, self.buffer_dtype, "vertex") self.b2 = vbobind(self.sdf_shader, self.buffer_dtype, "texpos") self.tex = Texture() # TODO: implement short-int caching # build a De-bruijn sequence (shorted substring containing all substrings) # self.int_seq = de_bruijn(4) # self.int_seq += self.int_seq[:3] def getStringMetrics(self, text): """ create an array of coord data for rendering :return: """ # In the future, we'll probably want to move to a texture-buffer-object # approach for storing glyph metrics, such that all we need to submit is an # array of character indicies and X-offsets # # This would pack into 8 bytes/char quite nicely (4 byte char index, float32 left) # With this, streaming text to the GPU would be much more effective # Starting pen X coordinate q = [] pen_x = 0 left, right, top, bottom = 0, 0, 0, 0 for ch in text: # Fetch the glyph from the atlas gp = self.sdf_atlas.getGlyph(ch) # width and height of the rendered quad is proportional to the glpyh size margin = self.sdf_atlas.margin w = (gp.w + margin * 2) h = (gp.h + margin * 2) # Calculate the offset to the corner of the character. c_off_x = pen_x + gp.l - margin # Y position is a bit tricky. the "top" of the glyph is whats specified, but we care about the bottom-left # so subtract the height c_off_y = gp.t - gp.h - margin left = min(left, (c_off_x + margin) / BASE_FONT) right = max(right, (c_off_x + w - margin) / BASE_FONT) bottom = min(bottom, (c_off_y + margin) / BASE_FONT) top = max(top, (c_off_y + h - margin) / BASE_FONT) x0 = (c_off_x) / BASE_FONT y0 = (c_off_y) / BASE_FONT x1 = (c_off_x + w) / BASE_FONT y1 = (c_off_y + h) / BASE_FONT q.append(((x0, y0), (gp.sx, gp.sy))) q.append(((x0, y1), (gp.sx, gp.ty))) q.append(((x1, y0), (gp.tx, gp.sy))) q.append(((x1, y0), (gp.tx, gp.sy))) q.append(((x0, y1), (gp.sx, gp.ty))) q.append(((x1, y1), (gp.tx, gp.ty))) # And increment to the next character pen_x += gp.hb return _StringMetrics(q, (left, right, bottom, top)) def updateTexture(self): # Don't update the texture if its up-to-date if len(self.sdf_atlas.atlas) == self.last_glyph_count: return self.last_glyph_count = len(self.sdf_atlas.atlas) # 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_LINEAR) 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. GL.glTexImage2D( GL.GL_TEXTURE_2D, 0, GL.GL_RED, self.sdf_atlas.image.shape[1], self.sdf_atlas.image.shape[0], 0, GL.GL_RED, GL.GL_UNSIGNED_BYTE, self.sdf_atlas.image.ctypes.data_as( ctypes.POINTER(ctypes.c_uint8)))
class ImageView(object): def __init__(self, il): """ :param il: :type il: pcbre.model.imagelayer.ImageLayer :return: """ self.il = il self.im = il.decoded_image # haaaaaax #flipy = pcbre.matrix.flip(1) #self.mat = flipy.dot(self.il.transform_matrix.dot(numpy.linalg.inv(self.dview_tmat))) #self.mat = self.dview_tmat self.mat = None def initGL(self, gls): 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) ]) 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, ar.dtype, "vertex") self.b2 = vbobind(self.prog, ar.dtype,"texpos") self.vbo = VBO(ar, GL.GL_STATIC_DRAW, GL.GL_ARRAY_BUFFER) self.mat_loc = GL.glGetUniformLocation(self.prog, "mat") self.tex1_loc = GL.glGetUniformLocation(self.prog, "tex1") self.vao = VAO() with self.vbo, self.vao: self.b1.assign() self.b2.assign() def render(self, viewPort): m_pre = self.mat if self.mat is None: m_pre = self.il.transform_matrix mat = viewPort.dot(m_pre) GL.glActiveTexture(GL.GL_TEXTURE0) with self.prog, self._tex.on(GL.GL_TEXTURE_2D), self.vao: GL.glUniformMatrix3fv(self.mat_loc, 1, True, mat.astype(numpy.float32)) GL.glUniform1i(self.tex1_loc, 0) GL.glDrawArrays(GL.GL_TRIANGLE_STRIP,0,4) def tfI2W(self, pt): x_, y_, t_ = self.il.transform_matrix.dot([pt[0], pt[1], 1.]) return (x_/t_, y_/t_) def tfW2I(self, pt): reverse = numpy.linalg.inv(self.il.transform_matrix) x_, y_, t_ = reverse.dot([pt[0], pt[1], 1.]) return (x_/t_, y_/t_)
class TextRender(object): def __init__(self, gls, sdf_atlas): self.gls = gls self.sdf_atlas = sdf_atlas self.last_glyph_count = 0 def initializeGL(self): self.sdf_shader = self.gls.shader_cache.get("image_vert", "tex_frag") self.buffer_dtype = numpy.dtype([ ("vertex", numpy.float32, 2), ("texpos", numpy.float32, 2) ]) self.b1 = vbobind(self.sdf_shader, self.buffer_dtype, "vertex") self.b2 = vbobind(self.sdf_shader, self.buffer_dtype, "texpos") self.tex = Texture() # TODO: implement short-int caching # build a De-bruijn sequence (shorted substring containing all substrings) # self.int_seq = de_bruijn(4) # self.int_seq += self.int_seq[:3] def getString(self, text): """ create an array of coord data for rendering :return: """ # In the future, we'll probably want to move to a texture-buffer-object # approach for storing glyph metrics, such that all we need to submit is an # array of character indicies and X-offsets # # This would pack into 8 bytes/char quite nicely (4 byte char index, float32 left) # With this, streaming text to the GPU would be much more effective # Starting pen X coordinate q = [] pen_x = 0 left, right, top, bottom = 0, 0, 0, 0 for ch in text: # Fetch the glyph from the atlas gp = self.sdf_atlas.getGlyph(ch) # width and height of the rendered quad is proportional to the glpyh size margin = self.sdf_atlas.margin w = (gp.w + margin * 2) h = (gp.h + margin * 2) # Calculate the offset to the corner of the character. c_off_x = pen_x + gp.l - margin # Y position is a bit tricky. the "top" of the glyph is whats specified, but we care about the bottom-left # so subtract the height c_off_y = gp.t - gp.h - margin left = min(left, (c_off_x + margin) / BASE_FONT) right = max(right, (c_off_x + w - margin) / BASE_FONT) bottom = min(bottom, (c_off_y + margin) / BASE_FONT) top = max(top, (c_off_y + h - margin) / BASE_FONT) x0 = (c_off_x) / BASE_FONT y0 = (c_off_y) / BASE_FONT x1 = (c_off_x + w) / BASE_FONT y1 = (c_off_y + h) / BASE_FONT q.append(((x0, y0), (gp.sx, gp.sy))) q.append(((x0, y1), (gp.sx, gp.ty))) q.append(((x1, y0), (gp.tx, gp.sy))) q.append(((x1, y0), (gp.tx, gp.sy))) q.append(((x0, y1), (gp.sx, gp.ty))) q.append(((x1, y1), (gp.tx, gp.ty))) # And increment to the next character pen_x += gp.hb return TextInfo(q, (left, right, bottom, top)) def updateTexture(self): # Don't update the texture if its up-to-date if len(self.sdf_atlas.atlas) == self.last_glyph_count: return self.last_glyph_count = len(self.sdf_atlas.atlas) # 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_LINEAR) 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_RED, self.sdf_atlas.image.shape[1], self.sdf_atlas.image.shape[0], 0, GL.GL_RED, GL.GL_UNSIGNED_BYTE, self.sdf_atlas.image.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)))
class CompositeManager: def __init__(self) -> None: self.__width = 0 self.__height = 0 self.__layer_fbs: 'List[RenderLayer]' = [] self.__active_count = 0 self.__keys: 'Dict[Any, RenderLayer]' = {} def get(self, key: Any) -> 'RenderLayer': # If we've already got a composite target for this key # return it if key in self.__keys: return self.__keys[key] # Allocate a new layer if required if self.__active_count == len(self.__layer_fbs): self.__layer_fbs.append(RenderLayer(self.__width, self.__height)) # Save new layer fb for reuse self.__keys[key] = self.__layer_fbs[self.__active_count] self.__active_count += 1 return self.__keys[key] def resize(self, width: int, height: int) -> None: if width == self.__width and height == self.__height: return self.__width = width self.__height = height for i in self.__layer_fbs: i.resize(width, height) self.__composite_vbo.set_array(self.__get_vbo_data()) with self.__composite_vbo: self.__composite_vbo.copy_data() def restart(self) -> None: """ Call at the start of rendering. Resets all layers to initial state :return: """ self.__keys = {} self.__active_count = 0 for n, _ in enumerate(self.__layer_fbs): self.reset_layer(n) def reset_layer(self, n: int) -> None: """ Reset a particular layer to an empty state. This implies alpha of 0 (transparent) and type of 0 (undrawn) :param n: :return: """ with self.__layer_fbs[n]: GL.glClearBufferfv(GL.GL_COLOR, 0, (0, 255, 0, 0)) def __get_vbo_data(self) -> 'npt.NDArray[numpy.float64]': if self.__width == 0 or self.__height == 0: assert False # Fullscreen textured quad filled_points = [ ((-1.0, -1.0), (0.0, 0.0)), ((1.0, -1.0), (1.0, 0.0)), ((-1.0, 1.0), (0.0, 1.0)), ((1.0, 1.0), (1.0, 1.0)), ] ar = numpy.array(filled_points, dtype=[("vertex", numpy.float32, 2), ("texpos", numpy.float32, 2)]) sscale = max(self.__width, self.__height) xscale = self.__width / sscale yscale = self.__height / sscale ar["vertex"][:, 0] *= xscale ar["vertex"][:, 1] *= yscale return ar 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() def set_color_table(self, colors: 'Sequence[Tuple[int, int, int, int]]') -> None: array: 'npt.NDArray[numpy.uint8]' = numpy.ndarray((256, 4), dtype=numpy.uint8) # Create a stub array with the color table data array.fill(0) array[:] = (255, 0, 255, 255) array[:len(colors)] = colors with self.__texture_colors.on(GL.GL_TEXTURE_1D): GL.glTexImage1D(GL.GL_TEXTURE_1D, 0, GL.GL_RGBA, 256, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, array) class _PREBIND: def __init__(self, layer_list: 'Dict[Any, RenderLayer]', composite_shader: 'EnhShaderProgram'): self.layer_list = layer_list self.composite_shader = composite_shader def composite(self, n: Any, layer_primary_color: 'Tuple[int,int,int]') -> None: alpha = 0.5 * 255 try: layer = self.layer_list[n] except KeyError: return GL.glActiveTexture(GL.GL_TEXTURE0) GL.glBindTexture(GL.GL_TEXTURE_2D, layer.info_texture.v) GL.glUniform4f(self.composite_shader.uniforms.layer_color, layer_primary_color[0], layer_primary_color[1], layer_primary_color[2], alpha) GL.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, 4) @contextlib.contextmanager def composite_prebind(self) -> Generator['_PREBIND', None, None]: GL.glActiveTexture(GL.GL_TEXTURE1) GL.glBindTexture(GL.GL_TEXTURE_1D, self.__texture_colors.v) GL.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA) # Composite the layer to the screen with self.__composite_shader.program, self.__composite_vao: GL.glUniform1i(self.__composite_shader.uniforms.layer_info, 0) GL.glUniform1i(self.__composite_shader.uniforms.color_tab, 1) yield self._PREBIND(self.__keys, self.__composite_shader)
class ImageView(object): def __init__(self, il): """ :param il: :type il: pcbre.model.imagelayer.ImageLayer :return: """ self.il = il self.im = il.decoded_image # haaaaaax #flipy = pcbre.matrix.flip(1) #self.mat = flipy.dot(self.il.transform_matrix.dot(numpy.linalg.inv(self.dview_tmat))) #self.mat = self.dview_tmat self.mat = None def initGL(self, gls): 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)]) 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, ar.dtype, "vertex") self.b2 = vbobind(self.prog, ar.dtype, "texpos") self.vbo = VBO(ar, GL.GL_STATIC_DRAW, GL.GL_ARRAY_BUFFER) self.mat_loc = GL.glGetUniformLocation(self.prog, "mat") self.tex1_loc = GL.glGetUniformLocation(self.prog, "tex1") self.vao = VAO() with self.vbo, self.vao: self.b1.assign() self.b2.assign() def render(self, viewPort): #proj = viewPort.dot(self.il.transform_matrix) #cv2.warpPerspective(self.im, proj, # (viewPort.width, viewPort.height), # No depth coord # surface.buffer, 0, cv2.BORDER_TRANSPARENT) m_pre = self.mat if self.mat is None: m_pre = self.il.transform_matrix mat = viewPort.dot(m_pre) GL.glActiveTexture(GL.GL_TEXTURE0) with self.prog, self._tex.on(GL.GL_TEXTURE_2D), self.vao: #with self.prog, self.vao: GL.glUniformMatrix3fv(self.mat_loc, 1, True, mat.astype(numpy.float32)) GL.glUniform1i(self.tex1_loc, 0) GL.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, 4) def tfI2W(self, pt): x_, y_, t_ = self.il.transform_matrix.dot([pt[0], pt[1], 1.]) return (x_ / t_, y_ / t_) def tfW2I(self, pt): reverse = numpy.linalg.inv(self.il.transform_matrix) x_, y_, t_ = reverse.dot([pt[0], pt[1], 1.]) return (x_ / t_, y_ / t_)