From 7f46ac13fa32baf9116567e1bb39cae8c33531eb Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Thu, 12 Mar 2026 01:07:46 +0100 Subject: [PATCH] rendering improvements --- implementations/Crafter.Graphics-Device.cpp | 10 +- implementations/Crafter.Graphics-Font.cpp | 2 +- .../Crafter.Graphics-GridElement.cpp | 5 - .../Crafter.Graphics-RenderingElement3D.cpp | 2 +- .../Crafter.Graphics-Transform2D.cpp | 7 - implementations/Crafter.Graphics-Window.cpp | 69 +++++- interfaces/Crafter.Graphics-Device.cppm | 1 + interfaces/Crafter.Graphics-GridElement.cppm | 1 - interfaces/Crafter.Graphics-MouseElement.cppm | 1 + .../Crafter.Graphics-RenderingElement2D.cppm | 198 +++++++++++------- ...after.Graphics-RenderingElement2DBase.cppm | 14 +- interfaces/Crafter.Graphics-Rendertarget.cppm | 178 ++++++++-------- interfaces/Crafter.Graphics-Transform2D.cppm | 1 - interfaces/Crafter.Graphics-Window.cppm | 8 + 14 files changed, 307 insertions(+), 190 deletions(-) diff --git a/implementations/Crafter.Graphics-Device.cpp b/implementations/Crafter.Graphics-Device.cpp index 55ca899..644fb67 100644 --- a/implementations/Crafter.Graphics-Device.cpp +++ b/implementations/Crafter.Graphics-Device.cpp @@ -340,10 +340,12 @@ void Device::PointerListenerHandleMotion(void* data, wl_pointer* wl_pointer, std if(element) { if(Device::focusedWindow->currentMousePos.x >= element->scaled.position.x && Device::focusedWindow->currentMousePos.x <= element->scaled.position.x+element->scaled.size.x && Device::focusedWindow->currentMousePos.y > element->scaled.position.y && Device::focusedWindow->currentMousePos.y < element->scaled.position.y+element->scaled.size.y) { element->onMouseMove.Invoke(); - if(!(Device::focusedWindow->lastMousePos.x >= element->scaled.position.x && Device::focusedWindow->lastMousePos.x <= element->scaled.position.x+element->scaled.size.x && Device::focusedWindow->lastMousePos.y > element->scaled.position.y && Device::focusedWindow->lastMousePos.y < element->scaled.position.y+element->scaled.size.y)) { + if(!element->mouseHover) { + element->mouseHover = true; element->onMouseEnter.Invoke(); } - } else if(Device::focusedWindow->lastMousePos.x >= element->scaled.position.x && Device::focusedWindow->lastMousePos.x <= element->scaled.position.x+element->scaled.size.x && Device::focusedWindow->lastMousePos.y > element->scaled.position.y && Device::focusedWindow->lastMousePos.y < element->scaled.position.y+element->scaled.size.y) { + } else if(element->mouseHover) { + element->mouseHover = false; element->onMouseLeave.Invoke(); } } @@ -352,8 +354,12 @@ void Device::PointerListenerHandleMotion(void* data, wl_pointer* wl_pointer, std } void Device::PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, std::uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { + Device::wlPointer = wl_pointer; for(Window* window : windows) { if(window->surface == surface) { + if(window->cursorSurface != nullptr) { + wl_pointer_set_cursor(wl_pointer, serial, window->cursorSurface, 0, 0); + } focusedWindow = window; window->onMouseEnter.Invoke(); return; diff --git a/implementations/Crafter.Graphics-Font.cpp b/implementations/Crafter.Graphics-Font.cpp index 9a09e62..e101528 100644 --- a/implementations/Crafter.Graphics-Font.cpp +++ b/implementations/Crafter.Graphics-Font.cpp @@ -29,7 +29,7 @@ using namespace Crafter; Font::Font(const std::filesystem::path& fontFilePath) { // 1. Load the font file into memory - std::ifstream fontFile("inter.ttf", std::ios::binary | std::ios::ate); + std::ifstream fontFile(fontFilePath, std::ios::binary | std::ios::ate); if (!fontFile.is_open()) { std::cerr << "Failed to open font file\n"; } diff --git a/implementations/Crafter.Graphics-GridElement.cpp b/implementations/Crafter.Graphics-GridElement.cpp index 39d7f3e..8b691f8 100644 --- a/implementations/Crafter.Graphics-GridElement.cpp +++ b/implementations/Crafter.Graphics-GridElement.cpp @@ -57,11 +57,6 @@ void GridElement::UpdatePositionScaled(RendertargetBase& window) { } } -void GridElement::UpdatePosition(RendertargetBase& window) { - ScaleElement(window.transform); - UpdatePositionScaled(window); -} - void GridElement::UpdatePosition(RendertargetBase& window, Transform2D& parent) { ScaleElement(parent); UpdatePositionScaled(window); diff --git a/implementations/Crafter.Graphics-RenderingElement3D.cpp b/implementations/Crafter.Graphics-RenderingElement3D.cpp index 523175f..c1c3a0f 100644 --- a/implementations/Crafter.Graphics-RenderingElement3D.cpp +++ b/implementations/Crafter.Graphics-RenderingElement3D.cpp @@ -25,9 +25,9 @@ module Crafter.Graphics:RenderingElement3D_impl; import :RenderingElement3D; import std; +#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN using namespace Crafter; -#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN std::vector RenderingElement3D::elements; diff --git a/implementations/Crafter.Graphics-Transform2D.cpp b/implementations/Crafter.Graphics-Transform2D.cpp index 86b9e09..f0f8b51 100644 --- a/implementations/Crafter.Graphics-Transform2D.cpp +++ b/implementations/Crafter.Graphics-Transform2D.cpp @@ -36,13 +36,6 @@ Transform2D::Transform2D(Anchor2D anchor) : anchor(anchor) { } -void Transform2D::UpdatePosition(RendertargetBase& window) { - ScaleElement(window.transform); - for(Transform2D* child : children) { - child->UpdatePosition(window, *this); - } -} - void Transform2D::UpdatePosition(RendertargetBase& window, Transform2D& parent) { ScaleElement(parent); for(Transform2D* child : children) { diff --git a/implementations/Crafter.Graphics-Window.cpp b/implementations/Crafter.Graphics-Window.cpp index 7aa7f1d..3ddf19e 100644 --- a/implementations/Crafter.Graphics-Window.cpp +++ b/implementations/Crafter.Graphics-Window.cpp @@ -69,7 +69,6 @@ using namespace Crafter; #ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND -#ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE void randname(char *buf) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); @@ -113,7 +112,6 @@ int create_shm_file(off_t size) { return fd; } #endif -#endif #ifdef CRAFTER_GRAPHICS_WINDOW_WIN32 CrafterKeys vk_to_crafter_key(WPARAM vk) @@ -304,10 +302,12 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if(element) { if(window->currentMousePos.x >= element->scaled.position.x && window->currentMousePos.x <= element->scaled.position.x+element->scaled.size.x && window->currentMousePos.y > element->scaled.position.y && window->currentMousePos.y < element->scaled.position.y+element->scaled.size.y) { element->onMouseMove.Invoke(); - if(!(window->lastMousePos.x >= element->scaled.position.x && window->lastMousePos.x <= element->scaled.position.x+element->scaled.size.x && window->lastMousePos.y > element->scaled.position.y && window->lastMousePos.y < element->scaled.position.y+element->scaled.size.y)) { + if(!element->mouseHover) { + element->mouseHover = true; element->onMouseEnter.Invoke(); } - } else if(window->lastMousePos.x >= element->scaled.position.x && window->lastMousePos.x <= element->scaled.position.x+element->scaled.size.x && window->lastMousePos.y > element->scaled.position.y && window->lastMousePos.y < element->scaled.position.y+element->scaled.size.y) { + } else if(element->mouseHover) { + element->mouseHover = false element->onMouseLeave.Invoke(); } } @@ -380,7 +380,7 @@ Window::Window(std::uint32_t width, std::uint32_t height, const std::string_view } #ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE -Window::Window(std::uint32_t width, std::uint32_t height) : width(width), height(height) : Rendertarget(width, height) { +Window::Window(std::uint32_t width, std::uint32_t height) : width(width), height(height), renderer(width, height) { #else Window::Window(std::uint32_t width, std::uint32_t height) : width(width), height(height) { #endif @@ -420,9 +420,7 @@ Window::Window(std::uint32_t width, std::uint32_t height) : width(width), height } // Map the shared memory file - renderer.buffer = reinterpret_cast*>(mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); - renderer.sizeX = width; - renderer.sizeY = height; + renderer.buffer = reinterpret_cast*>(mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); if (renderer.buffer == MAP_FAILED) { throw std::runtime_error("mmap failed"); } @@ -563,6 +561,61 @@ void Window::SetTitle(const std::string_view title) { #endif } +void Window::SetCusorImage(std::uint16_t sizeX, std::uint16_t sizeY) { + new (&cursorRenderer) Rendertarget(sizeX, sizeY); + #ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND + if(cursorSurface == nullptr) { + cursorSurface = wl_compositor_create_surface(Device::compositor); + } else { + wl_buffer_destroy(cursorWlBuffer); + munmap(cursorRenderer.buffer, cursorBufferOldSize); + } + + int stride = sizeX * 4; + int size = stride * sizeY; + cursorBufferOldSize = size; + + // Allocate a shared memory file with the right size + int fd = create_shm_file(size); + if (fd < 0) { + throw std::runtime_error(std::format("creating a buffer file for {}B failed", size)); + } + + cursorRenderer.buffer = reinterpret_cast*>(mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); + if (cursorRenderer.buffer == MAP_FAILED) { + throw std::runtime_error("mmap failed"); + } + + wl_shm_pool *pool = wl_shm_create_pool(Device::shm, fd, size); + cursorWlBuffer = wl_shm_pool_create_buffer(pool, 0, sizeX, sizeY, stride, WL_SHM_FORMAT_ARGB8888); + wl_shm_pool_destroy(pool); + + close(fd); + + wl_surface_attach(cursorSurface, cursorWlBuffer, 0, 0); + wl_surface_damage(cursorSurface, 0, 0, sizeX, sizeY); + wl_surface_commit(cursorSurface); + #endif +} + +void Window::SetCusorImageDefault() { + #ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND + wl_buffer_destroy(cursorWlBuffer); + wl_surface_destroy(cursorSurface); + cursorSurface = nullptr; + #endif +} + +void Window::UpdateCursorImage() { + cursorRenderer.Render(); + for(std::uint32_t i = 0; i < cursorBufferOldSize / 4; i++) { + std::swap(cursorRenderer.buffer[i].b, cursorRenderer.buffer[i].r); + } + wl_surface_attach(cursorSurface, cursorWlBuffer, 0, 0); + wl_surface_damage(cursorSurface, 0, 0, 9999999, 99999999); + wl_surface_commit(cursorSurface); +} + void Window::StartSync() { #ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND while (open && wl_display_dispatch(Device::display) != -1) { diff --git a/interfaces/Crafter.Graphics-Device.cppm b/interfaces/Crafter.Graphics-Device.cppm index 84ee9a0..0c5dd50 100644 --- a/interfaces/Crafter.Graphics-Device.cppm +++ b/interfaces/Crafter.Graphics-Device.cppm @@ -52,6 +52,7 @@ export namespace Crafter { inline static xkb_context* xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); inline static xkb_state* xkb_state; inline static std::vector windows; + inline static wl_pointer* wlPointer; static void seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities); static void xdg_surface_handle_preferred_scale(void* data, wp_fractional_scale_v1*, std::uint32_t scale); diff --git a/interfaces/Crafter.Graphics-GridElement.cppm b/interfaces/Crafter.Graphics-GridElement.cppm index 4de6cbb..c18d125 100644 --- a/interfaces/Crafter.Graphics-GridElement.cppm +++ b/interfaces/Crafter.Graphics-GridElement.cppm @@ -33,7 +33,6 @@ export namespace Crafter { std::int32_t paddingY; GridElement(std::uint32_t columns, std::uint32_t rows, std::int32_t spacingX, std::int32_t spacingY, std::int32_t paddingX, std::int32_t paddingY, Anchor2D anchor); void UpdatePositionScaled(RendertargetBase& window); - void UpdatePosition(RendertargetBase& window) override; void UpdatePosition(RendertargetBase& window, Transform2D& parent) override; }; } \ No newline at end of file diff --git a/interfaces/Crafter.Graphics-MouseElement.cppm b/interfaces/Crafter.Graphics-MouseElement.cppm index cf4f17c..07406d0 100644 --- a/interfaces/Crafter.Graphics-MouseElement.cppm +++ b/interfaces/Crafter.Graphics-MouseElement.cppm @@ -35,6 +35,7 @@ export namespace Crafter { Event onMouseLeftHold; Event onMouseRightRelease; Event onMouseLeftRelease; + bool mouseHover = false; MouseElement(); MouseElement(Window& window); diff --git a/interfaces/Crafter.Graphics-RenderingElement2D.cppm b/interfaces/Crafter.Graphics-RenderingElement2D.cppm index 581019a..98a6c77 100644 --- a/interfaces/Crafter.Graphics-RenderingElement2D.cppm +++ b/interfaces/Crafter.Graphics-RenderingElement2D.cppm @@ -29,8 +29,8 @@ import :Types; import :Window; export namespace Crafter { - template requires ((!Rotating || Scaling) && (!Owning || Scaling)) - struct RenderingElement2D : RenderingElement2DBase, ScalingBase, RotatingBase { + template requires ((!Rotating || Scaling) && (!Owning || Scaling)) + struct RenderingElement2D : RenderingElement2DBase, ScalingBase, RotatingBase { RenderingElement2D() = default; RenderingElement2D(Anchor2D anchor, OpaqueType opaque) : RenderingElement2DBase(anchor, opaque) { @@ -38,22 +38,22 @@ export namespace Crafter { RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t rotation) requires(Rotating) : RenderingElement2DBase(anchor, opaque), RotatingBase(rotation) { } - RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, Vector* scalingBuffer) requires(Scaling && !Owning) : RenderingElement2DBase(anchor, opaque), ScalingBase(bufferWidth, bufferHeight, scalingBuffer) { + RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, Vector* scalingBuffer) requires(Scaling && !Owning) : RenderingElement2DBase(anchor, opaque), ScalingBase(bufferWidth, bufferHeight, scalingBuffer) { } - RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, Vector* scalingBuffer, std::uint32_t rotation) requires(Scaling && !Owning && Rotating) : RenderingElement2DBase(anchor, opaque), ScalingBase(bufferWidth, bufferHeight, scalingBuffer), RotatingBase(rotation) { + RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, Vector* scalingBuffer, std::uint32_t rotation) requires(Scaling && !Owning && Rotating) : RenderingElement2DBase(anchor, opaque), ScalingBase(bufferWidth, bufferHeight, scalingBuffer), RotatingBase(rotation) { } - RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight) requires(Owning) : RenderingElement2DBase(anchor, opaque), ScalingBase(bufferWidth, bufferHeight) { + RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight) requires(Owning) : RenderingElement2DBase(anchor, opaque), ScalingBase(bufferWidth, bufferHeight) { } - RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, std::uint32_t rotation) requires(Owning && Rotating) : RenderingElement2DBase(anchor, opaque), ScalingBase(bufferWidth, bufferHeight) , RotatingBase(rotation) { + RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, std::uint32_t rotation) requires(Owning && Rotating) : RenderingElement2DBase(anchor, opaque), ScalingBase(bufferWidth, bufferHeight) , RotatingBase(rotation) { } - RenderingElement2D(Anchor2D anchor, TextureAsset> texture) requires(!Owning && Scaling) : RenderingElement2DBase(anchor, texture.opaque), ScalingBase(texture.sizeX, texture.sizeY, texture.pixels) { + RenderingElement2D(Anchor2D anchor, TextureAsset>& texture) requires(!Owning && Scaling) : RenderingElement2DBase(anchor, texture.opaque), ScalingBase(texture.pixels.data(), texture.sizeX, texture.sizeY) { } - RenderingElement2D(Anchor2D anchor, TextureAsset> texture, std::uint32_t rotation) requires(!Owning && Scaling && Rotating) : RenderingElement2DBase(anchor, texture.opaque), ScalingBase(texture.sizeX, texture.sizeY, texture.pixels), RotatingBase(rotation) { + RenderingElement2D(Anchor2D anchor, TextureAsset>& texture, std::uint32_t rotation) requires(!Owning && Scaling && Rotating) : RenderingElement2DBase(anchor, texture.opaque), ScalingBase(texture.pixels.data(), texture.sizeX, texture.sizeY), RotatingBase(rotation) { } @@ -62,10 +62,10 @@ export namespace Crafter { void ScaleNearestNeighbor() requires(Scaling) { for (std::uint32_t y = 0; y < scaled.size.y; y++) { - std::uint32_t srcY = y * ScalingBase::bufferHeight / scaled.size.y; + std::uint32_t srcY = y * ScalingBase::bufferHeight / scaled.size.y; for (std::uint32_t x = 0; x < scaled.size.x; x++) { - std::uint32_t srcX = x * ScalingBase::bufferWidth / scaled.size.x; - buffer[y * scaled.size.x + x] = ScalingBase::scalingBuffer[srcY * ScalingBase::bufferWidth + srcX]; + std::uint32_t srcX = x * ScalingBase::bufferWidth / scaled.size.x; + buffer[y * scaled.size.x + x] = ScalingBase::scalingBuffer[srcY * ScalingBase::bufferWidth + srcX]; } } } @@ -97,15 +97,15 @@ export namespace Crafter { const float dstCy = (static_cast(scaled.size.y) - 1.0) * 0.5; // Source center - const float srcCx = (static_cast(ScalingBase::bufferWidth) - 1.0) * 0.5; - const float srcCy = (static_cast(ScalingBase::bufferHeight) - 1.0) * 0.5; + const float srcCx = (static_cast(ScalingBase::bufferWidth) - 1.0) * 0.5; + const float srcCy = (static_cast(ScalingBase::bufferHeight) - 1.0) * 0.5; const float c = std::cos(RotatingBase::rotation); const float s = std::sin(RotatingBase::rotation); // Scale factors (destination → source) - const float scaleX = static_cast(ScalingBase::bufferWidth) / dstWidth; - const float scaleY = static_cast(ScalingBase::bufferHeight) / dstHeight; + const float scaleX = static_cast(ScalingBase::bufferWidth) / dstWidth; + const float scaleY = static_cast(ScalingBase::bufferHeight) / dstHeight; for (std::uint32_t yB = 0; yB < scaled.size.y; ++yB) { for (std::uint32_t xB = 0; xB < scaled.size.x; ++xB) { @@ -122,8 +122,8 @@ export namespace Crafter { const std::int32_t srcX = static_cast(std::round(sx)); const std::int32_t srcY = static_cast(std::round(sy)); - if (srcX >= 0 && srcX < ScalingBase::bufferWidth && srcY >= 0 && srcY < ScalingBase::bufferHeight) { - buffer[yB * scaled.size.x + xB] = ScalingBase::scalingBuffer[srcY * ScalingBase::bufferWidth + srcX]; + if (srcX >= 0 && srcX < ScalingBase::bufferWidth && srcY >= 0 && srcY < ScalingBase::bufferHeight) { + buffer[yB * scaled.size.x + xB] = ScalingBase::scalingBuffer[srcY * ScalingBase::bufferWidth + srcX]; } } } @@ -139,13 +139,14 @@ export namespace Crafter { } else if(oldScale.position.x != scaled.position.x || oldScale.position.y != scaled.position.y) { renderer.AddDirtyRect(oldScale); renderer.AddDirtyRect(scaled); - if(ScalingBase::bufferUpdated) { + if(ScalingBase::bufferUpdated) { ScaleNearestNeighbor(); - ScalingBase::bufferUpdated = false; + ScalingBase::bufferUpdated = false; } - } else if(ScalingBase::bufferUpdated) { + } else if(ScalingBase::bufferUpdated) { ScaleNearestNeighbor(); - ScalingBase::bufferUpdated = false; + ScalingBase::bufferUpdated = false; + renderer.AddDirtyRect(scaled); } } else if constexpr(Rotating) { if(oldScale.size.x != scaled.size.x || oldScale.size.y != scaled.size.y) { @@ -156,15 +157,16 @@ export namespace Crafter { } else if(oldScale.position.x != scaled.position.x || oldScale.position.y != scaled.position.y) { renderer.AddDirtyRect(oldScale); renderer.AddDirtyRect(scaled); - if(ScalingBase::bufferUpdated || RotatingBase::rotationUpdated) { + if(ScalingBase::bufferUpdated || RotatingBase::rotationUpdated) { ScaleRotating(); - ScalingBase::bufferUpdated = false; + ScalingBase::bufferUpdated = false; RotatingBase::rotationUpdated = false; } - } else if(ScalingBase::bufferUpdated || RotatingBase::rotationUpdated) { + } else if(ScalingBase::bufferUpdated || RotatingBase::rotationUpdated) { ScaleRotating(); - ScalingBase::bufferUpdated = false; + ScalingBase::bufferUpdated = false; RotatingBase::rotationUpdated = false; + renderer.AddDirtyRect(scaled); } } else { if(oldScale.size.x != scaled.size.x || oldScale.size.y != scaled.size.y) { @@ -179,15 +181,6 @@ export namespace Crafter { } } - void UpdatePosition(RendertargetBase& window) override { - ScaleData2D oldScale = scaled; - ScaleElement(window.transform); - UpdatePosition(window, oldScale); - for(Transform2D* child : children) { - child->UpdatePosition(window, *this); - } - } - void UpdatePosition(RendertargetBase& window, Transform2D& parent) override { ScaleData2D oldScale = scaled; ScaleElement(parent); @@ -197,7 +190,7 @@ export namespace Crafter { } } - std::vector ResizeText(Window& window, const std::string_view text, float size, Font& font, TextOverflowMode overflowMode = TextOverflowMode::Clip, TextScaleMode scaleMode = TextScaleMode::None, Transform2D* parent = nullptr) { + std::vector ResizeText(RendertargetBase& window, Transform2D& parent, const std::string_view text, float& size, Font& font, TextOverflowMode overflowMode = TextOverflowMode::Clip, TextScaleMode scaleMode = TextScaleMode::None) { float scale = stbtt_ScaleForPixelHeight(&font.font, size); int baseline = (int)(font.ascent * scale); @@ -241,14 +234,16 @@ export namespace Crafter { anchor.height = lineHeight * logicalPerPixelY; anchor.width = maxWidth * logicalPerPixelX; if(oldHeight != anchor.height || oldwidth != anchor.width) { - if(parent) { - UpdatePosition(window, *parent); - } else { - UpdatePosition(window); - } + UpdatePosition(window, parent); } } else if(scaleMode == TextScaleMode::Font) { - //todo + float lineHeightPerFont = lineHeight / size; + float lineWidthPerFont = maxWidth / size; + + float maxFontHeight = scaled.size.y / lineHeightPerFont; + float maxFontWidth = scaled.size.x / lineWidthPerFont; + + size = std::min(maxFontHeight, maxFontWidth); } else if(scaleMode == TextScaleMode::Buffer) { if constexpr(Scaling && Owning) { std::uint32_t neededHeight = lines.size() * lineHeight; @@ -256,12 +251,12 @@ export namespace Crafter { ScalingBase::bufferHeight = neededHeight; ScalingBase::bufferWidth = maxWidth; ScalingBase::bufferUpdated = true; - ScalingBase::scalingBuffer.resize(neededHeight*maxWidth); + ScalingBase::scalingBuffer.resize(neededHeight*maxWidth); } } } else { if constexpr(Scaling) { - lines.resize(ScalingBase::bufferHeight / lines.size()); + lines.resize(ScalingBase::bufferHeight / lines.size()); } else { lines.resize(scaled.size.y / lines.size()); } @@ -330,26 +325,23 @@ export namespace Crafter { float oldHeight = anchor.height; anchor.height = lineHeight * logicalPerPixelY; if(oldHeight != anchor.height) { - if(parent) { - UpdatePosition(window, *parent); - } else { - UpdatePosition(window); - } + UpdatePosition(window, parent); } } else if(scaleMode == TextScaleMode::Font) { - //todo + float lineHeightPerFont = lineHeight / size; + size = scaled.size.y / lineHeightPerFont; } else if(scaleMode == TextScaleMode::Buffer) { if constexpr(Scaling && Owning) { float neededHeight = lines.size() * lineHeight; if(neededHeight != ScalingBase::bufferHeight) { ScalingBase::bufferHeight = neededHeight; ScalingBase::bufferUpdated = true; - ScalingBase::scalingBuffer.resize(neededHeight*ScalingBase::bufferWidth); + ScalingBase::scalingBuffer.resize(neededHeight*ScalingBase::bufferWidth); } } } else { if constexpr(Scaling) { - lines.resize(ScalingBase::bufferHeight / lines.size()); + lines.resize(ScalingBase::bufferHeight / lines.size()); } else { lines.resize(scaled.size.y / lines.size()); } @@ -358,7 +350,7 @@ export namespace Crafter { return lines; } - void RenderText(Window& window, std::span lines, float size, Vector color, Font& font, TextAlignment alignment = TextAlignment::Left, std::uint32_t offsetX = 0, std::uint32_t offsetY = 0) { + void RenderText(std::span lines, float size, Vector color, Font& font, TextAlignment alignment = TextAlignment::Left, std::uint32_t offsetX = 0, std::uint32_t offsetY = 0, OpaqueType opaque = OpaqueType::FullyOpaque) { float scale = stbtt_ScaleForPixelHeight(&font.font, size); int baseline = (int)(font.ascent * scale); std::uint32_t lineHeight = (font.ascent - font.descent) * scale; @@ -372,19 +364,18 @@ export namespace Crafter { lineWidth += (int)(advance * scale); } - std::uint32_t startX = 0; + std::uint32_t x = 0; switch (alignment) { case TextAlignment::Left: - startX = 0; + x = 0; break; case TextAlignment::Center: - startX = (scaled.size.x - lineWidth) / 2; + x = (scaled.size.x - lineWidth) / 2; break; case TextAlignment::Right: - startX = scaled.size.x - lineWidth; + x = scaled.size.x - lineWidth; break; } - std::uint32_t x = startX; for (std::size_t i = 0; i < line.size(); ++i) { int codepoint = line[i]; @@ -403,28 +394,93 @@ export namespace Crafter { stbtt_MakeCodepointBitmap(&font.font, bitmap.data(), w, h, w, scale, scale, codepoint); // Only render characters that fit within the scaled bounds - for (int j = 0; j < h; j++) { - for (int i = 0; i < w; i++) { - int bufferX = x + i + c_x1 + offsetX; - int bufferY = currentY + j + c_y1 + offsetY; - - // Only draw pixels that are within our scaled buffer bounds - if constexpr(Scaling) { - if (bufferX >= 0 && bufferX < ScalingBase::bufferWidth && bufferY >= 0 && bufferY < ScalingBase::bufferHeight) { - ScalingBase::scalingBuffer[bufferY * ScalingBase::bufferWidth + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]}; - } - } else { - if (bufferX >= 0 && bufferX < (int)scaled.size.x && bufferY >= 0 && bufferY < (int)scaled.size.y) { - buffer[bufferY * scaled.size.x + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]}; + switch(opaque) { + case OpaqueType::FullyOpaque: { + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + int bufferX = x + i + c_x1 + offsetX; + int bufferY = currentY + j + c_y1 + offsetY; + + // Only draw pixels that are within our scaled buffer bounds + if constexpr(Scaling) { + if (bufferX >= 0 && bufferX < ScalingBase::bufferWidth && bufferY >= 0 && bufferY < ScalingBase::bufferHeight) { + ScalingBase::scalingBuffer[bufferY * ScalingBase::bufferWidth + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]}; + } + } else { + if (bufferX >= 0 && bufferX < (int)scaled.size.x && bufferY >= 0 && bufferY < (int)scaled.size.y) { + buffer[bufferY * scaled.size.x + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]}; + } + } } } + break; + } + case OpaqueType::SemiOpaque: { + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + int bufferX = x + i + c_x1 + offsetX; + int bufferY = currentY + j + c_y1 + offsetY; + + // Only draw pixels that are within our scaled buffer bounds + if constexpr(Scaling) { + if (bufferX >= 0 && bufferX < ScalingBase::bufferWidth && bufferY >= 0 && bufferY < ScalingBase::bufferHeight) { + ScalingBase::scalingBuffer[bufferY * ScalingBase::bufferWidth + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]}; + } + } else { + if (bufferX >= 0 && bufferX < (int)scaled.size.x && bufferY >= 0 && bufferY < (int)scaled.size.y) { + std::uint8_t alpha = bitmap[j * w + i]; + if(alpha != 0) { + buffer[bufferY * scaled.size.x + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]}; + } + } + } + } + } + break; + } + case OpaqueType::Transparent: { + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + int bufferX = x + i + c_x1 + offsetX; + int bufferY = currentY + j + c_y1 + offsetY; + + // Only draw pixels that are within our scaled buffer bounds + if constexpr(Scaling) { + if (bufferX >= 0 && bufferX < ScalingBase::bufferWidth && bufferY >= 0 && bufferY < ScalingBase::bufferHeight) { + ScalingBase::scalingBuffer[bufferY * ScalingBase::bufferWidth + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]}; + } + } else { + if (bufferX >= 0 && bufferX < (int)scaled.size.x && bufferY >= 0 && bufferY < (int)scaled.size.y) { + std::uint8_t alpha = bitmap[j * w + i]; + + if(alpha == 0) { + continue; + } + + Vector dst = buffer[bufferY * scaled.size.x + bufferX]; + + float srcA = (alpha / 255.0f) * (color.a / 255.0f); + float dstA = dst.a / 255.0f; + + float outA = srcA + dstA * (1.0f - srcA); + buffer[bufferY * scaled.size.x + bufferX] = Vector( + static_cast((color.r * srcA + dst.r * dstA * (1.0f - srcA)) / outA), + static_cast((color.g * srcA + dst.g * dstA * (1.0f - srcA)) / outA), + static_cast((color.b * srcA + dst.b * dstA * (1.0f - srcA)) / outA), + static_cast(outA * 255) + ); + } + } + } + } + break; } } x += (int)(ax * scale); if (i + 1 < line.size()) { - x += (int)stbtt_GetCodepointKernAdvance(&font.font, codepoint, line[i+1]); + x += (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, line[i+1]); } } currentY += lineHeight; diff --git a/interfaces/Crafter.Graphics-RenderingElement2DBase.cppm b/interfaces/Crafter.Graphics-RenderingElement2DBase.cppm index 383e434..3bbd2e3 100644 --- a/interfaces/Crafter.Graphics-RenderingElement2DBase.cppm +++ b/interfaces/Crafter.Graphics-RenderingElement2DBase.cppm @@ -42,8 +42,9 @@ export namespace Crafter { Buffer }; + template struct RenderElement2DScalingOwning { - std::vector> scalingBuffer; + std::vector> scalingBuffer; std::uint32_t bufferWidth; std::uint32_t bufferHeight; bool bufferUpdated = true; @@ -53,13 +54,14 @@ export namespace Crafter { } }; + template struct RenderElement2DScalingNonOwning { - Vector* scalingBuffer; + Vector* scalingBuffer; std::uint32_t bufferWidth; std::uint32_t bufferHeight; bool bufferUpdated = true; RenderElement2DScalingNonOwning() = default; - RenderElement2DScalingNonOwning(Vector* scalingBuffer, std::uint32_t bufferWidth, std::uint32_t bufferHeight) : scalingBuffer(scalingBuffer), bufferWidth(bufferWidth), bufferHeight(bufferHeight) { + RenderElement2DScalingNonOwning(Vector* scalingBuffer, std::uint32_t bufferWidth, std::uint32_t bufferHeight) : scalingBuffer(scalingBuffer), bufferWidth(bufferWidth), bufferHeight(bufferHeight) { } }; @@ -77,13 +79,13 @@ export namespace Crafter { struct EmptyScalingBase {}; struct EmptyRotatingBase {}; - template + template using ScalingBase = std::conditional_t< Scaling, std::conditional_t, + RenderElement2DScalingOwning, + RenderElement2DScalingNonOwning>, EmptyScalingBase >; diff --git a/interfaces/Crafter.Graphics-Rendertarget.cppm b/interfaces/Crafter.Graphics-Rendertarget.cppm index d08a0a3..b6ac98d 100644 --- a/interfaces/Crafter.Graphics-Rendertarget.cppm +++ b/interfaces/Crafter.Graphics-Rendertarget.cppm @@ -32,7 +32,7 @@ export namespace Crafter { Transform2D transform; std::int32_t sizeX; std::int32_t sizeY; - std::vector elements; + std::vector elements; std::vector dirtyRects; RendertargetBase() = default; RendertargetBase(std::int16_t sizeX, std::int16_t sizeY) : sizeX(sizeX), sizeY(sizeY), transform({0, 0, 1, 1, 0, 0, 0}){ @@ -42,13 +42,7 @@ export namespace Crafter { transform.scaled.position.y = 0; } void AddDirtyRect(ScaleData2D scale) { - ClipRect rect { - .left = std::max(scale.position.x, std::int32_t(0)), - .right = std::min(scale.position.x + scale.size.x, sizeX), - .top = std::max(scale.position.y, std::int32_t(0)), - .bottom = std::min(scale.position.y + scale.size.y, sizeY), - }; - dirtyRects.push_back(rect); + dirtyRects.emplace_back(std::max(scale.position.x, std::int32_t(0)), std::min(scale.position.x + scale.size.x, sizeX), std::max(scale.position.y, std::int32_t(0)), std::min(scale.position.y + scale.size.y, sizeY)); } }; @@ -59,89 +53,99 @@ export namespace Crafter { Rendertarget(std::int16_t sizeX, std::int16_t sizeY) : RendertargetBase(sizeX, sizeY) { } - void RenderElement(RenderingElement2DBase* element) { - #ifdef CRAFTER_TIMING - auto start = std::chrono::high_resolution_clock::now(); - #endif + void RenderElement(Transform2D* elementTransform) { + RenderingElement2DBase* element = dynamic_cast(elementTransform); + if(element) { + #ifdef CRAFTER_TIMING + auto start = std::chrono::high_resolution_clock::now(); + #endif - if(element->scaled.size.x < 1 || element->scaled.size.y < 1) { - return; - } - - for(ClipRect dirty : dirtyRects) { - dirty.left = std::max(element->scaled.position.x, dirty.left); - dirty.top = std::max(element->scaled.position.y, dirty.top); - dirty.right = std::min(element->scaled.position.x+element->scaled.size.x, dirty.right); - dirty.bottom = std::min(element->scaled.position.y+element->scaled.size.y, dirty.bottom); - - const Vector* src_buffer = element->buffer.data(); - std::int32_t src_width = element->scaled.size.x; - std::int32_t src_height = element->scaled.size.y; - - switch (element->opaque) { - case OpaqueType::FullyOpaque: - for (std::int32_t y = dirty.top; y < dirty.bottom; y++) { - std::int32_t src_y = y - element->scaled.position.y; - - for (std::int32_t x = dirty.left; x < dirty.right; x++) { - std::int32_t src_x = x - element->scaled.position.x; - - buffer[y * sizeX + x] = src_buffer[src_y * src_width + src_x]; - } - } - break; - - case OpaqueType::SemiOpaque: - // For semi-opaque, we can avoid blending when alpha is 0 or 255 - for (std::int32_t y = dirty.top; y < dirty.bottom; y++) { - std::int32_t src_y = y - element->scaled.position.y; - - for (std::int32_t x = dirty.left; x < dirty.right; x++) { - std::int32_t src_x = x - element->scaled.position.x; - Vector src_pixel = src_buffer[src_y * src_width + src_x]; - - if (src_pixel.a == 0) { - continue; - } - buffer[y * sizeX + x] = src_pixel; - } - } - break; - - case OpaqueType::Transparent: - // For transparent, always perform blending - for (std::int32_t y = dirty.top; y < dirty.bottom; y++) { - std::int32_t src_y = y - element->scaled.position.y; - for (std::int32_t x = dirty.left; x < dirty.right; x++) { - std::int32_t src_x = x - element->scaled.position.x; - Vector src = src_buffer[src_y * src_width + src_x]; - Vector dst = buffer[y * sizeX + x]; - - if(src.a == 0) { - continue; - } - - float srcA = src.a / 255.0f; - float dstA = dst.a / 255.0f; - - float outA = srcA + dstA * (1.0f - srcA); - buffer[y * sizeX + x] = Vector( - static_cast((src.r * srcA + dst.r * dstA * (1.0f - srcA)) / outA), - static_cast((src.g * srcA + dst.g * dstA * (1.0f - srcA)) / outA), - static_cast((src.b * srcA + dst.b * dstA * (1.0f - srcA)) / outA), - static_cast(outA * 255) - ); - } - } - break; + if(element->scaled.size.x < 1 || element->scaled.size.y < 1) { + return; } + + for(ClipRect dirty : dirtyRects) { + dirty.left = std::max(element->scaled.position.x, dirty.left); + dirty.top = std::max(element->scaled.position.y, dirty.top); + dirty.right = std::min(element->scaled.position.x+element->scaled.size.x, dirty.right); + dirty.bottom = std::min(element->scaled.position.y+element->scaled.size.y, dirty.bottom); + + const Vector* src_buffer = element->buffer.data(); + std::int32_t src_width = element->scaled.size.x; + std::int32_t src_height = element->scaled.size.y; + + switch (element->opaque) { + case OpaqueType::FullyOpaque: + for (std::int32_t y = dirty.top; y < dirty.bottom; y++) { + std::int32_t src_y = y - element->scaled.position.y; + + for (std::int32_t x = dirty.left; x < dirty.right; x++) { + std::int32_t src_x = x - element->scaled.position.x; + + buffer[y * sizeX + x] = src_buffer[src_y * src_width + src_x]; + } + } + break; + + case OpaqueType::SemiOpaque: + // For semi-opaque, we can avoid blending when alpha is 0 or 255 + for (std::int32_t y = dirty.top; y < dirty.bottom; y++) { + std::int32_t src_y = y - element->scaled.position.y; + + for (std::int32_t x = dirty.left; x < dirty.right; x++) { + std::int32_t src_x = x - element->scaled.position.x; + Vector src_pixel = src_buffer[src_y * src_width + src_x]; + + if (src_pixel.a == 0) { + continue; + } + buffer[y * sizeX + x] = src_pixel; + } + } + break; + + case OpaqueType::Transparent: + // For transparent, always perform blending + for (std::int32_t y = dirty.top; y < dirty.bottom; y++) { + std::int32_t src_y = y - element->scaled.position.y; + for (std::int32_t x = dirty.left; x < dirty.right; x++) { + std::int32_t src_x = x - element->scaled.position.x; + Vector src = src_buffer[src_y * src_width + src_x]; + Vector dst = buffer[y * sizeX + x]; + + if(src.a == 0) { + continue; + } + + float srcA = src.a / 255.0f; + float dstA = dst.a / 255.0f; + + float outA = srcA + dstA * (1.0f - srcA); + buffer[y * sizeX + x] = Vector( + static_cast((src.r * srcA + dst.r * dstA * (1.0f - srcA)) / outA), + static_cast((src.g * srcA + dst.g * dstA * (1.0f - srcA)) / outA), + static_cast((src.b * srcA + dst.b * dstA * (1.0f - srcA)) / outA), + static_cast(outA * 255) + ); + } + } + break; + } + } + #ifdef CRAFTER_TIMING + auto end = std::chrono::high_resolution_clock::now(); + renderTimings.push_back({element, element->scaled.size.x, element->scaled.size.y, end-start}); + #endif + } + std::sort(elementTransform->children.begin(), elementTransform->children.end(), [](Transform2D* a, Transform2D* b){ return a->anchor.z < b->anchor.z; }); + for(Transform2D* child : elementTransform->children) { + this->RenderElement(child); } - #ifdef CRAFTER_TIMING - auto end = std::chrono::high_resolution_clock::now(); - renderTimings.push_back({element, element->scaled.size.x, element->scaled.size.y, end-start}); - #endif } void Render() { + elements.erase(std::remove(elements.begin(), elements.end(), static_cast(nullptr)), elements.end()); + std::sort(elements.begin(), elements.end(), [](Transform2D* a, Transform2D* b){ return a->anchor.z < b->anchor.z; }); + //std::vector newClip; // for (std::uint32_t i = 0; i < dirtyRects.size(); i++) { // ClipRect rect = dirtyRects[i]; @@ -250,7 +254,7 @@ export namespace Crafter { } } - for(RenderingElement2DBase* child : elements) { + for(Transform2D* child : elements) { RenderElement(child); } dirtyRects.clear(); diff --git a/interfaces/Crafter.Graphics-Transform2D.cppm b/interfaces/Crafter.Graphics-Transform2D.cppm index 67a048e..f92b569 100644 --- a/interfaces/Crafter.Graphics-Transform2D.cppm +++ b/interfaces/Crafter.Graphics-Transform2D.cppm @@ -46,7 +46,6 @@ export namespace Crafter { Transform2D& operator=(Transform2D&) = delete; virtual ~Transform2D() = default; void ScaleElement(Transform2D& parent); - virtual void UpdatePosition(RendertargetBase& window); virtual void UpdatePosition(RendertargetBase& window, Transform2D& parent); }; } \ No newline at end of file diff --git a/interfaces/Crafter.Graphics-Window.cppm b/interfaces/Crafter.Graphics-Window.cppm index a4cb444..32f20dc 100644 --- a/interfaces/Crafter.Graphics-Window.cppm +++ b/interfaces/Crafter.Graphics-Window.cppm @@ -96,7 +96,9 @@ export namespace Crafter { bool mouseRightHeld = false; std::vector mouseElements; std::vector pendingMouseElements; + Rendertarget cursorRenderer; + Window() = default; Window(std::uint32_t width, std::uint32_t height); Window(std::uint32_t width, std::uint32_t height, const std::string_view title); Window(Window&) = delete; @@ -110,6 +112,9 @@ export namespace Crafter { void Resize(std::uint32_t width, std::uint32_t height); void Render(); void Update(); + void UpdateCursorImage(); + void SetCusorImage(std::uint16_t sizeX, std::uint16_t sizeY); + void SetCusorImageDefault(); #ifdef CRAFTER_TIMING std::chrono::nanoseconds totalUpdate; @@ -135,6 +140,9 @@ export namespace Crafter { wl_buffer* backBuffer = nullptr; xdg_surface* xdgSurface = nullptr; wl_callback* cb = nullptr; + wl_surface* cursorSurface = nullptr; + wl_buffer* cursorWlBuffer = nullptr; + std::uint32_t cursorBufferOldSize = 0; static void xdg_surface_handle_preferred_scale(void* data, wp_fractional_scale_v1*, std::uint32_t scale); static void wl_surface_frame_done(void *data, wl_callback *cb, uint32_t time);