Skip to content

Instantly share code, notes, and snippets.

@DCubix
Created February 19, 2016 04:47
Show Gist options
  • Save DCubix/b36b2e6fb777291897db to your computer and use it in GitHub Desktop.
Save DCubix/b36b2e6fb777291897db to your computer and use it in GitHub Desktop.
Real-Time GLSL Editor for BGE
try:
from Tkinter import *
from ttk import *
import ScrolledText as scrolledtext
import tkMessageBox as msgbox
import tkFileDialog as filedlg
except ImportError:
from tkinter import *
from tkinter.ttk import *
from tkinter import scrolledtext
import tkinter.messagebox as msgbox
import tkinter.filedialog as filedlg
import re
import socket
import pickle
class HText(scrolledtext.ScrolledText):
'''A text widget with a new method, highlight_pattern()
example:
text = CustomText()
text.tag_configure("red", foreground="#ff0000")
text.highlight_pattern("this should be red", "red")
The highlight_pattern method is a simplified python
version of the tcl code at http://wiki.tcl.tk/3246
'''
def __init__(self, *args, **kwargs):
scrolledtext.ScrolledText.__init__(self, *args, **kwargs)
def highlight_current(self, pattern, tag):
start = 1.0
match = re.findall(pattern, self.get(start, END))
for c in match:
pos = self.search(c, "insert linestart-1c", stopindex="insert lineend+1c")
while pos:
length = len(c)
row, col = pos.split('.')
end = int(col) + length
end = row + '.' + str(end)
self.tag_add(tag, pos, end)
start = end
pos = self.search(c, start, stopindex="insert lineend+1c")
def highlight_pattern(self, pattern, tag):
'''Apply the given tag to all text that matches the given pattern
If 'regexp' is set to True, pattern will be treated as a regular
expression according to Tcl's regular expression syntax.
'''
start = 1.0
match = re.findall(pattern, self.get(start, END))
for c in match:
pos = self.search(c, "1.0", stopindex=END)
while pos:
length = len(c)
row, col = pos.split('.')
end = int(col) + length
end = row + '.' + str(end)
self.tag_add(tag, pos, end)
start = end
pos = self.search(c, start, stopindex=END)
class App(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.modified = False
self.fileName = ""
self.kw1 = [
"float","double","int","void","bool","true","false","mat2","mat3",
"mat4","dmat2","dmat3","dmat4","mat2x2","mat2x3","mat2x4","dmat2x2",
"dmat2x3","dmat2x4","mat3x2","mat3x3","mat3x4","dmat3x2","dmat3x3",
"dmat3x4","mat4x2","mat4x3","mat4x4","dmat4x2","dmat4x3","dmat4x4","vec2",
"vec3","vec4","ivec2","ivec3","ivec4","bvec2","bvec3","bvec4","dvec2",
"dvec3","dvec4","uint","uvec2","uvec3","uvec4","sampler1D","sampler2D",
"sampler3D","samplerCube","sampler1DShadow","sampler2DShadow",
"samplerCubeShadow","sampler1DArray","sampler2DArray",
"sampler1DArrayShadow","sampler2DArrayShadow","isampler1D","isampler2D",
"isampler3D","isamplerCube","isampler1DArray","isampler2DArray",
"usampler1D","usampler2D","usampler3D","usamplerCube","usampler1DArray",
"usampler2DArray","sampler2DRect","sampler2DRectShadow","isampler2DRect",
"usampler2DRect","samplerBuffer","isamplerBuffer","usamplerBuffer",
"sampler2DMS","isampler2DMS","usampler2DMS","sampler2DMSArray",
"isampler2DMSArray","usampler2DMSArray","samplerCubeArray",
"samplerCubeArrayShadow","isamplerCubeArray","usamplerCubeArray",
"image1D","iimage1D","uimage1D","image2D","iimage2D","uimage2D","image3D",
"iimage3D","uimage3D","image2DRect","iimage2DRect","uimage2DRect",
"imageCube","iimageCube","uimageCube","imageBuffer","iimageBuffer",
"uimageBuffer","image1DArray","iimage1DArray","uimage1DArray",
"image2DArray","iimage2DArray","uimage2DArray","imageCubeArray",
"iimageCubeArray","uimageCubeArray","image2DMS","iimage2DMS","uimage2DMS",
"image2DMSArray","iimage2DMSArray","uimage2DMSArray","long","short",
"half","fixed","unsigned","hvec2","hvec3","hvec4","fvec2","fvec3","fvec4"
"sampler3DRect",
"attribute","const","uniform","varying","buffer","shared","coherent","volatile","restrict",
"readonly","writeonly","atomic_uint","layout","centroid","flat","smooth",
"noperspective","patch","sample","break","continue","do","for","while",
"switch","case","default","if","else","subroutine","in","out","inout",
"invariant","discard","return","lowp","mediump","highp","precision",
"struct","common","partition","active","asm","class","union","enum",
"typedef","template","this","packed","resource","goto","inline","noinline",
"public","static","extern","external","interface","superp","input","output",
"filter","sizeof","cast","namespace","using","row_major",
"early_fragment_tests", "varying", "attribute"
]
self.kw3 = [
"abs","acos","acosh","all","any","asin","asinh","atan","atanh",
"atomicCounter","atomicCounterDecrement","atomicCounterIncrement",
"barrier","bitCount","bitfieldExtract","bitfieldInsert","bitfieldReverse",
"ceil","clamp","cos","cosh","cross","degrees","determinant","dFdx","dFdy",
"dFdyFine","dFdxFine","dFdyCoarse","dFdxCourse",
"fwidthFine","fwidthCoarse",
"distance","dot","EmitStreamVertex","EmitVertex","EndPrimitive",
"EndStreamPrimitive","equal","exp","exp2","faceforward","findLSB",
"findMSB","floatBitsToInt","floatBitsToUint","floor","fma","fract",
"frexp","fwidth","greaterThan","greaterThanEqual","imageAtomicAdd",
"imageAtomicAnd","imageAtomicCompSwap","imageAtomicExchange",
"imageAtomicMax","imageAtomicMin","imageAtomicOr","imageAtomicXor",
"imageLoad","imageSize","imageStore","imulExtended","intBitsToFloat",
"imageSamples",
"interpolateAtCentroid","interpolateAtOffset","interpolateAtSample",
"inverse","inversesqrt","isinf","isnan","ldexp","length","lessThan",
"lessThanEqual","log","log2","matrixCompMult","max","memoryBarrier","min",
"mix","mod","modf","noise","normalize","not","notEqual","outerProduct",
"packDouble2x32","packHalf2x16","packSnorm2x16","packSnorm4x8",
"packUnorm2x16","packUnorm4x8","pow","radians","reflect","refract",
"round","roundEven","sign","sin","sinh","smoothstep","sqrt","step","tan",
"tanh","texelFetch","texelFetchOffset","texture","textureGather",
"textureGatherOffset","textureGatherOffsets","textureGrad",
"textureGradOffset","textureLod","textureLodOffset","textureOffset",
"textureProj","textureProjGrad","textureProjGradOffset","textureProjLod",
"textureProjLodOffset","textureProjOffset","textureQueryLevels","textureQueryLod",
"textureSize","transpose","trunc","uaddCarry","uintBitsToFloat",
"umulExtended","unpackDouble2x32","unpackHalf2x16","unpackSnorm2x16",
"unpackSnorm4x8","unpackUnorm2x16","unpackUnorm4x8","usubBorrow",
"texture1D", "texture1DProj", "texture1DLod", "texture1DProjLod",
"texture2D", "texture2DProj", "texture2DLod", "texture2DProjLod",
"texture2DRect", "texture2DRectProj",
"texture3D", "texture3DProj", "texture3DLod", "texture3DProjLod",
"shadow1D", "shadow1DProj", "shadow1DLod", "shadow1DProjLod",
"shadow2D", "shadow2DProj", "shadow2DLod", "shadow2DProjLod",
"textureCube", "textureCubeLod"
]
self.keywords = r"\b(?:%s)\b" % "|".join(self.kw1)
self.functions = r"\b(?:%s)\b" % "|".join(self.kw3)
self.builtin = r"gl_[^\s\)\(]*"
self.preprocessor = r"#.+"
self.comment = r"//.+"
self.keyword_color = "#CC1486"
self.function_color = "#CD2CF5"
self.builtin_color = "#2FD46E"
self.preprocessor_color = "#858585"
self.comment_color = "#858585"
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.setblocking(False)
self.address = ("127.0.0.1", 8080)
initdata = {
"cmd": "GLE_START",
"data": None
}
self.sock.sendto(pickle.dumps(initdata), self.address)
self.__init_ui__()
def __init_ui__(self):
sty = Style()
sty.theme_use("clam")
self.parent.title("glEdit")
# WORKSPACE
note = Notebook(self.parent)
vshader = Frame(note)
fshader = Frame(note)
note.add(vshader, text="Vertex Shader")
note.add(fshader, text="Fragment Shader")
vshaderText = HText(vshader)
vshaderText.configure(bg="#121212", fg="#EBEBEB", insertbackground="#EBEBEB")
vshaderText.tag_configure("keywords", foreground=self.keyword_color)
vshaderText.tag_configure("functions", foreground=self.function_color)
vshaderText.tag_configure("builtin", foreground=self.builtin_color)
vshaderText.tag_configure("preprocessor", foreground=self.preprocessor_color)
vshaderText.tag_configure("comment", foreground=self.comment_color)
vshaderText.pack(fill=BOTH, expand=1)
def vshaderUpdate(e):
vshaderText.highlight_current(self.keywords, "keywords")
vshaderText.highlight_current(self.functions, "functions")
vshaderText.highlight_current(self.builtin, "builtin")
vshaderText.highlight_current(self.preprocessor, "preprocessor")
vshaderText.highlight_current(self.comment, "comment")
self.modified = True
vshaderText.bind("<KeyRelease>", vshaderUpdate)
vshaderText.bind("<Enter>", vshaderUpdate)
fshaderText = HText(fshader)
fshaderText.configure(bg="#121212", fg="#EBEBEB", insertbackground="#EBEBEB")
fshaderText.tag_configure("keywords", foreground=self.keyword_color)
fshaderText.tag_configure("functions", foreground=self.function_color)
fshaderText.tag_configure("builtin", foreground=self.builtin_color)
fshaderText.tag_configure("preprocessor", foreground=self.preprocessor_color)
fshaderText.tag_configure("comment", foreground=self.comment_color)
fshaderText.pack(fill=BOTH, expand=1)
def fshaderUpdate(e):
fshaderText.highlight_current(self.keywords, "keywords")
fshaderText.highlight_current(self.functions, "functions")
fshaderText.highlight_current(self.builtin, "builtin")
fshaderText.highlight_current(self.preprocessor, "preprocessor")
fshaderText.highlight_current(self.comment, "comment")
self.modified = True
fshaderText.bind("<KeyRelease>", fshaderUpdate)
fshaderText.bind("<Enter>", fshaderUpdate)
note.pack(side=TOP, fill=BOTH, expand=1)
# Main menu
mainMenu = Menu(self.parent)
self.parent.config(menu=mainMenu)
def NewProgramCMD(e=None):
if self.modified:
if msgbox.askyesno("New Program", "This shader program has not been saved. Do you want to proceed?"):
self.modified = False
vshaderText.delete("1.0", END)
fshaderText.delete("1.0", END)
else:
vshaderText.delete("1.0", END)
fshaderText.delete("1.0", END)
self.modified = False
def SaveFile():
vert = vshaderText.get("1.0", END)
frag = fshaderText.get("1.0", END)
with open(self.fileName, "w") as f:
f.write("[Vertex Shader]\n")
f.write(vert+"\n")
f.write("[Fragment Shader]\n")
f.write(frag)
pdata = {
"cmd": "GLE_ALL_CHANGE",
"data": [vert.strip("\n"), frag.strip("\n")]
}
self.sock.sendto(pickle.dumps(pdata), self.address)
def SaveFileDLG():
filename = filedlg.asksaveasfilename(filetypes=[("glEdit Shader Program", "*.glp")])
if filename:
self.fileName = filename
SaveFile()
def LoadFile():
filename = filedlg.askopenfilename(filetypes=[("glEdit Shader Program", "*.glp")])
if filename:
self.fileName = filename
lines = []
with open(filename, "r") as f:
lines = f.readlines()
vshaderText.delete("1.0", END)
fshaderText.delete("1.0", END)
vert = ""
frag = ""
mode = ""
for line in lines:
lowline = line.lower().strip()
if lowline == "[vertex shader]" or lowline == "[vs]" or lowline == "[vert]":
mode = "v"
continue
elif lowline == "[fragment shader]" or lowline == "[fs]" or lowline == "[frag]":
mode = "f"
continue
if mode == "v":
vert += line
elif mode == "f":
frag += line
vshaderText.insert(INSERT, vert)
fshaderText.insert(INSERT, frag)
vshaderText.highlight_pattern(self.keywords, "keywords")
vshaderText.highlight_pattern(self.functions, "functions")
vshaderText.highlight_pattern(self.builtin, "builtin")
vshaderText.highlight_pattern(self.preprocessor, "preprocessor")
vshaderText.highlight_pattern(self.comment, "preprocessor")
fshaderText.highlight_pattern(self.keywords, "keywords")
fshaderText.highlight_pattern(self.functions, "functions")
fshaderText.highlight_pattern(self.builtin, "builtin")
fshaderText.highlight_pattern(self.preprocessor, "preprocessor")
fshaderText.highlight_pattern(self.comment, "preprocessor")
pdata = {
"cmd": "GLE_ALL_CHANGE",
"data": [vert.strip("\n"), frag.strip("\n")]
}
self.sock.sendto(pickle.dumps(pdata), self.address)
def OpenProgramCMD(e=None):
if self.modified:
if msgbox.askyesno("Open Program", "This shader program has not been saved. Do you want to proceed?"):
self.modified = False
LoadFile()
else:
LoadFile()
self.modified = False
def SaveProgramCMD(e=None):
if self.modified:
if len(self.fileName) == 0:
SaveFileDLG()
else:
SaveFile()
self.modified = False
def SaveProgramAsCMD(e=None):
SaveFileDLG()
self.modified = False
def ExitCMD(e=None):
if self.modified:
if msgbox.askyesno("Exit", "The current program has not been saved. Are you sure?"):
pdata = {
"cmd": "GLE_QUIT",
"data": None
}
self.sock.sendto(pickle.dumps(pdata), self.address)
self.parent.quit()
self.sock.close()
else:
pdata = {
"cmd": "GLE_QUIT",
"data": None
}
self.sock.sendto(pickle.dumps(pdata), self.address)
seld.parent.quit()
self.sock.close()
self.parent.protocol("WM_DELETE_WINDOW", ExitCMD)
# FILE
fileMenu = Menu(mainMenu)
fileMenu.add_command(label="New Program", command=NewProgramCMD, accelerator="Ctrl+N")
fileMenu.add_separator()
fileMenu.add_command(label="Open Program", command=OpenProgramCMD, accelerator="Ctrl+O")
fileMenu.add_command(label="Save Program", command=SaveProgramCMD, accelerator="Ctrl+S")
fileMenu.add_command(label="Save Program As...", command=SaveProgramAsCMD, accelerator="Ctrl+Shift+S")
fileMenu.add_separator()
fileMenu.add_command(label="Exit", command=ExitCMD, accelerator="Ctrl+Q")
mainMenu.add_cascade(label="File", menu=fileMenu)
self.bind_all("<Control-n>", NewProgramCMD)
self.bind_all("<Control-o>", OpenProgramCMD)
self.bind_all("<Control-s>", SaveProgramCMD)
self.bind_all("<Control-Shift-s>", SaveProgramAsCMD)
self.bind_all("<Control-q>", ExitCMD)
self.pack(fill=BOTH)
def main():
_tk = Tk()
_tk.geometry("640x512+20+20")
app = App(_tk)
_tk.mainloop()
if "__main__" in __name__:
main()
from bge import logic as G, types as T
import socket
import pickle
class ShaderBinder:
def __init__(self, obj):
self.ob = obj
self.shader = None
self.v = "void main() { gl_Position = ftransform(); }"
self.f = "void main() { gl_FragColor = vec4(1.0); }"
def update(self):
try:
mesh = self.ob.meshes[0]
for mat in mesh.materials:
shader = mat.getShader()
if shader != None:
shader.delSource()
shader.setSource(self.v, self.f, 1)
except:
pass
def setVertexShader(self, shad):
self.v = shad
self.update()
def setFragmentShader(self, shad):
self.f = shad
self.update()
def setBoth(self, v, f):
self.v = v
self.f = f
self.update()
class Receiver(T.KX_GameObject):
def __init__(self, o):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.setblocking(False)
self.address = ("127.0.0.1", 8080)
self.sock.bind(self.address)
self.binder = ShaderBinder(self)
#self.binder.update()
def __del__(self):
self.sock.close()
def receive_step(self):
try:
data, addr = self.sock.recvfrom(4096)
rec = None
if data:
rec = pickle.loads(data)
if rec["cmd"] == "GLE_START":
print("Connected to glEdit!")
elif rec["cmd"] == "GLE_QUIT":
print("Disconnected from glEdit.")
self.sock.close()
elif rec["cmd"] == "GLE_VERTEX_CHANGE":
vertex_shader = rec["data"]
if len(vertex_shader.strip()) > 0:
self.binder.setVertexShader(vertex_shader)
elif rec["cmd"] == "GLE_FRAGMENT_CHANGE":
fragment_shader = rec["data"]
if len(fragment_shader.strip()) > 0:
self.binder.setFragmentShader(fragment_shader)
elif rec["cmd"] == "GLE_ALL_CHANGE":
vertex_shader = rec["data"][0]
fragment_shader = rec["data"][1]
self.binder.setBoth(vertex_shader, fragment_shader)
except:
pass
class Exit:
def __init__(self, t):
self.t = t
def __del__(self):
self.t.close()
def main(cont):
o = cont.owner
if "init" not in o:
o = Receiver(o)
o["init"] = True
else:
o.receive_step()
if cont.sensors["EXIT"].positive:
o.sock.close()
G.endGame()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment