-- authors: Luxinia Dev (Eike Decker & Christoph Kubisch) --------------------------------------------------------- local binpath = ide.config.path.glslcbin or os.getenv("GLSLC_BIN_PATH") return binpath and { fninit = function(frame,menuBar) if (wx.wxFileName(binpath):IsRelative()) then local editorDir = string.gsub(ide.editorFilename:gsub("[^/\\]+$",""),"\\","/") binpath = editorDir..binpath end local myMenu = wx.wxMenu{ { ID "glslc.compile.input", "&Custom Args", "when set a popup for custom compiler args will be envoked", wx.wxITEM_CHECK }, { ID "glslc.compile.separable", "Separable", "when set separable programs are used", wx.wxITEM_CHECK }, { }, { ID "glslc.compile.ext", "Compile from .ext\tCtrl-1", "Compile based on file extension" }, { ID "glslc.compile.all", "Link multiple .ext\tCtrl-2", "Tries to link multiple shaders based on filename" }, { ID "glslc.compile.vertex", "Compile &Vertex", "Compile Vertex program" }, { ID "glslc.compile.fragment", "Compile &Fragment", "Compile Fragment program" }, { ID "glslc.compile.geometry", "Compile &Geometry", "Compile Geometry program" }, { ID "glslc.compile.tessctrl", "Compile T.Ctrl", "Compile T.Ctrl program" }, { ID "glslc.compile.tesseval", "Compile T.Eval", "Compile T.Eval program" }, { ID "glslc.compile.compute", "Compile Compute", "Compile Compute program" }, { }, { ID "glslc.format.asm", "Annotate ASM", "indent and add comments to ASM output" }, } menuBar:Append(myMenu, "&GLSL") local data = {} data.customarg = false data.separable = false data.custom = "" data.domains = { [ID "glslc.compile.vertex"] = 1, [ID "glslc.compile.fragment"] = 2, [ID "glslc.compile.geometry"] = 3, [ID "glslc.compile.tessctrl"] = 4, [ID "glslc.compile.tesseval"] = 5, [ID "glslc.compile.compute"] = 6, } data.domainprofiles = { "vertex", "fragment", "geometry", "tesscontrol", "tessevaluation", "compute", } data.domaindefs = { " -D_VERTEX_ ", " -D_FRAGMENT_ ", " -D_GEOMETRY_ ", " -D_TESS_CONTROL_ ", " -D_TESS_EVAL_ ", " -D_COMPUTE_ ", } local function beautifyAsmEach(tx) local newtx = "" local indent = 0 local maxindent = 0 local isbranch = { ["IF"]=true,["REP"]=true,["ELSE"]=true,["LOOP"]=true, } local startindent = { ["IF"]=true,["REP"]=true,["ELSE"]=true,["LOOP"]=true,["BB"]=true, } local endindent = { ["ENDIF"]=true,["ENDREP"]=true,["ELSE"]=true,["ENDLOOP"]=true,["END"]=true,["RET"]=true, } local function check(str,tab) local res local chk = str:match("%s*(BB)%d+.*:") chk = chk or str:match("%s*(%w+)") res = chk and tab[chk] and chk return res end local argregistry = {} local argbuffersfixed = false local registercc = 0 local registermem = 0 local registers = 0 local instructions = 0 local branches = 0 local function fixargbuffers() if (argbuffersfixed) then return end local argnew = {} for i,v in pairs(argregistry) do local buf,bufstart = string.match(i,"buf(%d+)%[(%d+)%]") if (buf and bufstart) then bufstart = tonumber(bufstart)/16 argnew["buf"..buf.."["..tostring(bufstart).."]"] = v else argnew[i] = v end end argregistry = argnew argbuffersfixed = true end local function checkregistry(w) local regsuccess = true local vtype,vname,sem,resource,pnum,pref = string.match(w,"#var ([_%w]+) ([%[%]%._%w]+) : ([^%:]*) : ([^%:]*) : ([^%:]*) : (%d*)") local funcname,subroutine = string.match(w,"#function %d+ ([_%w]+)%((%d+)%)") if (pref == "1") then local descriptor = vtype.." "..vname -- check if resource is array local resstart,rescnt = string.match(resource,"c%[(%d+)%], (%d+)") resstart = tonumber(resstart) rescnt = tonumber(rescnt) -- check if resource is buffer/buffer array local buf,bufstart,bufcnt = string.match(resource,"buffer%[(%d+)%]%[(%d+)%],? ?(%d*)") buf = tonumber(buf) bufstart = tonumber(bufstart) bufcnt = tonumber(bufcnt) -- check if texture local texnum = string.match(resource,"texunit (%d+)") local argnames = {} if (rescnt) then for i=0,(rescnt-1) do table.insert(argnames,"c["..tostring(resstart + i).."]") end elseif (texnum) then table.insert(argnames,"texture["..tostring(texnum).."]") table.insert(argnames,"texture"..tostring(texnum)) elseif (buf) then table.insert(argnames,"buf"..tostring(buf).."["..tostring(bufstart).."]") else table.insert(argnames,resource) end for i,v in ipairs(argnames) do argregistry[v] = descriptor end elseif (funcname and subroutine) then argregistry["SUBROUTINENUM("..subroutine..")"] = "function "..funcname elseif string.find(w,"BUFFER4 ") then fixargbuffers() elseif string.find(w,"TEMP ") then --TEMP R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11; --TEMP RC, HC; --TEMP lmem[9]; for i in string.gmatch(w,"C") do registercc = registercc + 1 end for i in string.gmatch(w,"R%d+") do registers = registers + 1 end registermem = tonumber(string.match(w,"lmem%[(%d+)%]")) elseif (string.find(w,"CBUFFER ") or string.find(w,"ATTRIB ") or string.find(w,"OPTION ") or string.find(w,"OUTPUT ") or string.find(w,"PARAM ") or string.find(w,"!!NV") or string.find(w,"STORAGE ")) then else regsuccess = false end return regsuccess end local function checkargs(str) local comment = "#" local declared = {} for i in string.gmatch(str,"([%[%]%(%)%w]+)") do local descr = argregistry[i] if (descr and not declared[i]) then comment = comment.." "..i.." = "..descr declared[i] = true end end return comment ~= "#" and comment end local registerlevels = {{}} local function checkregisters(str,indent) if (indent < 0) then return end local cur = registerlevels[indent+1] for i in string.gmatch(str,"R(%d+)") do cur[i] = true end end local function clearregisters(indent) registerlevels[math.max(0,indent)+1] = {} end local function outputregisters(indent) if (indent < 0) then return "" end local tab = registerlevels[indent+1] local out = {} for i,v in pairs(tab) do table.insert(out,i) end table.sort(out) local cnt = #out if (cnt < 1) then return "" end local str = string.rep(" ",indent).."# "..tostring(cnt).." R used: " for i,v in ipairs(out) do str = str..tostring(v)..((i==cnt) and "" or ", ") end return str.."\n" end -- check declarations local lastline = "" local addinstr = false for w in string.gmatch(tx, "[^\n]*\n") do if (not checkregistry(w)) then if (not w:match("%s*#")) then instructions = instructions + 1 end if (check(w,isbranch)) then branches = branches + 1 end if (check(w,endindent)) then newtx = newtx..outputregisters(indent) if (indent == 0) then clearregisters(indent) end indent = math.max(0,indent - 1) end local firstchar = string.sub(w,1,1) local indentstr = (firstchar ~= " " and firstchar ~= "\t" and string.rep(" ",indent) or "") local linestr = indentstr..w local argcomment = (firstchar ~= "#") and checkargs(w) checkregisters(w,indent) newtx = newtx..(argcomment and (indentstr..argcomment.."\n") or "") newtx = newtx..linestr if (check(w,startindent)) then indent = indent + 1 maxindent = math.max(maxindent,indent) clearregisters(indent) end else newtx = newtx..w end lastline = w end local registers = tonumber(string.match(lastline, "(%d+) R%-regs")) or registers registermem = registermem or 0 registercc = registercc or 0 local stats = "# "..instructions.." ~ instructions\n" stats = stats.."# "..branches.." ~ branches\n" stats = stats.."# "..registers.." R-regs\n" stats = stats.."# "..tostring(registercc).." C-regs, "..tostring(registermem).." L-regs\n" stats = stats.."# "..tostring(registercc + registermem + registers).." maximum registers\n" stats = stats.."# "..maxindent.." maximum nesting level\n" newtx = newtx..stats.."\n" return newtx,stats end local function beautifyAsm(tx) local newtx = "" local stats for t in tx:gmatch("!!.-END[^%w]%s*") do local nt nt,stats = beautifyAsmEach(t) newtx = newtx..nt end return newtx,stats end local function beautifyAsmFile(filePath) local file_text = "" local statlines = "" local handle = io.open(filePath, "rb") if handle then file_text = handle:read("*a") file_text,statlines = beautifyAsm(file_text) handle:close() end if (file_text == "") then return end local handle = io.open(filePath, "wb") if handle then handle:write(file_text) handle:close() end return statlines end -- Compile Arg frame:Connect(ID "glslc.compile.input",wx.wxEVT_COMMAND_MENU_SELECTED, function(event) data.customarg = event:IsChecked() end) frame:Connect(ID "glslc.compile.separable",wx.wxEVT_COMMAND_MENU_SELECTED, function(event) data.separable = event:IsChecked() end) -- Compile local function evCompile(event) local filename,info = GetEditorFileAndCurInfo() local editor = GetEditor() local glsl = true if (not (filename and binpath)) then DisplayOutput("Error: GLSL Compile: Insufficient parameters (nofile)\n") return end local function getDomain(filename) local fname = filename:GetFullName() if (fname:match("%.v")) then domain = 1 elseif (fname:match("%.f")) then domain = 2 elseif (fname:match("%.ge")) then domain = 3 elseif (fname:match("%.t.*c")) then domain = 4 elseif (fname:match("%.t.*e")) then domain = 5 elseif (fname:match("%.c")) then domain = 6 end if (not domain) then DisplayOutput("Error: GLSL Compile: could not derive domain\n") end return domain end local function getCompileArg(filename,domain) return "-profile "..data.domainprofiles[domain]..' "'..filename:GetFullPath()..'" ' end local outname local outsuffix local compileargs local getinstructions if (event:GetId() == ID "glslc.compile.all") then -- look for multiple files to link local basename = filename:GetFullName():match(".-%.") outname = filename:GetPathWithSep()..basename local cnt,files = wx.wxDir.GetAllFiles(filename:GetPathWithSep(), basename.."*" ) compileargs = "" for i,v in ipairs(files) do local filename = wx.wxFileName(v) if (filename:GetExt() ~= "glp" and filename:GetExt() ~= "bak") then local domain = getDomain(filename) if (not domain) then return end compileargs = compileargs..getCompileArg(filename,domain) end end else -- compile single file getinstructions = true local domain = data.domains[event:GetId()] if (not domain) then domain = getDomain(filename) end if (not domain) then return end local profile = data.domainprofiles[domain] local fullname = filename:GetFullPath() outname = fullname.."." outsuffix = profile compileargs = data.domaindefs[domain].." "..getCompileArg(filename,domain) end -- popup for custom input data.custom = data.customarg and wx.wxGetTextFromUser("Compiler Args","GLSLC",data.custom) or data.custom local args = data.customarg and data.custom or "" args = args:len() > 0 and args or nil outname = outname..(args and "^"..args:gsub("%s*[%-%/]",";-")..";^" or "") outname = outname..(outsuffix or "") outname = outname..((outsuffix or args) and "." or "").."glp" outname = '"'..outname..'"' local cmdline = binpath.."/glslc.exe " cmdline = cmdline..(args and args.." " or "") cmdline = cmdline..(data.separable and "-separable " or "") cmdline = cmdline.."-o "..outname.." " cmdline = cmdline..compileargs local function compilecallback(str) local postfunc -- check for errors, if none, launch nvperf -- and indentation if (string.find(str,"successfully linked")) then postfunc = function() -- beautify asm if (true) then local statlines = beautifyAsmFile(outname:sub(2,-2)) if (getinstructions) then DisplayOutput(statlines) end end end end return str,postfunc end local wdir = filename:GetPath(wx.wxPATH_GET_VOLUME) -- run compiler process CommandLineRun(cmdline,wdir,true,nil,compilecallback) end frame:Connect(ID "glslc.compile.vertex",wx.wxEVT_COMMAND_MENU_SELECTED,evCompile) frame:Connect(ID "glslc.compile.fragment",wx.wxEVT_COMMAND_MENU_SELECTED,evCompile) frame:Connect(ID "glslc.compile.geometry",wx.wxEVT_COMMAND_MENU_SELECTED,evCompile) frame:Connect(ID "glslc.compile.tessctrl",wx.wxEVT_COMMAND_MENU_SELECTED,evCompile) frame:Connect(ID "glslc.compile.tesseval",wx.wxEVT_COMMAND_MENU_SELECTED,evCompile) frame:Connect(ID "glslc.compile.compute",wx.wxEVT_COMMAND_MENU_SELECTED,evCompile) frame:Connect(ID "glslc.compile.ext",wx.wxEVT_COMMAND_MENU_SELECTED,evCompile) frame:Connect(ID "glslc.compile.all",wx.wxEVT_COMMAND_MENU_SELECTED,evCompile) -- indent asm frame:Connect(ID "glslc.format.asm", wx.wxEVT_COMMAND_MENU_SELECTED, function(event) local curedit = GetEditor() local newtx = beautifyAsm( curedit:GetText() ) curedit:SetText(newtx) end) end, }