From ee3fbc68314865384d2a7d78a22221b5987979a4 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 7 Mar 2025 20:15:25 +0300 Subject: [PATCH 1/8] fix: incorrect canvas Y direction --- src/graphics/ui/elements/Canvas.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphics/ui/elements/Canvas.cpp b/src/graphics/ui/elements/Canvas.cpp index dd69ce22..be3fc145 100644 --- a/src/graphics/ui/elements/Canvas.cpp +++ b/src/graphics/ui/elements/Canvas.cpp @@ -16,5 +16,5 @@ void gui::Canvas::draw(const DrawContext& pctx, const Assets& assets) { auto batch = pctx.getBatch2D(); batch->texture(mTexture.get()); - batch->rect(pos.x, pos.y, size.x, size.y, 0, 0, 0, {}, false, true, col); + batch->rect(pos.x, pos.y, size.x, size.y, 0, 0, 0, {}, false, false, col); } From dca4f283ccb08fa4c4e0b3fb47c3a5b703b1d319 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 7 Mar 2025 20:15:59 +0300 Subject: [PATCH 2/8] add canvas:clear(...), canvas:line(...) --- .../lua/usertypes/lua_type_canvas.cpp | 147 +++++++++++++++--- 1 file changed, 129 insertions(+), 18 deletions(-) diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index acba361d..ad79f0ca 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -17,6 +17,7 @@ union RGBA { struct { uint8_t r, g, b, a; }; + uint8_t arr[4]; uint32_t rgba; }; @@ -49,32 +50,140 @@ static int l_at(State* L) { return 0; } +static RGBA get_rgba(State* L, int first) { + RGBA rgba {}; + rgba.a = 255; + switch (gettop(L) - first) { + case 0: + rgba.rgba = static_cast(tointeger(L, first)); + break; + case 3: + rgba.a = static_cast(tointeger(L, first + 3)); + [[fallthrough]]; + case 2: + rgba.r = static_cast(tointeger(L, first)); + rgba.g = static_cast(tointeger(L, first + 1)); + rgba.b = static_cast(tointeger(L, first + 2)); + break; + } + return rgba; +} + static int l_set(State* L) { auto x = static_cast(tointeger(L, 2)); auto y = static_cast(tointeger(L, 3)); if (auto pixel = get_at(L, x, y)) { - switch (gettop(L)) { - case 4: - pixel->rgba = static_cast(tointeger(L, 4)); - return 1; - case 6: - pixel->r = static_cast(tointeger(L, 4)); - pixel->g = static_cast(tointeger(L, 5)); - pixel->b = static_cast(tointeger(L, 6)); - pixel->a = 255; - return 1; - case 7: - pixel->r = static_cast(tointeger(L, 4)); - pixel->g = static_cast(tointeger(L, 5)); - pixel->b = static_cast(tointeger(L, 6)); - pixel->a = static_cast(tointeger(L, 7)); - return 1; - default: - return 0; + *pixel = get_rgba(L, 4); + } + return 0; +} + +static int l_clear(State* L) { + RGBA rgba {}; + if (gettop(L) > 1) { + rgba = get_rgba(L, 2); + } + if (auto canvas = touserdata(L, 1)) { + auto& image = canvas->data(); + ubyte* data = image.getData(); + size_t pixels = image.getWidth() * image.getHeight(); + const size_t channels = 4; + for (size_t i = 0; i < pixels * channels; i++) { + data[i] = rgba.arr[i % channels]; } } + return 0; +} +bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) { + const int left = 0; + const int right = width; + const int bottom = 0; + const int top = height; + + int dx = x2 - x1; + int dy = y2 - y1; + + float t0 = 0.0f; + float t1 = 1.0f; + + auto clip = [](int p, int q, float& t0, float& t1) { + if (p == 0) { + return q >= 0; + } + float t = static_cast(q) / p; + if (p < 0) { + if (t > t1) return false; + if (t > t0) t0 = t; + } else { + if (t < t0) return false; + if (t < t1) t1 = t; + } + return true; + }; + + if (!clip(-dx, x1 - left, t0, t1)) return false; + if (!clip( dx, right - x1, t0, t1)) return false; + if (!clip(-dy, y1 - bottom, t0, t1)) return false; + if (!clip( dy, top - y1, t0, t1)) return false; + + if (t1 < 1.0f) { + x2 = x1 + static_cast(std::round(t1 * dx)); + y2 = y1 + static_cast(std::round(t1 * dy)); + } + if (t0 > 0.0f) { + x1 = x1 + static_cast(std::round(t0 * dx)); + y1 = y1 + static_cast(std::round(t0 * dy)); + } + return true; +} + +static int l_line(State* L) { + int x1 = tointeger(L, 2); + int y1 = tointeger(L, 3); + + int x2 = tointeger(L, 4); + int y2 = tointeger(L, 5); + + RGBA rgba = get_rgba(L, 6); + if (auto canvas = touserdata(L, 1)) { + auto& image = canvas->data(); + ubyte* data = image.getData(); + uint width = image.getWidth(); + uint height = image.getHeight(); + const uint channels = 4; + + if ((x1 < 0 || x1 >= width || x2 < 0 || x2 >= width || + y1 < 0 || y1 >= height || y2 < 0 || y2 >= height) && + !clip_line(x1, y1, x2, y2, width, height)) { + return 0; + } + + int dx = glm::abs(x2 - x1); + int dy = -glm::abs(y2 - y1); + int sx = x1 < x2 ? 1 : -1; + int sy = y1 < y2 ? 1 : -1; + int err = dx + dy; + + while (true) { + size_t pos = (y1 * width + x1) * channels; + for (int i = 0; i < channels; i++) { + data[pos + i] = rgba.arr[i]; + } + if (x1 == x2 && y1 == y2) break; + + int e2 = 2 * err; + if (e2 >= dy) { + err += dy; + x1 += sx; + } + if (e2 <= dx) { + err += dx; + y1 += sy; + } + } + } return 0; } @@ -88,6 +197,8 @@ static int l_update(State* L) { static std::unordered_map methods { {"at", lua::wrap}, {"set", lua::wrap}, + {"clear", lua::wrap}, + {"line", lua::wrap}, {"update", lua::wrap} }; From e8c6c9a7b3f1799462231bcf4c120f994a7914b6 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 7 Mar 2025 20:31:50 +0300 Subject: [PATCH 3/8] update doc/*/scripting/ui.md --- doc/en/scripting/ui.md | 12 +++++++++--- doc/ru/scripting/ui.md | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index 441115c8..debbad4c 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -166,12 +166,18 @@ Properties: Methods: +Here, *color* can be specified in the following ways: +- rgba: int +- r: int, g: int, b: int +- r: int, g: int, b: int, a: int + | Method | Description | |----------------------------------------------------------|---------------------------------------------------------| | data:at(x: int, y: int) | returns an RGBA pixel at the given coordinates | -| data:set(x: int, y: int, rgba: int) | updates an RGBA pixel at the given coordinates | -| data:set(x: int, y: int, r: int, g: int, b: int) | updates an RGBA pixel at the given coordinates | -| data:set(x: int, y: int, r: int, g: int, b: int, a: int) | updates an RGBA pixel at the given coordinates | +| data:set(x: int, y: int, *color*) | updates an RGBA pixel at the given coordinates | +| data:line(x1: int, y1: int, x2: int, y2: int, *color*) | draws a line with the specified RGBA color | +| data:clear() | clears the canvas | +| data:clear(*color*) | fills the canvas with the specified RGBA color | | data:update() | applies changes to the canvas and uploads it to the GPU | diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index cca9790e..3422e972 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -167,12 +167,18 @@ document["worlds-panel"]:clear() Методы: +Здесь *цвет* может быть указан следующими способами: +- rgba: int +- r: int, g: int, b: int +- r: int, g: int, b: int, a: int + | Метод | Описание | |----------------------------------------------------------|-----------------------------------------------------| | data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам | -| data:set(x: int, y: int, rgba: int) | изменяет RGBA пиксель по указанным координатам | -| data:set(x: int, y: int, r: int, g: int, b: int) | изменяет RGBA пиксель по указанным координатам | -| data:set(x: int, y: int, r: int, g: int, b: int, a: int) | изменяет RGBA пиксель по указанным координатам | +| data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам | +| data:line(x1: int, y1: int, x2: int, y2: int, *цвет*) | рисует линию с указанным RGBA цветом | +| data:clear() | очищает холст | +| data:clear(*цвет*) | заполняет холст указанным RGBA цветом | | data:update() | применяет изменения и загружает холст в видеопамять | ## Inventory (inventory) From 5bbba7f39d1da5a76c4ace96a7102d6f78d9fa8e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 8 Mar 2025 13:36:43 +0300 Subject: [PATCH 4/8] add ImageData.drawLine --- src/graphics/core/ImageData.cpp | 94 +++++++++++++++ src/graphics/core/ImageData.hpp | 7 ++ .../lua/usertypes/lua_type_canvas.cpp | 113 ++++-------------- 3 files changed, 121 insertions(+), 93 deletions(-) diff --git a/src/graphics/core/ImageData.cpp b/src/graphics/core/ImageData.cpp index d6617c57..b5e05d1c 100644 --- a/src/graphics/core/ImageData.cpp +++ b/src/graphics/core/ImageData.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include ImageData::ImageData(ImageFormat format, uint width, uint height) @@ -93,6 +94,99 @@ void ImageData::blit(const ImageData* image, int x, int y) { throw std::runtime_error("mismatching format"); } +static bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) { + const int left = 0; + const int right = width; + const int bottom = 0; + const int top = height; + + int dx = x2 - x1; + int dy = y2 - y1; + + float t0 = 0.0f; + float t1 = 1.0f; + + auto clip = [](int p, int q, float& t0, float& t1) { + if (p == 0) { + return q >= 0; + } + float t = static_cast(q) / p; + if (p < 0) { + if (t > t1) return false; + if (t > t0) t0 = t; + } else { + if (t < t0) return false; + if (t < t1) t1 = t; + } + return true; + }; + + if (!clip(-dx, x1 - left, t0, t1)) return false; + if (!clip( dx, right - x1, t0, t1)) return false; + if (!clip(-dy, y1 - bottom, t0, t1)) return false; + if (!clip( dy, top - y1, t0, t1)) return false; + + if (t1 < 1.0f) { + x2 = x1 + static_cast(std::round(t1 * dx)); + y2 = y1 + static_cast(std::round(t1 * dy)); + } + if (t0 > 0.0f) { + x1 = x1 + static_cast(std::round(t0 * dx)); + y1 = y1 + static_cast(std::round(t0 * dy)); + } + return true; +} + +template +static void draw_line(ImageData& image, int x1, int y1, int x2, int y2, const glm::ivec4& color) { + ubyte* data = image.getData(); + uint width = image.getWidth(); + uint height = image.getHeight(); + + if ((x1 < 0 || x1 >= width || x2 < 0 || x2 >= width || + y1 < 0 || y1 >= height || y2 < 0 || y2 >= height) && + !clip_line(x1, y1, x2, y2, width, height)) { + return; + } + + int dx = std::abs(x2 - x1); + int dy = -std::abs(y2 - y1); + int sx = x1 < x2 ? 1 : -1; + int sy = y1 < y2 ? 1 : -1; + int err = dx + dy; + + while (true) { + size_t pos = (y1 * width + x1) * channels; + for (int i = 0; i < channels; i++) { + data[pos + i] = color[i]; + } + if (x1 == x2 && y1 == y2) break; + + int e2 = 2 * err; + if (e2 >= dy) { + err += dy; + x1 += sx; + } + if (e2 <= dx) { + err += dx; + y1 += sy; + } + } +} + +void ImageData::drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color) { + switch (format) { + case ImageFormat::rgb888: + draw_line<3>(*this, x1, y1, x2, y2, color); + break; + case ImageFormat::rgba8888: + draw_line<4>(*this, x1, y1, x2, y2, color); + break; + default: + break; + } +} + void ImageData::blitRGB_on_RGBA(const ImageData* image, int x, int y) { ubyte* source = image->getData(); uint srcwidth = image->getWidth(); diff --git a/src/graphics/core/ImageData.hpp b/src/graphics/core/ImageData.hpp index 328ff896..00e59f6a 100644 --- a/src/graphics/core/ImageData.hpp +++ b/src/graphics/core/ImageData.hpp @@ -2,6 +2,7 @@ #include "typedefs.hpp" +#include #include enum class ImageFormat { @@ -23,6 +24,7 @@ public: void flipX(); void flipY(); + void drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color); void blitRGB_on_RGBA(const ImageData* image, int x, int y); void blitMatchingFormat(const ImageData* image, int x, int y); void blit(const ImageData* image, int x, int y); @@ -44,6 +46,11 @@ public: uint getHeight() const { return height; } + + size_t getDataSize() const { + size_t channels = 3 + (format == ImageFormat::rgba8888); + return width * height * channels; + } }; std::unique_ptr add_atlas_margins(ImageData* image, int grid_size); diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index ad79f0ca..f3950c23 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -80,65 +80,26 @@ static int l_set(State* L) { } static int l_clear(State* L) { - RGBA rgba {}; - if (gettop(L) > 1) { - rgba = get_rgba(L, 2); + auto canvas = touserdata(L, 1); + if (canvas == nullptr) { + throw std::runtime_error("used canvas.clear instead of canvas:clear"); } - if (auto canvas = touserdata(L, 1)) { - auto& image = canvas->data(); - ubyte* data = image.getData(); - size_t pixels = image.getWidth() * image.getHeight(); - const size_t channels = 4; - for (size_t i = 0; i < pixels * channels; i++) { - data[i] = rgba.arr[i % channels]; - } + auto& image = canvas->data(); + ubyte* data = image.getData(); + RGBA rgba {}; + if (gettop(L) == 1) { + std::fill(data, data + image.getDataSize(), 0); + return 0; + } + rgba = get_rgba(L, 2); + size_t pixels = image.getWidth() * image.getHeight(); + const size_t channels = 4; + for (size_t i = 0; i < pixels * channels; i++) { + data[i] = rgba.arr[i % channels]; } return 0; } -bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) { - const int left = 0; - const int right = width; - const int bottom = 0; - const int top = height; - - int dx = x2 - x1; - int dy = y2 - y1; - - float t0 = 0.0f; - float t1 = 1.0f; - - auto clip = [](int p, int q, float& t0, float& t1) { - if (p == 0) { - return q >= 0; - } - float t = static_cast(q) / p; - if (p < 0) { - if (t > t1) return false; - if (t > t0) t0 = t; - } else { - if (t < t0) return false; - if (t < t1) t1 = t; - } - return true; - }; - - if (!clip(-dx, x1 - left, t0, t1)) return false; - if (!clip( dx, right - x1, t0, t1)) return false; - if (!clip(-dy, y1 - bottom, t0, t1)) return false; - if (!clip( dy, top - y1, t0, t1)) return false; - - if (t1 < 1.0f) { - x2 = x1 + static_cast(std::round(t1 * dx)); - y2 = y1 + static_cast(std::round(t1 * dy)); - } - if (t0 > 0.0f) { - x1 = x1 + static_cast(std::round(t0 * dx)); - y1 = y1 + static_cast(std::round(t0 * dy)); - } - return true; -} - static int l_line(State* L) { int x1 = tointeger(L, 2); int y1 = tointeger(L, 3); @@ -149,40 +110,9 @@ static int l_line(State* L) { RGBA rgba = get_rgba(L, 6); if (auto canvas = touserdata(L, 1)) { auto& image = canvas->data(); - ubyte* data = image.getData(); - uint width = image.getWidth(); - uint height = image.getHeight(); - const uint channels = 4; - - if ((x1 < 0 || x1 >= width || x2 < 0 || x2 >= width || - y1 < 0 || y1 >= height || y2 < 0 || y2 >= height) && - !clip_line(x1, y1, x2, y2, width, height)) { - return 0; - } - - int dx = glm::abs(x2 - x1); - int dy = -glm::abs(y2 - y1); - int sx = x1 < x2 ? 1 : -1; - int sy = y1 < y2 ? 1 : -1; - int err = dx + dy; - - while (true) { - size_t pos = (y1 * width + x1) * channels; - for (int i = 0; i < channels; i++) { - data[pos + i] = rgba.arr[i]; - } - if (x1 == x2 && y1 == y2) break; - - int e2 = 2 * err; - if (e2 >= dy) { - err += dy; - x1 += sx; - } - if (e2 <= dx) { - err += dx; - y1 += sy; - } - } + image.drawLine( + x1, y1, x2, y2, glm::ivec4 {rgba.r, rgba.g, rgba.b, rgba.a} + ); } return 0; } @@ -197,10 +127,9 @@ static int l_update(State* L) { static std::unordered_map methods { {"at", lua::wrap}, {"set", lua::wrap}, - {"clear", lua::wrap}, {"line", lua::wrap}, - {"update", lua::wrap} -}; + {"clear", lua::wrap}, + {"update", lua::wrap}}; static int l_meta_index(State* L) { auto texture = touserdata(L, 1); @@ -237,9 +166,7 @@ static int l_meta_newindex(State* L) { if (isnumber(L, 2) && isnumber(L, 3)) { if (auto pixel = get_at(data, static_cast(tointeger(L, 2)))) { pixel->rgba = static_cast(tointeger(L, 3)); - return 1; } - return 1; } return 0; } From 2af55a022716d142a0aa4c349bd9d63efc7cd5d8 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 8 Mar 2025 14:02:57 +0300 Subject: [PATCH 5/8] add Canvas constructor & add canvas:blit --- src/graphics/core/Atlas.cpp | 2 +- src/graphics/core/ImageData.cpp | 32 ++++++------ src/graphics/core/ImageData.hpp | 7 +-- src/logic/scripting/lua/lua_custom_types.hpp | 6 ++- .../lua/usertypes/lua_type_canvas.cpp | 50 ++++++++++++++++--- 5 files changed, 71 insertions(+), 26 deletions(-) diff --git a/src/graphics/core/Atlas.cpp b/src/graphics/core/Atlas.cpp index 039db4d2..be205d1e 100644 --- a/src/graphics/core/Atlas.cpp +++ b/src/graphics/core/Atlas.cpp @@ -97,7 +97,7 @@ std::unique_ptr AtlasBuilder::build(uint extrusion, bool prepare, uint ma uint y = rect.y; uint w = rect.width; uint h = rect.height; - canvas->blit(entry.image.get(), rect.x, rect.y); + canvas->blit(*entry.image, rect.x, rect.y); for (uint j = 0; j < extrusion; j++) { canvas->extrude(x - j, y - j, w + j*2, h + j*2); } diff --git a/src/graphics/core/ImageData.cpp b/src/graphics/core/ImageData.cpp index b5e05d1c..a2b1d524 100644 --- a/src/graphics/core/ImageData.cpp +++ b/src/graphics/core/ImageData.cpp @@ -81,13 +81,13 @@ void ImageData::flipY() { } } -void ImageData::blit(const ImageData* image, int x, int y) { - if (format == image->format) { +void ImageData::blit(const ImageData& image, int x, int y) { + if (format == image.format) { blitMatchingFormat(image, x, y); return; } if (format == ImageFormat::rgba8888 && - image->format == ImageFormat::rgb888) { + image.format == ImageFormat::rgb888) { blitRGB_on_RGBA(image, x, y); return; } @@ -187,10 +187,10 @@ void ImageData::drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color } } -void ImageData::blitRGB_on_RGBA(const ImageData* image, int x, int y) { - ubyte* source = image->getData(); - uint srcwidth = image->getWidth(); - uint srcheight = image->getHeight(); +void ImageData::blitRGB_on_RGBA(const ImageData& image, int x, int y) { + ubyte* source = image.getData(); + uint srcwidth = image.getWidth(); + uint srcheight = image.getHeight(); for (uint srcy = std::max(0, -y); srcy < std::min(srcheight, height - y); @@ -210,7 +210,7 @@ void ImageData::blitRGB_on_RGBA(const ImageData* image, int x, int y) { } } -void ImageData::blitMatchingFormat(const ImageData* image, int x, int y) { +void ImageData::blitMatchingFormat(const ImageData& image, int x, int y) { uint comps; switch (format) { case ImageFormat::rgb888: comps = 3; break; @@ -218,20 +218,24 @@ void ImageData::blitMatchingFormat(const ImageData* image, int x, int y) { default: throw std::runtime_error("only unsigned byte formats supported"); } - ubyte* source = image->getData(); - uint srcwidth = image->getWidth(); - uint srcheight = image->getHeight(); + ubyte* source = image.getData(); + + const uint width = this->width; + const uint height = this->height; + const uint src_width = image.getWidth(); + const uint src_height = image.getHeight(); + ubyte* data = this->data.get(); for (uint srcy = std::max(0, -y); - srcy < std::min(srcheight, height - y); + srcy < std::min(src_height, height - y); srcy++) { for (uint srcx = std::max(0, -x); - srcx < std::min(srcwidth, width - x); + srcx < std::min(src_width, width - x); srcx++) { uint dstx = srcx + x; uint dsty = srcy + y; uint dstidx = (dsty * width + dstx) * comps; - uint srcidx = (srcy * srcwidth + srcx) * comps; + uint srcidx = (srcy * src_width + srcx) * comps; for (uint c = 0; c < comps; c++) { data[dstidx + c] = source[srcidx + c]; } diff --git a/src/graphics/core/ImageData.hpp b/src/graphics/core/ImageData.hpp index 00e59f6a..213a352c 100644 --- a/src/graphics/core/ImageData.hpp +++ b/src/graphics/core/ImageData.hpp @@ -15,6 +15,9 @@ class ImageData { uint width; uint height; std::unique_ptr data; + + void blitRGB_on_RGBA(const ImageData& image, int x, int y); + void blitMatchingFormat(const ImageData& image, int x, int y); public: ImageData(ImageFormat format, uint width, uint height); ImageData(ImageFormat format, uint width, uint height, std::unique_ptr data); @@ -25,9 +28,7 @@ public: void flipY(); void drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color); - void blitRGB_on_RGBA(const ImageData* image, int x, int y); - void blitMatchingFormat(const ImageData* image, int x, int y); - void blit(const ImageData* image, int x, int y); + void blit(const ImageData& image, int x, int y); void extrude(int x, int y, int w, int h); void fixAlphaColor(); diff --git a/src/logic/scripting/lua/lua_custom_types.hpp b/src/logic/scripting/lua/lua_custom_types.hpp index 145cc4d8..a844b1f1 100644 --- a/src/logic/scripting/lua/lua_custom_types.hpp +++ b/src/logic/scripting/lua/lua_custom_types.hpp @@ -114,10 +114,14 @@ namespace lua { return *mData; } + [[nodiscard]] bool hasTexture() const { + return mTexture != nullptr; + } + static int createMetatable(lua::State*); inline static std::string TYPENAME = "Canvas"; private: - std::shared_ptr mTexture; + std::shared_ptr mTexture; // nullable std::shared_ptr mData; }; static_assert(!std::is_abstract()); diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index f3950c23..f041c1ff 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -79,12 +79,18 @@ static int l_set(State* L) { return 0; } -static int l_clear(State* L) { - auto canvas = touserdata(L, 1); - if (canvas == nullptr) { - throw std::runtime_error("used canvas.clear instead of canvas:clear"); +static LuaCanvas& require_canvas(State* L, int idx) { + if (const auto canvas = touserdata(L, idx)) { + return *canvas; } - auto& image = canvas->data(); + throw std::runtime_error( + "canvas expected as argument #" + std::to_string(idx) + ); +} + +static int l_clear(State* L) { + auto& canvas = require_canvas(L, 1); + auto& image = canvas.data(); ubyte* data = image.getData(); RGBA rgba {}; if (gettop(L) == 1) { @@ -117,9 +123,20 @@ static int l_line(State* L) { return 0; } +static int l_blit(State* L) { + auto& dst = require_canvas(L, 1); + auto& src = require_canvas(L, 2); + int dst_x = tointeger(L, 3); + int dst_y = tointeger(L, 4); + dst.data().blit(src.data(), dst_x, dst_y); + return 0; +} + static int l_update(State* L) { if (auto canvas = touserdata(L, 1)) { - canvas->texture().reload(canvas->data()); + if (canvas->hasTexture()) { + canvas->texture().reload(canvas->data()); + } } return 0; } @@ -128,8 +145,10 @@ static std::unordered_map methods { {"at", lua::wrap}, {"set", lua::wrap}, {"line", lua::wrap}, + {"blit", lua::wrap}, {"clear", lua::wrap}, - {"update", lua::wrap}}; + {"update", lua::wrap}, +}; static int l_meta_index(State* L) { auto texture = touserdata(L, 1); @@ -171,11 +190,28 @@ static int l_meta_newindex(State* L) { return 0; } +static int l_meta_meta_call(lua::State* L) { + auto size = glm::ivec2(tovec2(L, 2)); + if (size.x <= 0 || size.y <= 0) { + throw std::runtime_error("size must be positive"); + } + return newuserdata( + L, + nullptr, + std::make_shared(ImageFormat::rgba8888, size.x, size.y) + ); +} + int LuaCanvas::createMetatable(State* L) { createtable(L, 0, 3); pushcfunction(L, lua::wrap); setfield(L, "__index"); pushcfunction(L, lua::wrap); setfield(L, "__newindex"); + + createtable(L, 0, 1); + pushcfunction(L, lua::wrap); + setfield(L, "__call"); + setmetatable(L); return 1; } From 22d49c1c65fca50841e1fd9e4a6df3331199bb17 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 8 Mar 2025 14:08:44 +0300 Subject: [PATCH 6/8] update doc/*/scripting/ui.md --- doc/en/scripting/ui.md | 1 + doc/ru/scripting/ui.md | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index debbad4c..0df1ed72 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -176,6 +176,7 @@ Here, *color* can be specified in the following ways: | data:at(x: int, y: int) | returns an RGBA pixel at the given coordinates | | data:set(x: int, y: int, *color*) | updates an RGBA pixel at the given coordinates | | data:line(x1: int, y1: int, x2: int, y2: int, *color*) | draws a line with the specified RGBA color | +| data:blit(src: Canvas, dst_x: int, dst_y: int) | draws the src canvas at the specified coordinates | | data:clear() | clears the canvas | | data:clear(*color*) | fills the canvas with the specified RGBA color | | data:update() | applies changes to the canvas and uploads it to the GPU | diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index 3422e972..f3a3c44f 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -177,6 +177,7 @@ document["worlds-panel"]:clear() | data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам | | data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам | | data:line(x1: int, y1: int, x2: int, y2: int, *цвет*) | рисует линию с указанным RGBA цветом | +| data:blit(src: Canvas, dst_x: int, dst_y: int) | рисует src-холст на указанных координатах | | data:clear() | очищает холст | | data:clear(*цвет*) | заполняет холст указанным RGBA цветом | | data:update() | применяет изменения и загружает холст в видеопамять | From 6078a8802e8dd00149865b592e104a31636ca636 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 8 Mar 2025 14:55:17 +0300 Subject: [PATCH 7/8] add canvas:set_data --- doc/en/scripting/ui.md | 1 + doc/ru/scripting/ui.md | 19 +++++++++--------- src/graphics/ui/elements/Canvas.hpp | 2 +- .../lua/usertypes/lua_type_canvas.cpp | 20 +++++++++++++++++++ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index 0df1ed72..4cac91b7 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -180,6 +180,7 @@ Here, *color* can be specified in the following ways: | data:clear() | clears the canvas | | data:clear(*color*) | fills the canvas with the specified RGBA color | | data:update() | applies changes to the canvas and uploads it to the GPU | +| data:set_data(data: table) | replaces pixel data (width * height * 4 numbers) | ## Inventory diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index f3a3c44f..137ff57b 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -172,15 +172,16 @@ document["worlds-panel"]:clear() - r: int, g: int, b: int - r: int, g: int, b: int, a: int -| Метод | Описание | -|----------------------------------------------------------|-----------------------------------------------------| -| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам | -| data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам | -| data:line(x1: int, y1: int, x2: int, y2: int, *цвет*) | рисует линию с указанным RGBA цветом | -| data:blit(src: Canvas, dst_x: int, dst_y: int) | рисует src-холст на указанных координатах | -| data:clear() | очищает холст | -| data:clear(*цвет*) | заполняет холст указанным RGBA цветом | -| data:update() | применяет изменения и загружает холст в видеопамять | +| Метод | Описание | +|----------------------------------------------------------|------------------------------------------------------| +| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам | +| data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам | +| data:line(x1: int, y1: int, x2: int, y2: int, *цвет*) | рисует линию с указанным RGBA цветом | +| data:blit(src: Canvas, dst_x: int, dst_y: int) | рисует src-холст на указанных координатах | +| data:clear() | очищает холст | +| data:clear(*цвет*) | заполняет холст указанным RGBA цветом | +| data:update() | применяет изменения и загружает холст в видеопамять | +| data:set_data(data: table) | заменяет данные пикселей (ширина * высота * 4 чисел) | ## Inventory (inventory) diff --git a/src/graphics/ui/elements/Canvas.hpp b/src/graphics/ui/elements/Canvas.hpp index ae1f02b0..adfb5e2a 100644 --- a/src/graphics/ui/elements/Canvas.hpp +++ b/src/graphics/ui/elements/Canvas.hpp @@ -26,4 +26,4 @@ namespace gui { std::shared_ptr<::Texture> mTexture; std::shared_ptr mData; }; -} \ No newline at end of file +} diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index f041c1ff..944f62a2 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -132,6 +132,25 @@ static int l_blit(State* L) { return 0; } +static int l_set_data(State* L) { + auto& canvas = require_canvas(L, 1); + auto& image = canvas.data(); + auto data = image.getData(); + int len = objlen(L, 2); + if (len < image.getDataSize()) { + throw std::runtime_error( + "data size mismatch expected " + + std::to_string(image.getDataSize()) + ", got " + std::to_string(len) + ); + } + for (size_t i = 0; i < len; i++) { + rawgeti(L, i + 1, 2); + data[i] = tointeger(L, -1); + pop(L); + } + return 0; +} + static int l_update(State* L) { if (auto canvas = touserdata(L, 1)) { if (canvas->hasTexture()) { @@ -148,6 +167,7 @@ static std::unordered_map methods { {"blit", lua::wrap}, {"clear", lua::wrap}, {"update", lua::wrap}, + {"set_data", lua::wrap}, }; static int l_meta_index(State* L) { From e48216da4016532b096794548f49ab97ef337a99 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 8 Mar 2025 17:33:56 +0300 Subject: [PATCH 8/8] experimental optimize canvas:set_data using ffi --- res/scripts/stdmin.lua | 28 +++++++++++++++++++ src/logic/scripting/lua/lua_engine.cpp | 19 +++++++------ src/logic/scripting/lua/lua_util.cpp | 8 ++++-- src/logic/scripting/lua/lua_wrapper.hpp | 6 +++- .../lua/usertypes/lua_type_canvas.cpp | 11 +++++++- src/logic/scripting/scripting.cpp | 2 +- 6 files changed, 61 insertions(+), 13 deletions(-) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index ce592277..e2e4d3c7 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -1,3 +1,31 @@ +local _ffi = ffi +ffi = nil + +-- Lua has no parallelizm, also _set_data does not call any lua functions so +-- may be reused one global ffi buffer per lua_State +local canvas_ffi_buffer +local canvas_ffi_buffer_size = 0 + +function __vc_Canvas_set_data(self, data) + if type(data) == "cdata" then + self:_set_data(tostring(_ffi.cast("uintptr_t", data))) + end + local width = self.width + local height = self.height + + local size = width * height * 4 + if size > canvas_ffi_buffer_size then + canvas_ffi_buffer = _ffi.new( + string.format("unsigned char[%s]", size) + ) + canvas_ffi_buffer_size = size + end + for i=0, size - 1 do + canvas_ffi_buffer[i] = data[i + 1] + end + self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer))) +end + -- Check if given table is an array function is_array(x) if #x > 0 then diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 251f789b..09c0f424 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -88,14 +88,17 @@ static void create_libs(State* L, StateType stateType) { void lua::init_state(State* L, StateType stateType) { // Allowed standard libraries - pop(L, luaopen_base(L)); - pop(L, luaopen_math(L)); - pop(L, luaopen_string(L)); - pop(L, luaopen_table(L)); - pop(L, luaopen_debug(L)); - pop(L, luaopen_jit(L)); - pop(L, luaopen_bit(L)); - pop(L, luaopen_os(L)); + luaL_openlibs(L); + + if (getglobal(L, "require")) { + pushstring(L, "ffi"); + if (call_nothrow(L, 1, 1)) { + setglobal(L, "ffi"); + } + } + pushnil(L); + setglobal(L, "io"); + const char* removed_os[] { "execute", "exit", "remove", "rename", "setlocale", "tmpname", nullptr}; remove_lib_funcs(L, "os", removed_os); diff --git a/src/logic/scripting/lua/lua_util.cpp b/src/logic/scripting/lua/lua_util.cpp index d740165b..8cd77fb1 100644 --- a/src/logic/scripting/lua/lua_util.cpp +++ b/src/logic/scripting/lua/lua_util.cpp @@ -163,20 +163,23 @@ int lua::call(State* L, int argc, int nresults) { int handler_pos = gettop(L) - argc; pushcfunction(L, l_error_handler); insert(L, handler_pos); + int top = gettop(L); if (lua_pcall(L, argc, nresults, handler_pos)) { std::string log = tostring(L, -1); pop(L); remove(L, handler_pos); throw luaerror(log); } + int added = gettop(L) - (top - argc - 1); remove(L, handler_pos); - return nresults == -1 ? 1 : nresults; + return added; } int lua::call_nothrow(State* L, int argc, int nresults) { int handler_pos = gettop(L) - argc; pushcfunction(L, l_error_handler); insert(L, handler_pos); + int top = gettop(L); if (lua_pcall(L, argc, -1, handler_pos)) { auto errorstr = tostring(L, -1); if (errorstr) { @@ -188,8 +191,9 @@ int lua::call_nothrow(State* L, int argc, int nresults) { remove(L, handler_pos); return 0; } + int added = gettop(L) - (top - argc - 1); remove(L, handler_pos); - return 1; + return added; } void lua::dump_stack(State* L) { diff --git a/src/logic/scripting/lua/lua_wrapper.hpp b/src/logic/scripting/lua/lua_wrapper.hpp index 417314cd..f82f5bd4 100644 --- a/src/logic/scripting/lua/lua_wrapper.hpp +++ b/src/logic/scripting/lua/lua_wrapper.hpp @@ -25,7 +25,8 @@ namespace lua { if (n < 0) { abort(); } - if (lua_gettop(L) < n) { + int top = lua_gettop(L); + if (top < n) { abort(); } #endif @@ -63,6 +64,9 @@ namespace lua { inline void rawseti(lua::State* L, int n, int idx = -2) { lua_rawseti(L, idx, n); } + inline void rawset(lua::State* L, int idx = -3) { + lua_rawset(L, idx); + } inline int createtable(lua::State* L, int narr, int nrec) { lua_createtable(L, narr, nrec); diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index 944f62a2..a4292f56 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -136,6 +136,12 @@ static int l_set_data(State* L) { auto& canvas = require_canvas(L, 1); auto& image = canvas.data(); auto data = image.getData(); + + if (lua::isstring(L, 2)) { + auto ptr = reinterpret_cast(std::stoull(lua::tostring(L, 2))); + std::memcpy(data, ptr, image.getDataSize()); + return 0; + } int len = objlen(L, 2); if (len < image.getDataSize()) { throw std::runtime_error( @@ -167,7 +173,7 @@ static std::unordered_map methods { {"blit", lua::wrap}, {"clear", lua::wrap}, {"update", lua::wrap}, - {"set_data", lua::wrap}, + {"_set_data", lua::wrap}, }; static int l_meta_index(State* L) { @@ -189,6 +195,9 @@ static int l_meta_index(State* L) { if (!strcmp(name, "height")) { return pushinteger(L, data.getHeight()); } + if (!strcmp(name, "set_data")) { + return getglobal(L, "__vc_Canvas_set_data"); + } if (auto func = methods.find(tostring(L, 2)); func != methods.end()) { return pushcfunction(L, func->second); } diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 92ed66a2..66606776 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -120,7 +120,7 @@ std::unique_ptr scripting::start_coroutine( lua::loadbuffer(L, 0, source, script.name()); if (lua::call(L, 1)) { int id = lua::tointeger(L, -1); - lua::pop(L, 2); + lua::pop(L, 1); return std::make_unique(L, id); } lua::pop(L);