1 /* 2 Copyright (c) 2018-2022 Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 Permission is hereby granted, free of charge, to any person or organization 6 obtaining a copy of the software and accompanying documentation covered by 7 this license (the "Software") to use, reproduce, display, distribute, 8 execute, and transmit the Software, and to prepare derivative works of the 9 Software, and to permit third-parties to whom the Software is furnished to 10 do so, all subject to the following: 11 12 The copyright notices in the Software and this entire statement, including 13 the above license grant, this restriction and the following disclaimer, 14 must be included in all copies of the Software, in whole or in part, and 15 all derivative works of the Software, unless such copies or derivative 16 works are solely in the form of machine-executable object code generated by 17 a source language processor. 18 19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 DEALINGS IN THE SOFTWARE. 26 */ 27 28 module dagon.graphics.shader; 29 30 import core.stdc.string; 31 import std.stdio; 32 import std.stdio; 33 import std.string; 34 import std.algorithm; 35 import std.file; 36 37 import dlib.core.ownership; 38 import dlib.core.memory; 39 import dlib.container.array; 40 import dlib.container.dict; 41 import dlib.math.vector; 42 import dlib.math.matrix; 43 import dlib.math.utils: min2; 44 import dlib.image.color; 45 import dlib.filesystem.stdfs; 46 import dlib.text.str; 47 48 import dagon.core.application; 49 import dagon.core.bindings; 50 import dagon.graphics.shaderloader; 51 import dagon.graphics.texture; 52 import dagon.graphics.state; 53 54 // TODO: move to separate module 55 class MappedList(T): Owner 56 { 57 Array!T data; 58 Dict!(size_t, string) indices; 59 60 this(Owner o) 61 { 62 super(o); 63 indices = New!(Dict!(size_t, string))(); 64 } 65 66 void set(string name, T val) 67 { 68 data.append(val); 69 indices[name] = data.length - 1; 70 } 71 72 T get(string name) 73 { 74 return data[indices[name]]; 75 } 76 77 ~this() 78 { 79 data.free(); 80 Delete(indices); 81 } 82 } 83 84 /** 85 A shader program class that can be shared between multiple Shaders. 86 */ 87 class ShaderProgram: Owner 88 { 89 immutable GLuint program; 90 91 this(string vertexShaderSrc, string fragmentShaderSrc, Owner o) 92 { 93 super(o); 94 95 GLuint vert = compileShader(vertexShaderSrc, ShaderStage.vertex); 96 GLuint frag = compileShader(fragmentShaderSrc, ShaderStage.fragment); 97 if (vert != 0 && frag != 0) 98 program = linkShaders(vert, frag); 99 } 100 101 void bind() 102 { 103 glUseProgram(program); 104 } 105 106 void unbind() 107 { 108 glUseProgram(0); 109 } 110 } 111 112 /** 113 A shader class that wraps OpenGL shader creation and uniform initialization. 114 */ 115 abstract class BaseShaderParameter: Owner 116 { 117 Shader shader; 118 string name; 119 GLint location; 120 bool autoBind = true; 121 122 this(Shader shader, string name) 123 { 124 super(shader); 125 this.shader = shader; 126 this.name = name; 127 } 128 129 void initUniform(); 130 void bind(); 131 void unbind(); 132 } 133 134 enum ShaderType 135 { 136 Vertex, 137 Fragment 138 } 139 140 class ShaderSubroutine: BaseShaderParameter 141 { 142 ShaderType shaderType; 143 GLint location; 144 GLuint index; 145 string subroutineName; 146 147 this(Shader shader, ShaderType shaderType, string name, string subroutineName) 148 { 149 super(shader, name); 150 this.shaderType = shaderType; 151 this.subroutineName = subroutineName; 152 initUniform(); 153 } 154 155 override void initUniform() 156 { 157 if (shaderType == ShaderType.Vertex) 158 { 159 location = glGetSubroutineUniformLocation(shader.program.program, GL_VERTEX_SHADER, toStringz(name)); 160 index = glGetSubroutineIndex(shader.program.program, GL_VERTEX_SHADER, toStringz(subroutineName)); 161 } 162 else if (shaderType == ShaderType.Fragment) 163 { 164 location = glGetSubroutineUniformLocation(shader.program.program, GL_FRAGMENT_SHADER, toStringz(name)); 165 index = glGetSubroutineIndex(shader.program.program, GL_FRAGMENT_SHADER, toStringz(subroutineName)); 166 } 167 } 168 169 override void bind() 170 { 171 if (shaderType == ShaderType.Vertex) 172 { 173 if (location != -1) 174 shader.vertexSubroutineIndices[location] = index; 175 } 176 else if (shaderType == ShaderType.Fragment) 177 { 178 if (location != -1) 179 shader.fragmentSubroutineIndices[location] = index; 180 } 181 } 182 183 override void unbind() 184 { 185 } 186 } 187 188 class ShaderParameter(T): BaseShaderParameter 189 if (is(T == bool) || 190 is(T == int) || 191 is(T == float) || 192 is(T == Vector2f) || 193 is(T == Vector3f) || 194 is(T == Vector4f) || 195 is(T == Color4f) || 196 is(T == Matrix4x4f)) 197 { 198 T* source; 199 T value; 200 T delegate() callback; 201 202 this(Shader shader, string name, T* source) 203 { 204 super(shader, name); 205 this.source = source; 206 this.callback = null; 207 initUniform(); 208 } 209 210 this(Shader shader, string name, T value) 211 { 212 super(shader, name); 213 this.source = null; 214 this.value = value; 215 this.callback = null; 216 initUniform(); 217 } 218 219 this(Shader shader, string name, T delegate() callback) 220 { 221 super(shader, name); 222 this.source = null; 223 this.value = value; 224 this.callback = callback; 225 initUniform(); 226 } 227 228 override void initUniform() 229 { 230 location = glGetUniformLocation(shader.program.program, toStringz(name)); 231 } 232 233 override void bind() 234 { 235 if (callback) 236 value = callback(); 237 else if (source) 238 value = *source; 239 240 static if (is(T == bool) || is(T == int)) 241 { 242 glUniform1i(location, value); 243 } 244 else static if (is(T == float)) 245 { 246 glUniform1f(location, value); 247 } 248 else static if (is(T == Vector2f)) 249 { 250 glUniform2fv(location, 1, value.arrayof.ptr); 251 } 252 else static if (is(T == Vector3f)) 253 { 254 glUniform3fv(location, 1, value.arrayof.ptr); 255 } 256 else static if (is(T == Vector4f)) 257 { 258 glUniform4fv(location, 1, value.arrayof.ptr); 259 } 260 else static if (is(T == Color4f)) 261 { 262 glUniform4fv(location, 1, value.arrayof.ptr); 263 } 264 else static if (is(T == Matrix4x4f)) 265 { 266 glUniformMatrix4fv(location, 1, GL_FALSE, value.arrayof.ptr); 267 } 268 } 269 270 override void unbind() 271 { 272 //TODO 273 } 274 } 275 276 class Shader: Owner 277 { 278 ShaderProgram program; 279 MappedList!BaseShaderParameter parameters; 280 GLuint[] vertexSubroutineIndices; 281 GLuint[] fragmentSubroutineIndices; 282 283 this(ShaderProgram program, Owner o) 284 { 285 super(o); 286 this.program = program; 287 this.parameters = New!(MappedList!BaseShaderParameter)(this); 288 } 289 290 static String load(string filename) 291 { 292 if (!exists(filename)) 293 { 294 exitWithError("Error: shader \"" ~ filename ~ "\" not found!"); 295 } 296 297 auto fs = New!StdFileSystem(); 298 auto istrm = fs.openForInput(filename); 299 string inputText = readText(istrm); 300 Delete(istrm); 301 302 string includePath = "data/__internal/shaders/include/"; 303 String outputText; 304 foreach(line; lineSplitter(inputText)) 305 { 306 auto s = line.strip; 307 if (s.startsWith("#include")) 308 { 309 char[64] buf; 310 if (sscanf(s.ptr, "#include <%s>", buf.ptr) == 1) 311 { 312 string includeFilename = cast(string)buf[0..strlen(buf.ptr)-1]; 313 String includeFullPath = includePath; 314 includeFullPath ~= includeFilename; 315 if (exists(includeFullPath)) 316 { 317 istrm = fs.openForInput(includeFullPath.toString); 318 string includeText = readText(istrm); 319 Delete(istrm); 320 outputText ~= includeText; 321 outputText ~= "\n"; 322 Delete(includeText); 323 } 324 includeFullPath.free(); 325 } 326 else 327 { 328 writeln("Error"); 329 break; 330 } 331 } 332 else 333 { 334 outputText ~= line; 335 outputText ~= "\n"; 336 } 337 } 338 339 Delete(fs); 340 Delete(inputText); 341 342 return outputText; 343 } 344 345 ShaderSubroutine setParameterSubroutine(string name, ShaderType shaderType, string subroutineName) 346 { 347 if (name in parameters.indices) 348 { 349 auto sp = cast(ShaderSubroutine)parameters.get(name); 350 if (sp is null) 351 { 352 writefln("Warning: type mismatch for shader parameter \"%s\"", name); 353 return null; 354 } 355 sp.shaderType = shaderType; 356 sp.subroutineName = subroutineName; 357 sp.initUniform(); 358 return sp; 359 } 360 else 361 { 362 auto sp = New!ShaderSubroutine(this, shaderType, name, subroutineName); 363 parameters.set(name, sp); 364 return sp; 365 } 366 } 367 368 ShaderParameter!T setParameter(T)(string name, T val) 369 { 370 if (name in parameters.indices) 371 { 372 auto sp = cast(ShaderParameter!T)parameters.get(name); 373 if (sp is null) 374 { 375 writefln("Warning: type mismatch for shader parameter \"%s\"", name); 376 return null; 377 } 378 379 sp.value = val; 380 sp.source = null; 381 return sp; 382 } 383 else 384 { 385 auto sp = New!(ShaderParameter!T)(this, name, val); 386 parameters.set(name, sp); 387 return sp; 388 } 389 } 390 391 ShaderParameter!T setParameterRef(T)(string name, ref T val) 392 { 393 if (name in parameters.indices) 394 { 395 auto sp = cast(ShaderParameter!T)parameters.get(name); 396 if (sp is null) 397 { 398 writefln("Warning: type mismatch for shader parameter \"%s\"", name); 399 return null; 400 } 401 402 sp.source = &val; 403 return sp; 404 } 405 else 406 { 407 auto sp = New!(ShaderParameter!T)(this, name, &val); 408 parameters.set(name, sp); 409 return sp; 410 } 411 } 412 413 ShaderParameter!T setParameterCallback(T)(string name, T delegate() val) 414 { 415 if (name in parameters.indices) 416 { 417 auto sp = cast(ShaderParameter!T)parameters.get(name); 418 if (sp is null) 419 { 420 writefln("Warning: type mismatch for shader parameter \"%s\"", name); 421 return null; 422 } 423 424 sp.callback = val; 425 return sp; 426 } 427 else 428 { 429 auto sp = New!(ShaderParameter!T)(this, name, val); 430 parameters.set(name, sp); 431 return sp; 432 } 433 } 434 435 BaseShaderParameter getParameter(string name) 436 { 437 if (name in parameters.indices) 438 { 439 return parameters.get(name); 440 } 441 else 442 { 443 writefln("Warning: unknown shader parameter \"%s\"", name); 444 return null; 445 } 446 } 447 448 T getParameterValue(T)(string name) 449 { 450 if (name in parameters.indices) 451 { 452 auto sp = cast(ShaderParameter!T)parameters.get(name); 453 if (sp is null) 454 { 455 writefln("Warning: type mismatch for shader parameter \"%s\"", name); 456 return T.init; 457 } 458 459 if (sp.source) 460 return *sp.source; 461 else 462 return sp.value; 463 } 464 else 465 { 466 writefln("Warning: unknown shader parameter \"%s\"", name); 467 return T.init; 468 } 469 } 470 471 void bind() 472 { 473 program.bind(); 474 } 475 476 void unbind() 477 { 478 program.unbind(); 479 } 480 481 void bindParameters(GraphicsState* state) 482 { 483 GLsizei n; 484 glGetProgramStageiv(program.program, GL_VERTEX_SHADER, GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS, &n); 485 if (n > 0 && n != vertexSubroutineIndices.length) 486 vertexSubroutineIndices = New!(GLuint[])(n); 487 488 glGetProgramStageiv(program.program, GL_FRAGMENT_SHADER, GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS, &n); 489 if (n > 0 && n != fragmentSubroutineIndices.length) 490 fragmentSubroutineIndices = New!(GLuint[])(n); 491 492 foreach(v; parameters.data) 493 { 494 if (v.autoBind) 495 v.bind(); 496 } 497 498 if (vertexSubroutineIndices.length) 499 glUniformSubroutinesuiv(GL_VERTEX_SHADER, cast(uint)vertexSubroutineIndices.length, vertexSubroutineIndices.ptr); 500 501 if (fragmentSubroutineIndices.length) 502 glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, cast(uint)fragmentSubroutineIndices.length, fragmentSubroutineIndices.ptr); 503 504 debug validate(); 505 } 506 507 void unbindParameters(GraphicsState* state) 508 { 509 foreach(v; parameters.data) 510 { 511 if (v.autoBind) 512 v.unbind(); 513 } 514 } 515 516 void validate() 517 { 518 glValidateProgram(program.program); 519 520 GLint status; 521 glGetProgramiv(program.program, GL_VALIDATE_STATUS, &status); 522 523 if (status != GL_TRUE) 524 { 525 GLint infolen; 526 glGetProgramiv(program.program, GL_INFO_LOG_LENGTH, &infolen); 527 if (infolen > 0) 528 { 529 char[logMaxLen + 1] infobuffer = 0; 530 glGetProgramInfoLog(program.program, logMaxLen, null, infobuffer.ptr); 531 infolen = min2(infolen - 1, logMaxLen); 532 char[] s = stripRight(infobuffer[0..infolen]); 533 writeln(s); 534 } 535 } 536 assert(status == GL_TRUE, "Shader program validation failed"); 537 } 538 539 ~this() 540 { 541 if (vertexSubroutineIndices.length) 542 Delete(vertexSubroutineIndices); 543 if (fragmentSubroutineIndices.length) 544 Delete(fragmentSubroutineIndices); 545 } 546 }