diff --git a/asciterm.py b/asciterm.py index b6c8593..6665b7f 100755 --- a/asciterm.py +++ b/asciterm.py @@ -48,7 +48,7 @@ def add_subparser(self): def run(self, args): from asciterm_vispy import ArtSciTermVispy - terminal = ArtSciTermVispy(args, 1500, 1500, scale=1.0) + terminal = ArtSciTermVispy(args) terminal.run() @@ -61,7 +61,7 @@ def add_subparser(self): def run(self, args): from asciterm_glumpy import ArtSciTermGlumpy - terminal = ArtSciTermGlumpy(args, 1000, 700, scale=1.0) + terminal = ArtSciTermGlumpy(args) terminal.run() @@ -74,7 +74,7 @@ def add_subparser(self): def run(self, args): from asciterm_kivy import ArtSciTermKivy - terminal = ArtSciTermKivy(args, 1000, 1000, scale=2.5) + terminal = ArtSciTermKivy(args) terminal.run() @@ -86,10 +86,12 @@ def run(raw_args=None): epilog="") parser.add_argument('--record', default='a.mpg') - parser.add_argument('--log-level', + parser.add_argument('-s', '--scale', default='2') + parser.add_argument('-d', '--size', default='1000x1000') + parser.add_argument('-l', '--log-level', choices=['debug', 'info', 'warn', 'error', 'fatal'], default='info') - parser.add_argument('-l', '--libvterm-path', + parser.add_argument('-p', '--libvterm-path', help="path to the libvterm library, otherwise we use ldconf to find it") subparsers = parser.add_subparsers(dest='command') @@ -121,8 +123,7 @@ def run(raw_args=None): handler.setLevel(log_level) if args[0].command is None: - parser.print_help() - return 1 + args[0].command = "vispy-term" cmd = cmd_map[args[0].command] print(sys.argv) diff --git a/asciterm_generic.py b/asciterm_generic.py index 08abf43..dad3b02 100644 --- a/asciterm_generic.py +++ b/asciterm_generic.py @@ -199,10 +199,18 @@ def adapt_to_dim(self, width, height): self.handle_main_vbuffer() - def __init__(self, args, width, height, scale): - self.width = width - self.height = height - self.scale = scale + def __init__(self, args): + + self.width = 1000 + self.height = 1000 + size = args[0].size.split("x") + if len(size) == 2: + self.width = int(size[0]) + self.height = int(size[1]) + else: + print("arg szie has to be in form x, instead got {args[0].size}, using defaults {self.width}, {self.height}") + + self.scale = float(args[0].scale) self.char_width = 6.0 self.char_height = 13.0 self.start_time = time.time() @@ -332,9 +340,8 @@ def draw(self, event): # self.window.clear() with self.progman_lock: - mouse = (2*self.mouse[0]/self.width - 1, 1 - 2*self.mouse[1]/self.height ) for internal_id, prog_wrap in self.progman.prog_wraps.items(): - prog_wrap.draw(time_now = time_now, mouse = mouse) + prog_wrap.draw(time_now = time_now, mouse = self.mouse) self.program.draw(self.program.GL_POINTS) diff --git a/asciterm_glumpy.py b/asciterm_glumpy.py index 4d1b219..87a8459 100644 --- a/asciterm_glumpy.py +++ b/asciterm_glumpy.py @@ -33,14 +33,12 @@ def to_gl_constant(self, what): class ArtSciTermGlumpy(ArtSciTerm): - def __init__(self, args, width, height, x=0, y=0, scale=2): + def __init__(self, args): self.factory = Null() setattr(self.factory, "create_program", ArtSciTermGlumpyProgram) setattr(self.factory, "ortho", glm.ortho) - #self.gloo = gloo - #self._app = app - ArtSciTerm.__init__(self, args, width, height, scale) + ArtSciTerm.__init__(self, args) def run(self): app.use("qt5") diff --git a/asciterm_vispy.py b/asciterm_vispy.py index ffa4fcc..6c7b7a3 100644 --- a/asciterm_vispy.py +++ b/asciterm_vispy.py @@ -31,7 +31,7 @@ def to_gl_constant(self, txt): class ArtSciTermVispy(app.Canvas, ArtSciTerm): - def __init__(self, args, width, height, x=0, y=0, scale=2): + def __init__(self, args): self.factory = Null() setattr(self.factory, "create_program", ArtSciTermVispyProgram) setattr(self.factory, "ortho", util.transforms.ortho) @@ -39,7 +39,7 @@ def __init__(self, args, width, height, x=0, y=0, scale=2): #self.gloo = gloo #self._app = app app.Canvas.__init__(self) - ArtSciTerm.__init__(self, args, width, height, scale) + ArtSciTerm.__init__(self, args) def run(self): super().show() diff --git a/examples/client_lib.py b/examples/client_lib.py new file mode 100644 index 0000000..956c1dc --- /dev/null +++ b/examples/client_lib.py @@ -0,0 +1,25 @@ +import msgpack +import base64 + +def transcode(what): + return base64.b64encode(what.encode('ascii')).decode('ascii') + +def envelope(title="untitled", cmd="create", draw_mode="trianglestrip", start_col = 10, cols=80, rows=12, vertex_shader=None, fragment_shader=None, + attributes=None, uniforms=None): + + ret = f"\033_Atitle='{title}',cmd='{cmd}',start_col={start_col},rows={rows},cols={cols},draw_mode='{draw_mode}'" + if vertex_shader: + ret += f",vertex_shader='{transcode(vertex_shader)}'" + + if fragment_shader: + ret += f",fragment_shader='{transcode(fragment_shader)}'" + + if attributes: + attributes = msgpack.packb(attributes) + ret += f",attributes='{base64.b64encode(attributes).decode('ascii')}'" + + if uniforms: + ret += f",uniforms='{base64.b64encode(uniforms).decode('ascii')}'" + + ret += "\033\\" + return ret diff --git a/examples/mandelbrot.py b/examples/mandelbrot.py index 46999e3..3062031 100755 --- a/examples/mandelbrot.py +++ b/examples/mandelbrot.py @@ -1,33 +1,30 @@ #!/usr/bin/env python3 -import base64 -import msgpack import time import sys import numpy as np -#import msgpack_numpy as m - -#m.patch() +from client_lib import envelope # Shader source code: from https://github.com/vispy/vispy/blob/master/examples/demo/gloo/mandelbrot.py # ----------------------------------------------------------------------------- vertex_shader = """ - attribute vec2 position; - attribute vec2 texcoord; - varying vec2 v_texcoord; - varying float v_zoom; - varying float v_depth; - uniform float time; - void main() - { - //gl_Position = ; - gl_Position = vec4(position.x, position.y, 0.0, 1.0); - v_texcoord = texcoord; - v_zoom = 1.0/pow(2, (1.01 + sin(time/8))*8); - v_depth = 400.0 * (2 + sin(time/8)) - 400; - } +$vertex_shader_variables; +attribute vec2 position; +attribute vec2 texcoord; +varying vec2 v_texcoord; +varying float v_zoom; +varying float v_depth; +void main() +{ + gl_Position = vec4(position.x, position.y, 0.0, 1.0); + v_texcoord = texcoord; + v_zoom = 1.0/pow(2, (1.01 + sin(time/8))*8); + v_depth = 400.0 * (2 + sin(time/8)) - 400; + $vertex_shader_epilog; +} """ fragment_shader = """ +$fragment_shader_variables; varying vec2 v_texcoord; varying float v_zoom; @@ -71,34 +68,10 @@ """ -def transcode(what): - return base64.b64encode(what.encode('ascii')).decode('ascii') - -def envelope(cmd="create", draw_mode="triangle_strip", cols=80, rows=12, vertex_shader=None, fragment_shader=None, - attributes=None, uniforms=None): - - ret = f"\033_Acmd='{cmd}',rows={rows},cols={cols},draw_mode='{draw_mode}'" - if vertex_shader: - ret += f",vertex_shader='{transcode(vertex_shader)}'" - - if fragment_shader: - ret += f",fragment_shader='{transcode(fragment_shader)}'" - - if attributes: - ret += f",attributes='{base64.b64encode(attributes).decode('ascii')}'" - - if uniforms: - ret += f",uniforms='{base64.b64encode(uniforms).decode('ascii')}'" - - ret += "\033\\" - - return ret - - def main(): - attributes = msgpack.packb({ + attributes = { 'position': [(-1,-1), (-1, 1), ( 1,-1), ( 1, 1)], - 'texcoord': [( -1, 1), ( -1, -1), ( 1, 1), ( 1, -1)]}) + 'texcoord': [( -1, 1), ( -1, -1), ( 1, 1), ( 1, -1)]} print("first we just print some lines...\r\n" * 5) print(envelope( @@ -111,7 +84,8 @@ def main(): attributes=attributes), end="") sys.stdout.flush() - #time.sleep(100) + if len(sys.argv) > 1: + time.sleep(float(sys.argv[1])) if __name__ == "__main__": main() diff --git a/examples/random_points.py b/examples/random_points.py index f4a1d2f..a4abaab 100755 --- a/examples/random_points.py +++ b/examples/random_points.py @@ -1,10 +1,9 @@ #!/usr/bin/env python3 -import base64 -import msgpack import time import sys import numpy as np +from client_lib import envelope vertex_shader = """ attribute vec2 position; @@ -17,8 +16,6 @@ """ fragment_shader = """ -uniform float time; -uniform vec3 color; void main() { gl_FragColor = vec4(sin(time), sin(time/2), sin(time/3), 1.0); //gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); @@ -26,34 +23,10 @@ """ -def transcode(what): - return base64.b64encode(what.encode('ascii')).decode('ascii') - -def envelope(cmd="create", draw_mode="trianglestrip", cols=80, rows=12, vertex_shader=None, fragment_shader=None, - attributes=None, uniforms=None): - - ret = f"\033_Acmd='{cmd}',rows={rows},cols={cols},draw_mode='{draw_mode}'" - if vertex_shader: - ret += f",vertex_shader='{transcode(vertex_shader)}'" - - if fragment_shader: - ret += f",fragment_shader='{transcode(fragment_shader)}'" - - if attributes: - ret += f",attributes='{base64.b64encode(attributes).decode('ascii')}'" - - if uniforms: - ret += f",uniforms='{base64.b64encode(uniforms).decode('ascii')}'" - - ret += "\033\\" - - return ret - - def main(): data = np.random.uniform(-1.0, 1.0, size=(20000, 2)).astype(np.float32).tolist() - attributes = msgpack.packb({"position": data}) + attributes = {"position": data} print("first we just print some lines...\r\n" * 5) print(envelope( @@ -66,7 +39,9 @@ def main(): attributes=attributes), end="") sys.stdout.flush() - #time.sleep(100) + if len(sys.argv) > 1: + time.sleep(float(sys.argv[1])) + if __name__ == "__main__": main() diff --git a/examples/voronoi.py b/examples/voronoi.py new file mode 100755 index 0000000..1077910 --- /dev/null +++ b/examples/voronoi.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +import sys +import time +from client_lib import envelope + +# based on https://thebookofshaders.com/12/ + +vertex_shader = """ +$vertex_shader_variables; +attribute vec2 position; +void main() { + gl_Position = vec4(position, 0.0, 1.0); + $vertex_shader_epilog; +} +""" + +fragment_shader = """ +$fragment_shader_variables; + +void main() { + $fragment_shader_prolog; + vec2 st = relFragCoord.xy/viewport.zw; + st.x *= viewport.z/viewport.w; + + vec3 color = vec3(.0); + + // Cell positions + vec2 point[5]; + point[0] = vec2(0.83,0.75); + point[1] = vec2(0.60,0.07); + point[2] = vec2(0.28,0.64); + point[3] = vec2(0.31,0.26); + point[4] = mousePos/viewport.zw; + + float m_dist = 1 + (1 + sin(time))/100; // minimum distance + + // Iterate through the points positions + for (int i = 0; i < 5; i++) { + float dist = distance(st, point[i]); + + // Keep the closer distance + m_dist = min(m_dist, dist); + } + + // Draw the min distance (distance field) + color += m_dist; + + // Show isolines + //color -= step(.7,abs(sin(50.0*m_dist)))*.3; + gl_FragColor = vec4(color * (1 + (1 + sin(time)) / 2 ),1.0); +} +""" + +def main(): + attributes = { + 'position': [(-1,-1), (-1, 1), ( 1,-1), ( 1, 1)]} + + print("first we just print some lines...\r\n" * 5) + print(envelope( + title="voronoi", + cmd="create", + draw_mode="triangle_strip", + start_col = 10, + rows=35, + cols=80, + vertex_shader=vertex_shader, + fragment_shader=fragment_shader, + attributes=attributes), end="") + + sys.stdout.flush() + if len(sys.argv) > 1: + time.sleep(float(sys.argv[1])) + +if __name__ == "__main__": + main() diff --git a/progman.py b/progman.py index c9240be..ee787da 100644 --- a/progman.py +++ b/progman.py @@ -3,26 +3,55 @@ import base64 import msgpack import numpy as np +import re +import sys -def adapt_vertex_shader(vertex_shader): - """ we are injecting the viewport adapter - we are not going to parse the entire grammar, we just search for the closing curly bracket +from string import Template + +VERTEX_SHADER_VARIABLES = """ +uniform float time; +uniform vec4 normViewport; +""" - """ - vertex_shader = "uniform vec4 viewport;\n" + vertex_shader - curly_c_pos = vertex_shader.rfind("}") - vertex_shader = vertex_shader[:vertex_shader.rfind("}")] + """ +VERTEX_SHADER_EPILOG = """ gl_Position = vec4( - viewport.x + (1 + gl_Position.x) * (viewport.z/2.0), - viewport.y + (1 + gl_Position.y) * (viewport.w/2.0), + normViewport.x + (1 + gl_Position.x) * (normViewport.z/2.0), + normViewport.y + (1 + gl_Position.y) * (normViewport.w/2.0), 0.0, 1.0); -} +""" + +FRAGMENT_SHADER_VARIABLES = """ +uniform float time; +uniform vec4 viewport; +uniform vec2 mousePos; +uniform vec2 screenMousePos; +uniform vec2 screenResolution; +""" + +FRAGMENT_SHADER_PROLOG = """ + vec4 relFragCoord = gl_FragCoord - vec4(viewport.xy, 0, 0) +""" + +def adapt_vertex_shader(vertex_shader): + + vertex_shader = Template(vertex_shader).substitute( + vertex_shader_variables = VERTEX_SHADER_VARIABLES, + vertex_shader_epilog = VERTEX_SHADER_EPILOG) + print("vertex_shader: \n", vertex_shader) - """ return vertex_shader +def adapt_fragment_shader(fragment_shader): + + fragment_shader = Template(fragment_shader).substitute( + fragment_shader_variables = FRAGMENT_SHADER_VARIABLES, + fragment_shader_prolog = FRAGMENT_SHADER_PROLOG) + print("fragment_shader:\n", fragment_shader) + return fragment_shader + class ProgWrap: def __init__(self, factory, cmd, progman): + self.viewport = (0, 0, 0, 0) self.progman = progman self.is_sane = False @@ -32,35 +61,24 @@ def __init__(self, factory, cmd, progman): attributes = {} uniforms = {} - if "name" in cmd: - name = cmd["name"] - - if "id" in cmd: - prog_id = cmd["id"] + self.title = cmd.get("title", "untitled") if "vertex_shader" in cmd: - vertex_shader = base64.b64decode(cmd["vertex_shader"].encode('ascii')).decode('ascii') - vertex_shader = adapt_vertex_shader(vertex_shader) - #print(vertex_shader) + vertex_shader = adapt_vertex_shader( + base64.b64decode(cmd["vertex_shader"].encode('ascii')).decode('ascii')) if "fragment_shader" in cmd: - fragment_shader = base64.b64decode(cmd["fragment_shader"].encode('ascii')).decode('ascii') - #print(fragment_shader) + fragment_shader = adapt_fragment_shader( + base64.b64decode(cmd["fragment_shader"].encode('ascii')).decode('ascii')) if "uniforms" in cmd: uniforms = msgpack.unpackb(base64.b64decode(cmd["uniforms"].encode('ascii'))) if "attributes" in cmd: attributes = msgpack.unpackb(base64.b64decode(cmd["attributes"].encode('ascii'))) - draw_mode = "triangle_strip" - if "draw_mode" in cmd: - draw_mode = cmd["draw_mode"] - rows = 0 #meaning all rows - if "rows" in cmd: - rows = cmd["rows"] - cols = 0 #all colls - if "cols" in cmd: - cols = cmd["cols"] - - # inject the time uniform + self.draw_mode = cmd.get("draw_mode", "triangle_strip") + self.start_col = cmd.get("start_col", 0) + self.rows = cmd.get("rows", 0) + self.cols = cmd.get("cols", 0) + try: self.program = factory.create_program(vertex_shader, fragment_shader) self.is_active = False @@ -69,52 +87,69 @@ def __init__(self, factory, cmd, progman): return for key, value in attributes.items(): - #print("attr: ", key, value) - self.program[key.decode('ascii')] = value - #program[key] = value + try: + key = key.decode('ascii') + self.program[key] = value + except IndexError as exc: + print(f"IndexError, could not set attribute {key}: ", exc) + except ValueError as exc: + print(f"ValueError, could not set attribute {key}: ", exc) for key, value in uniforms.items(): - #print("unif: ", key, value) - self.program[key.decode('ascii')] = value - - has_time = False - has_mouse = False - has_resolution = False - for uniform in self.program.get_uniforms(): - if 'time' == uniform[0]: - has_time = True - if 'mouse' == uniform[0]: - has_mouse = True - if 'resolution' == uniform[0]: - has_resolution = True - - self.cols = cols - self.rows = rows - self.has_time = has_time + try: + key = key.decode('ascii') + self.program[key] = value + except IndexError as exc: + print(f"could not uniform {key}") + + self.start_time = time.time() - self.has_mouse = has_mouse - self.has_resolution = has_resolution + + self.program["time"] = 0 + self.program["screenMousePos"] = (0, 0) + self.program['mousePos'] = (0, 0) + self.program["normViewport"] = (0, 0, 0, 0) + self.first_row = 0 - self.draw_mode = draw_mode self.is_sane = True def update_viewport(self): - view_width = 2 * self.cols / self.progman.screen_cols - view_height = 2 * self.rows / self.progman.screen_rows - viewport = (-1, + norm_view_width = 2 * self.cols / self.progman.screen_cols + norm_view_height = 2 * self.rows / self.progman.screen_rows + self.norm_viewport = ( + -1 + 2 * self.start_col / self.progman.screen_cols, 1 - 2.0 * (self.last_row - 1)/self.progman.screen_rows, + norm_view_width, + norm_view_height) + self.program["normViewport"] = self.norm_viewport + + view_width = self.progman.scale * self.cols * self.progman.char_width + view_height = self.progman.scale * self.rows * self.progman.char_height + + self.viewport = ( + self.progman.scale * self.start_col * self.progman.char_width, + self.progman.scale * (self.progman.screen_rows - self.last_row + 1) * self.progman.char_height, view_width, view_height) - self.program["viewport"] = viewport + + self.program["viewport"] = self.viewport + self.program["screenResolution"] = (self.progman.screen_width, self.progman.screen_height) def draw(self, time_now, mouse): - if self.has_time: - self.program["time"] = time_now - self.start_time - if self.has_mouse: - program['mouse'] = mouse + mouse = (mouse[0], self.progman.screen_height - mouse[1]) if self.is_active: - self.program.draw(self.program.to_gl_constant(self.draw_mode)) + try: + #TODO .. refactor this.. don't update what does not change + self.program["viewport"] = self.viewport + self.program["screenResolution"] = (self.progman.screen_width, self.progman.screen_height) + self.program["time"] = time_now - self.start_time + self.program["screenMousePos"] = mouse + self.program['mousePos'] = (mouse[0] - self.viewport[0], mouse[1] - self.viewport[1]) + self.program.draw(self.program.to_gl_constant(self.draw_mode)) + except RuntimeError as exc: + print("Cannot draw...: ", exc) + class ProgramManager: