1 /* 2 Copyright (c) 2018-2020 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.image.color; 44 import dlib.filesystem.stdfs; 45 import dlib.text.str; 46 47 import dagon.core.bindings; 48 import dagon.graphics.shaderloader; 49 import dagon.graphics.texture; 50 import dagon.graphics.state; 51 52 // TODO: move to separate module 53 class MappedList(T): Owner 54 { 55 Array!T data; 56 Dict!(size_t, string) indices; 57 58 this(Owner o) 59 { 60 super(o); 61 indices = New!(Dict!(size_t, string))(); 62 } 63 64 void set(string name, T val) 65 { 66 data.append(val); 67 indices[name] = data.length - 1; 68 } 69 70 T get(string name) 71 { 72 return data[indices[name]]; 73 } 74 75 ~this() 76 { 77 data.free(); 78 Delete(indices); 79 } 80 } 81 82 /** 83 A shader program class that can be shared between multiple Shaders. 84 */ 85 class ShaderProgram: Owner 86 { 87 immutable GLuint program; 88 89 this(string vertexShaderSrc, string fragmentShaderSrc, Owner o) 90 { 91 super(o); 92 93 GLuint vert = compileShader(vertexShaderSrc, ShaderStage.vertex); 94 GLuint frag = compileShader(fragmentShaderSrc, ShaderStage.fragment); 95 if (vert != 0 && frag != 0) 96 program = linkShaders(vert, frag); 97 } 98 99 void bind() 100 { 101 glUseProgram(program); 102 } 103 104 void unbind() 105 { 106 glUseProgram(0); 107 } 108 } 109 110 /** 111 A shader class that wraps OpenGL shader creation and uniform initialization. 112 */ 113 abstract class BaseShaderParameter: Owner 114 { 115 Shader shader; 116 string name; 117 GLint location; 118 bool autoBind = true; 119 120 this(Shader shader, string name) 121 { 122 super(shader); 123 this.shader = shader; 124 this.name = name; 125 } 126 127 void initUniform(); 128 void bind(); 129 void unbind(); 130 } 131 132 enum ShaderType 133 { 134 Vertex, 135 Fragment 136 } 137 138 class ShaderSubroutine: BaseShaderParameter 139 { 140 ShaderType shaderType; 141 GLint location; 142 GLuint index; 143 string subroutineName; 144 145 this(Shader shader, ShaderType shaderType, string name, string subroutineName) 146 { 147 super(shader, name); 148 this.shaderType = shaderType; 149 this.subroutineName = subroutineName; 150 initUniform(); 151 } 152 153 override void initUniform() 154 { 155 if (shaderType == ShaderType.Vertex) 156 { 157 location = glGetSubroutineUniformLocation(shader.program.program, GL_VERTEX_SHADER, toStringz(name)); 158 index = glGetSubroutineIndex(shader.program.program, GL_VERTEX_SHADER, toStringz(subroutineName)); 159 } 160 else if (shaderType == ShaderType.Fragment) 161 { 162 location = glGetSubroutineUniformLocation(shader.program.program, GL_FRAGMENT_SHADER, toStringz(name)); 163 index = glGetSubroutineIndex(shader.program.program, GL_FRAGMENT_SHADER, toStringz(subroutineName)); 164 } 165 } 166 167 override void bind() 168 { 169 if (shaderType == ShaderType.Vertex) 170 { 171 if (location != -1) 172 shader.vertexSubroutineIndices[location] = index; 173 } 174 else if (shaderType == ShaderType.Fragment) 175 { 176 if (location != -1) 177 shader.fragmentSubroutineIndices[location] = index; 178 } 179 } 180 181 override void unbind() 182 { 183 } 184 } 185 186 class ShaderParameter(T): BaseShaderParameter 187 if (is(T == bool) || 188 is(T == int) || 189 is(T == float) || 190 is(T == Vector2f) || 191 is(T == Vector3f) || 192 is(T == Vector4f) || 193 is(T == Color4f) || 194 is(T == Matrix4x4f)) 195 { 196 T* source; 197 T value; 198 T delegate() callback; 199 200 this(Shader shader, string name, T* source) 201 { 202 super(shader, name); 203 this.source = source; 204 this.callback = null; 205 initUniform(); 206 } 207 208 this(Shader shader, string name, T value) 209 { 210 super(shader, name); 211 this.source = null; 212 this.value = value; 213 this.callback = null; 214 initUniform(); 215 } 216 217 this(Shader shader, string name, T delegate() callback) 218 { 219 super(shader, name); 220 this.source = null; 221 this.value = value; 222 this.callback = callback; 223 initUniform(); 224 } 225 226 override void initUniform() 227 { 228 location = glGetUniformLocation(shader.program.program, toStringz(name)); 229 } 230 231 override void bind() 232 { 233 if (callback) 234 value = callback(); 235 else if (source) 236 value = *source; 237 238 static if (is(T == bool) || is(T == int)) 239 { 240 glUniform1i(location, value); 241 } 242 else static if (is(T == float)) 243 { 244 glUniform1f(location, value); 245 } 246 else static if (is(T == Vector2f)) 247 { 248 glUniform2fv(location, 1, value.arrayof.ptr); 249 } 250 else static if (is(T == Vector3f)) 251 { 252 glUniform3fv(location, 1, value.arrayof.ptr); 253 } 254 else static if (is(T == Vector4f)) 255 { 256 glUniform4fv(location, 1, value.arrayof.ptr); 257 } 258 else static if (is(T == Color4f)) 259 { 260 glUniform4fv(location, 1, value.arrayof.ptr); 261 } 262 else static if (is(T == Matrix4x4f)) 263 { 264 glUniformMatrix4fv(location, 1, GL_FALSE, value.arrayof.ptr); 265 } 266 } 267 268 override void unbind() 269 { 270 //TODO 271 } 272 } 273 274 class Shader: Owner 275 { 276 ShaderProgram program; 277 MappedList!BaseShaderParameter parameters; 278 GLuint[] vertexSubroutineIndices; 279 GLuint[] fragmentSubroutineIndices; 280 281 this(ShaderProgram program, Owner o) 282 { 283 super(o); 284 this.program = program; 285 this.parameters = New!(MappedList!BaseShaderParameter)(this); 286 } 287 288 static String load(string filename) 289 { 290 auto fs = New!StdFileSystem(); 291 auto istrm = fs.openForInput(filename); 292 string inputText = readText(istrm); 293 Delete(istrm); 294 295 string includePath = "data/__internal/shaders/include/"; 296 String outputText; 297 foreach(line; lineSplitter(inputText)) 298 { 299 auto s = line.strip; 300 if (s.startsWith("#include")) 301 { 302 char[64] buf; 303 if (sscanf(s.ptr, "#include <%s>", buf.ptr) == 1) 304 { 305 string includeFilename = cast(string)buf[0..strlen(buf.ptr)-1]; 306 String includeFullPath = includePath; 307 includeFullPath ~= includeFilename; 308 if (exists(includeFullPath)) 309 { 310 istrm = fs.openForInput(includeFullPath.toString); 311 string includeText = readText(istrm); 312 Delete(istrm); 313 outputText ~= includeText; 314 outputText ~= "\n"; 315 Delete(includeText); 316 } 317 includeFullPath.free(); 318 } 319 else 320 { 321 writeln("Error"); 322 break; 323 } 324 } 325 else 326 { 327 outputText ~= line; 328 outputText ~= "\n"; 329 } 330 } 331 332 Delete(fs); 333 Delete(inputText); 334 335 return outputText; 336 } 337 338 ShaderSubroutine setParameterSubroutine(string name, ShaderType shaderType, string subroutineName) 339 { 340 if (name in parameters.indices) 341 { 342 auto sp = cast(ShaderSubroutine)parameters.get(name); 343 if (sp is null) 344 { 345 writefln("Warning: type mismatch for shader parameter \"%s\"", name); 346 return null; 347 } 348 sp.shaderType = shaderType; 349 sp.subroutineName = subroutineName; 350 sp.initUniform(); 351 return sp; 352 } 353 else 354 { 355 auto sp = New!ShaderSubroutine(this, shaderType, name, subroutineName); 356 parameters.set(name, sp); 357 return sp; 358 } 359 } 360 361 ShaderParameter!T setParameter(T)(string name, T val) 362 { 363 if (name in parameters.indices) 364 { 365 auto sp = cast(ShaderParameter!T)parameters.get(name); 366 if (sp is null) 367 { 368 writefln("Warning: type mismatch for shader parameter \"%s\"", name); 369 return null; 370 } 371 372 sp.value = val; 373 sp.source = null; 374 return sp; 375 } 376 else 377 { 378 auto sp = New!(ShaderParameter!T)(this, name, val); 379 parameters.set(name, sp); 380 return sp; 381 } 382 } 383 384 ShaderParameter!T setParameterRef(T)(string name, ref T val) 385 { 386 if (name in parameters.indices) 387 { 388 auto sp = cast(ShaderParameter!T)parameters.get(name); 389 if (sp is null) 390 { 391 writefln("Warning: type mismatch for shader parameter \"%s\"", name); 392 return null; 393 } 394 395 sp.source = &val; 396 return sp; 397 } 398 else 399 { 400 auto sp = New!(ShaderParameter!T)(this, name, &val); 401 parameters.set(name, sp); 402 return sp; 403 } 404 } 405 406 ShaderParameter!T setParameterCallback(T)(string name, T delegate() val) 407 { 408 if (name in parameters.indices) 409 { 410 auto sp = cast(ShaderParameter!T)parameters.get(name); 411 if (sp is null) 412 { 413 writefln("Warning: type mismatch for shader parameter \"%s\"", name); 414 return null; 415 } 416 417 sp.callback = val; 418 return sp; 419 } 420 else 421 { 422 auto sp = New!(ShaderParameter!T)(this, name, val); 423 parameters.set(name, sp); 424 return sp; 425 } 426 } 427 428 BaseShaderParameter getParameter(string name) 429 { 430 if (name in parameters.indices) 431 { 432 return parameters.get(name); 433 } 434 else 435 { 436 writefln("Warning: unknown shader parameter \"%s\"", name); 437 return null; 438 } 439 } 440 441 T getParameterValue(T)(string name) 442 { 443 if (name in parameters.indices) 444 { 445 auto sp = cast(ShaderParameter!T)parameters.get(name); 446 if (sp is null) 447 { 448 writefln("Warning: type mismatch for shader parameter \"%s\"", name); 449 return T.init; 450 } 451 452 if (sp.source) 453 return *sp.source; 454 else 455 return sp.value; 456 } 457 else 458 { 459 writefln("Warning: unknown shader parameter \"%s\"", name); 460 return T.init; 461 } 462 } 463 464 void bind() 465 { 466 program.bind(); 467 } 468 469 void unbind() 470 { 471 program.unbind(); 472 } 473 474 void bindParameters(GraphicsState* state) 475 { 476 GLsizei n; 477 glGetProgramStageiv(program.program, GL_VERTEX_SHADER, GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS, &n); 478 if (n > 0 && n != vertexSubroutineIndices.length) 479 vertexSubroutineIndices = New!(GLuint[])(n); 480 481 glGetProgramStageiv(program.program, GL_FRAGMENT_SHADER, GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS, &n); 482 if (n > 0 && n != fragmentSubroutineIndices.length) 483 fragmentSubroutineIndices = New!(GLuint[])(n); 484 485 foreach(v; parameters.data) 486 { 487 if (v.autoBind) 488 v.bind(); 489 } 490 491 if (vertexSubroutineIndices.length) 492 glUniformSubroutinesuiv(GL_VERTEX_SHADER, cast(uint)vertexSubroutineIndices.length, vertexSubroutineIndices.ptr); 493 494 if (fragmentSubroutineIndices.length) 495 glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, cast(uint)fragmentSubroutineIndices.length, fragmentSubroutineIndices.ptr); 496 } 497 498 void unbindParameters(GraphicsState* state) 499 { 500 foreach(v; parameters.data) 501 { 502 if (v.autoBind) 503 v.unbind(); 504 } 505 } 506 507 void validate() 508 { 509 glValidateProgram(program.program); 510 } 511 512 ~this() 513 { 514 if (vertexSubroutineIndices.length) 515 Delete(vertexSubroutineIndices); 516 if (fragmentSubroutineIndices.length) 517 Delete(fragmentSubroutineIndices); 518 } 519 }