/* 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 */ module; #include "../lib/stb_truetype.h" #ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN #include #endif export module Crafter.Graphics:Rendertarget; import Crafter.Math; import Crafter.Asset; import std; import :Types; import :Transform2D; import :RenderingElement2DBase; #ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN import :Device; import :VulkanBuffer; #endif export namespace Crafter { struct RendertargetBase { #ifdef CRAFTER_TIMING std::vector> renderTimings; #endif Transform2D transform; std::uint16_t sizeX; std::uint16_t sizeY; RendertargetBase() = default; RendertargetBase(std::uint16_t sizeX, std::uint16_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; } }; #ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN struct RenderingElement2DVulkanBase; struct __attribute__((packed)) RenderingElement2DVulkanTransformInfo { ScaleData2D scaled; // 0 - 8 bytes std::uint16_t bufferX; // 8 - 2 bytes std::uint16_t bufferY; // 10 - 2 bytes //12 bytes total; }; struct DescriptorHeapVulkan; struct RendertargetVulkan : RendertargetBase { std::uint8_t frame; std::vector elements; VulkanBuffer transformBuffer[3]; RendertargetVulkan() = default; RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY, std::vector&& elements); RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY); void UpdateBuffer(std::uint8_t frame); void ReorderBuffer(std::uint8_t frame); void WriteDescriptors(std::span infos, std::span ranges, std::uint16_t start, std::uint32_t bufferOffset, DescriptorHeapVulkan& descriptorHeap); void SetOrderResursive(Transform2D* elementTransform); }; #endif template struct Rendertarget : RendertargetBase { Vector* buffer[Frames]; Rendertarget() = default; Rendertarget(std::uint16_t sizeX, std::uint16_t sizeY) : RendertargetBase(sizeX, sizeY) { } void RenderElement(Transform2D* elementTransform, std::uint8_t frame, std::vector&& dirtyRects) { 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::uint16_t(std::max(element->scaled.position.x, std::int16_t(dirty.left))); dirty.top = std::uint16_t(std::max(element->scaled.position.y,std::int16_t(dirty.top))); dirty.right = std::min(std::uint16_t(element->scaled.position.x+element->scaled.size.x), dirty.right); dirty.bottom = std::min(std::uint16_t(element->scaled.position.y+element->scaled.size.y), dirty.bottom); if(dirty.right <= dirty.left || dirty.bottom <= dirty.top) { continue; } const Vector* src_buffer = element->buffer.data(); std::uint16_t src_width = element->scaled.size.x; std::uint16_t src_height = element->scaled.size.y; switch (element->opaque) { case OpaqueType::FullyOpaque: { for (std::uint16_t y = dirty.top; y < dirty.bottom; y++) { std::uint16_t src_y = y - element->scaled.position.y; std::uint16_t src_x = dirty.left - element->scaled.position.x; std::memcpy(&this->buffer[frame][y * this->sizeX + dirty.left], &src_buffer[src_y * src_width + src_x], (dirty.right - dirty.left) * sizeof(Vector)); } break; } case OpaqueType::SemiOpaque: case OpaqueType::Transparent: if constexpr(std::same_as) { for (std::uint16_t y = dirty.top; y < dirty.bottom; y++) { std::uint16_t src_y = y - element->scaled.position.y; std::uint16_t pixel_width = dirty.right - dirty.left; constexpr std::uint32_t simd_width = VectorF16<1, 1>::MaxElement / 4; std::uint32_t rows = pixel_width / simd_width; for (std::uint32_t x = 0; x < rows; x++) { std::uint16_t px = dirty.left + x * simd_width; std::uint16_t src_x = px - element->scaled.position.x; VectorF16<4, simd_width> src(&src_buffer[src_y * src_width + src_x].v[0]); VectorF16<4, simd_width> dst(&buffer[frame][y * this->sizeX + px].v[0]); VectorF16<4, simd_width> oneMinusSrcA = VectorF16<4, simd_width>(1) - src.Shuffle<{{3, 3, 3, 3}}>(); VectorF16<4, simd_width> result = VectorF16<4, simd_width>::MulitplyAdd(dst, oneMinusSrcA, src); result.Store(&buffer[frame][y * this->sizeX + px].v[0]); } std::uint32_t remainder = pixel_width - (rows * simd_width); std::uint16_t remainder_start = dirty.left + rows * simd_width; for (std::uint8_t x = 0; x < remainder; x++) { std::uint16_t px = remainder_start + x; std::uint16_t src_x = px - element->scaled.position.x; Vector src = src_buffer[src_y * src_width + src_x]; Vector dst = buffer[frame][y * this->sizeX + px]; _Float16 oneMinusSrcA = (_Float16)1.0f - src.a; buffer[frame][y * this->sizeX + px] = Vector( src.r + dst.r * oneMinusSrcA, src.g + dst.g * oneMinusSrcA, src.b + dst.b * oneMinusSrcA, src.a + dst.a * oneMinusSrcA ); } } } else { for (std::uint16_t y = dirty.top; y < dirty.bottom; y++) { std::uint16_t src_y = y - element->scaled.position.y; std::uint16_t src_x = dirty.left - element->scaled.position.x; std::memcpy(&this->buffer[frame][y * this->sizeX + dirty.left], &src_buffer[src_y * src_width + src_x], (dirty.right - dirty.left) * sizeof(Vector)); } } 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, frame, std::move(dirtyRects)); } } void AddOldRects(Transform2D* elementTransform, std::uint8_t frame, std::vector& clipRects) { RenderingElement2DBase* element = dynamic_cast*>(elementTransform); if(element) { // if(element->scaled.position.x != element->oldScale[frame].position.x || element->scaled.position.y != element->oldScale[frame].position.y || element->scaled.size.x != element->oldScale[frame].size.x || element->scaled.size.y != element->oldScale[frame].size.y || element->redraw[frame]) { // clipRects.emplace_back(std::max(element->scaled.position.x, std::uint16_t(0)), std::min(std::uint16_t(element->scaled.position.x + element->scaled.size.x), this->sizeX), std::max(element->scaled.position.y, std::uint16_t(0)), std::min(std::uint16_t(element->scaled.position.y + element->scaled.size.y), this->sizeY)); // clipRects.emplace_back(std::max(element->oldScale[frame].position.x, std::uint16_t(0)), std::min(std::uint16_t(element->oldScale[frame].position.x + element->oldScale[frame].size.x), this->sizeX), std::max(element->oldScale[frame].position.y, std::uint16_t(0)), std::min(std::uint16_t(element->oldScale[frame].position.y + element->oldScale[frame].size.y), this->sizeY)); // element->oldScale[frame] = element->scaled; // element->redraw[frame] = false; // } else if(element->redraw[frame]) { // clipRects.emplace_back(std::max(element->scaled.position.x, std::uint16_t(0)), std::min(std::uint16_t(element->scaled.position.x + element->scaled.size.x), this->sizeX), std::max(element->scaled.position.y, std::uint16_t(0)), std::min(std::uint16_t(element->scaled.position.y + element->scaled.size.y), this->sizeY)); // element->oldScale[frame] = element->scaled; // element->redraw[frame] = false; // } } for(Transform2D* child : elementTransform->children) { AddOldRects(child, frame, clipRects); } } bool Render(std::uint8_t frame) { std::sort(this->transform.children.begin(), this->transform.children.end(), [](Transform2D* a, Transform2D* b){ return a->anchor.z < b->anchor.z; }); std::vector clipRects; for(Transform2D* child : this->transform.children) { AddOldRects(child, frame, clipRects); } //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 (!clipRects.empty()) { for (ClipRect rect : clipRects) { for (std::int32_t y = rect.top; y < rect.bottom; y++) { for (std::int32_t x = rect.left; x < rect.right; x++) { this->buffer[frame][y * this->sizeX + x] = {0, 0, 0, 0}; } } } for(Transform2D* child : this->transform.children) { RenderElement(child, frame, std::move(clipRects)); } return true; } else { return false; } } }; }