1-- Copyright: 2015-2017, Björn Ståhl 2-- License: 3-Clause BSD 3-- Reference: http://durden.arcan-fe.com 4-- Description: Shader compilation and setup. 5 6if (SHADER_LANGUAGE == "GLSL120") then 7local old_build = build_shader; 8function build_shader(vertex, fragment, label) 9 vertex = vertex and ("#define VERTEX\n" .. vertex) or nil; 10 fragment = fragment and ([[ 11 #ifdef GL_ES 12 #ifdef GL_FRAGMENT_PRECISION_HIGH 13 precision highp float; 14 #else 15 precision mediump float; 16 #endif 17 #else 18 #define lowp 19 #define mediump 20 #define highp 21 #endif 22 ]] .. fragment) or nil; 23 24-- 25-- [ dump to get real line numbers ] 26-- local debug = string.split(fragment, "\n"); 27-- for i,v in ipairs(debug) do print(i, v); end 28-- 29-- 30 return old_build(vertex, fragment, label); 31end 32end 33 34local shdrtbl = { 35 effect = {}, 36 ui = {}, 37 display = {}, 38 audio = {}, 39 simple = {} 40}; 41 42local groups = {"effect", "ui", "display", "audio", "simple"}; 43 44function shdrmgmt_scan() 45 for a,b in ipairs(groups) do 46 local path = string.format("shaders/%s/", b); 47 48 for i,j in ipairs(glob_resource(path .. "*.lua", APPL_RESOURCE)) do 49 local res = system_load(path .. j, false); 50 if (res) then 51 res = res(); 52 if (not res or type(res) ~= "table" or res.version ~= 1) then 53 warning("shader " .. j .. " failed validation"); 54 else 55 local key = string.sub(j, 1, string.find(j, '.', 1, true)-1); 56 shdrtbl[b][key] = res; 57 end 58 else 59 warning("error parsing " .. path .. j); 60 end 61 end 62 end 63end 64 65shdrmgmt_scan(); 66 67local function set_uniform(dstid, name, typestr, vals, source) 68 local len = string.len(typestr); 69 if (type(vals) == "table" and len ~= #vals) or 70 (type(vals) ~= "table" and len > 1) then 71 warning("set_uniform called from broken source: " .. source); 72 return false; 73 end 74 if (type(vals) == "table") then 75 shader_uniform(dstid, name, typestr, unpack(vals)); 76 else 77 shader_uniform(dstid, name, typestr, vals); 78 end 79 return true; 80end 81 82local function load_from_file(relp, lim, defs) 83 local res = {}; 84 if (open_rawresource(relp)) then 85 if defs then 86 for k,v in ipairs(defs) do 87 table.insert(res, "#define " .. v); 88 end 89 end 90 91 local line = read_rawresource(); 92 while (line ~= nil and lim -1 ~= 0) do 93 table.insert(res, line); 94 line = read_rawresource(); 95 lim = lim - 1; 96 end 97 close_rawresource(); 98 else 99 warning(string.format("shader, load from file: %s failed, EEXIST", relp)); 100 end 101 102 return table.concat(res, "\n"); 103end 104 105local function setup_shader(shader, name, group) 106 if (shader.shid) then 107 return true; 108 end 109 110-- ugly non-blocking read (note, this does not cover variants) 111 if (not shader.vert and shader.vert_source) then 112 shader.vert = load_from_file(string.format( 113 "shaders/%s/%s", group, shader.vert_source), 1000, shader.vert_defs); 114 end 115 116 if (not shader.frag and shader.frag_source) then 117 shader.frag = load_from_file(string.format( 118 "shaders/%s/%s", group, shader.frag_source), 1000, shader.vert_defs); 119 end 120 121 local dvf = (shader.vert and 122 type(shader.vert == "table") and shader.vert[SHADER_LANGUAGE]) 123 and shader.vert[SHADER_LANGUAGE] or shader.vert; 124 125 local dff = (shader.frag and 126 type(shader.frag == "table") and shader.frag[SHADER_LANGUAGE]) 127 and shader.frag[SHADER_LANGUAGE] or shader.frag; 128 129 shader.shid = build_shader(dvf, dff, group.."_"..name); 130 if (not shader.shid) then 131 shader.broken = true; 132 warning("building shader failed for " .. group.."_"..name); 133 return false; 134 end 135-- this is not very robust, bad written shaders will yield fatal() 136 for k,v in pairs(shader.uniforms) do 137 set_uniform(shader.shid, k, v.utype, v.default, name .. "-" .. k); 138 end 139 return true; 140end 141 142local function preload_effect_shader(shdr, group, name) 143 for _,v in ipairs(shdr.passes) do 144 if (not v.shid) then 145 setup_shader(v, name, group); 146 end 147 if (not shdr.scale) then 148 shdr.scale = {1, 1}; 149 end 150 if (not shdr.filter) then 151 shdr.filter = "bilinear"; 152 end 153 if (not shdr.maps) then 154 shdr.maps = {}; 155 else 156-- asynch- shader lookup maps 157 for i,v in ipairs(shdr.maps) do 158 if (type(v) == "string") then 159 if (v == ":source") then 160 shdr.maps[i] = function(src) 161 local surf = null_surface(1, 1); 162 image_sharestorage(src, surf); 163 return surf; 164 end 165 else 166 shdr.maps[i] = load_image_asynch( 167 string.format("shaders/lut/%s", v), 168-- defer shader application if the LUT can't be loaded? 169 function() end 170 ); 171 end 172 end 173 end 174 end 175 end 176end 177 178-- for display, state is actually the display name 179local function dsetup(shader, dst, group, name, state) 180 if (not setup_shader(shader, dst, name)) then 181 return; 182 end 183 184 if (not shader.states) then 185 shader.states = {}; 186 end 187 188 if (not shader.states[state]) then 189 shader.states[state] = shader_ugroup(shader.shid); 190 end 191 image_shader(dst, shader.states[state]); 192end 193 194local function filter_strnum(fltstr) 195 if (fltstr == "bilinear") then 196 return FILTER_BILINEAR; 197 elseif (fltstr == "linear") then 198 return FILTER_LINEAR; 199 else 200 return FILTER_NONE; 201 end 202end 203 204local function ssetup(shader, dst, group, name, state) 205 if (not shader.shid) then 206 setup_shader(shader, name, group); 207 208-- states inherit shaders, define different uniform values 209 if (shader.states) then 210 for k,v in pairs(shader.states) do 211 shader.states[k].shid = shader_ugroup(shader.shid); 212 213 for i,j in pairs(v.uniforms) do 214 set_uniform(v.shid, i, shader.uniforms[i].utype, j, 215 string.format("%s-%s-%s", name, k, i)); 216 end 217 end 218 end 219 end 220-- now the shader exists, apply 221 local shid = ((state and shader.states and shader.states[state]) and 222 shader.states[state].shid) or shader.shid; 223 224 if (valid_vid(dst)) then 225 image_shader(dst, shid); 226 end 227end 228 229local function esetup(shader, dst, group, name) 230 if (not shader.passes or #shader.passes == 0) then 231 return; 232 end 233 234-- Special shortpath, only one pass - used when we want global settings but 235-- otherwise the behavior of a simple shader 236 if (#shader.passes == 1 and shader.no_rendertarget) then 237 return ssetup(shader.passes[1], dst, group, name); 238 end 239 240-- Track the order in which the rendertargets are created. This is needed as 241-- each rendertarget is setup with manual update controls as a means of synching 242-- with the frame delivery rate of the source. 243 local rtgt_list = {}; 244 245-- the process of taking a pass description, creating an intermediate FBO 246-- applying the pass shader and returning the outcome. Subtle edge conditions 247-- to look out for here. 248 local build_pass = function(invid, pass) 249 local props = image_storage_properties(invid); 250 local fmt = ALLOC_QUALITY_NORMAL; 251 252 if (pass.float) then 253 fmt = ALLOC_QUALITY_FLOAT16; 254 elseif (pass.float32) then 255 fmt = ALLOC_QUALITY_FLOAT32; 256 end 257 if (pass.filter) then 258 image_texfilter(invid, filter_strnum(pass.filter)); 259 end 260 261-- min-clamp as there's a limit for the rendertarget backend store, 262-- note that scaling doesn't work with all modes (e.g. autocrop) or client types 263 local outw = math.clamp(props.width * pass.scale[1], 32); 264 local outh = math.clamp(props.height * pass.scale[2], 32); 265 266 local outvid = alloc_surface(outw, outh, true, fmt); 267 if (not valid_vid(outvid)) then 268 return invid; 269 end 270 271-- for the passes that require lookup textures, asynch- preloaded or through 272-- a function, switch the invid to a multitextured frameset and assign the slots 273-- accordingly. 274 local tmp_vid = null_surface(1, 1); 275 if valid_vid(tmp_vid) then 276 if (#pass.maps > 0) then 277 image_framesetsize(invid, #pass.maps + 1, FRAMESET_MULTITEXTURE); 278 for i,v in ipairs(pass.maps) do 279 if type(v) == "function" then 280 v(dst, tmp_vid); 281 elseif valid_vid(v) then 282 image_sharestorage(v, tmp_vid); 283-- fallback to source store if the maps were setup wrong 284 else 285 image_sharestorage(dst, tmp_vid); 286 end 287 set_image_as_frame(pass.maps, tmp_vid, i); 288 end 289 end 290 delete_image(tmp_vid); 291 end 292 293-- sanity checks and resource loading/preloading 294 define_rendertarget(outvid, {invid}, 295 RENDERTARGET_DETACH, RENDERTARGET_NOSCALE, 0); 296 image_shader(invid, pass.shid); 297 resize_image(invid, outw, outh); 298 move_image(invid, 0, 0); 299 show_image({invid, outvid}); 300 table.insert(rtgt_list, outvid); 301 rendertarget_forceupdate(outvid); 302 return outvid; 303 end 304 305-- this is currently quite wasteful, there is a blit-out copy stage in order 306-- to get an output buffer that can simply be sharestorage()d into the canvas 307-- slot rather than all the complications with swap-in-out. 308 local function build_passes() 309 local props = image_storage_properties(dst); 310 local invid = null_surface(props.width, props.height); 311 image_sharestorage(dst, invid); 312 313 for i=1,#shader.passes do 314 invid = build_pass(invid, shader.passes[i]); 315 end 316 317-- chain finished and stored in invid, final blitout pass so we have a 318-- shareable storage format 319 local outprops = image_storage_properties(invid); 320 local outvid = alloc_surface(outprops.width, outprops.height); 321 define_rendertarget(outvid, {invid}, 322 RENDERTARGET_DETACH, RENDERTARGET_NOSCALE, 0); 323 table.insert(rtgt_list, outvid); 324-- show_image(outvid); 325 if (shader.filter) then 326 image_texfilter(outvid, filter_strnum(shader.filter)); 327 end 328 return outvid; 329 end 330 331 preload_effect_shader(shader, group, name); 332 local outvid = build_passes(); 333 rendertarget_forceupdate(outvid); 334 hide_image(outvid); 335 336-- return a reference to the video object, a refresh function and a 337-- rebuild-or-destroy function. 338 return outvid, function() 339 for i,v in ipairs(rtgt_list) do 340 rendertarget_forceupdate(v); 341 end 342 end, 343 function(vid, destroy) 344 for i,v in ipairs(rtgt_list) do 345 delete_image(v); 346 end 347 rtgt_list = {}; 348-- this is unnecessarily expensive, better approach would be to re-enumerate 349-- the passes and just resize rendertarget and inputs / outputs 350 if (not destroy and valid_vid(vid)) then 351 dst = vid; 352 return build_passes(); 353 end 354 end; 355end 356 357-- note: boolean and 4x4 matrices are currently ignored 358local utype_lut = { 359i = 1, f = 1, ff = 1, fff = 1, ffff = 1 360}; 361 362local function add_stateref(res, uniforms, shid) 363 for k,v in pairs(uniforms) do 364 if (not v.ignore) then 365 table.insert(res, { 366 name = k, 367 label = v.label, 368 kind = "value", 369 hint = (type(v.default) == "table" and 370 table.concat(v.default, " ")) or tostring(v.default), 371 eval = function() 372 return utype_lut[v.utype] ~= nil; 373 end, 374 validator = suppl_valid_typestr(v.utype, v.low, v.high, v.default), 375 handler = function(ctx, val) 376 shader_uniform(shid, k, v.utype, unpack( 377 suppl_unpack_typestr(v.utype, val, v.low, v.high))); 378 end 379 }); 380 end 381 end 382end 383 384-- the different shader types: 385-- 'ui' Has states that need to be forwarded. Right now, there is just 386-- one shared for all UI elements because the delete_shader approach for 387-- ugroups is faulty, so we'd have problems after 64k such changes but we 388-- do want to be able to forward more window specific parameters, like 389-- privilege level and so on. 390-- 391-- 'simple, audio' are treated as ui, though won't have an instanced state. 392-- 393-- displays are inherently single pass. 394-- 395-- 'effect' is more complicated as it needs to support multiple passes 396-- with indirect offscreen rendering and will be chainable in the future. 397-- 398local function smenu(shdr, grp, name) 399 if (not shdr.uniforms) then 400 return; 401 end 402 403 local found = false; 404 for k,v in pairs(shdr.uniforms) do 405 if (not v.ignore) then 406 found = true; 407 break; 408 end 409 end 410 if (not found) then 411 return; 412 end 413 414 local res = { 415 }; 416 417 if (shdr.states) then 418 for k,v in pairs(shdr.states) do 419-- build even if it hasn't been used yet, otherwise this might cause menu 420-- entries not being available at start - and add stateref needs a shid to 421-- reference the uniform to 422 if (not v.shid and not v.broken) then 423 local nsrf = null_surface(1, 1); 424 ssetup(shdr, nsrf, grp, name); 425 delete_image(nsrf); 426 end 427 428 if (v.shid) then 429 table.insert(res, { 430 name = "state_" .. k, 431 label = k, 432 kind = "action", 433 submenu = true, 434 handler = function() 435 local res = {}; 436 add_stateref(res, shdr.uniforms, v.shid); 437 return res; 438 end 439 }); 440 end 441 end 442 else 443 add_stateref(res, shdr.uniforms, shdr.shid); 444 end 445 446 return res; 447end 448 449local function emenu(shdr, grp, name, state) 450 if (not shdr.passes or #shdr.passes == 0) then 451 return {}; 452 end 453 454 local get_pass_menu = function(pass) 455 local res = {}; 456 if (not pass.shid) then 457 setup_shader(pass, name, grp); 458 end 459 add_stateref(res, pass.uniforms, pass.shid); 460 return res; 461 end 462 463 if (#shdr.passes == 1) then 464 return get_pass_menu(shdr.passes[1]); 465 end 466 467 local res = {}; 468 for i,pass in ipairs(shdr.passes) do 469 table.insert(res, { 470 submenu = true, 471 kind = "action", 472 name = "pass_" .. tostring(i), 473-- dynamic call this as it might trigger shader compilation which scales poorly 474 handler = function() return get_pass_menu(pass); end, 475 label = tostring(i) 476 }); 477 end 478 return res; 479end 480 481local function dmenu(shdr, grp, name, state) 482 local res = {}; 483 if (not shdr.uniforms) then 484 return res; 485 end 486 487 if (not shdr.states[state]) then 488 warning("display shader does not have matching display"); 489 return res; 490 end 491 492 local found = false; 493 for k,v in pairs(shdr.uniforms) do 494 if (not v.ignore) then 495 found = true; 496 break; 497 end 498 end 499 500 if (not found) then 501 return res; 502 end 503 504 add_stateref(res, shdr.uniforms, shdr.states[state]); 505 return res; 506end 507 508-- argument one [ setup ], argument two, [ configuration menu ] 509local fmtgroups = { 510 ui = {ssetup, smenu}, 511 effect = {esetup, emenu}, 512 display = {dsetup, dmenu}, 513 audio = {ssetup, smenu}, 514 simple = {ssetup, smenu} 515}; 516 517-- Prepare a shader with the rules applies from [group, name] in the optional 518-- state [state]. If the group is [effect] it will return the output of the 519-- chain using [dst] as initial input - if this is different from [dst] it is 520-- to be treated as a dynamically allocated/resolution sensitive effect chain 521-- with the last stage of the chain applied as effect. 522function shader_setup(dst, group, name, state) 523 if (not fmtgroups[group]) then 524 group = group and group or "no group"; 525 warning("shader_setup called with unknown group " .. group); 526 return dst; 527 end 528 529 if (not shdrtbl[group] or not shdrtbl[group][name]) then 530 warning(string.format( 531 "shader_setup called with unknown group(%s) or name (%s) ", 532 group and group or "nil", 533 name and name or "nil" 534 )); 535 return dst; 536 end 537 538 return fmtgroups[group][1](shdrtbl[group][name], dst, group, name, state); 539end 540 541-- workaround for not being able to expose custom arguments from normal _setup 542function shader_ui_lookup(dst, group, name, state) 543 local a, b, c = fmtgroups[group][1](shdrtbl[group][name], dst, group, name, state); 544 local shid = shdrtbl[group][name].shid; 545 return shid, a, b, c; 546end 547 548function shader_uform_menu(name, group, state) 549 if (not fmtgroups[group]) then 550 warning("shader_setup called with unknown group " .. group); 551 return {}; 552 end 553 554 if (not shdrtbl[group] or not shdrtbl[group][name]) then 555 warning(string.format( 556 "shader_setup called with unknown group(%s) or name (%s) ", 557 group and group or "nil", 558 name and name or "nil" 559 )); 560 return {}; 561 end 562 563 return fmtgroups[group][2](shdrtbl[group][name], group, name, state); 564end 565 566-- update shader [sname] in group [domain] for the uniform [uname], 567-- targetting either the global [states == nil] or each individual 568-- instanced ugroup in [states]. 569function shader_update_uniform(sname, domain, uname, args, states) 570 assert(shdrtbl[domain]); 571 assert(shdrtbl[domain][sname]); 572 local shdr = shdrtbl[domain][sname]; 573 if (not states) then 574 states = {"default"}; 575 end 576 577 for k,v in ipairs(states) do 578 local dstid, dstuni; 579-- special handling, allow default group to be updated alongside substates 580 if (v == "default") then 581 dstid = shdr.shid; 582 dstuni = shdr.uniforms; 583 else 584 if (shdr.states[v]) then 585 dstid = shdr.states[v].shid; 586 dstuni = shdr.states[v].uniforms; 587 end 588 end 589-- update the current "default" if this is set, in order to implement 590-- uniform persistance across restarts 591 if (dstid) then 592 if (set_uniform(dstid, uname, shdr.uniforms[uname].utype, 593 args, "update_uniform-" .. sname .. "-"..uname) and dstuni[uname]) then 594 dstuni[uname].default = args; 595 end 596 end 597 end 598end 599 600function shader_getkey(name, domain) 601 if (not domain) then 602 domain = groups; 603 end 604 605 if (type(domain) ~= "table") then 606 domain = {domain}; 607 end 608 609-- the end- slide of Lua, why u no continue .. 610 for i,j in ipairs(domain) do 611 if (shdrtbl[j]) then 612 for k,v in pairs(shdrtbl[j]) do 613 if (v.label == name or k == name) then 614 return k, j; 615 end 616 end 617 end 618 end 619end 620 621function shader_key(label, domain) 622 for k,v in ipairs(shdrtbl[domain]) do 623 if (v.label == label) then 624 return k; 625 end 626 end 627end 628 629function shader_list(domain) 630 local res = {}; 631 632 if (type(domain) ~= "table") then 633 domain = {domain}; 634 end 635 636 for i,j in ipairs(domain) do 637 if (shdrtbl[j]) then 638 for k,v in pairs(shdrtbl[j]) do 639 table.insert(res, v.label); 640 end 641 end 642 end 643 return res; 644end 645