diff --git a/libzhl/IsaacRepentance_static.cpp b/libzhl/IsaacRepentance_static.cpp index 639c4848e..f01ff2772 100644 --- a/libzhl/IsaacRepentance_static.cpp +++ b/libzhl/IsaacRepentance_static.cpp @@ -621,40 +621,47 @@ void Entity_Bomb::UpdateDirtColor() { } } -void DestinationQuad::RotateRadians(const Vector& pivot, float radians) +inline void SourceQuad::ConvertToPixelSpace(KAGE_Graphics_ImageBase& image) { - if (radians == 0.0) + if (this->_coordinateSpace == eCoordinateSpace::PIXEL) { return; } - float sin = std::sin(radians); - float cos = std::cos(radians); - - // translate - _topLeft -= pivot; - _topRight -= pivot; - _bottomLeft -= pivot; - _bottomRight -= pivot; - - // apply rotation - auto rotate = [](auto& p, float sin, float cos) { - float x = p.x; - float y = p.y; - p.x = cos * x - sin * y; - p.y = sin * x + cos * y; - }; - - rotate(_topLeft, sin, cos); - rotate(_topRight, sin, cos); - rotate(_bottomLeft, sin, cos); - rotate(_bottomRight, sin, cos); - - // undo translation - _topLeft += pivot; - _topRight += pivot; - _bottomLeft += pivot; - _bottomRight += pivot; + float width = (float)image.GetWidth(); + float height = (float)image.GetHeight(); + + this->_topLeft.x *= width; + this->_topLeft.y *= height; + this->_topRight.x *= width; + this->_topRight.y *= height; + this->_bottomLeft.x *= width; + this->_bottomLeft.y *= height; + this->_bottomRight.x *= width; + this->_bottomRight.y *= height; +} + +inline void SourceQuad::ConvertToUVSpace(KAGE_Graphics_ImageBase& image) +{ + if (this->_coordinateSpace == eCoordinateSpace::NORMALIZED_UV) + { + return; + } + + int width = image.GetWidth(); + int height = image.GetHeight(); + + float inverseWidth = 1.0f / (float)width; + float inverseHeight = 1.0f / (float)height; + + this->_topLeft.x *= inverseWidth; + this->_topLeft.y *= inverseHeight; + this->_topRight.x *= inverseWidth; + this->_topRight.y *= inverseHeight; + this->_bottomLeft.x *= inverseWidth; + this->_bottomLeft.y *= inverseHeight; + this->_bottomRight.x *= inverseWidth; + this->_bottomRight.y *= inverseHeight; } ModReference* LuaEngine::GetModRefByTable(int tblIdx) diff --git a/libzhl/functions/DestinationQuad.zhl b/libzhl/functions/DestinationQuad.zhl index fd746e1a3..1f16d7f66 100644 --- a/libzhl/functions/DestinationQuad.zhl +++ b/libzhl/functions/DestinationQuad.zhl @@ -1,24 +1,115 @@ -struct DestinationQuad depends (Vector) { {{ - void LIBZHL_API DestinationQuad::RotateRadians(const Vector& pivot, float radians); +{{ +struct RenderMatrix +{ + float a = 1.0f, b = 0.0f, tx = 0.0f; + float c = 0.0f, d = 1.0f, ty = 0.0f; +}; +}} - inline void DestinationQuad::FlipX() +struct DestinationQuad depends (Vector) { {{ + inline void FlipX() { std::swap(_topLeft, _topRight); std::swap(_bottomLeft, _bottomRight); } - inline void DestinationQuad::FlipY() + inline void FlipY() { std::swap(_topLeft, _bottomLeft); std::swap(_topRight, _bottomRight); } + inline void Translate(const Vector& offset) + { + this->_topLeft += offset; + this->_topRight += offset; + this->_bottomLeft += offset; + this->_bottomRight += offset; + } + + inline void Scale(const Vector& scale, const Vector& anchor) + { + this->Translate(-anchor); + + auto scale_point = [&](Vector& vec) { + vec.x *= scale.x; + vec.y *= scale.y; + }; + + scale_point(this->_topLeft); + scale_point(this->_topRight); + scale_point(this->_bottomLeft); + scale_point(this->_bottomRight); + + this->Translate(anchor); + } + + inline void RotateRadians(const Vector& pivot, float radians) + { + float sin = std::sin(radians); + float cos = std::cos(radians); + + this->Translate(-pivot); + + auto rotate = [](auto& p, float sin, float cos) { + float x = p.x; + float y = p.y; + p.x = cos * x - sin * y; + p.y = sin * x + cos * y; + }; + + rotate(_topLeft, sin, cos); + rotate(_topRight, sin, cos); + rotate(_bottomLeft, sin, cos); + rotate(_bottomRight, sin, cos); + + this->Translate(pivot); + } + inline void RotateDegrees(const Vector& pivot, float degrees) { constexpr float DEGREES_TO_RADIANS = 3.14159265358979323846f / 180.0f; float radians = degrees * DEGREES_TO_RADIANS; RotateRadians(pivot, radians); } + + inline void Shear(const Vector& shear, const Vector& anchor) + { + this->Translate(-anchor); + + auto shear_point = [&](Vector& vec) { + float x = vec.x; + float y = vec.y; + vec.x += shear.x * y; + vec.y += shear.y * x; + }; + + shear_point(this->_topLeft); + shear_point(this->_topRight); + shear_point(this->_bottomLeft); + shear_point(this->_bottomRight); + + this->Translate(anchor); + } + + inline void ApplyMatrix(const RenderMatrix& matrix, const Vector& anchor) + { + this->Translate(-anchor); + + auto apply_matrix = [](Vector& v, const RenderMatrix& m) { + float x = v.x; + float y = v.y; + v.x = (m.a * x + m.b * y + m.tx); + v.y = (m.c * x + m.d * y + m.ty); + }; + + apply_matrix(this->_topLeft, matrix); + apply_matrix(this->_topRight, matrix); + apply_matrix(this->_bottomLeft, matrix); + apply_matrix(this->_bottomRight, matrix); + + this->Translate(anchor); + } }} Vector _topLeft : 0x0, _topRight : 0x8, _bottomLeft : 0x10, _bottomRight : 0x18; } : 0x20; diff --git a/libzhl/functions/Global.zhl b/libzhl/functions/Global.zhl index 5ec53c9df..e31e2da46 100644 --- a/libzhl/functions/Global.zhl +++ b/libzhl/functions/Global.zhl @@ -3323,6 +3323,7 @@ enum class eImageFlag : uint64_t { }; enum class eVertexAttributeFormat : int { + TERMINATOR = 0, // Used to define the end of the VertexAttributeDescriptor array FLOAT = 1, VEC_2 = 2, VEC_3 = 3, diff --git a/libzhl/functions/KAGE_Graphics_ImageManager.zhl b/libzhl/functions/KAGE_Graphics_ImageManager.zhl index 53d13c057..f06f312f1 100644 --- a/libzhl/functions/KAGE_Graphics_ImageManager.zhl +++ b/libzhl/functions/KAGE_Graphics_ImageManager.zhl @@ -1,10 +1,23 @@ -struct KAGE_Graphics_ImageManager { +struct KAGE_Graphics_ImageManager depends (KAGE_SmartPointer_ImageBase, KColor) { vector_ImagePtr _frameImages : 0x10; vector_pair_RenderBatchPtr_ImagePtr _transparentBatches : 0x28; + + {{ + static KAGE_SmartPointer_ImageBase CreateProceduralImage(uint32_t width, uint32_t height, const char* name, KColor& color) + { + KAGE_SmartPointer_ImageBase result; + CreateProceduralImage_Internal(&result, width, height, nullptr, name, 0, 0, color); + return result; + } + }} } : 0x3c; "538bdc83ec0883e4f883c404558b6b??896c24??8bec83ec30568b35": __thiscall void KAGE_Graphics_ImageManager::apply_frame_images(); +// Libzhl doesn't push the implicit_output on it's own. The function assumes the smart pointer is not initialized so the helper is created to avoid potential problems. +"558bec6aff68????????64a1????????5083ec14535657a1????????33c5508d45??64a3????????c745??00000000c745??00000000": +static KAGE_SmartPointer_ImageBase* KAGE_Graphics_ImageManager::CreateProceduralImage_Internal(KAGE_SmartPointer_ImageBase* implicit_output, uint32_t width, uint32_t height, void* unused_1, const char* name, uint32_t unused_2, uint32_t unused_3, KColor color); + "a1(????????)8d0c??8b34": reference KAGE_Graphics_ImageManager g_KAGE_Graphics_ImageManager; \ No newline at end of file diff --git a/libzhl/functions/KAGE_Graphics_Manager_GL.zhl b/libzhl/functions/KAGE_Graphics_Manager_GL.zhl index 9f1d5e4aa..95415d851 100644 --- a/libzhl/functions/KAGE_Graphics_Manager_GL.zhl +++ b/libzhl/functions/KAGE_Graphics_Manager_GL.zhl @@ -1,4 +1,4 @@ struct KAGE_Graphics_Manager_GL depends (KAGE_Graphics_VertexAttributeDescriptor, KAGE_Graphics_Shader) {}; "538bdc83ec0883e4f883c404558b6b??896c24??8bec6aff68????????64a1????????505381ec80000000a1????????33c58945??5657508d45??64a3????????8995": -static cleanup void KAGE_Graphics_Manager_GL::LoadShader(KAGE_Graphics_Shader *shader, KAGE_Graphics_VertexAttributeDescriptor *descriptor, const char* shaderPath); +static cleanup void KAGE_Graphics_Manager_GL::LoadShader(KAGE_Graphics_Shader *shader, const KAGE_Graphics_VertexAttributeDescriptor *descriptor, const char* shaderPath); diff --git a/libzhl/functions/KAGE_Graphics_Shader.zhl b/libzhl/functions/KAGE_Graphics_Shader.zhl index 9f4e88531..ea555d4ce 100644 --- a/libzhl/functions/KAGE_Graphics_Shader.zhl +++ b/libzhl/functions/KAGE_Graphics_Shader.zhl @@ -5,21 +5,60 @@ reference KAGE_Graphics_Shader* g_CurrentShader; reference KAGE_Graphics_Shader* g_AllShaders; "c705????????(????????)c705????????ffffffffc605????????00c705????????000000000f2905": -reference void* KAGE_Graphics_ShaderBase_vftable; +reference void* KAGE_Graphics_Shader_vftable; struct KAGE_Graphics_ShaderBase { {{ - KAGE_Graphics_ShaderBase() : _vtable(__ptr_KAGE_Graphics_ShaderBase_vftable) {}; + // ShaderBase has pure methods and so it should not be instantiable + protected: + KAGE_Graphics_ShaderBase() : _vtable(__ptr_KAGE_Graphics_Shader_vftable) { + std::memset((void*)((uintptr_t)this + 0x4), 0, sizeof(*this) - 0x4); // the constructor would normally set everything to 0 so we emulate that here + }; + + public: + ~KAGE_Graphics_ShaderBase() { + this->Free(false); + } }} + + __vtable { + pure void Free(bool deletePtr); + skip; // Initialize + pure void Shutdown(); + }; + void* _vtable : 0x0; - KAGE_Graphics_ImageBase_VertexAttributeDescriptor* _vertexAttributes : 0x8; + bool _initialized : 0x4; + KAGE_Graphics_VertexAttributeDescriptor* _vertexAttributes : 0x8; uint32_t _numVertexAttributes : 0xc; } : 0x28; -struct KAGE_Graphics_Shader depends (KAGE_Graphics_ShaderBase) { +struct KAGE_Graphics_Shader : public KAGE_Graphics_ShaderBase { {{ - inline int GetShaderId() { return *(int*)((char*)this + 0x28); } + KAGE_Graphics_Shader() { + _vtable = __ptr_KAGE_Graphics_Shader_vftable; + this->_glProgram = -1; + } + + ~KAGE_Graphics_Shader() { + if (this->_initialized) + { + // We must call Shutdown manually, as the Free method only calls the base class Shutdown, causing _glProgram to leak. + // _glProgram is not reset to a default value on Shutdown, so we can't be sure that we are not deleting an already deleted glProgram, + // but based on usage the only time glDeleteProgram is called is on Shutdown (where _initialized is set to false), + // and when Initialize fails (where _initialized is left as false). + this->Shutdown(); + } + } + + // Use this if you want to reload the shader, as none of the methods gracefully handle a double initialization, causing some resources to leak. + void HardReset() + { + this->~KAGE_Graphics_Shader(); + new (this) KAGE_Graphics_Shader(); + } + + inline int GetShaderId() { return this->_glProgram; } }} - KAGE_Graphics_ShaderBase _shaderBase : 0x0; unsigned int _glProgram : 0x28; } : 0x2c; diff --git a/libzhl/functions/KAGE_Graphics_Vertex.zhl b/libzhl/functions/KAGE_Graphics_Vertex.zhl index 9287ae646..e97265137 100644 --- a/libzhl/functions/KAGE_Graphics_Vertex.zhl +++ b/libzhl/functions/KAGE_Graphics_Vertex.zhl @@ -34,4 +34,19 @@ struct KAGE_Graphics_RenderBatch depends (KAGE_Graphics_GraphicsBufferObject) { KAGE_Graphics_GraphicsBufferObject _indexBuffer : 0x30; } : 0x3c; // (checked) -struct KAGE_Graphics_VertexAttributeDescriptor {}; +struct KAGE_Graphics_VertexAttributeDescriptor { + const char* name : 0x0; + uint32_t format : 0x4; // eVertexFormat + + {{ + bool operator==(const KAGE_Graphics_VertexAttributeDescriptor& other) const + { + return std::strcmp(this->name, other.name) == 0 && this->format == other.format; + } + + bool operator!=(const KAGE_Graphics_VertexAttributeDescriptor& other) const + { + return !(*this == other); + } + }} +} : 0x8; diff --git a/libzhl/functions/SourceQuad.zhl b/libzhl/functions/SourceQuad.zhl index 5c9a3cb1c..eddc680b0 100644 --- a/libzhl/functions/SourceQuad.zhl +++ b/libzhl/functions/SourceQuad.zhl @@ -1,3 +1,15 @@ struct SourceQuad depends(Vector, DestinationQuad) : public DestinationQuad { - int __coordinateSpaceEnum : 0x20; + int _coordinateSpace : 0x20; + + {{ + enum eCoordinateSpace + { + PIXEL = 0, + NORMALIZED_UV = 1, + }; + + LIBZHL_API void ConvertToPixelSpace(KAGE_Graphics_ImageBase& image); + LIBZHL_API void ConvertToUVSpace(KAGE_Graphics_ImageBase& image); + }} + } : 0x24; diff --git a/libzhl/functions/Vector2.zhl b/libzhl/functions/Vector2.zhl index 4234b2dcf..03ca966d2 100644 --- a/libzhl/functions/Vector2.zhl +++ b/libzhl/functions/Vector2.zhl @@ -5,6 +5,10 @@ struct Vector { {{ Vector() : x(0.f), y(0.f) {} Vector(float _x, float _y) : x(_x), y(_y) {} + Vector operator-() const { + return Vector(-x, -y); + } + Vector operator+(const Vector& other) { return Vector(x + other.x, y + other.y); diff --git a/repentogon/LuaInterfaces/LuaRender.cpp b/repentogon/LuaInterfaces/LuaRender.cpp index 624f24d00..0e97c165e 100644 --- a/repentogon/LuaInterfaces/LuaRender.cpp +++ b/repentogon/LuaInterfaces/LuaRender.cpp @@ -14,6 +14,9 @@ #include "IsaacRepentance.h" #include "LuaCore.h" #include "LuaRender.h" +#include "../ShaderLoader.h" +#include "../Utils/ImageUtils.hpp" +#include "../Utils/ShaderUtils.hpp" using LuaRender::LuaImage; using LuaRender::LuaTransformer; @@ -25,6 +28,195 @@ LuaRender::RenderContext LuaRender::VerticesRenderContext; static constexpr bool EnableCustomRendering = false; +// ============================================================================ +// Utils + +template +static void RenderToSurface(KAGE_Graphics_ImageBase& surface, Func&& renderFn) +{ + auto& manager = g_KAGE_Graphics_Manager; + + Rendering::PushCurrentRenderTarget(); + manager.SetCurrentRenderTarget(&surface, false); + + renderFn(); + + manager.Present(); + Rendering::RestorePreviousRenderTarget(); +} + +static void fill_shader_vertices(float* vertexBuffer, const KAGE_Graphics_ShaderBase& shader, lua_State* L, int tableIdx, std::vector& errorFields) +{ + lua::LuaStackProtector protector(L); + tableIdx = lua_absindex(L, tableIdx); + + auto fill_single_float = [&](const char* name, size_t attributeOffset, size_t vertexSize) + { + lua_getfield(L, tableIdx, name); + if (!lua_isnumber(L, -1)) + { + errorFields.push_back(name); + lua_pop(L, 1); + return; + } + + float data = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + + for (size_t i = 0; i < 4; i++) + { + vertexBuffer[attributeOffset + (i * vertexSize)] = data; + } + }; + + auto fill_vector = [&](const char* name, size_t attributeOffset, size_t vectorSize, size_t vertexSize) + { + lua_getfield(L, tableIdx, name); + if (!lua_istable(L, -1)) + { + errorFields.push_back(name); + lua_pop(L, 1); // pop table field + return; + } + + float* data = (float*)alloca(vectorSize * sizeof(float)); + + for (size_t i = 0; i < vectorSize; i++) + { + lua_rawgeti(L, -1, i + 1); + if (!lua_isnumber(L, -1)) + { + errorFields.push_back(name); + lua_pop(L, 2); + return; + } + + data[i] = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + } + + lua_pop(L, 1); + + for (size_t i = 0; i < 4; i++) + { + std::memcpy(&vertexBuffer[attributeOffset + (i * vertexSize)], data, vectorSize * sizeof(float)); + } + }; + + KAGE_Graphics_VertexAttributeDescriptor* attributes = shader._vertexAttributes; + size_t numAttributes = shader._numVertexAttributes; + size_t vertexSize = ShaderUtils::GetVertexSize(attributes, numAttributes); + size_t attributeOffset = 0; + for (size_t i = 0; i < numAttributes; i++) + { + auto& attribute = attributes[i]; + + size_t formatSize = 0; + switch (attribute.format) + { + case (uint32_t)eVertexAttributeFormat::FLOAT: + formatSize = 1; + fill_single_float(attribute.name, attributeOffset, vertexSize); + break; + case (uint32_t)eVertexAttributeFormat::VEC_2: + formatSize = 2; + fill_vector(attribute.name, attributeOffset, formatSize, vertexSize); + break; + case (uint32_t)eVertexAttributeFormat::VEC_3: + formatSize = 3; + fill_vector(attribute.name, attributeOffset, formatSize, vertexSize); + break; + case (uint32_t)eVertexAttributeFormat::VEC_4: + formatSize = 4; + fill_vector(attribute.name, attributeOffset, formatSize, vertexSize); + break; + default: + formatSize = ShaderUtils::GetFormatSize(attribute.format); + break; + } + + attributeOffset += formatSize; + } +} + +static RenderMatrix get_render_matrix(lua_State* L, int idx) +{ + if (!lua_istable(L, idx)) + { + luaL_argerror(L, idx, "render matrix is not a table"); + } + + RenderMatrix matrix; + + int luaMatrix = lua_absindex(L, idx); + lua_rawgeti(L, luaMatrix, 1); + lua_rawgeti(L, luaMatrix, 2); + + int xTransform = lua_absindex(L, -2); + int yTransform = lua_absindex(L, -1); + + if (!lua_istable(L, xTransform) || !lua_istable(L, yTransform)) + { + luaL_argerror(L, luaMatrix, "render matrix row is not a table"); + } + + auto assign_matrix_field = [](float& field, lua_State* L, int luaMatrix, int row, int columnIdx) + { + lua_rawgeti(L, row, columnIdx); + if (!lua_isnumber(L, -1)) + { + luaL_argerror(L, luaMatrix, "render matrix element is not a number!"); + } + + field = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + }; + + assign_matrix_field(matrix.a, L, luaMatrix, xTransform, 1); + assign_matrix_field(matrix.b, L, luaMatrix, xTransform, 2); + assign_matrix_field(matrix.tx, L, luaMatrix, xTransform, 3); + + assign_matrix_field(matrix.c, L, luaMatrix, yTransform, 1); + assign_matrix_field(matrix.d, L, luaMatrix, yTransform, 2); + assign_matrix_field(matrix.ty, L, luaMatrix, yTransform, 3); + + lua_pop(L, 2); // pop xTransform, yTransform + + return matrix; +} + +// =========================================================================== +// Shader +namespace LuaShader { + struct Userdata { + static constexpr char* MT = "Shader"; + KAGE_Graphics_Shader* shader = nullptr; + + Userdata(KAGE_Graphics_Shader* shader) + : shader(shader) { + } + }; + + static Userdata* GetUserdata(lua_State* L, int idx) { + return lua::GetRawUserdata(L, idx, Userdata::MT); + } + + static Userdata* NewUserdata(lua_State* L, KAGE_Graphics_Shader* shader) + { + Userdata* userdata = new (lua_newuserdata(L, sizeof(Userdata))) Userdata(shader); + luaL_setmetatable(L, Userdata::MT); + return userdata; + } + + static void RegisterUserdataClass(lua_State* L) { + luaL_Reg functions[] = { + { NULL, NULL } + }; + + lua::RegisterNewClass(L, Userdata::MT, Userdata::MT, functions); + } +} + // ============================================================================ // Image @@ -38,13 +230,192 @@ LUA_FUNCTION(lua_Image_gc) { return 0; } +LUA_FUNCTION(Lua_Image_GetWidth) +{ + LuaImage* luaImage = LuaRender::GetLuaImage(L, 1); + lua_pushinteger(L, luaImage->image.image->GetWidth()); + return 1; +} + +LUA_FUNCTION(Lua_Image_GetHeight) +{ + LuaImage* luaImage = LuaRender::GetLuaImage(L, 1); + lua_pushinteger(L, luaImage->image.image->GetHeight()); + return 1; +} + +LUA_FUNCTION(Lua_Image_GetPaddedWidth) +{ + LuaImage* luaImage = LuaRender::GetLuaImage(L, 1); + lua_pushinteger(L, luaImage->image.image->GetPaddedWidth()); + return 1; +} + +LUA_FUNCTION(Lua_Image_GetPaddedHeight) +{ + LuaImage* luaImage = LuaRender::GetLuaImage(L, 1); + lua_pushinteger(L, luaImage->image.image->GetPaddedHeight()); + return 1; +} + +LUA_FUNCTION(Lua_Image_GetName) +{ + LuaImage* luaImage = LuaRender::GetLuaImage(L, 1); + lua_pushstring(L, luaImage->image.image->_name); + return 1; +} + +LUA_FUNCTION(Lua_Image_Render) +{ + LuaImage* luaImage = LuaRender::GetLuaImage(L, 1); + SourceQuad* sourceQuad = LuaRender::GetSourceQuad(L, 2); + DestinationQuad* destQuad = LuaRender::GetDestQuad(L, 3); + KColor color = *lua::GetLuabridgeUserdata(L, 4, lua::Metatables::KCOLOR, "KColor"); + + auto& image = *luaImage->image.image; + + auto& shader = *__ptr_g_AllShaders[ShaderType::SHADER_COLOR_OFFSET]; + float* vertexBuffer = ImageUtils::SubmitQuadForShader(image, shader, *sourceQuad, *destQuad, ImageUtils::QuadColor(color)); + if (vertexBuffer) + { + ShaderUtils::ColorOffset::FillVertices(vertexBuffer, image, ColorMod()); + } + + return 0; +} + +LUA_FUNCTION(Lua_Image_RenderWithShader) +{ + LuaImage* luaImage = LuaRender::GetLuaImage(L, 1); + SourceQuad* sourceQuad = LuaRender::GetSourceQuad(L, 2); + DestinationQuad* destQuad = LuaRender::GetDestQuad(L, 3); + KColor color = *lua::GetLuabridgeUserdata(L, 4, lua::Metatables::KCOLOR, "KColor"); + LuaShader::Userdata* luaShader = LuaShader::GetUserdata(L, 5); + if (!lua_istable(L, 6)) + { + return luaL_typeerror(L, 6, lua_typename(L, LUA_TTABLE)); + } + + auto& shader = *luaShader->shader; + if (!shader._initialized) + { + // shader was shutdown or never successfully compiled. + return luaL_argerror(L, 5, "invalid shader used"); + } + + auto& image = *luaImage->image.image; + + float* vertexBuffer = ImageUtils::SubmitQuadForShader(image, shader, *sourceQuad, *destQuad, ImageUtils::QuadColor(color)); + if (!vertexBuffer) + { + return 0; + } + + // no reserve, since the error path should be a rare and non desirable occurrence. + std::vector errorFields; + fill_shader_vertices(vertexBuffer, shader, L, 6, errorFields); + + if (!errorFields.empty()) + { + std::string errorMessage = "some fields were not properly set :"; + for (size_t i = 0; i < errorFields.size(); i++) + { + const char* separator = i == 0 ? " " : ", "; + errorMessage += std::string(separator) + errorFields[i]; + } + + // field not being setup correctly is purely the caller's fault, so even tho we can safely continue it's better to error. + return luaL_argerror(L, 6, errorMessage.c_str()); + } + + return 0; +} + +LUA_FUNCTION(Lua_Image_GetTexelRegion) +{ + LuaImage* luaImage = LuaRender::GetLuaImage(L, 1); + int x = (int)luaL_checkinteger(L, 2); + int y = (int)luaL_checkinteger(L, 3); + uint32_t width = (uint32_t)luaL_checkinteger(L, 4); + uint32_t height = (uint32_t)luaL_checkinteger(L, 5); + + auto& image = *luaImage->image.image; + uint32_t size = width * height * 4; // (RGBA) + luaL_Buffer buffer; + luaL_buffinit(L, &buffer); + + uint8_t* dst = (uint8_t*)luaL_prepbuffsize(&buffer, size); + image.GetTexelRegion(x, y, width, height, dst); + + luaL_addsize(&buffer, size); + luaL_pushresult(&buffer); + return 1; +} + static void RegisterImageClass(lua_State* L) { luaL_Reg functions[] = { + { "GetWidth", Lua_Image_GetWidth }, + { "GetHeight", Lua_Image_GetHeight }, + { "GetPaddedWidth", Lua_Image_GetPaddedWidth }, + { "GetPaddedHeight", Lua_Image_GetPaddedHeight }, + { "GetName", Lua_Image_GetName }, + { "GetTexelRegion", Lua_Image_GetTexelRegion }, + { "Render", Lua_Image_Render }, + { "RenderWithShader", Lua_Image_RenderWithShader }, { NULL, NULL } }; lua::RegisterNewClass(L, "Image", "Image", functions, lua_Image_gc); } +// =========================================================================== +// SurfaceRenderController + +// Used for functions that only make sense during a Surface render operation +namespace LuaSurfaceRenderController { + struct Userdata { + static constexpr char* MT = "SurfaceRenderController"; + bool valid = true; + + Userdata() = default; + }; + + static Userdata* get_userdata(lua_State* L, int idx) { + return lua::GetRawUserdata(L, idx, Userdata::MT); + } + + static Userdata* get_valid_surface_render_controller(lua_State* L, int idx) { + Userdata* controller = get_userdata(L, idx); + if (!controller->valid) + { + luaL_error(L, "This surface render controller has already been applied and cannot be used again"); + } + + return controller; + } + + LUA_FUNCTION(lua_clear) + { + Userdata* controller = get_valid_surface_render_controller(L, 1); + g_KAGE_Graphics_Manager.Clear(); + return 0; + } + + static Userdata* NewUserdata(lua_State* L) + { + Userdata* userdata = new (lua_newuserdata(L, sizeof(Userdata))) Userdata; + luaL_setmetatable(L, Userdata::MT); + return userdata; + } + + static void RegisterUserdataClass(lua_State* L) { + luaL_Reg functions[] = { + { "Clear", lua_clear }, + { NULL, NULL } + }; + lua::RegisterNewClass(L, Userdata::MT, Userdata::MT, functions); + } +} + // ============================================================================ // Transformer @@ -113,15 +484,15 @@ LUA_FUNCTION(lua_Transformer_Apply) { if (!transformer->_valid) { return luaL_error(L, "No operations allowed after a transformer had been applied"); } - Rendering::PushCurrentRenderTarget(); - __ptr_g_KAGE_Graphics_Manager->SetCurrentRenderTarget(transformer->_output.image, false); - // __ptr_g_KAGE_Graphics_Manager->Clear(); - for (Transformation& transformation : transformer->_transformations) { - KAGE_Graphics_ImageBase* image = transformation._input.image; - image->Render(transformation._source, transformation._dest, transformation._color1, transformation._color2, transformation._color3, transformation._color4); - } - __ptr_g_KAGE_Graphics_Manager->Present(); - Rendering::RestorePreviousRenderTarget(); + + RenderToSurface(*transformer->_output.image, [&](){ + // __ptr_g_KAGE_Graphics_Manager->Clear(); + for (Transformation& transformation : transformer->_transformations) { + KAGE_Graphics_ImageBase* image = transformation._input.image; + image->Render(transformation._source, transformation._dest, transformation._color1, transformation._color2, transformation._color3, transformation._color4); + } + }); + transformer->_valid = false; return 0; } @@ -146,6 +517,19 @@ static void RegisterTransformerClass(lua_State* L) { // ============================================================================ // Quads +// DestinationQuad is used as the generic quad. +static DestinationQuad* get_quad(lua_State* L, int idx) +{ + DestinationQuad* ud = (DestinationQuad*)luaL_testudata(L, idx, LuaRender::SourceQuadMT); + if (ud) + { + return ud; + } + + ud = (DestinationQuad*)luaL_checkudata(L, idx, LuaRender::DestinationQuadMT); + return ud; +} + SourceQuad* LuaRender::GetSourceQuad(lua_State* L, int idx) { return (SourceQuad*)luaL_checkudata(L, idx, LuaRender::SourceQuadMT); } @@ -154,99 +538,210 @@ DestinationQuad* LuaRender::GetDestQuad(lua_State* L, int idx) { return (DestinationQuad*)luaL_checkudata(L, idx, LuaRender::DestinationQuadMT); } -typedef std::variant QuadVar; - -static QuadVar GetQuad(lua_State* L, int idx = 1) { - if (SourceQuad* source = (SourceQuad*)luaL_testudata(L, 1, LuaRender::SourceQuadMT)) { - return source; - } - else if (DestinationQuad* destination = (DestinationQuad*)luaL_checkudata(L, 1, LuaRender::DestinationQuadMT)) { - return destination; - } - else { - luaL_error(L, "Expected either SourceQuad or DestinationQuad"); - return (SourceQuad*)nullptr; - } -} - -static bool IsSource(QuadVar const& quad) { - return std::holds_alternative(quad); -} +LUA_FUNCTION(Lua_DestinationQuad_Copy) +{ + DestinationQuad* quad = LuaRender::GetDestQuad(L, 1); -static SourceQuad* AsSource(QuadVar const& quad) { - return std::get(quad); -} + DestinationQuad* result = (DestinationQuad*)lua_newuserdata(L, sizeof(DestinationQuad)); + luaL_setmetatable(L, LuaRender::DestinationQuadMT); + *result = *quad; -static DestinationQuad* AsDest(QuadVar const& quad) { - return std::get(quad); + return 1; } -#define QUAD_GET(slot, var) static Vector* QuadGet##slot##(QuadVar const& quad) {\ - if (IsSource(quad)) {\ - return &AsSource(quad)->##var##; \ - }\ - else {\ - return &AsDest(quad)->##var##;\ - }\ -} +LUA_FUNCTION(Lua_SourceQuad_Copy) +{ + SourceQuad* quad = LuaRender::GetSourceQuad(L, 1); -QUAD_GET(TopLeft, _topLeft) -QUAD_GET(TopRight, _topRight) -QUAD_GET(BottomLeft, _bottomLeft) -QUAD_GET(BottomRight, _bottomRight) + SourceQuad* result = (SourceQuad*)lua_newuserdata(L, sizeof(SourceQuad)); + luaL_setmetatable(L, LuaRender::SourceQuadMT); + *result = *quad; -static void PushQuadComponent(lua_State* L, Vector* (*fn)(QuadVar const&)) { - Vector* component = fn(GetQuad(L)); - lua::luabridge::UserdataValue::push(L, lua::GetMetatableKey(lua::Metatables::VECTOR), *component); + return 1; } LUA_FUNCTION(lua_Quad_GetTopLeft) { - PushQuadComponent(L, QuadGetTopLeft); + DestinationQuad* quad = get_quad(L, 1); + lua::luabridge::UserdataValue::push(L, lua::GetMetatableKey(lua::Metatables::VECTOR), quad->_topLeft); return 1; } LUA_FUNCTION(lua_Quad_GetTopRight) { - PushQuadComponent(L, QuadGetTopRight); + DestinationQuad* quad = get_quad(L, 1); + lua::luabridge::UserdataValue::push(L, lua::GetMetatableKey(lua::Metatables::VECTOR), quad->_topRight); return 1; } LUA_FUNCTION(lua_Quad_GetBottomLeft) { - PushQuadComponent(L, QuadGetBottomLeft); + DestinationQuad* quad = get_quad(L, 1); + lua::luabridge::UserdataValue::push(L, lua::GetMetatableKey(lua::Metatables::VECTOR), quad->_bottomLeft); return 1; } LUA_FUNCTION(lua_Quad_GetBottomRight) { - PushQuadComponent(L, QuadGetBottomRight); + DestinationQuad* quad = get_quad(L, 1); + lua::luabridge::UserdataValue::push(L, lua::GetMetatableKey(lua::Metatables::VECTOR), quad->_bottomRight); return 1; } -static void SetQuadComponent(lua_State* L, Vector* (*fn)(QuadVar const&)) { - Vector* component = fn(GetQuad(L)); - *component = *lua::GetLuabridgeUserdata(L, 2, lua::Metatables::VECTOR, "Vector"); -} - LUA_FUNCTION(lua_Quad_SetTopLeft) { - SetQuadComponent(L, QuadGetTopLeft); + DestinationQuad* quad = get_quad(L, 1); + quad->_topLeft = *lua::GetLuabridgeUserdata(L, 2, lua::Metatables::VECTOR, "Vector"); return 0; } LUA_FUNCTION(lua_Quad_SetTopRight) { - SetQuadComponent(L, QuadGetTopRight); + DestinationQuad* quad = get_quad(L, 1); + quad->_topRight = *lua::GetLuabridgeUserdata(L, 2, lua::Metatables::VECTOR, "Vector"); return 0; } LUA_FUNCTION(lua_Quad_SetBottomLeft) { - SetQuadComponent(L, QuadGetBottomLeft); + DestinationQuad* quad = get_quad(L, 1); + quad->_bottomLeft = *lua::GetLuabridgeUserdata(L, 2, lua::Metatables::VECTOR, "Vector"); return 0; } LUA_FUNCTION(lua_Quad_SetBottomRight) { - SetQuadComponent(L, QuadGetBottomRight); + DestinationQuad* quad = get_quad(L, 1); + quad->_bottomRight = *lua::GetLuabridgeUserdata(L, 2, lua::Metatables::VECTOR, "Vector"); + return 0; +} + +LUA_FUNCTION(Lua_Quad_Translate) +{ + DestinationQuad* quad = get_quad(L, 1); + Vector offset = *lua::GetLuabridgeUserdata(L, 2, lua::Metatables::VECTOR, "Vector"); + + quad->Translate(offset); + + return 0; +} + +LUA_FUNCTION(Lua_Quad_Scale) +{ + DestinationQuad* quad = get_quad(L, 1); + Vector scale = *lua::GetLuabridgeUserdata(L, 2, lua::Metatables::VECTOR, "Vector"); + Vector anchor = *lua::GetLuabridgeUserdata(L, 3, lua::Metatables::VECTOR, "Vector"); + + quad->Scale(scale, anchor); + + return 0; +} + +LUA_FUNCTION(Lua_Quad_Rotate) +{ + DestinationQuad* quad = get_quad(L, 1); + float rotation = (float)luaL_checknumber(L, 2); + Vector anchor = *lua::GetLuabridgeUserdata(L, 3, lua::Metatables::VECTOR, "Vector"); + + quad->RotateDegrees(anchor, rotation); + return 0; } +LUA_FUNCTION(Lua_Quad_Shear) +{ + DestinationQuad* quad = get_quad(L, 1); + Vector shear = *lua::GetLuabridgeUserdata(L, 2, lua::Metatables::VECTOR, "Vector"); + Vector anchor = *lua::GetLuabridgeUserdata(L, 3, lua::Metatables::VECTOR, "Vector"); + + quad->Shear(shear, anchor); + + return 0; +} + +LUA_FUNCTION(Lua_Quad_ApplyMatrix) +{ + DestinationQuad* quad = get_quad(L, 1); + if (!lua_istable(L, 2)) + { + luaL_typeerror(L, 2, lua_typename(L, LUA_TTABLE)); + } + RenderMatrix matrix = get_render_matrix(L, 2); + Vector anchor = *lua::GetLuabridgeUserdata(L, 3, lua::Metatables::VECTOR, "Vector"); + + quad->ApplyMatrix(matrix, anchor); + + return 0; +} + +LUA_FUNCTION(Lua_Quad_Flip) +{ + DestinationQuad* quad = get_quad(L, 1); + bool flipX = lua_isnoneornil(L, 2) ? true : lua_toboolean(L, 2); + bool flipY = lua_isnoneornil(L, 3) ? true : lua_toboolean(L, 3); + + if (flipX) + { + quad->FlipX(); + } + + if (flipY) + { + quad->FlipY(); + } + + return 0; +} + +LUA_FUNCTION(Lua_DestQuad_ToString) +{ + DestinationQuad* quad = LuaRender::GetDestQuad(L, 1); + + lua_pushfstring(L, "[DestQuad: TopLeft %f %f | TopRight %f %f | BottomLeft %f %f | BottomRight %f %f]", + quad->_topLeft.x, quad->_topLeft.y, + quad->_topRight.x, quad->_topRight.y, + quad->_bottomLeft.x, quad->_bottomLeft.y, + quad->_bottomRight.x, quad->_bottomRight.y); + + return 1; +} + +LUA_FUNCTION(Lua_SourceQuad_IsUVSpace) +{ + SourceQuad* quad = LuaRender::GetSourceQuad(L, 1); + lua_pushboolean(L, quad->_coordinateSpace == SourceQuad::eCoordinateSpace::NORMALIZED_UV); + return 1; +} + +LUA_FUNCTION(Lua_SourceQuad_ConvertToPixelSpace) +{ + SourceQuad* quad = LuaRender::GetSourceQuad(L, 1); + LuaImage* luaImage = LuaRender::GetLuaImage(L, 2); + + quad->ConvertToPixelSpace(*luaImage->image.image); + + return 0; +} + +LUA_FUNCTION(Lua_SourceQuad_ConvertToUVSpace) +{ + SourceQuad* quad = LuaRender::GetSourceQuad(L, 1); + LuaImage* luaImage = LuaRender::GetLuaImage(L, 2); + + quad->ConvertToUVSpace(*luaImage->image.image); + + return 0; +} + +LUA_FUNCTION(Lua_SourceQuad_ToString) +{ + SourceQuad* quad = LuaRender::GetSourceQuad(L, 1); + + lua_pushfstring(L, "[SourceQuad: TopLeft %f %f | TopRight %f %f | BottomLeft %f %f | BottomRight %f %f | UV %s]", + quad->_topLeft.x, quad->_topLeft.y, + quad->_topRight.x, quad->_topRight.y, + quad->_bottomLeft.x, quad->_bottomLeft.y, + quad->_bottomRight.x, quad->_bottomRight.y, + quad->_coordinateSpace == SourceQuad::eCoordinateSpace::NORMALIZED_UV ? "true" : "false"); + + return 1; +} + static void RegisterQuadClasses(lua_State* L) { - luaL_Reg quadFunctions[] = { + luaL_Reg destinationQuadFunctions[] = { + { "Copy", Lua_DestinationQuad_Copy }, { "GetTopLeft", lua_Quad_GetTopLeft }, { "GetTopRight", lua_Quad_GetTopRight }, { "GetBottomLeft", lua_Quad_GetBottomLeft }, @@ -255,49 +750,46 @@ static void RegisterQuadClasses(lua_State* L) { { "SetTopRight", lua_Quad_SetTopRight }, { "SetBottomLeft", lua_Quad_SetBottomLeft }, { "SetBottomRight", lua_Quad_SetBottomRight }, + { "Translate", Lua_Quad_Translate }, + { "Scale", Lua_Quad_Scale }, + { "Rotate", Lua_Quad_Rotate }, + { "Shear", Lua_Quad_Shear }, + { "ApplyMatrix", Lua_Quad_ApplyMatrix }, + { "Flip", Lua_Quad_Flip }, + { "__tostring", Lua_DestQuad_ToString }, { NULL, NULL } }; - lua::RegisterNewClass(L, LuaRender::QuadMT, LuaRender::QuadMT, quadFunctions); - - // Missing convert_to_coordinate_space luaL_Reg sourceQuadFunctions[] = { + { "Copy", Lua_SourceQuad_Copy }, + { "IsUVSpace", Lua_SourceQuad_IsUVSpace }, + { "ConvertToPixelSpace", Lua_SourceQuad_ConvertToPixelSpace }, + { "ConvertToUVSpace", Lua_SourceQuad_ConvertToUVSpace }, + { "__tostring", Lua_SourceQuad_ToString }, { NULL, NULL } }; + lua::RegisterNewClass(L, LuaRender::DestinationQuadMT, LuaRender::DestinationQuadMT, destinationQuadFunctions); + lua::RegisterNewClass(L, LuaRender::SourceQuadMT, LuaRender::SourceQuadMT, sourceQuadFunctions); luaL_getmetatable(L, LuaRender::SourceQuadMT); - luaL_setmetatable(L, LuaRender::QuadMT); - lua_pop(L, 1); - - luaL_Reg destinationQuadFunctions[] = { - { NULL, NULL } - }; - - lua::RegisterNewClass(L, LuaRender::DestinationQuadMT, LuaRender::DestinationQuadMT, destinationQuadFunctions); - luaL_getmetatable(L, LuaRender::DestinationQuadMT); - luaL_setmetatable(L, LuaRender::QuadMT); + luaL_setmetatable(L, LuaRender::DestinationQuadMT); lua_pop(L, 1); } -static void FillQuad(lua_State* L, void* quad) { - char* addr = (char*)quad; - for (int i = 1; i <= lua_gettop(L); ++i) { - *(Vector*)addr = *lua::GetLuabridgeUserdata(L, i, lua::Metatables::VECTOR, "Vector"); - addr += sizeof(Vector); - } - - for (int i = lua_gettop(L) + 1; i <= 4; ++i) { - Vector* vect = (Vector*)addr; - vect->x = vect->y = 0; - addr += sizeof(Vector); - } +static void FillQuad(lua_State* L, DestinationQuad& quad, int startIdx) { + quad._topLeft = *lua::GetLuabridgeUserdata(L, startIdx + 0, lua::Metatables::VECTOR, "Vector"); + quad._topRight = *lua::GetLuabridgeUserdata(L, startIdx + 1, lua::Metatables::VECTOR, "Vector"); + quad._bottomLeft = *lua::GetLuabridgeUserdata(L, startIdx + 2, lua::Metatables::VECTOR, "Vector"); + quad._bottomRight = *lua::GetLuabridgeUserdata(L, startIdx + 3, lua::Metatables::VECTOR, "Vector"); } LUA_FUNCTION(lua_SourceQuad_new) { SourceQuad quad; - quad.__coordinateSpaceEnum = 0; - FillQuad(L, &quad); + FillQuad(L, quad, 1); + bool uvSpace = lua_toboolean(L, 5); + quad._coordinateSpace = uvSpace ? SourceQuad::eCoordinateSpace::NORMALIZED_UV : SourceQuad::eCoordinateSpace::PIXEL; + SourceQuad* result = (SourceQuad*)lua_newuserdata(L, sizeof(SourceQuad)); luaL_setmetatable(L, LuaRender::SourceQuadMT); memcpy(result, &quad, sizeof(SourceQuad)); @@ -306,7 +798,8 @@ LUA_FUNCTION(lua_SourceQuad_new) { LUA_FUNCTION(lua_DestinationQuad_new) { DestinationQuad quad; - FillQuad(L, &quad); + FillQuad(L, quad, 1); + DestinationQuad* result = (DestinationQuad*)lua_newuserdata(L, sizeof(DestinationQuad)); luaL_setmetatable(L, LuaRender::DestinationQuadMT); memcpy(result, &quad, sizeof(DestinationQuad)); @@ -1517,7 +2010,7 @@ namespace GL { } static void Lua_BindUniform(lua_State* L, const char* mt) { - Shader** shader = lua::GetRawUserdata(L, 1, LuaRender::ShaderMT); + Shader** shader = lua::GetRawUserdata(L, 1, LuaRender::GLShaderMT); std::string name(luaL_checkstring(L, 2)); GLSLValue* value = lua::GetRawUserdata(L, 3, mt); (*shader)->BindUniform(name, value); @@ -1570,7 +2063,7 @@ namespace GL { { NULL, NULL} }; - lua::RegisterNewClass(L, LuaRender::ShaderMT, LuaRender::ShaderMT, funcs); + lua::RegisterNewClass(L, LuaRender::GLShaderMT, LuaRender::GLShaderMT, funcs); } // Shaders outlive everything once registered. Store them in static memory @@ -1643,7 +2136,7 @@ namespace GL { LUA_FUNCTION(Lua_NewPass) { Pipeline* pipeline = lua::GetRawUserdata(L, 1, LuaRender::PipelineMT); size_t nbVertices = (size_t) luaL_checkinteger(L, 2); - Shader** shader = lua::GetRawUserdata(L, 3, LuaRender::ShaderMT); + Shader** shader = lua::GetRawUserdata(L, 3, LuaRender::GLShaderMT); size_t nbElements = (size_t) luaL_checkinteger(L, 4); VertexBuffer* buffer = pipeline->NewPass(nbVertices, *shader, nbElements); @@ -1835,7 +2328,7 @@ namespace GL { public: LUA_FUNCTION(Lua_SliceSingle) { VertexBuffer** buffer = lua::GetRawUserdata(L, 1, LuaRender::VertexBufferMT); - Shader** shader = lua::GetRawUserdata(L, 2, LuaRender::ShaderMT); + Shader** shader = lua::GetRawUserdata(L, 2, LuaRender::GLShaderMT); int nElements = (int) luaL_checkinteger(L, 3); int first = (int) luaL_checkinteger(L, 4); @@ -2214,10 +2707,10 @@ void __stdcall LuaPreDrawElements(KAGE_Graphics_RenderBatch* descriptor, GLenum case LUA_TUSERDATA: lua_getmetatable(L, -1); - luaL_getmetatable(L, LuaRender::ShaderMT); + luaL_getmetatable(L, LuaRender::GLShaderMT); if (lua_rawequal(L, -1, -2)) { lua_pop(L, 2); - GL::Shader* shader = *lua::GetRawUserdata(L, -1, LuaRender::ShaderMT); + GL::Shader* shader = *lua::GetRawUserdata(L, -1, LuaRender::GLShaderMT); shader->Use(); auto loc = glGetUniformLocation(shader->GetProgram(), "Transform"); glUniformMatrix4fv(loc, 1, GL_TRUE, GL::ProjectionMatrix.data); @@ -2276,6 +2769,12 @@ static void RegisterCustomRenderMetatables(lua_State* L) { LUA_FUNCTION(lua_Renderer_LoadImage) { const char* path = luaL_checkstring(L, 1); + std::filesystem::path p = path; + if (!p.is_relative()) + { + return luaL_error(L, "Image %s does not exist", path); + } + KAGE_SmartPointer_ImageBase image; Manager::LoadImage(&image, path, __ptr_g_VertexAttributeDescriptor_Position, false); @@ -2290,6 +2789,69 @@ LUA_FUNCTION(lua_Renderer_LoadImage) { return 1; } +LUA_FUNCTION(Lua_Renderer_CreateImage) { + uint32_t width = (uint32_t)luaL_checkinteger(L, 1); + uint32_t height = (uint32_t)luaL_checkinteger(L, 2); + const char* name = luaL_checkstring(L, 3); + KColor color = KColor(0.0, 0.0, 0.0, 0.0); + + // prevent name clashes with real images (since these are added to the cache for some reason, even though they are never loaded) + std::string trueName = REPENTOGON::StringFormat("%s.procedural", name); + KAGE_SmartPointer_ImageBase pointer = KAGE_Graphics_ImageManager::CreateProceduralImage(width, height, trueName.c_str(), color); + + if (!pointer.image) { + return luaL_error(L, "Unable to create Image"); + } + + LuaImage* ud = new (lua_newuserdata(L, sizeof(LuaImage))) LuaImage; + memset(ud, 0, sizeof(LuaImage)); + luaL_setmetatable(L, LuaRender::ImageMT); + ud->image = pointer; + return 1; +} + +LUA_FUNCTION(Lua_Renderer_RenderToImage) { + LuaImage* luaImage = LuaRender::GetLuaImage(L); + auto* image = luaImage->image.image; + if ((image->_flags & (uint64_t)eImageFlag::PROCEDURAL) == 0) + { + return luaL_error(L, "Cannot use a non procedural image as the render target"); + } + + luaL_checktype(L, 2, LUA_TFUNCTION); + int renderFn = lua_absindex(L, 2); + + lua::LuaStackProtector protector(L); + auto* renderController = LuaSurfaceRenderController::NewUserdata(L); + int renderControllerIdx = lua_absindex(L, -1); + + RenderToSurface(*image, [&](){ + lua_pushvalue(L, renderFn); + lua::LuaResults results = lua::LuaCaller(L) + .pushvalue(renderControllerIdx) + .call(0); + + if (results.getResultCode() != LUA_OK) + { + if (lua_isstring(L, -1)) + { + const char* msg = lua_tostring(L, -1); + g_Game->GetConsole()->PrintError(REPENTOGON::StringFormat("An error occurred while Rendering to Surface \"%s\": %s\n", image->_name, msg)); + } + else + { + g_Game->GetConsole()->PrintError(REPENTOGON::StringFormat("An error occurred while Rendering to Surface \"%s\"\n", image->_name)); + } + } + + renderController->valid = false; + }); + + lua_pop(L, 1); // pop renderController + + return 0; +} + LUA_FUNCTION(lua_Renderer_StartTransformation) { LuaImage* luaImage = LuaRender::GetLuaImage(L); auto& pointer = luaImage->image; @@ -2305,7 +2867,7 @@ LUA_FUNCTION(lua_Renderer_StartTransformation) { return 1; } -LUA_FUNCTION(Lua_Renderer_Shader) { +LUA_FUNCTION(Lua_Renderer_GLShader) { std::string vertexShader(luaL_checkstring(L, 1)); std::string fragmentShader(luaL_checkstring(L, 2)); GL::VertexDescriptor* descriptor = lua::GetRawUserdata(L, 3, LuaRender::VertexDescriptorMT); @@ -2315,7 +2877,7 @@ LUA_FUNCTION(Lua_Renderer_Shader) { shader = new GL::Shader(vertexShader, fragmentShader, descriptor); std::unique_ptr ptr(shader); GL::Shader** ud = (GL::Shader**)lua_newuserdata(L, sizeof(shader)); - luaL_setmetatable(L, LuaRender::ShaderMT); + luaL_setmetatable(L, LuaRender::GLShaderMT); *ud = shader; GL::Shader::_shaders.push_back(std::move(ptr)); } @@ -2328,6 +2890,93 @@ LUA_FUNCTION(Lua_Renderer_Shader) { return 1; } +LUA_FUNCTION(Lua_Renderer_LoadShader) +{ + std::string path = luaL_checkstring(L, 1); + if (!lua_istable(L, 2)) + { + return luaL_typeerror(L, 2, lua_typename(L, LUA_TTABLE)); + } + + int descTbl = lua_absindex(L, 2); + + size_t numAttributes = (size_t)lua_rawlen(L, descTbl); + std::vector descriptor; + descriptor.reserve(numAttributes + 1); // + 1 for terminator + + // lua table to descriptor + for (size_t i = 0; i < numAttributes; i++) + { + auto& attributeDesc = descriptor.emplace_back(); + + int type = lua_rawgeti(L, descTbl, i + 1); + if (!lua_istable(L, -1)) + { + lua_pop(L, 1); + return luaL_error(L, "vertex attribute %i: table expected", i + 1); + } + + lua_rawgeti(L, -1, 1); + if (!lua_isstring(L, -1)) + { + lua_pop(L, 2); + return luaL_error(L, "vertex attribute %i name: string expected", i + 1); + } + attributeDesc.name = lua_tostring(L, -1); + lua_pop(L, 1); // pop name + + lua_rawgeti(L, -1, 2); + if (!lua_isinteger(L, -1)) + { + lua_pop(L, 2); + return luaL_error(L, "vertex attribute %i format: integer expected", i + 1); + } + int format = (int)lua_tointeger(L, -1); + if (!(1 <= format && format <= 8)) + { + lua_pop(L, 2); + return luaL_error(L, "vertex attribute %i format: invalid format", i + 1); + } + attributeDesc.format = format; + lua_pop(L, 1); // pop format + + lua_pop(L, 1); // pop attribute + } + + auto& terminator = descriptor.emplace_back(); + terminator.name = ""; + terminator.format = (size_t)eVertexAttributeFormat::TERMINATOR; + + KAGE_Graphics_Shader* shader = ShaderLoader::LoadShader(path, descriptor.data()); + if (!shader) + { + luaL_error(L, "Unable to load shader \"%s\"", path); + } + + // confirm correct vertex descriptor + if (!ShaderUtils::UsesVertexDescriptor(*shader, descriptor.data(), descriptor.size() - 1)) + { + luaL_error(L, "Incorrect VertexDescriptor for \"%s\"", path); + } + + LuaShader::NewUserdata(L, shader); + + return 1; +} + +LUA_FUNCTION(Lua_Renderer_GetShaderByType) +{ + int shaderType = (int)luaL_checkinteger(L, 1); + + if (!(0 <= shaderType && shaderType < ShaderType::SHADER_MAX)) + { + return luaL_argerror(L, 2, "invalid shader type"); + } + + LuaShader::Userdata* ud = LuaShader::NewUserdata(L, __ptr_g_AllShaders[shaderType]); + return 1; +} + LUA_FUNCTION(Lua_Renderer_VertexDescriptor) { lua::place(L, LuaRender::VertexDescriptorMT); return 1; @@ -2373,6 +3022,24 @@ LUA_FUNCTION(Lua_Renderer_RenderSet) { return 1; } +LUA_FUNCTION(Lua_Renderer_GetPixelationAmount) +{ + lua_pushnumber(L, g_ANM2_PixelationAmount); + return 1; +} + +LUA_FUNCTION(Lua_Renderer_GetClipPaneNormal) +{ + lua::luabridge::UserdataValue::push(L, lua::GetMetatableKey(lua::Metatables::VECTOR), g_ANM2_ClipPaneNormal); + return 1; +} + +LUA_FUNCTION(Lua_Renderer_GetClipPaneThreshold) +{ + lua_pushnumber(L, g_ANM2_ClipPaneThreshold); + return 1; +} + // ============================================================================ // Hooks @@ -2451,6 +3118,8 @@ HOOK_METHOD(LuaEngine, RegisterClasses, () -> void) { lua_State* L = _state; lua::LuaStackProtector protector(L); RegisterImageClass(L); + LuaShader::RegisterUserdataClass(L); + LuaSurfaceRenderController::RegisterUserdataClass(L); RegisterTransformerClass(L); RegisterQuadClasses(L); RegisterCustomRenderMetatables(L); @@ -2462,15 +3131,22 @@ HOOK_METHOD(LuaEngine, RegisterClasses, () -> void) { luaL_Reg renderFunctions[] = { { "LoadImage", lua_Renderer_LoadImage }, + { "CreateImage", Lua_Renderer_CreateImage }, + { "RenderToImage", Lua_Renderer_RenderToImage }, { "StartTransformation", lua_Renderer_StartTransformation }, - { "Shader", Lua_Renderer_Shader }, - { "Vec2", Lua_Renderer_Vec2 }, - { "Vec3", Lua_Renderer_Vec3 }, - { "Vec4", Lua_Renderer_Vec4 }, - { "ProjectionMatrix", Lua_Renderer_ProjectionMatrix }, - { "Pipeline", Lua_Renderer_Pipeline }, - { "VertexDescriptor", Lua_Renderer_VertexDescriptor}, - { "RenderSet", Lua_Renderer_RenderSet }, + // { "GLShader", Lua_Renderer_GLShader }, + { "GetShaderByType", Lua_Renderer_GetShaderByType }, + { "LoadShader", Lua_Renderer_LoadShader }, + // { "Vec2", Lua_Renderer_Vec2 }, + // { "Vec3", Lua_Renderer_Vec3 }, + // { "Vec4", Lua_Renderer_Vec4 }, + // { "ProjectionMatrix", Lua_Renderer_ProjectionMatrix }, + // { "Pipeline", Lua_Renderer_Pipeline }, + // { "VertexDescriptor", Lua_Renderer_VertexDescriptor}, + // { "RenderSet", Lua_Renderer_RenderSet }, + { "GetPixelationAmount", Lua_Renderer_GetPixelationAmount }, + { "GetClipPaneNormal", Lua_Renderer_GetClipPaneNormal }, + { "GetClipPaneThreshold", Lua_Renderer_GetClipPaneThreshold }, { NULL, NULL } }; @@ -2509,6 +3185,17 @@ HOOK_METHOD(LuaEngine, RegisterClasses, () -> void) { lua::TableAssoc(L, "SHADER_MIRROR", SHADER_MIRROR); lua_rawset(L, -3); + lua_pushstring(L, "VertexAttributeFormat"); + lua_newtable(L); + lua::TableAssoc(L, "FLOAT", (int)eVertexAttributeFormat::FLOAT); + lua::TableAssoc(L, "VEC2", (int)eVertexAttributeFormat::VEC_2); + lua::TableAssoc(L, "VEC3", (int)eVertexAttributeFormat::VEC_3); + lua::TableAssoc(L, "VEC4", (int)eVertexAttributeFormat::VEC_4); + lua::TableAssoc(L, "POSITION", (int)eVertexAttributeFormat::POSITION); + lua::TableAssoc(L, "COLOR", (int)eVertexAttributeFormat::COLOR); + lua::TableAssoc(L, "TEX_COORD", (int)eVertexAttributeFormat::TEX_COORD); + lua_rawset(L, -3); + lua_setglobal(L, "Renderer"); } diff --git a/repentogon/LuaInterfaces/LuaRender.h b/repentogon/LuaInterfaces/LuaRender.h index c6d573091..bd828d4a6 100644 --- a/repentogon/LuaInterfaces/LuaRender.h +++ b/repentogon/LuaInterfaces/LuaRender.h @@ -42,7 +42,7 @@ namespace LuaRender { constexpr static const char* VertexBufferMT = "VertexBuffer"; constexpr static const char* ElementsArrayMT = "ElementsArray"; - constexpr static const char* ShaderMT = "Shader"; + constexpr static const char* GLShaderMT = "GLShader"; constexpr static const char* GLFloatMT = "GLFloat"; constexpr static const char* GLVec2MT = "GLVec2"; constexpr static const char* GLVec3MT = "GLVec3"; diff --git a/repentogon/Patches/Anm2Extras.cpp b/repentogon/Patches/Anm2Extras.cpp index a9519ed23..142372d1b 100644 --- a/repentogon/Patches/Anm2Extras.cpp +++ b/repentogon/Patches/Anm2Extras.cpp @@ -12,6 +12,14 @@ #include "../repentogon/Utils/ImageUtils.hpp" #include "../MiscFunctions.h" #include "ExtraRenderSteps.h" +#include "../ShaderLoader.h" + +namespace { + struct CustomShader { + std::string path; // Relative path starting from `.../resources/`, without the file extensions, ie `shaders/myshader` + KAGE_Graphics_Shader* shader; + }; +} //shitass workaround for crashing pause menu anm2 HOOK_METHOD(PauseScreen, Show, ()-> void) { @@ -33,33 +41,34 @@ std::string NormalizeShaderPath(const std::string& input_path) { // Layer-specific extra data. struct Anm2LayerExtras { - CustomShader* custom_shader = nullptr; - CustomShader* custom_champion_shader = nullptr; + std::optional custom_shader; + std::optional custom_champion_shader; - void SetCustomShader(CustomShader* shader, const bool champion) { + void SetCustomShader(KAGE_Graphics_Shader* shader, const std::string& input_path, const bool champion) { + CustomShader customShader = { NormalizeShaderPath(input_path), shader }; if (champion) { - custom_champion_shader = shader; + custom_champion_shader = customShader; } else { - custom_shader = shader; + custom_shader = customShader; } } - CustomShader* GetCustomShader(const bool champion) { + const std::optional& GetCustomShader(const bool champion) { return champion ? custom_champion_shader : custom_shader; } bool HasCustomShader(const bool champion) { - return GetCustomShader(champion) != nullptr; + return GetCustomShader(champion) != std::nullopt; } bool HasCustomShader(const std::string& path, const bool champion) { - CustomShader* shader = GetCustomShader(champion); - return shader != nullptr && shader->path == NormalizeShaderPath(path); + const std::optional& shader = GetCustomShader(champion); + return shader.has_value() && shader->path == NormalizeShaderPath(path); } void ClearCustomShader(const bool champion) { if (champion) { - custom_champion_shader = nullptr; + custom_champion_shader = std::nullopt; } else { - custom_shader = nullptr; + custom_shader = std::nullopt; } } }; @@ -72,9 +81,6 @@ struct Anm2Extras { std::unordered_map anm2_extras; -std::unordered_map custom_shaders; -std::unordered_map custom_champion_shaders; - // Clear an ANM2's data whenever it is destroyed or reset. HOOK_METHOD(ANM2, destructor, ()->void) { anm2_extras.erase(this); @@ -126,68 +132,45 @@ Anm2LayerExtras* GetLayerExtras(LayerState* layer) { // Returns the custom shader that this layer should use, if any. // Prefer layer-specific shader, if none check the ANM2 instead. -CustomShader* GetCustomShaderForLayer(LayerState* layer, const bool champion) { +KAGE_Graphics_Shader* GetCustomShaderForLayer(LayerState* layer, const bool champion) { Anm2LayerExtras* layer_extras = GetLayerExtras(layer); if (layer_extras) { - CustomShader* shader = layer_extras->GetCustomShader(champion); - if (shader) { - return shader; + const std::optional& customShader = layer_extras->GetCustomShader(champion); + if (customShader && customShader->shader->_initialized) { + return customShader->shader; } } Anm2LayerExtras* default_extras = GetDefaultLayerExtras(layer); if (default_extras) { - return default_extras->GetCustomShader(champion); - } - return nullptr; -} - -// Finds the full filepath to the shader and checks if the .fs and .vs files exist. -// Returns an empty string if no matching files are found. -std::string FindShaderFullPath(const std::string& path) { - for (ModEntry* mod : g_Manager->GetModManager()->_mods) { - if (!mod->IsEnabled()) continue; - - const std::string fullpath = (std::filesystem::path(mod->_resourcesDirectory) / path).string(); - - if (std::filesystem::exists(fullpath + ".fs") && std::filesystem::exists(fullpath + ".vs")) { - return fullpath; + const std::optional& customShader = default_extras->GetCustomShader(champion); + if (customShader && customShader->shader->_initialized) { + return customShader->shader; } } - return ""; -} -// Abstracted shader loading (for reloading shaders via console) -bool LoadCustomShader(const std::string& path, KAGE_Graphics_Shader* shader, bool champion) { - const std::string fullpath = FindShaderFullPath(path); - if (fullpath.empty()) - return false; - KAGE_Graphics_VertexAttributeDescriptor* desc = champion ? &g_ColorOffset_Champion_VertexAttributes : &g_ColorOffset_VertexAttributes; - KAGE_Graphics_Manager_GL::LoadShader(shader, desc, fullpath.c_str()); - return true; + return nullptr; } // Returns the custom shader corresponding to the relative path. // Initializes the shader if necessary. -CustomShader* GetOrLoadCustomShader(const std::string& input_path, const bool champion) { - if (input_path.empty()) +KAGE_Graphics_Shader* GetCustomShader(const std::string& input_path, const bool champion) { + const KAGE_Graphics_VertexAttributeDescriptor* desc = champion ? &g_ColorOffset_Champion_VertexAttributes : &g_ColorOffset_VertexAttributes; + size_t numAttributes = champion ? ShaderUtils::ColorOffsetChampion::NUM_ATTRIBUTES : ShaderUtils::ColorOffset::NUM_ATTRIBUTES; + KAGE_Graphics_Shader* shader = ShaderLoader::LoadShader(input_path, desc); + + if (!shader || !shader->_initialized || !ShaderUtils::UsesVertexDescriptor(*shader, desc, numAttributes)) + { return nullptr; - const std::string path = NormalizeShaderPath(input_path); - auto& shader_map = champion ? custom_champion_shaders : custom_shaders; - if (shader_map.count(path) == 0) { - shader_map[path].path = path; - if (!LoadCustomShader(path, &shader_map[path].shader, champion)) { - shader_map.erase(path); - return nullptr; - } } - return &shader_map[path]; + + return shader; } bool SetCustomShader(ANM2* anm2, const std::string& path, const bool champion) { - CustomShader* shader = GetOrLoadCustomShader(path, champion); + KAGE_Graphics_Shader* shader = GetCustomShader(path, champion); if (shader) { - anm2_extras[anm2].default_layer_extras.SetCustomShader(shader, champion); + anm2_extras[anm2].default_layer_extras.SetCustomShader(shader, path, champion); return true; } return false; @@ -197,9 +180,9 @@ bool SetCustomShader(LayerState* layer, const std::string& path, const bool cham ANM2* anm2 = layer->_animation; if (!anm2) return false; - CustomShader* shader = GetOrLoadCustomShader(path, champion); + KAGE_Graphics_Shader* shader = GetCustomShader(path, champion); if (shader) { - anm2_extras[anm2].layer_extras[layer->GetLayerID()].SetCustomShader(shader, champion); + anm2_extras[anm2].layer_extras[layer->GetLayerID()].SetCustomShader(shader, path, champion); return true; } return false; @@ -253,10 +236,10 @@ bool HasCustomShader(LayerState* layer, const std::string& path, const bool cham static KAGE_Graphics_Shader* get_color_offset_shader(LayerState* layerState, const bool champion) { - CustomShader* shader = GetCustomShaderForLayer(layerState, champion); + KAGE_Graphics_Shader* shader = GetCustomShaderForLayer(layerState, champion); if (shader) { // Use the custom shader. - return &shader->shader; + return shader; } // Use the normal shader. return __ptr_g_AllShaders[champion ? SHADER_COLOR_OFFSET_CHAMPION : SHADER_COLOR_OFFSET]; diff --git a/repentogon/Patches/Anm2Extras.h b/repentogon/Patches/Anm2Extras.h index 850ee5f46..417cdd592 100644 --- a/repentogon/Patches/Anm2Extras.h +++ b/repentogon/Patches/Anm2Extras.h @@ -11,14 +11,4 @@ void ClearCustomShader(LayerState* layer, const bool champion); bool HasCustomShader(ANM2* anm2, const bool champion); bool HasCustomShader(ANM2* anm2, const std::string& path, const bool champion); bool HasCustomShader(LayerState* anm2, const bool champion); -bool HasCustomShader(LayerState* anm2, const std::string& path, const bool champion); - -struct CustomShader { - std::string path; // Relative path starting from `.../resources/`, without the file extensions, ie `shaders/myshader` - KAGE_Graphics_Shader shader; -}; - -bool LoadCustomShader(const std::string& path, KAGE_Graphics_Shader* shader, bool champion); - -extern std::unordered_map custom_shaders; -extern std::unordered_map custom_champion_shaders; \ No newline at end of file +bool HasCustomShader(LayerState* anm2, const std::string& path, const bool champion); \ No newline at end of file diff --git a/repentogon/Patches/ConsoleHooks.cpp b/repentogon/Patches/ConsoleHooks.cpp index 0c7feceb1..c212fb9e5 100644 --- a/repentogon/Patches/ConsoleHooks.cpp +++ b/repentogon/Patches/ConsoleHooks.cpp @@ -5,6 +5,7 @@ #include "LuaCore.h" #include "../Patches/ModReloading.h" #include "../REPENTOGONFileMap.h" +#include "../ShaderLoader.h" #include "Anm2Extras.h" #include @@ -182,10 +183,7 @@ HOOK_METHOD(Console, RunCommand, (std::string& in, std::string* out, Entity_Play */ if ((in == "reloadshaders") || (in.rfind("reloadshaders ", 0) == 0)) { - for (auto & [ key, value ] : custom_shaders) - LoadCustomShader(key, &value.shader, false); - for (auto & [ key, value ] : custom_champion_shaders) - LoadCustomShader(key, &value.shader, true); + ShaderLoader::detail::ReloadShaders(); } if ((in == "help") || (in.rfind("help ", 0) == 0)) { diff --git a/repentogon/Patches/MesaShaderFix.cpp b/repentogon/Patches/MesaShaderFix.cpp index 0f6d5f356..a68a1153e 100644 --- a/repentogon/Patches/MesaShaderFix.cpp +++ b/repentogon/Patches/MesaShaderFix.cpp @@ -1,7 +1,7 @@ #include "IsaacRepentance.h" #include "HookSystem.h" -HOOK_STATIC(KAGE_Graphics_Manager_GL, LoadShader, (KAGE_Graphics_Shader* shader, KAGE_Graphics_VertexAttributeDescriptor* descriptor, const char* shaderPath) -> void, __cdecl) { +HOOK_STATIC(KAGE_Graphics_Manager_GL, LoadShader, (KAGE_Graphics_Shader* shader, const KAGE_Graphics_VertexAttributeDescriptor* descriptor, const char* shaderPath) -> void, __cdecl) { const char* targetShader = "shaders/coloroffset_gold"; if (strcmp(shaderPath, targetShader) == 0) { shaderPath = "shaders/coloroffset_gold_mesafix"; diff --git a/repentogon/ShaderLoader.cpp b/repentogon/ShaderLoader.cpp new file mode 100644 index 000000000..2ce2ad29c --- /dev/null +++ b/repentogon/ShaderLoader.cpp @@ -0,0 +1,152 @@ +#include "ShaderLoader.h" +#include "Utils/ShaderUtils.hpp" +#include "MiscFunctions.h" +#include +#include + +namespace { + struct ShaderMetadata + { + std::string path; + std::vector vertexDescriptor; + + private: + void free_vertex_descriptor() + { + for (int i = 0; i < (int)this->vertexDescriptor.size() - 1; i++) + { + auto& attribute = this->vertexDescriptor[i]; + delete[] attribute.name; + attribute.name = nullptr; + } + } + + public: + ~ShaderMetadata() + { + this->free_vertex_descriptor(); + } + }; + + struct Shader + { + KAGE_Graphics_Shader shader; + ShaderMetadata metadata; + + void SetMetadata(std::string_view name, const KAGE_Graphics_VertexAttributeDescriptor* descriptor) + { + this->metadata.path = name; + + // Set attributes + size_t numAttributes = ShaderUtils::GetNumVertexAttributes(descriptor); + auto& vertexDescriptor = this->metadata.vertexDescriptor; + vertexDescriptor.clear(); + vertexDescriptor.reserve(numAttributes + 1); + + // copy descriptor + for (size_t i = 0; i < numAttributes; i++) + { + auto& myAttribute = vertexDescriptor.emplace_back(); + auto& attribute = descriptor[i]; + + size_t len = std::strlen(attribute.name); + char* name = new char[len + 1]; + std::memcpy(name, attribute.name, len + 1); + myAttribute.name = name; + + myAttribute.format = attribute.format; + } + + auto& terminator = vertexDescriptor.emplace_back(); + terminator.name = ""; + terminator.format = (int)eVertexAttributeFormat::TERMINATOR; + } + }; +} + +static std::unordered_map shaderMap; + +// Normalizes input shader paths for use as keys and for identification. +// Converts to lowercase and strips excess slashes, ie `shaders\\MyShader` -> `shaders/myshader` +static std::string normalize_shader_path(const std::string& input_path) { + std::string path = std::regex_replace(input_path.c_str(), std::regex(R"([\/\\]+)"), "/"); + std::transform(path.begin(), path.end(), path.begin(), [](unsigned char c) { return std::tolower(c); }); + return path; +} + +// Finds the full filepath to the shader and checks if the .fs and .vs files exist. +// Returns an empty string if no matching files are found. +static std::string find_shader(const std::string& path) +{ + for (ModEntry* mod : g_Manager->GetModManager()->_mods) + { + if (!mod->_loaded) continue; + + const std::string fullpath = (std::filesystem::path(mod->_resourcesDirectory) / path).string(); + if (std::filesystem::exists(fullpath + ".fs") && std::filesystem::exists(fullpath + ".vs")) { + return fullpath; + } + } + + return ""; +} + +KAGE_Graphics_Shader* ShaderLoader::LoadShader(const std::string& path, const KAGE_Graphics_VertexAttributeDescriptor* vertexDesc) +{ + const std::string key = normalize_shader_path(path); + auto [it, inserted] = shaderMap.try_emplace(key); + Shader& shader = it->second; + + if (inserted) + { + const std::string modPath = find_shader(path); + if (modPath.empty()) + { + shaderMap.erase(it); + return nullptr; + } + + KAGE_Graphics_Manager_GL::LoadShader(&shader.shader, vertexDesc, modPath.c_str()); + if (!shader.shader._initialized) + { + shaderMap.erase(it); + return nullptr; + } + + shader.SetMetadata(path, vertexDesc); + } + + return &shader.shader; +} + +void ShaderLoader::detail::ReloadShaders() +{ + for (auto& [key, value] : shaderMap) + { + auto& shader = value.shader; + auto& metadata = value.metadata; + + // A hard reset is necessary, as LoadShader/Initialize does not gracefully handle initializing an already initialized shader + // causing the glProgram, and other heap allocated resources to leak. + shader.HardReset(); + const std::string modPath = find_shader(metadata.path); + + bool failed; + if (modPath.empty()) + { + failed = true; + } + else + { + KAGE_Graphics_Manager_GL::LoadShader(&shader, metadata.vertexDescriptor.data(), modPath.c_str()); + failed = !shader._initialized; + } + + if (failed) + { + std::string message = REPENTOGON::StringFormat("Unable to reload shader \"%s\"", metadata.path.c_str()); + g_Game->GetConsole()->PrintError(message); + ZHL::Log(message.c_str()); + } + } +} \ No newline at end of file diff --git a/repentogon/ShaderLoader.h b/repentogon/ShaderLoader.h new file mode 100644 index 000000000..08a7cf861 --- /dev/null +++ b/repentogon/ShaderLoader.h @@ -0,0 +1,13 @@ +#pragma once + +#include "IsaacRepentance.h" + +namespace ShaderLoader +{ + KAGE_Graphics_Shader* LoadShader(const std::string& path, const KAGE_Graphics_VertexAttributeDescriptor* vertexDesc); +} + +namespace ShaderLoader::detail +{ + void ReloadShaders(); +} \ No newline at end of file diff --git a/repentogon/Utils/ImageUtils.hpp b/repentogon/Utils/ImageUtils.hpp index fbd1d46c9..9c904c453 100644 --- a/repentogon/Utils/ImageUtils.hpp +++ b/repentogon/Utils/ImageUtils.hpp @@ -15,10 +15,10 @@ namespace ImageUtils } inline ImageUtils::ShaderRenderData::ShaderRenderData(const KAGE_Graphics_Shader& shader) - : imageVertexDescriptor(std::make_unique(shader._shaderBase._numVertexAttributes)), - vertexStride(ShaderUtils::GetVertexStride(shader._shaderBase._vertexAttributes, shader._shaderBase._numVertexAttributes)) + : imageVertexDescriptor(std::make_unique(shader._numVertexAttributes)), + vertexStride(ShaderUtils::GetVertexStride(shader._vertexAttributes, shader._numVertexAttributes)) { - ShaderUtils::ToImageVertexDescriptor(this->imageVertexDescriptor.get(), shader._shaderBase._vertexAttributes, shader._shaderBase._numVertexAttributes); + ShaderUtils::ToImageVertexDescriptor(this->imageVertexDescriptor.get(), shader._vertexAttributes, shader._numVertexAttributes); } inline float* ImageUtils::SubmitQuadForShader(KAGE_Graphics_ImageBase &image, KAGE_Graphics_Shader& shader, const ImageUtils::ShaderRenderData& shaderData, const SourceQuad& sourceQuad, const DestinationQuad& destinationQuad, const ImageUtils::QuadColor& color) @@ -33,7 +33,7 @@ inline float* ImageUtils::SubmitQuadForShader(KAGE_Graphics_ImageBase &image, KA uint8_t* oldVertexFormat = image._vertexFormat; uint8_t oldNumVertexAttributes = image._numVertexAttributes; image._vertexFormat = shaderData.imageVertexDescriptor.get(); - image._numVertexAttributes = shader._shaderBase._numVertexAttributes; + image._numVertexAttributes = shader._numVertexAttributes; // change the vertexStride so that the vertex buffer is allocated with the proper size, when creating a new RenderBatch. uint16_t oldVertexStride = image._vertexFormatStride; diff --git a/repentogon/Utils/ShaderUtils.h b/repentogon/Utils/ShaderUtils.h index c4a9d11d9..32ee44740 100644 --- a/repentogon/Utils/ShaderUtils.h +++ b/repentogon/Utils/ShaderUtils.h @@ -7,11 +7,14 @@ namespace ShaderUtils // size is expressed in number of floats constexpr size_t GetFormatSize(uint32_t format); constexpr size_t GetFormatStride(uint32_t format); - size_t GetNumVertexAttributes(KAGE_Graphics_ImageBase_VertexAttributeDescriptor* descriptor); + // This method expects a descriptor with a Terminator format. + size_t GetNumVertexAttributes(const KAGE_Graphics_VertexAttributeDescriptor* descriptor); // size is expressed in number of floats - uint32_t GetVertexSize(KAGE_Graphics_ImageBase_VertexAttributeDescriptor* descriptor, size_t numAttributes); - uint32_t GetVertexStride(KAGE_Graphics_ImageBase_VertexAttributeDescriptor* descriptor, size_t numAttributes); - void ToImageVertexDescriptor(uint8_t* result, KAGE_Graphics_ImageBase_VertexAttributeDescriptor* descriptor, size_t numAttributes); + uint32_t GetVertexSize(KAGE_Graphics_VertexAttributeDescriptor* descriptor, size_t numAttributes); + uint32_t GetVertexStride(KAGE_Graphics_VertexAttributeDescriptor* descriptor, size_t numAttributes); + void ToImageVertexDescriptor(uint8_t* result, KAGE_Graphics_VertexAttributeDescriptor* descriptor, size_t numAttributes); + bool AreVerticesEqual(const KAGE_Graphics_VertexAttributeDescriptor* desc, const KAGE_Graphics_VertexAttributeDescriptor* other, size_t numAttributes); + bool UsesVertexDescriptor(const KAGE_Graphics_ShaderBase& shader, const KAGE_Graphics_VertexAttributeDescriptor* vertexDescriptor, size_t numAttributes); } inline constexpr size_t ShaderUtils::GetFormatSize(uint32_t format) @@ -54,6 +57,7 @@ namespace ShaderUtils constexpr size_t CLIP_PANE_OFFSET = PIXELATION_AMOUNT_OFFSET + ShaderUtils::GetFormatSize((uint32_t)eVertexAttributeFormat::FLOAT); constexpr size_t VERTEX_SIZE = CLIP_PANE_OFFSET + ShaderUtils::GetFormatSize((uint32_t)eVertexAttributeFormat::VEC_3); constexpr size_t VERTEX_STRIDE = VERTEX_SIZE * sizeof(float); + constexpr size_t NUM_ATTRIBUTES = 8; void FillVertices(float* vertexBuffer, KAGE_Graphics_ImageBase& image, const ColorMod& colorMod); } @@ -71,6 +75,7 @@ namespace ShaderUtils constexpr size_t CHAMPION_COLOR_OFFSET = CLIP_PANE_OFFSET + ShaderUtils::GetFormatSize((uint32_t)eVertexAttributeFormat::VEC_3); constexpr size_t VERTEX_SIZE = CHAMPION_COLOR_OFFSET + ShaderUtils::GetFormatSize((uint32_t)eVertexAttributeFormat::VEC_4); constexpr size_t VERTEX_STRIDE = VERTEX_SIZE * sizeof(float); + constexpr size_t NUM_ATTRIBUTES = 9; void FillVertices(float* vertexBuffer, KAGE_Graphics_ImageBase& image, const ColorMod& colorMod, const ColorMod& championColor); } diff --git a/repentogon/Utils/ShaderUtils.hpp b/repentogon/Utils/ShaderUtils.hpp index c862806ea..edd586e02 100644 --- a/repentogon/Utils/ShaderUtils.hpp +++ b/repentogon/Utils/ShaderUtils.hpp @@ -2,7 +2,7 @@ #include "ShaderUtils.h" -inline size_t ShaderUtils::GetNumVertexAttributes(KAGE_Graphics_ImageBase_VertexAttributeDescriptor *descriptor) +inline size_t ShaderUtils::GetNumVertexAttributes(const KAGE_Graphics_VertexAttributeDescriptor *descriptor) { size_t i = 0; unsigned int format = descriptor[i].format; @@ -16,7 +16,7 @@ inline size_t ShaderUtils::GetNumVertexAttributes(KAGE_Graphics_ImageBase_Vertex return i; } -inline uint32_t ShaderUtils::GetVertexSize(KAGE_Graphics_ImageBase_VertexAttributeDescriptor* descriptor, size_t numAttributes) +inline uint32_t ShaderUtils::GetVertexSize(KAGE_Graphics_VertexAttributeDescriptor* descriptor, size_t numAttributes) { uint32_t stride = 0; @@ -28,12 +28,12 @@ inline uint32_t ShaderUtils::GetVertexSize(KAGE_Graphics_ImageBase_VertexAttribu return stride; } -inline uint32_t ShaderUtils::GetVertexStride(KAGE_Graphics_ImageBase_VertexAttributeDescriptor* descriptor, size_t numAttributes) +inline uint32_t ShaderUtils::GetVertexStride(KAGE_Graphics_VertexAttributeDescriptor* descriptor, size_t numAttributes) { return GetVertexSize(descriptor, numAttributes) * sizeof(float); } -inline void ShaderUtils::ToImageVertexDescriptor(uint8_t *result, KAGE_Graphics_ImageBase_VertexAttributeDescriptor *descriptor, size_t numAttributes) +inline void ShaderUtils::ToImageVertexDescriptor(uint8_t *result, KAGE_Graphics_VertexAttributeDescriptor *descriptor, size_t numAttributes) { for (size_t i = 0; i < numAttributes; i++) { @@ -41,6 +41,29 @@ inline void ShaderUtils::ToImageVertexDescriptor(uint8_t *result, KAGE_Graphics_ } } +inline bool ShaderUtils::AreVerticesEqual(const KAGE_Graphics_VertexAttributeDescriptor* desc, const KAGE_Graphics_VertexAttributeDescriptor* other, size_t numAttributes) +{ + for (size_t i = 0; i < numAttributes; i++) + { + if (desc[i] != other[i]) + { + return false; + } + } + + return true; +} + +inline bool ShaderUtils::UsesVertexDescriptor(const KAGE_Graphics_ShaderBase& shader, const KAGE_Graphics_VertexAttributeDescriptor* vertexDescriptor, size_t numAttributes) +{ + if (shader._numVertexAttributes != numAttributes) + { + return false; + } + + return ShaderUtils::AreVerticesEqual(shader._vertexAttributes, vertexDescriptor, numAttributes); +} + inline void ShaderUtils::ColorOffset::FillVertices(float* vertexBuffer, KAGE_Graphics_ImageBase& image, const ColorMod& colorMod) { assert(vertexBuffer != nullptr);