1 | /** |
2 | * Copyright (c) 2006-2023 LOVE Development Team |
3 | * |
4 | * This software is provided 'as-is', without any express or implied |
5 | * warranty. In no event will the authors be held liable for any damages |
6 | * arising from the use of this software. |
7 | * |
8 | * Permission is granted to anyone to use this software for any purpose, |
9 | * including commercial applications, and to alter it and redistribute it |
10 | * freely, subject to the following restrictions: |
11 | * |
12 | * 1. The origin of this software must not be misrepresented; you must not |
13 | * claim that you wrote the original software. If you use this software |
14 | * in a product, an acknowledgment in the product documentation would be |
15 | * appreciated but is not required. |
16 | * 2. Altered source versions must be plainly marked as such, and must not be |
17 | * misrepresented as being the original software. |
18 | * 3. This notice may not be removed or altered from any source distribution. |
19 | **/ |
20 | |
21 | #include "wrap_Shader.h" |
22 | #include "wrap_Texture.h" |
23 | #include "math/MathModule.h" |
24 | #include "math/Transform.h" |
25 | #include "Graphics.h" |
26 | |
27 | #include <string> |
28 | #include <algorithm> |
29 | #include <cmath> |
30 | #include <limits> |
31 | |
32 | namespace love |
33 | { |
34 | namespace graphics |
35 | { |
36 | |
37 | Shader *luax_checkshader(lua_State *L, int idx) |
38 | { |
39 | return luax_checktype<Shader>(L, idx); |
40 | } |
41 | |
42 | int w_Shader_getWarnings(lua_State *L) |
43 | { |
44 | Shader *shader = luax_checkshader(L, 1); |
45 | std::string warnings = shader->getWarnings(); |
46 | lua_pushstring(L, warnings.c_str()); |
47 | return 1; |
48 | } |
49 | |
50 | static int _getCount(lua_State *L, int startidx, const Shader::UniformInfo *info) |
51 | { |
52 | return std::min(std::max(lua_gettop(L) - startidx + 1, 1), info->count); |
53 | } |
54 | |
55 | template <typename T, typename luaT, luaT (*checknum)(lua_State *, int)> |
56 | static void _updateNumbers(lua_State *L, int startidx, T *values, int components, int count) |
57 | { |
58 | if (components == 1) |
59 | { |
60 | for (int i = 0; i < count; ++i) |
61 | values[i] = (T) checknum(L, startidx + i); |
62 | } |
63 | else |
64 | { |
65 | for (int i = 0; i < count; i++) |
66 | { |
67 | luaL_checktype(L, startidx + i, LUA_TTABLE); |
68 | |
69 | for (int k = 1; k <= components; k++) |
70 | { |
71 | lua_rawgeti(L, startidx + i, k); |
72 | values[i * components + k - 1] = (T) checknum(L, -1); |
73 | } |
74 | |
75 | lua_pop(L, components); |
76 | } |
77 | } |
78 | } |
79 | |
80 | int w_Shader_sendFloats(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info, bool colors) |
81 | { |
82 | int count = _getCount(L, startidx, info); |
83 | int components = info->components; |
84 | float *values = info->floats; |
85 | |
86 | if (colors) |
87 | _updateNumbers<float, lua_Number, luax_checknumberclamped01>(L, startidx, values, components, count); |
88 | else |
89 | _updateNumbers<float, lua_Number, luaL_checknumber>(L, startidx, values, components, count); |
90 | |
91 | if (colors && graphics::isGammaCorrect()) |
92 | { |
93 | // alpha is always linear (when present). |
94 | int gammacomponents = std::min(components, 3); |
95 | |
96 | for (int i = 0; i < count; i++) |
97 | { |
98 | for (int j = 0; j < gammacomponents; j++) |
99 | values[i * components + j] = math::gammaToLinear(values[i * components + j]); |
100 | } |
101 | } |
102 | |
103 | luax_catchexcept(L, [&]() { shader->updateUniform(info, count); }); |
104 | return 0; |
105 | } |
106 | |
107 | int w_Shader_sendInts(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info) |
108 | { |
109 | int count = _getCount(L, startidx, info); |
110 | _updateNumbers<int, lua_Integer, luaL_checkinteger>(L, startidx, info->ints, info->components, count); |
111 | luax_catchexcept(L, [&]() { shader->updateUniform(info, count); }); |
112 | return 0; |
113 | } |
114 | |
115 | int w_Shader_sendUnsignedInts(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info) |
116 | { |
117 | int count = _getCount(L, startidx, info); |
118 | _updateNumbers<unsigned int, lua_Integer, luaL_checkinteger>(L, startidx, info->uints, info->components, count); |
119 | luax_catchexcept(L, [&]() { shader->updateUniform(info, count); }); |
120 | return 0; |
121 | } |
122 | |
123 | int w_Shader_sendBooleans(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info) |
124 | { |
125 | int count = _getCount(L, startidx, info); |
126 | int components = info->components; |
127 | |
128 | // We have to send booleans as ints. |
129 | int *values = info->ints; |
130 | |
131 | if (components == 1) |
132 | { |
133 | for (int i = 0; i < count; i++) |
134 | { |
135 | luaL_checktype(L, startidx + i, LUA_TBOOLEAN); |
136 | values[i] = (int) lua_toboolean(L, startidx + i); |
137 | } |
138 | } |
139 | else |
140 | { |
141 | for (int i = 0; i < count; i++) |
142 | { |
143 | luaL_checktype(L, startidx + i, LUA_TTABLE); |
144 | |
145 | for (int k = 1; k <= components; k++) |
146 | { |
147 | lua_rawgeti(L, startidx + i, k); |
148 | luaL_checktype(L, -1, LUA_TBOOLEAN); |
149 | values[i * components + k - 1] = (int) lua_toboolean(L, -1); |
150 | } |
151 | |
152 | lua_pop(L, components); |
153 | } |
154 | } |
155 | |
156 | luax_catchexcept(L, [&]() { shader->updateUniform(info, count); }); |
157 | return 0; |
158 | } |
159 | |
160 | int w_Shader_sendMatrices(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info) |
161 | { |
162 | bool columnmajor = false; |
163 | |
164 | if (lua_type(L, startidx) == LUA_TSTRING) |
165 | { |
166 | const char *layoutstr = lua_tostring(L, startidx); |
167 | math::Transform::MatrixLayout layout; |
168 | if (!math::Transform::getConstant(layoutstr, layout)) |
169 | return luax_enumerror(L, "matrix layout" , math::Transform::getConstants(layout), layoutstr); |
170 | |
171 | columnmajor = (layout == math::Transform::MATRIX_COLUMN_MAJOR); |
172 | startidx++; |
173 | } |
174 | |
175 | int count = _getCount(L, startidx, info); |
176 | int columns = info->matrix.columns; |
177 | int rows = info->matrix.rows; |
178 | int elements = columns * rows; |
179 | |
180 | float *values = info->floats; |
181 | |
182 | for (int i = 0; i < count; i++) |
183 | { |
184 | if (columns == 4 && rows == 4 && luax_istype(L, startidx + i, math::Transform::type)) |
185 | { |
186 | math::Transform *t = luax_totype<math::Transform>(L, startidx + i); |
187 | memcpy(&values[i * 16], t->getMatrix().getElements(), sizeof(float) * 16); |
188 | continue; |
189 | } |
190 | |
191 | luaL_checktype(L, startidx + i, LUA_TTABLE); |
192 | |
193 | lua_rawgeti(L, startidx + i, 1); |
194 | bool table_of_tables = lua_istable(L, -1); |
195 | lua_pop(L, 1); |
196 | |
197 | if (table_of_tables) |
198 | { |
199 | int n = i * elements; |
200 | |
201 | if (columnmajor) |
202 | { |
203 | for (int column = 0; column < columns; column++) |
204 | { |
205 | lua_rawgeti(L, startidx + i, column + 1); |
206 | |
207 | for (int row = 0; row < rows; row++) |
208 | { |
209 | lua_rawgeti(L, -(row + 1), row + 1); |
210 | values[n + (column * rows + row)] = (float) luaL_checknumber(L, -1); |
211 | } |
212 | |
213 | lua_pop(L, rows + 1); |
214 | } |
215 | } |
216 | else |
217 | { |
218 | for (int row = 0; row < rows; row++) |
219 | { |
220 | lua_rawgeti(L, startidx + i, row + 1); |
221 | |
222 | for (int column = 0; column < columns; column++) |
223 | { |
224 | // The table has the matrix elements laid out in row-major |
225 | // order, but we need to store them column-major in memory. |
226 | lua_rawgeti(L, -(column + 1), column + 1); |
227 | values[n + (column * rows + row)] = (float) luaL_checknumber(L, -1); |
228 | } |
229 | |
230 | lua_pop(L, columns + 1); |
231 | } |
232 | } |
233 | } |
234 | else |
235 | { |
236 | int n = i * elements; |
237 | |
238 | if (columnmajor) |
239 | { |
240 | for (int column = 0; column < columns; column++) |
241 | { |
242 | for (int row = 0; row < rows; row++) |
243 | { |
244 | lua_rawgeti(L, startidx + i, column * rows + row + 1); |
245 | values[n + (column * rows + row)] = (float) luaL_checknumber(L, -1); |
246 | } |
247 | } |
248 | } |
249 | else |
250 | { |
251 | for (int column = 0; column < columns; column++) |
252 | { |
253 | for (int row = 0; row < rows; row++) |
254 | { |
255 | // The table has the matrix elements laid out in row-major |
256 | // order, but we need to store them column-major in memory. |
257 | lua_rawgeti(L, startidx + i, row * columns + column + 1); |
258 | values[n + (column * rows + row)] = (float) luaL_checknumber(L, -1); |
259 | } |
260 | } |
261 | } |
262 | |
263 | lua_pop(L, elements); |
264 | } |
265 | } |
266 | |
267 | shader->updateUniform(info, count); |
268 | return 0; |
269 | } |
270 | |
271 | int w_Shader_sendTextures(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info) |
272 | { |
273 | int count = _getCount(L, startidx, info); |
274 | |
275 | std::vector<Texture *> textures; |
276 | textures.reserve(count); |
277 | |
278 | for (int i = 0; i < count; i++) |
279 | { |
280 | Texture *tex = luax_checktexture(L, startidx + i); |
281 | if (tex->getTextureType() != info->textureType) |
282 | return luaL_argerror(L, startidx + i, "invalid texture type for uniform" ); |
283 | textures.push_back(tex); |
284 | } |
285 | |
286 | luax_catchexcept(L, [&]() { shader->sendTextures(info, textures.data(), count); }); |
287 | return 0; |
288 | } |
289 | |
290 | static int w_Shader_sendLuaValues(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info, const char *name) |
291 | { |
292 | switch (info->baseType) |
293 | { |
294 | case Shader::UNIFORM_FLOAT: |
295 | return w_Shader_sendFloats(L, startidx, shader, info, false); |
296 | case Shader::UNIFORM_MATRIX: |
297 | return w_Shader_sendMatrices(L, startidx, shader, info); |
298 | case Shader::UNIFORM_INT: |
299 | return w_Shader_sendInts(L, startidx, shader, info); |
300 | case Shader::UNIFORM_UINT: |
301 | return w_Shader_sendUnsignedInts(L, startidx, shader, info); |
302 | case Shader::UNIFORM_BOOL: |
303 | return w_Shader_sendBooleans(L, startidx, shader, info); |
304 | case Shader::UNIFORM_SAMPLER: |
305 | return w_Shader_sendTextures(L, startidx, shader, info); |
306 | default: |
307 | return luaL_error(L, "Unknown variable type for shader uniform '%s" , name); |
308 | } |
309 | } |
310 | |
311 | static int w_Shader_sendData(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info, bool colors) |
312 | { |
313 | if (info->baseType == Shader::UNIFORM_SAMPLER) |
314 | return luaL_error(L, "Uniform sampler values (textures) cannot be sent to Shaders via Data objects." ); |
315 | |
316 | math::Transform::MatrixLayout layout = math::Transform::MATRIX_ROW_MAJOR; |
317 | int dataidx = startidx; |
318 | if (info->baseType == Shader::UNIFORM_MATRIX) |
319 | { |
320 | if (lua_type(L, startidx) == LUA_TSTRING) |
321 | { |
322 | // (matrixlayout, data, ...) |
323 | const char *layoutstr = lua_tostring(L, startidx); |
324 | if (!math::Transform::getConstant(layoutstr, layout)) |
325 | return luax_enumerror(L, "matrix layout" , math::Transform::getConstants(layout), layoutstr); |
326 | |
327 | startidx++; |
328 | dataidx = startidx; |
329 | } |
330 | else if (lua_type(L, startidx + 1) == LUA_TSTRING) |
331 | { |
332 | // (data, matrixlayout, ...) |
333 | // Should be deprecated in the future (doesn't match the argument |
334 | // order of Shader:send(name, matrixlayout, table)) |
335 | const char *layoutstr = lua_tostring(L, startidx + 1); |
336 | if (!math::Transform::getConstant(layoutstr, layout)) |
337 | return luax_enumerror(L, "matrix layout" , math::Transform::getConstants(layout), layoutstr); |
338 | |
339 | startidx++; |
340 | } |
341 | } |
342 | |
343 | bool columnmajor = (layout == math::Transform::MATRIX_COLUMN_MAJOR); |
344 | |
345 | Data *data = luax_checktype<Data>(L, dataidx); |
346 | size_t size = data->getSize(); |
347 | |
348 | ptrdiff_t offset = (ptrdiff_t) luaL_optinteger(L, startidx + 1, 0); |
349 | if (offset < 0) |
350 | return luaL_error(L, "Offset cannot be negative." ); |
351 | else if ((size_t) offset >= size) |
352 | return luaL_error(L, "Offset must be less than the size of the Data." ); |
353 | |
354 | size_t uniformstride = info->dataSize / info->count; |
355 | |
356 | if (!lua_isnoneornil(L, startidx + 2)) |
357 | { |
358 | lua_Integer sizearg = luaL_checkinteger(L, startidx + 2); |
359 | if (sizearg <= 0) |
360 | return luaL_error(L, "Size must be greater than 0." ); |
361 | else if ((size_t) sizearg > size - offset) |
362 | return luaL_error(L, "Size and offset must fit within the Data's bounds." ); |
363 | else if (sizearg % uniformstride != 0) |
364 | return luaL_error(L, "Size (%d) must be a multiple of the uniform's size in bytes (%d)." , sizearg, uniformstride); |
365 | else if ((size_t) sizearg > info->dataSize) |
366 | return luaL_error(L, "Size must not be greater than the uniform's total size in bytes." ); |
367 | |
368 | size = (size_t) sizearg; |
369 | } |
370 | else |
371 | { |
372 | size -= offset; |
373 | size = std::min((size / uniformstride) * uniformstride, info->dataSize); |
374 | } |
375 | |
376 | if (size == 0) |
377 | return luaL_error(L, "Size to copy must be greater than 0." ); |
378 | |
379 | int count = (int) (size / uniformstride); |
380 | const char *mem = (const char *) data->getData() + offset; |
381 | |
382 | if (info->baseType != Shader::UNIFORM_MATRIX || columnmajor) |
383 | memcpy(info->data, mem, size); |
384 | else |
385 | { |
386 | int columns = info->matrix.columns; |
387 | int rows = info->matrix.rows; |
388 | |
389 | const float *src = (const float *) mem; |
390 | float *dst = info->floats; |
391 | |
392 | for (int i = 0; i < count; i++) |
393 | { |
394 | for (int row = 0; row < rows; row++) |
395 | { |
396 | for (int column = 0; column < columns; column++) |
397 | dst[column * rows + row] = src[row * columns + column]; |
398 | } |
399 | |
400 | src += columns * rows; |
401 | dst += columns * rows; |
402 | } |
403 | } |
404 | |
405 | if (colors && graphics::isGammaCorrect()) |
406 | { |
407 | // alpha is always linear (when present). |
408 | int components = info->components; |
409 | int gammacomponents = std::min(components, 3); |
410 | float *values = info->floats; |
411 | |
412 | for (int i = 0; i < count; i++) |
413 | { |
414 | for (int j = 0; j < gammacomponents; j++) |
415 | values[i * components + j] = math::gammaToLinear(values[i * components + j]); |
416 | } |
417 | } |
418 | |
419 | shader->updateUniform(info, count); |
420 | return 0; |
421 | } |
422 | |
423 | int w_Shader_send(lua_State *L) |
424 | { |
425 | Shader *shader = luax_checkshader(L, 1); |
426 | const char *name = luaL_checkstring(L, 2); |
427 | |
428 | const Shader::UniformInfo *info = shader->getUniformInfo(name); |
429 | if (info == nullptr) |
430 | return luaL_error(L, "Shader uniform '%s' does not exist.\nA common error is to define but not use the variable." , name); |
431 | |
432 | int startidx = 3; |
433 | |
434 | if (luax_istype(L, startidx, Data::type) || (info->baseType == Shader::UNIFORM_MATRIX && luax_istype(L, startidx + 1, Data::type))) |
435 | return w_Shader_sendData(L, startidx, shader, info, false); |
436 | else |
437 | return w_Shader_sendLuaValues(L, startidx, shader, info, name); |
438 | } |
439 | |
440 | int w_Shader_sendColors(lua_State *L) |
441 | { |
442 | Shader *shader = luax_checkshader(L, 1); |
443 | const char *name = luaL_checkstring(L, 2); |
444 | |
445 | const Shader::UniformInfo *info = shader->getUniformInfo(name); |
446 | if (info == nullptr) |
447 | return luaL_error(L, "Shader uniform '%s' does not exist.\nA common error is to define but not use the variable." , name); |
448 | |
449 | if (info->baseType != Shader::UNIFORM_FLOAT || info->components < 3) |
450 | return luaL_error(L, "sendColor can only be used on vec3 or vec4 uniforms." ); |
451 | |
452 | if (luax_istype(L, 3, Data::type)) |
453 | return w_Shader_sendData(L, 3, shader, info, true); |
454 | else |
455 | return w_Shader_sendFloats(L, 3, shader, info, true); |
456 | } |
457 | |
458 | int w_Shader_hasUniform(lua_State *L) |
459 | { |
460 | Shader *shader = luax_checkshader(L, 1); |
461 | const char *name = luaL_checkstring(L, 2); |
462 | luax_pushboolean(L, shader->hasUniform(name)); |
463 | return 1; |
464 | } |
465 | |
466 | static const luaL_Reg w_Shader_functions[] = |
467 | { |
468 | { "getWarnings" , w_Shader_getWarnings }, |
469 | { "send" , w_Shader_send }, |
470 | { "sendColor" , w_Shader_sendColors }, |
471 | { "hasUniform" , w_Shader_hasUniform }, |
472 | { 0, 0 } |
473 | }; |
474 | |
475 | extern "C" int luaopen_shader(lua_State *L) |
476 | { |
477 | return luax_register_type(L, &Shader::type, w_Shader_functions, nullptr); |
478 | } |
479 | |
480 | } // graphics |
481 | } // love |
482 | |
483 | |