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
32namespace love
33{
34namespace graphics
35{
36
37Shader *luax_checkshader(lua_State *L, int idx)
38{
39 return luax_checktype<Shader>(L, idx);
40}
41
42int 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
50static 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
55template <typename T, typename luaT, luaT (*checknum)(lua_State *, int)>
56static 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
80int 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
107int 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
115int 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
123int 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
160int 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
271int 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
290static 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
311static 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
423int 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
440int 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
458int 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
466static 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
475extern "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