From 9e650041280123243f209ef6069d6f2bc4ccb1f6 Mon Sep 17 00:00:00 2001 From: Quantum Date: Tue, 28 Aug 2018 18:30:43 -0400 Subject: [PATCH] Converted info display to use shaders. --- punyverse/assets/textures/font.png | Bin 0 -> 4306 bytes punyverse/entity.py | 6 ++-- punyverse/glgeom.py | 52 ++++++++++++++++++++++++++- punyverse/loader.py | 15 ++++++-- punyverse/shader.py | 3 ++ punyverse/shaders/text.fragment.glsl | 12 +++++++ punyverse/shaders/text.vertex.glsl | 14 ++++++++ punyverse/texture.py | 13 +++---- punyverse/ui.py | 44 +++++++++++++++++++---- punyverse/world.json | 1 + punyverse/world.py | 3 ++ 11 files changed, 143 insertions(+), 20 deletions(-) create mode 100644 punyverse/assets/textures/font.png create mode 100644 punyverse/shaders/text.fragment.glsl create mode 100644 punyverse/shaders/text.vertex.glsl diff --git a/punyverse/assets/textures/font.png b/punyverse/assets/textures/font.png new file mode 100644 index 0000000000000000000000000000000000000000..589c370fdfa563ffa6228db5b92a3668874f6a9f GIT binary patch literal 4306 zcmV;@5H0VCP)oz&jrzzHu65g@fd(;`qOYJOQZl0n*7#&jbh=L`ofK zvOB(-aA9`9kon9#`3p-Q*v2maATvXw*CH$k$i8~O1Yq63^c=tPeraXVqE#?d3(kJD z%m8R|J{q9qsl3=BK#(Z7F%*Eeo<;+KLI~juydIYL)SWg$2q9vi;epJw;3{DEL&n`1 zFEVt;nN!yS|JgO)I(VNUG)TV#pc2K&0j9v{>v`wha1|mi`cs{MZwL@V2rrN(UC)c| zi;Eg?%Ykb`2(bWygb)JAd>6wrn_$c}0J?~e;6N_UVjZ|9f=x(v#%2Ym#MAp*T(;F`lFKY(*SA)f>MJY=A{DU46E{`nAK`EZiG5JHHb z0KSmifYswQ_gF1SzXDJ_d8>RH_P@K@`9-D;Md&w5DZ0YqW299tWWEf1eP2>B;9T`M zJ>}P`mAn%0)Xb~xvr7MFT=^WpO4J{F79gU%Nr53o@xGsg0=vAr{_2!i9WG4ci~RsZ z&H_L>GZz4ZOY;Hh2FBl*Ygr+O8(dQ;_XuS;87H$l`2OS|(+Fub(7s!K+na{V+vX{{%^eQ%*_b||iobiFrlt{co; zk>m`h+znrU;0-fX{gf^Ia8*@OI%X{f=R-7fjE7;zCw<|TUk$Cf)3X3w z0{A=*s8ig0-C}^VJqSNe-K^oXE`A4Q+%o}C-gg3mv=es2T{qt7K>%AuUGDLmo08JK zY+tDgpv_aGQu5_g44|(7!0*ph071)NQZdI#bJe{9%-Jqudb$8CuS@_kL`{!Gx zJL>0p{P;5vatg~d&AcsN3Hk&u?vTz<^dO)^q|`R9O4++%yDc+qgE6FxJ)L{N8t8wc zNK63b+JwApKd{)8ow(Fk7YWqfn=OQBM9PG-PYzVkm-^R)5GQ~WUex(*t%NoKc&?4Q z+cPZ)Qbt5QIaC#F!*UbXSVGG1Gr0$?!E$3p?v+Il`1zbizqx?b_Dmsa;3tF-B2bfP z{5Lc7u?cj`QT{LsAY!DQY6@(PG#;8|20b1th!jGI-T+07G$Yd`0PpW*!}WC8n7G=s zG6Be60rK-@M36zgdHZuxKw{LcUiW~Ay0z-%1OuC!2fu$F`v=3b*#1$xo>CV4*xh|^p#r|~GyvM4%ZAgYn$;BJN4T8J(a|P- z9FLN5ENGxnIaXt@^n?DPWGFc}7DYPoJ^`J_0JGofHsQfe0LKq}x$}jF6eLv z=FO>R|B1fTZy`z3H5O|f3m-2^?0>v4byeq~X0T#ry?UNV`!nr3yDSD)E~;zl3)^g( z*G{wmCbn!Ewi@QuYA8u6yrke2lH^fhW>>Kr^bgC9wF9UXn>5n5ewRLj3U?LtfWg~x zXeUX7_U;2Dsj&123ro-EXy=;4%bDPR8Op8!=wO?nu74jymyp-zc^Lw>^Tq&L!b5JhyJw?+H$67Z=0V9;OZEbY1ruoXfG5eYmU>wmfv z(6MOArw?d*X2D6{v}|MEKc)WJu^F(x=v2gcYUbqV1Ij`HqlFOez)&HC3pNSV5kdu~ zLWrNs8{f3+hh&tZAZ&19R=xsKqk02Y>3}-V1~6_nBLM3R9lbSwW8d@O{>{4h2?k}& zhV`RHhfva$X12XhWWaW#)4u_~ZS@2xL&bA3Ms-VSrnEYv3yQ{b6-agR` z@D5Mk=mt~$L)Acyfs>OTr-5?l^wgL&{aG$`%9kWFcxzkc@tcmo)T64CP0AU+&9U&w zGV;n^)z+IR!0Wi`WFy)-d8OT+HyD@_p1#oyn*mAL2lhv<%}M6NPi6a zY~hX_N5@g?4*R!m+Ta<7I`~7(`>X}1{lzH2s9#!AY1QcV%_NLNG8V$UH^Jdy^AqJb z_?Mwn1yCR(!XMa*ZwCqmIX=mLvweY8t30z#SNS;YL!T|3?#m>tRk~JcO#nRi0uzMj z^0g55C1TD(h(x*T_wtVdxy{Fq?0LT+L&jL#WuO4snRlKsl}{AfH6#zI+Q*|l!+>cC z$*WueW}LbRY_M)I&UX|UAC7pm;8-aK8c0S)04b6rNm4ri)B+$o&S4wWy5D z_mi&xR!suZtN;u1OxT|nylf2_)d36h3>fKE1n_Nf^8i49iyftv{6{Fui|-k9Jew_s82~Csw}Qr30ccRHx!3iin?kIs zrI)`ltZL_`Q5yL|<8lnX(coJ-nFgr>uXBy&5JOe>Jc#j?)T877}R+3-*R2 z%q^z|+S1#web#AW?G&O{wW1XG(lze~V3WIrd(_z>W3vv2WS+ zD~B4faM!TaoeIp$KOFJ|;J!^xxF3?1eRQKkEV zpWiJir0cLDYu77))GG%8%$La@_yBhzJbpLS5*X}0U?b^%08Q>fie`M-233*l;UNtr zNs^6O-6DDOxGT@GcyMlbgWu;qur-4QiJE6lqx)nTR6(+r38-h*@|@@iXxAKzjKCVW zknA#G3*51D3pI7T1FT+GFd4XV^S#0T0An(C0XPib@XS(lw+;?tX%&^&^Now2Wx+c; zpEn=>7RVBSQOp84=e0CmO9QB#?$9p+%Qd5=;$qb2y1%VBM@^aF)wgu5)=}lTsFn70 zDY{wew*Zc9cL2mhEVpU%nV+6LWt{6xO-s?WUnuU9B#r%5`W{ez;1)<;*c==C>DvMC z>qmj?g5H+4?Qa7X&Ax878U5m)GAS5<9ep=WmsJ3kyW6+B2{0o0HK4$NW^aogC*F^i zB+1FSAz;vO_+gTEtnQ&`xeB26q$y?h7f&AA^_IC+Im2Dttm^2a032GkuUbjBvH;Lj z0Ic~Gc4w`%X}r=uTgTs>yVj=h%2hc4Vd>)ljGTI$DbbI4Fi&YeEfn~Euv;jk*X?+9 z-;4|_HUylVW3Wi?f7Ze1XG2<}Iq>jYm{z%GJnTJy24=0j*F(^xZl=KPgo)MGwglBt zKL@!LMeixLO}CbBON(Vc9jx)Y%&4Rl)tfB{036KP%vj0ogsE0*+m#X#*D@s_wp(3* zJMI?V9{_Ca({no{Rq@z=;Imj5X~FKNyazLY3qlAH4MJ$%72?FB2<;J-G0{MzNI%*cuntX6 zSY!;|f?tEv>X^k!0PA>7>1i1KMC-BnDfu?wF70}QM@miLT)Xe?&wAp= znK=NlGf;`byUQ)WWMO<>#9O$E;?$oyB0C45-PBU9N-9cPU-le8w_BNeY|)$pICj`j zvyp#14s^}|ql83gc~YIcz)-X60MP1Wahk6LF&;4fp}C6?>f1g>pxa$&dp18| zsR__rTn6fj4H%_Ivofg)^nKb+Cuf%@gbla zfWgezA~69v#bz#9Y{R1_ZtKA&%RQFBs5iu|Hn zzU}FNa(b&gZuIBE`a@WU@2P;)>V>a3jEW;Ps3Py`6^1?mF!@K|4-&u&qDf8zq#;ks zJ@Bz4 AY5)KL literal 0 HcmV?d00001 diff --git a/punyverse/entity.py b/punyverse/entity.py index 19cecc3..b06afc1 100644 --- a/punyverse/entity.py +++ b/punyverse/entity.py @@ -5,10 +5,10 @@ from pyglet.gl import * # noinspection PyUnresolvedReferences from six.moves import range -from punyverse.glgeom import compile, glRestore, belt, Disk, OrbitVBO, Matrix4f, SimpleSphere, TangentSphere, Cube +from punyverse.glgeom import * from punyverse.model import load_model, WavefrontVBO from punyverse.orbit import KeplerOrbit -from punyverse.texture import get_best_texture, load_clouds, get_cube_map, load_texture_1d +from punyverse.texture import get_best_texture, load_alpha_mask, get_cube_map, load_texture_1d from punyverse.utils import cached_property G = 6.67384e-11 # Gravitation Constant @@ -325,7 +325,7 @@ class SphericalBody(Body): atm_texture = atmosphere_data.get('diffuse_texture', None) cloud_texture = atmosphere_data.get('cloud_texture', None) if cloud_texture is not None: - self.cloud_transparency = get_best_texture(cloud_texture, loader=load_clouds) + self.cloud_transparency = get_best_texture(cloud_texture, loader=load_alpha_mask) self.cloud_radius = self.radius + 2 self.clouds = self._get_sphere(division, tangent=False) diff --git a/punyverse/glgeom.py b/punyverse/glgeom.py index 37964c7..c6c968a 100644 --- a/punyverse/glgeom.py +++ b/punyverse/glgeom.py @@ -1,3 +1,5 @@ +from __future__ import division + from array import array from ctypes import c_int, c_float, byref, cast, POINTER, c_uint, c_short, c_ushort from math import * @@ -10,7 +12,8 @@ from six.moves import range TWOPI = pi * 2 __all__ = ['compile', 'ortho', 'frustrum', 'crosshair', 'circle', 'belt', - 'glSection', 'glRestore', 'progress_bar'] + 'glSection', 'glRestore', 'glContext', 'progress_bar', + 'FontEngine', 'Matrix4f', 'Disk', 'OrbitVBO', 'SimpleSphere', 'TangentSphere', 'Cube'] class glContext(object): @@ -320,6 +323,53 @@ class OrbitVBO(object): self.close() +class FontEngine(object): + type = GL_SHORT + stride = 4 * 2 + position_offset = 0 + position_size = 2 + tex_offset = position_size * 2 + tex_size = 2 + + def __init__(self, max_length=256): + self.storage = array('h', max_length * 24 * [0]) + vbo = GLuint() + glGenBuffers(1, byref(vbo)) + self.vbo = vbo.value + self.vertex_count = None + + def draw(self, string): + index = 0 + row = 0 + col = 0 + for c in string: + if c == '\n': + row += 1 + col = 0 + continue + o = ord(c) + if 32 <= o < 128: + self.storage[24*index:24*index+24] = array('h', [ + row, col, o - 32, 1, + row + 1, col, o - 32, 0, + row + 1, col + 1, o - 31, 0, + row, col, o - 32, 1, + row + 1, col + 1, o - 31, 0, + row, col + 1, o - 31, 1, + ]) + index += 1 + col += 1 + + self.vertex_count = index * 6 + + glBindBuffer(GL_ARRAY_BUFFER, self.vbo) + glBufferData(GL_ARRAY_BUFFER, self.storage.itemsize * len(self.storage), + array_to_ctypes(self.storage), GL_STREAM_DRAW) + + def end(self): + glBindBuffer(GL_ARRAY_BUFFER, 0) + + def belt(radius, cross, object, count): for i in range(count): theta = TWOPI * random() diff --git a/punyverse/loader.py b/punyverse/loader.py index 958858f..fd31b55 100644 --- a/punyverse/loader.py +++ b/punyverse/loader.py @@ -1,5 +1,6 @@ from __future__ import print_function +import os import sys import time @@ -26,21 +27,29 @@ def get_context_info(context): class LoaderWindow(pyglet.window.Window): + MONOSPACE = ('Consolas', 'Droid Sans Mono', 'Courier', 'Courier New', 'Dejavu Sans Mono') + def __init__(self, *args, **kwargs): super(LoaderWindow, self).__init__(*args, **kwargs) + # work around pyglet bug: decoding font names as utf-8 instead of mbcs when using EnumFontsA. + stderr = sys.stderr + sys.stderr = open(os.devnull, 'w') + pyglet.font.have_font(self.MONOSPACE[0]) + sys.stderr = stderr + self.loading_phase = pyglet.text.Label( - font_name='Consolas', font_size=20, x=10, y=self.height - 50, + font_name=self.MONOSPACE, font_size=20, x=10, y=self.height - 50, color=(255, 255, 255, 255), width=self.width - 20, align='center', multiline=True, text='Punyverse is starting...' ) self.loading_label = pyglet.text.Label( - font_name='Consolas', font_size=16, x=10, y=self.height - 120, + font_name=self.MONOSPACE, font_size=16, x=10, y=self.height - 120, color=(255, 255, 255, 255), width=self.width - 20, align='center', multiline=True ) self.info_label = pyglet.text.Label( - font_name='Consolas', font_size=13, x=10, y=self.height - 220, + font_name=self.MONOSPACE, font_size=13, x=10, y=self.height - 220, color=(255, 255, 255, 255), width=self.width - 20, multiline=True ) diff --git a/punyverse/shader.py b/punyverse/shader.py index c1ef974..835571f 100644 --- a/punyverse/shader.py +++ b/punyverse/shader.py @@ -103,6 +103,9 @@ class Program(object): def uniform_bool(self, name, value): glUniform1i(self.uniforms[name], bool(value)) + def uniform_vec2(self, name, a, b): + glUniform2f(self.uniforms[name], a, b) + def uniform_vec3(self, name, a, b, c): glUniform3f(self.uniforms[name], a, b, c) diff --git a/punyverse/shaders/text.fragment.glsl b/punyverse/shaders/text.fragment.glsl new file mode 100644 index 0000000..3b28333 --- /dev/null +++ b/punyverse/shaders/text.fragment.glsl @@ -0,0 +1,12 @@ +#version 130 + +in vec2 v_uv; + +out vec4 o_fragColor; + +uniform sampler2D u_alpha; +uniform vec3 u_color; + +void main() { + o_fragColor = vec4(u_color, texture(u_alpha, v_uv).r); +} diff --git a/punyverse/shaders/text.vertex.glsl b/punyverse/shaders/text.vertex.glsl new file mode 100644 index 0000000..89a9621 --- /dev/null +++ b/punyverse/shaders/text.vertex.glsl @@ -0,0 +1,14 @@ +#version 130 + +in vec2 a_rc; +in vec2 a_tex; + +out vec2 v_uv; + +uniform mat4 u_projMatrix; +uniform vec2 u_start; + +void main() { + gl_Position = u_projMatrix * vec4(a_rc.y * 8 + u_start.x, a_rc.x * 16 + u_start.y, 0, 1); + v_uv = vec2(a_tex.x * 8 / 1024, a_tex.y); +} diff --git a/punyverse/texture.py b/punyverse/texture.py index 36c3f7b..8bb1680 100644 --- a/punyverse/texture.py +++ b/punyverse/texture.py @@ -42,7 +42,8 @@ except ImportError: result[y1 * row:y1 * row + row] = source[y2 * row:y2 * row + row] return six.binary_type(result) -__all__ = ['load_texture', 'load_clouds', 'load_image', 'get_best_texture', 'max_texture_size', 'get_cube_map'] +__all__ = ['load_texture', 'load_alpha_mask', 'load_image', 'get_best_texture', 'max_texture_size', + 'get_cube_map', 'load_texture_1d'] id = 0 cache = {} @@ -259,11 +260,8 @@ def load_texture_1d(file, clamp=False): return id -def load_clouds(file): +def load_alpha_mask(file, clamp=False): path, file = get_file_path(file) - if path in cache: - return cache[path] - path, width, height, depth, mode, texture = load_image(file, path) if depth != 1: @@ -284,10 +282,13 @@ def load_clouds(file): glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) + if clamp: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) + if gl_info.have_extension('GL_EXT_texture_filter_anisotropic'): glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, glGetInteger(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT)) - cache[path] = id return id diff --git a/punyverse/ui.py b/punyverse/ui.py index 4b35521..70cc18a 100644 --- a/punyverse/ui.py +++ b/punyverse/ui.py @@ -1,4 +1,5 @@ #!/usr/bin/python +from __future__ import division import os import time from math import hypot @@ -54,8 +55,6 @@ class Punyverse(pyglet.window.Window): self.key_handler = {} self.mouse_press_handler = {} - self.label = pyglet.text.Label('', font_name='Consolas', font_size=12, x=10, y=self.height - 20, - color=(255,) * 4, multiline=True, width=600) self.exclusive = False self.modifiers = 0 @@ -127,6 +126,8 @@ class Punyverse(pyglet.window.Window): glEnable(GL_DEPTH_TEST) glShadeModel(GL_SMOOTH) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + glMatrixMode(GL_MODELVIEW) glEnable(GL_LIGHTING) @@ -144,6 +145,8 @@ class Punyverse(pyglet.window.Window): glLightfv(GL_LIGHT1, GL_DIFFUSE, fv4(.5, .5, .5, 1)) glLightfv(GL_LIGHT1, GL_SPECULAR, fv4(1, 1, 1, 1)) + self.info_engine = FontEngine() + pyglet.clock.schedule(self.update) self.on_resize(self.width, self.height) # On resize handler does nothing unless it's loaded @@ -213,9 +216,8 @@ class Punyverse(pyglet.window.Window): if not width or not height: # Sometimes this happen for no reason? return - self.label.y = height - 20 - glViewport(0, 0, width, height) + glViewport(0, 0, width, height) glMatrixMode(GL_PROJECTION) self.world.resize(width, height) glLoadMatrixf(self.world.projection_matrix()) @@ -262,7 +264,13 @@ class Punyverse(pyglet.window.Window): width, height = self.get_size() if self.info: - ortho(width, height) + projection = Matrix4f([ + 2 / width, 0, 0, 0, + 0, -2 / height, 0, 0, + 0, 0, -1, 0, + -1, 1, 0, 1, + ]) + if self.info_precise: info = ('%d FPS @ (x=%.2f, y=%.2f, z=%.2f) @ %s, %s/s\n' 'Direction(pitch=%.2f, yaw=%.2f, roll=%.2f)\nTick: %d' % @@ -271,8 +279,30 @@ class Punyverse(pyglet.window.Window): else: info = ('%d FPS @ (x=%.2f, y=%.2f, z=%.2f) @ %s, %s/s\n' % (pyglet.clock.get_fps(), c.x, c.y, c.z, self.world.cam.speed, self.get_time_per_second())) - self.label.text = info - self.label.draw() + + glEnable(GL_BLEND) + shader = self.world.activate_shader('text') + shader.uniform_mat4('u_projMatrix', projection) + self.info_engine.draw(info) + + glActiveTexture(GL_TEXTURE0) + glBindTexture(GL_TEXTURE_2D, self.world.font_tex) + shader.uniform_texture('u_alpha', 0) + shader.uniform_vec3('u_color', 1, 1, 1) + shader.uniform_vec2('u_start', 10, 10) + + shader.vertex_attribute('a_rc', self.info_engine.position_size, self.info_engine.type, GL_FALSE, + self.info_engine.stride, self.info_engine.position_offset) + shader.vertex_attribute('a_tex', self.info_engine.tex_size, self.info_engine.type, GL_FALSE, + self.info_engine.stride, self.info_engine.tex_offset) + + glDrawArrays(GL_TRIANGLES, 0, self.info_engine.vertex_count) + + self.info_engine.end() + self.world.activate_shader(None) + glDisable(GL_BLEND) + + ortho(width, height) with glRestore(GL_CURRENT_BIT | GL_LINE_BIT): glLineWidth(2) cx, cy = width / 2, height / 2 diff --git a/punyverse/world.json b/punyverse/world.json index 0fac145..5807862 100644 --- a/punyverse/world.json +++ b/punyverse/world.json @@ -312,6 +312,7 @@ "yaw": -97 }, "asteroids": ["asteroids/01.obj", "asteroids/02.obj", "asteroids/03.obj"], + "font": "font.png", "start": { "z": "AU - 400", "yaw": 180 diff --git a/punyverse/world.py b/punyverse/world.py index 0297f86..f48549e 100644 --- a/punyverse/world.py +++ b/punyverse/world.py @@ -24,6 +24,7 @@ class World(object): 'star': ('star.vertex.glsl', 'star.fragment.glsl'), 'ring': ('ring.vertex.glsl', 'ring.fragment.glsl'), 'atmosphere': ('atmosphere.vertex.glsl', 'atmosphere.fragment.glsl'), + 'text': ('text.vertex.glsl', 'text.fragment.glsl'), } def __init__(self, file, callback): @@ -133,6 +134,8 @@ class World(object): self.callback('Loading asteroids...', 'Loading %s...' % file, i / len(asteroids)) self.asteroids.load(file) + self.font_tex = load_alpha_mask(root['font'], clamp=True) + def _body(self, name, info, parent=None): if 'texture' in info: body = SphericalBody(name, self, info, parent)