diff --git a/implementations/Crafter.Graphics-GridElement.cpp b/implementations/Crafter.Graphics-GridElement.cpp index edaf37e..39d7f3e 100644 --- a/implementations/Crafter.Graphics-GridElement.cpp +++ b/implementations/Crafter.Graphics-GridElement.cpp @@ -30,7 +30,7 @@ GridElement::GridElement(std::uint32_t columns, std::uint32_t rows, std::int32_t } -void GridElement::UpdatePositionScaled(Rendertarget& window) { +void GridElement::UpdatePositionScaled(RendertargetBase& window) { std::int32_t cellWidth = (paddingX * 2) - (spacingX * (columns - 1)) / columns; std::int32_t cellHeight = (paddingY * 2) - (spacingY * (rows - 1)) / rows; @@ -57,12 +57,12 @@ void GridElement::UpdatePositionScaled(Rendertarget& window) { } } -void GridElement::UpdatePosition(Rendertarget& window) { +void GridElement::UpdatePosition(RendertargetBase& window) { ScaleElement(window.transform); UpdatePositionScaled(window); } -void GridElement::UpdatePosition(Rendertarget& window, Transform2D& parent) { +void GridElement::UpdatePosition(RendertargetBase& window, Transform2D& parent) { ScaleElement(parent); UpdatePositionScaled(window); } \ No newline at end of file diff --git a/implementations/Crafter.Graphics-Rendertarget.cpp b/implementations/Crafter.Graphics-Rendertarget.cpp deleted file mode 100644 index b742ffd..0000000 --- a/implementations/Crafter.Graphics-Rendertarget.cpp +++ /dev/null @@ -1,249 +0,0 @@ -/* -Crafter®.Graphics -Copyright (C) 2026 Catcrafts® -Catcrafts.net - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -module Crafter.Graphics:Rendertarget_impl; -import :Window; -import :RenderingElement2D; -import std; - -using namespace Crafter; - -Rendertarget::Rendertarget(std::int32_t sizeX, std::int32_t sizeY) : transform({0, 0, static_cast(sizeX), static_cast(sizeY), 0, 0, 0}) { - transform.scaled.size.x = sizeX; - transform.scaled.size.y = sizeY; - transform.scaled.position.x = 0; - transform.scaled.position.y = 0; -} - -void Rendertarget::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); -} - -inline void blend_pixel_optimized(Vector& dst, const Vector& src) { - if(src.a == 0) { - return; - } - - float srcA = src.a / 255.0f; - float dstA = dst.a / 255.0f; - - float outA = srcA + dstA * (1.0f - srcA); - if (outA > 0.0f) { - dst = Vector( - static_cast((src.b * srcA + dst.b * dstA * (1.0f - srcA)) / outA), - static_cast((src.g * srcA + dst.g * dstA * (1.0f - srcA)) / outA), - static_cast((src.r * srcA + dst.r * dstA * (1.0f - srcA)) / outA), - static_cast(outA * 255) - ); - } -} - -void Rendertarget::RenderElement(RenderingElement2DBase* 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 fully opaque, just copy pixels directly - 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; - blend_pixel_optimized(buffer[y * sizeX + x], src_buffer[src_y * src_width + src_x]); - } - } - 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 -} - -void Rendertarget::Render() { - //std::vector newClip; - - // for (std::uint32_t i = 0; i < dirtyRects.size(); i++) { - // ClipRect rect = dirtyRects[i]; - // for (std::uint32_t i2 = i + 1; i2 < dirtyRects.size(); i2++) { - // ClipRect existing = dirtyRects[i2]; - // if(rect.bottom >= existing.top && rect.top <= existing.top) { - // newClip.push_back({ - // .left = rect.left, - // .right = rect.right, - // .top = rect.top, - // .bottom = existing.top, - // }); - // //-| shape - // if(rect.right > existing.right) { - // newClip.push_back({ - // .left = existing.right, - // .right = rect.right, - // .top = existing.top, - // .bottom = existing.bottom, - // }); - // } - // //|- shape - // if(rect.left < existing.left) { - // newClip.push_back({ - // .left = rect.left, - // .right = existing.left, - // .top = existing.top, - // .bottom = existing.bottom, - // }); - // } - // //-| or |- shape where rect extends further down - // if(rect.bottom > existing.bottom) { - // newClip.push_back({ - // .left = rect.left, - // .right = rect.right, - // .top = existing.bottom, - // .bottom = rect.bottom, - // }); - // } - // goto inner; - // } - // if (rect.left <= existing.right && rect.right >= existing.left) { - // newClip.push_back({ - // .left = rect.left, - // .right = existing.left, - // .top = rect.top, - // .bottom = rect.bottom, - // }); - // if (rect.right > existing.right) { - // newClip.push_back({ - // .left = existing.right, - // .right = rect.right, - // .top = rect.top, - // .bottom = rect.bottom, - // }); - // } - // goto inner; - // } - // } - // newClip.push_back(rect); - // inner:; - // } - - //dirtyRects = std::move(newClip); - - // std::memset(buffer, 0, width*height*4); - - // std::cout << dirtyRects.size() << std::endl; - // // Color palette - // static const std::vector> colors = { - // {255, 0, 0, 255}, // red - // { 0, 255, 0, 255}, // green - // { 0, 0, 255, 255}, // blue - // {255, 255, 0, 255}, // yellow - // {255, 0, 255, 255}, // magenta - // { 0, 255, 255, 255}, // cyan - // }; - - // std::size_t rectIndex = 0; - - // for (const ClipRect& rect : dirtyRects) { - // const Vector& color = colors[rectIndex % colors.size()]; - - // std::cout << std::format( - // "ClipRect {}: [{}, {}, {}, {}] Color = RGBA({}, {}, {}, {})", - // rectIndex, - // rect.left, rect.top, rect.right, rect.bottom, - // color.r, color.g, color.b, color.a - // ) << std::endl; - - // for (std::int32_t y = rect.top; y < rect.bottom; ++y) { - // for (std::int32_t x = rect.left; x < rect.right; ++x) { - // buffer[y * width + x] = color; - // } - // } - - // ++rectIndex; - // } - - if (!dirtyRects.empty()) { - for (ClipRect rect : dirtyRects) { - for (std::int32_t y = rect.top; y < rect.bottom; y++) { - for (std::int32_t x = rect.left; x < rect.right; x++) { - buffer[y * sizeX + x] = {0, 0, 0, 0}; - } - } - } - - for(RenderingElement2DBase* child : elements) { - RenderElement(child); - } - dirtyRects.clear(); - } -} \ No newline at end of file diff --git a/implementations/Crafter.Graphics-Transform2D.cpp b/implementations/Crafter.Graphics-Transform2D.cpp index 2394803..86b9e09 100644 --- a/implementations/Crafter.Graphics-Transform2D.cpp +++ b/implementations/Crafter.Graphics-Transform2D.cpp @@ -36,14 +36,14 @@ Transform2D::Transform2D(Anchor2D anchor) : anchor(anchor) { } -void Transform2D::UpdatePosition(Rendertarget& window) { +void Transform2D::UpdatePosition(RendertargetBase& window) { ScaleElement(window.transform); for(Transform2D* child : children) { child->UpdatePosition(window, *this); } } -void Transform2D::UpdatePosition(Rendertarget& window, Transform2D& parent) { +void Transform2D::UpdatePosition(RendertargetBase& window, Transform2D& parent) { ScaleElement(parent); for(Transform2D* child : children) { child->UpdatePosition(window, *this); diff --git a/interfaces/Crafter.Graphics-GridElement.cppm b/interfaces/Crafter.Graphics-GridElement.cppm index b8db891..4de6cbb 100644 --- a/interfaces/Crafter.Graphics-GridElement.cppm +++ b/interfaces/Crafter.Graphics-GridElement.cppm @@ -32,8 +32,8 @@ export namespace Crafter { std::int32_t paddingX; 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(Rendertarget& window); - void UpdatePosition(Rendertarget& window) override; - void UpdatePosition(Rendertarget& window, Transform2D& parent) override; + 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-ImageVulkan.cppm b/interfaces/Crafter.Graphics-ImageVulkan.cppm index d15efcc..2b95d24 100644 --- a/interfaces/Crafter.Graphics-ImageVulkan.cppm +++ b/interfaces/Crafter.Graphics-ImageVulkan.cppm @@ -41,7 +41,7 @@ export namespace Crafter { VkImageView imageView; VkDescriptorImageInfo descriptor; - void Create(std::uint16_t width, std::uint16_t height, std::uint8_t mipLevels, VkCommandBuffer cmd, VkFormat format) { + void Create(std::uint16_t width, std::uint16_t height, std::uint8_t mipLevels, VkCommandBuffer cmd, VkFormat format, VkImageCreateFlags flags, VkImageLayout layout) { this->width = width; this->height = height; this->mipLevels = mipLevels; @@ -58,7 +58,7 @@ export namespace Crafter { imageInfo.format = format; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + imageInfo.usage = flags; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; @@ -89,12 +89,14 @@ export namespace Crafter { Device::CheckVkResult(vkCreateImageView(Device::device, &viewInfo, nullptr, &imageView)); // Final transition to shader read-only layout - TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, mipLevels); + TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_UNDEFINED, layout, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, 0, VK_ACCESS_SHADER_READ_BIT, 0, mipLevels); + + descriptor = { .imageView = imageView, .imageLayout = layout }; } - void Update(VkCommandBuffer cmd) { + void Update(VkCommandBuffer cmd, VkImageLayout layout) { buffer.FlushDevice(cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); - TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, mipLevels); + TransitionImageLayout(cmd, image, layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, 0, mipLevels); VkBufferImageCopy region{}; region.bufferOffset = 0; @@ -116,7 +118,7 @@ export namespace Crafter { ); if(mipLevels > 1) { - TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, 0, 1); + TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, 0, 1); for (std::uint16_t i = 1; i < mipLevels; ++i) { std::uint16_t mipWidth = width >> i; @@ -141,11 +143,13 @@ export namespace Crafter { blit.dstOffsets[1] = { (int32_t)mipWidth, (int32_t)mipHeight, 1 }; vkCmdBlitImage(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR); - TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, i, 1); + TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, i, 1); } - } - TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, mipLevels); + TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, layout, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_SHADER_READ_BIT, 0, mipLevels); + } else { + TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, layout, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, 0, mipLevels); + } } void Destroy() { @@ -155,7 +159,7 @@ export namespace Crafter { } private: - void TransitionImageLayout(VkCommandBuffer cmd, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, std::uint32_t mipLevel, std::uint32_t count) { + void TransitionImageLayout(VkCommandBuffer cmd, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, VkPipelineStageFlags sourceStage, VkPipelineStageFlags destinationStage, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, std::uint32_t mipLevel, std::uint32_t count) { VkImageMemoryBarrier barrier = {}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.oldLayout = oldLayout; @@ -168,71 +172,10 @@ export namespace Crafter { barrier.subresourceRange.levelCount = count; barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; - barrier.srcAccessMask = 0; - barrier.dstAccessMask = 0; - - VkPipelineStageFlags sourceStage; - VkPipelineStageFlags destinationStage; - - // Different cases for layout transition - if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { - barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - // barrier.srcQueueFamilyIndex = VK_QUEUE_TRANSFER_BIT; - // barrier.dstQueueFamilyIndex = VK_QUEUE_TRANSFER_BIT; - } if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { - barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - destinationStage = VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; - // barrier.srcQueueFamilyIndex = VK_QUEUE_TRANSFER_BIT; - // barrier.dstQueueFamilyIndex = VK_QUEUE_GRAPHICS_BIT; - } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - destinationStage = VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; - // barrier.srcQueueFamilyIndex = VK_QUEUE_TRANSFER_BIT; - // barrier.dstQueueFamilyIndex = VK_QUEUE_GRAPHICS_BIT; - } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - destinationStage = VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; - // barrier.srcQueueFamilyIndex = VK_QUEUE_TRANSFER_BIT; - // barrier.dstQueueFamilyIndex = VK_QUEUE_GRAPHICS_BIT; - } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - // barrier.srcQueueFamilyIndex = VK_QUEUE_TRANSFER_BIT; - // barrier.dstQueueFamilyIndex = VK_QUEUE_TRANSFER_BIT; - } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - // barrier.srcQueueFamilyIndex = VK_QUEUE_TRANSFER_BIT; - // barrier.dstQueueFamilyIndex = VK_QUEUE_TRANSFER_BIT; - } else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - sourceStage = VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; - destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - // barrier.srcQueueFamilyIndex = VK_QUEUE_GRAPHICS_BIT; - // barrier.dstQueueFamilyIndex = VK_QUEUE_TRANSFER_BIT; - } else { - std::cout << oldLayout << std::endl; - std::cout << newLayout << std::endl; - throw std::invalid_argument("unsupported layout transition!"); - } + barrier.srcAccessMask = srcAccessMask; + barrier.dstAccessMask = dstAccessMask; vkCmdPipelineBarrier(cmd, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier); - - descriptor = { .imageView = imageView, .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }; } }; #endif diff --git a/interfaces/Crafter.Graphics-RenderingElement2D.cppm b/interfaces/Crafter.Graphics-RenderingElement2D.cppm index 047733c..581019a 100644 --- a/interfaces/Crafter.Graphics-RenderingElement2D.cppm +++ b/interfaces/Crafter.Graphics-RenderingElement2D.cppm @@ -23,93 +23,12 @@ export module Crafter.Graphics:RenderingElement2D; import Crafter.Asset; import std; import :Transform2D; +import :RenderingElement2DBase; import :Font; import :Types; import :Window; export namespace Crafter { - enum class TextAlignment { - Left, - Center, - Right - }; - - enum class TextOverflowMode { - Clip, - Wrap - }; - - enum class TextScaleMode { - None, - Font, - Element, - Buffer - }; - - struct RenderElement2DScalingOwning { - std::vector> scalingBuffer; - std::uint32_t bufferWidth; - std::uint32_t bufferHeight; - bool bufferUpdated = true; - RenderElement2DScalingOwning() = default; - RenderElement2DScalingOwning(std::uint32_t bufferWidth, std::uint32_t bufferHeight) : scalingBuffer(bufferWidth*bufferHeight), bufferWidth(bufferWidth), bufferHeight(bufferHeight) { - - } - }; - - struct RenderElement2DScalingNonOwning { - 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) { - - } - }; - - struct RenderElement2DRotating { - float rotation; - bool rotationUpdated = true; - RenderElement2DRotating() = default; - RenderElement2DRotating(float rotation) : rotation(rotation) { - - } - }; - - - struct EmptyScalingBase {}; - struct EmptyRotatingBase {}; - - template - using ScalingBase = - std::conditional_t< - Scaling, - std::conditional_t, - EmptyScalingBase - >; - - template - using RotatingBase = - std::conditional_t< - Rotating, - RenderElement2DRotating, - EmptyRotatingBase - >; - - struct RenderingElement2DBase : Transform2D { - std::vector> buffer; - OpaqueType opaque; - RenderingElement2DBase(Anchor2D anchor) : Transform2D(anchor) { - scaled.size.x = 0; - } - RenderingElement2DBase(Anchor2D anchor, OpaqueType opaque) : Transform2D(anchor), opaque(opaque) { - scaled.size.x = 0; - } - }; - template requires ((!Rotating || Scaling) && (!Owning || Scaling)) struct RenderingElement2D : RenderingElement2DBase, ScalingBase, RotatingBase { RenderingElement2D() = default; @@ -210,7 +129,7 @@ export namespace Crafter { } } - void UpdatePosition(Rendertarget& renderer, ScaleData2D oldScale) { + void UpdatePosition(RendertargetBase& renderer, ScaleData2D oldScale) { if constexpr(Scaling && !Rotating) { if(oldScale.size.x != scaled.size.x || oldScale.size.y != scaled.size.y) { buffer.resize(scaled.size.x * scaled.size.y); @@ -260,7 +179,7 @@ export namespace Crafter { } } - void UpdatePosition(Rendertarget& window) override { + void UpdatePosition(RendertargetBase& window) override { ScaleData2D oldScale = scaled; ScaleElement(window.transform); UpdatePosition(window, oldScale); @@ -269,7 +188,7 @@ export namespace Crafter { } } - void UpdatePosition(Rendertarget& window, Transform2D& parent) override { + void UpdatePosition(RendertargetBase& window, Transform2D& parent) override { ScaleData2D oldScale = scaled; ScaleElement(parent); UpdatePosition(window, oldScale); diff --git a/interfaces/Crafter.Graphics-RenderingElement2DBase.cppm b/interfaces/Crafter.Graphics-RenderingElement2DBase.cppm new file mode 100644 index 0000000..383e434 --- /dev/null +++ b/interfaces/Crafter.Graphics-RenderingElement2DBase.cppm @@ -0,0 +1,108 @@ +/* +Crafter®.Graphics +Copyright (C) 2026 Catcrafts® +catcrafts.net + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 3.0 as published by the Free Software Foundation; + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +export module Crafter.Graphics:RenderingElement2DBase; +import Crafter.Asset; +import Crafter.Math; +import std; +import :Transform2D; + +export namespace Crafter { + enum class TextAlignment { + Left, + Center, + Right + }; + + enum class TextOverflowMode { + Clip, + Wrap + }; + + enum class TextScaleMode { + None, + Font, + Element, + Buffer + }; + + struct RenderElement2DScalingOwning { + std::vector> scalingBuffer; + std::uint32_t bufferWidth; + std::uint32_t bufferHeight; + bool bufferUpdated = true; + RenderElement2DScalingOwning() = default; + RenderElement2DScalingOwning(std::uint32_t bufferWidth, std::uint32_t bufferHeight) : scalingBuffer(bufferWidth*bufferHeight), bufferWidth(bufferWidth), bufferHeight(bufferHeight) { + + } + }; + + struct RenderElement2DScalingNonOwning { + 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) { + + } + }; + + struct RenderElement2DRotating { + float rotation; + bool rotationUpdated = true; + RenderElement2DRotating() = default; + RenderElement2DRotating(float rotation) : rotation(rotation) { + + } + }; + + + struct EmptyScalingBase {}; + struct EmptyRotatingBase {}; + + template + using ScalingBase = + std::conditional_t< + Scaling, + std::conditional_t, + EmptyScalingBase + >; + + template + using RotatingBase = + std::conditional_t< + Rotating, + RenderElement2DRotating, + EmptyRotatingBase + >; + + struct RenderingElement2DBase : Transform2D { + std::vector> buffer; + OpaqueType opaque; + RenderingElement2DBase(Anchor2D anchor) : Transform2D(anchor) { + scaled.size.x = 0; + } + RenderingElement2DBase(Anchor2D anchor, OpaqueType opaque) : Transform2D(anchor), opaque(opaque) { + scaled.size.x = 0; + } + }; +} \ No newline at end of file diff --git a/interfaces/Crafter.Graphics-Rendertarget.cppm b/interfaces/Crafter.Graphics-Rendertarget.cppm index d07c03e..d08a0a3 100644 --- a/interfaces/Crafter.Graphics-Rendertarget.cppm +++ b/interfaces/Crafter.Graphics-Rendertarget.cppm @@ -18,15 +18,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ export module Crafter.Graphics:Rendertarget; import Crafter.Math; +import Crafter.Asset; import std; import :Types; import :Transform2D; +import :RenderingElement2DBase; export namespace Crafter { - struct RenderingElement2DBase; - struct Window; - struct Rendertarget { - Vector* buffer; + struct RendertargetBase { #ifdef CRAFTER_TIMING std::vector> renderTimings; #endif @@ -35,9 +34,227 @@ export namespace Crafter { std::int32_t sizeY; std::vector elements; std::vector dirtyRects; - Rendertarget(std::int32_t sizeX, std::int32_t sizeY); - void RenderElement(RenderingElement2DBase* transform); - void AddDirtyRect(ScaleData2D rect); - void Render(); + RendertargetBase() = default; + RendertargetBase(std::int16_t sizeX, std::int16_t sizeY) : sizeX(sizeX), sizeY(sizeY), transform({0, 0, 1, 1, 0, 0, 0}){ + transform.scaled.size.x = sizeX; + transform.scaled.size.y = sizeY; + transform.scaled.position.x = 0; + 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); + } + }; + + template + struct Rendertarget : RendertargetBase { + Vector* buffer; + Rendertarget() = default; + 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 + + 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 + } + void Render() { + //std::vector newClip; + // for (std::uint32_t i = 0; i < dirtyRects.size(); i++) { + // ClipRect rect = dirtyRects[i]; + // for (std::uint32_t i2 = i + 1; i2 < dirtyRects.size(); i2++) { + // ClipRect existing = dirtyRects[i2]; + // if(rect.bottom >= existing.top && rect.top <= existing.top) { + // newClip.push_back({ + // .left = rect.left, + // .right = rect.right, + // .top = rect.top, + // .bottom = existing.top, + // }); + // //-| shape + // if(rect.right > existing.right) { + // newClip.push_back({ + // .left = existing.right, + // .right = rect.right, + // .top = existing.top, + // .bottom = existing.bottom, + // }); + // } + // //|- shape + // if(rect.left < existing.left) { + // newClip.push_back({ + // .left = rect.left, + // .right = existing.left, + // .top = existing.top, + // .bottom = existing.bottom, + // }); + // } + // //-| or |- shape where rect extends further down + // if(rect.bottom > existing.bottom) { + // newClip.push_back({ + // .left = rect.left, + // .right = rect.right, + // .top = existing.bottom, + // .bottom = rect.bottom, + // }); + // } + // goto inner; + // } + // if (rect.left <= existing.right && rect.right >= existing.left) { + // newClip.push_back({ + // .left = rect.left, + // .right = existing.left, + // .top = rect.top, + // .bottom = rect.bottom, + // }); + // if (rect.right > existing.right) { + // newClip.push_back({ + // .left = existing.right, + // .right = rect.right, + // .top = rect.top, + // .bottom = rect.bottom, + // }); + // } + // goto inner; + // } + // } + // newClip.push_back(rect); + // inner:; + // } + + //dirtyRects = std::move(newClip); + + // std::memset(buffer, 0, width*height*4); + + // std::cout << dirtyRects.size() << std::endl; + // // Color palette + // static const std::vector> colors = { + // {255, 0, 0, 255}, // red + // { 0, 255, 0, 255}, // green + // { 0, 0, 255, 255}, // blue + // {255, 255, 0, 255}, // yellow + // {255, 0, 255, 255}, // magenta + // { 0, 255, 255, 255}, // cyan + // }; + + // std::size_t rectIndex = 0; + + // for (const ClipRect& rect : dirtyRects) { + // const Vector& color = colors[rectIndex % colors.size()]; + + // std::cout << std::format( + // "ClipRect {}: [{}, {}, {}, {}] Color = RGBA({}, {}, {}, {})", + // rectIndex, + // rect.left, rect.top, rect.right, rect.bottom, + // color.r, color.g, color.b, color.a + // ) << std::endl; + + // for (std::int32_t y = rect.top; y < rect.bottom; ++y) { + // for (std::int32_t x = rect.left; x < rect.right; ++x) { + // buffer[y * width + x] = color; + // } + // } + + // ++rectIndex; + // } + + if (!dirtyRects.empty()) { + for (ClipRect rect : dirtyRects) { + for (std::int32_t y = rect.top; y < rect.bottom; y++) { + for (std::int32_t x = rect.left; x < rect.right; x++) { + buffer[y * sizeX + x] = {0, 0, 0, 0}; + } + } + } + + for(RenderingElement2DBase* child : elements) { + RenderElement(child); + } + dirtyRects.clear(); + } + } }; } \ No newline at end of file diff --git a/interfaces/Crafter.Graphics-Transform2D.cppm b/interfaces/Crafter.Graphics-Transform2D.cppm index 86e5abf..67a048e 100644 --- a/interfaces/Crafter.Graphics-Transform2D.cppm +++ b/interfaces/Crafter.Graphics-Transform2D.cppm @@ -22,7 +22,7 @@ import std; import :Types; export namespace Crafter { - struct Rendertarget; + struct RendertargetBase; struct Anchor2D { float x; float y; @@ -39,13 +39,14 @@ export namespace Crafter { Anchor2D anchor; ScaleData2D scaled; std::vector children; + Transform2D() = default; Transform2D(Anchor2D anchor); Transform2D(Transform2D&) = delete; Transform2D(Transform2D&&) = delete; Transform2D& operator=(Transform2D&) = delete; virtual ~Transform2D() = default; void ScaleElement(Transform2D& parent); - virtual void UpdatePosition(Rendertarget& window); - virtual void UpdatePosition(Rendertarget& window, 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 65b3a1f..a4cb444 100644 --- a/interfaces/Crafter.Graphics-Window.cppm +++ b/interfaces/Crafter.Graphics-Window.cppm @@ -125,7 +125,7 @@ export namespace Crafter { #ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND float scale; #ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE - Rendertarget renderer; + Rendertarget renderer; #endif bool configured = false; xdg_toplevel* xdgToplevel = nullptr; diff --git a/interfaces/Crafter.Graphics.cppm b/interfaces/Crafter.Graphics.cppm index 2ee5caf..926422f 100644 --- a/interfaces/Crafter.Graphics.cppm +++ b/interfaces/Crafter.Graphics.cppm @@ -23,6 +23,7 @@ export module Crafter.Graphics; export import :Window; export import :Transform2D; export import :RenderingElement2D; +export import :RenderingElement2DBase; export import :MouseElement; export import :GridElement; export import :Types; diff --git a/project.json b/project.json index 8eb7e26..866e244 100644 --- a/project.json +++ b/project.json @@ -9,7 +9,6 @@ "implementations/Crafter.Graphics-MouseElement", "implementations/Crafter.Graphics-Transform2D", "implementations/Crafter.Graphics-GridElement", - "implementations/Crafter.Graphics-Rendertarget", "implementations/Crafter.Graphics-Device", "implementations/Crafter.Graphics-Mesh", "implementations/Crafter.Graphics-RenderingElement3D" @@ -21,6 +20,7 @@ "interfaces/Crafter.Graphics-Font", "interfaces/Crafter.Graphics-Animation", "interfaces/Crafter.Graphics-RenderingElement2D", + "interfaces/Crafter.Graphics-RenderingElement2DBase", "interfaces/Crafter.Graphics-MouseElement", "interfaces/Crafter.Graphics-Transform2D", "interfaces/Crafter.Graphics-GridElement",