/* 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:Rendertarget; import Crafter.Math; import Crafter.Asset; import std; import :Types; import :Transform2D; import :RenderingElement2DBase; export namespace Crafter { template 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; } }; template struct Rendertarget : RendertargetBase { Vector* buffer[Frames]; Rendertarget() = default; Rendertarget(std::int16_t sizeX, std::int16_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::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; std::memcpy(&this->buffer[frame][y * this->sizeX], &src_buffer[src_y * src_width], 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 rowSize = dirty.right - dirty.left; constexpr std::uint8_t elementsPerVector = VectorF16L<1, 1, 1>::MaxSize/VectorF16L<1, 1, 1>::MaxElement; while(rowSize > 0) { if(rowSize < elementsPerVector) { for(; rowSize > 0; rowSize--) { std::uint16_t src_x = rowSize - element->scaled.position.x; Vector src = src_buffer[src_y * src_width + src_x]; Vector dst = buffer[frame][y * this->sizeX + rowSize]; _Float16 outA = src.a + dst.a * (1.0f - src.a); this->buffer[frame][y * this->sizeX + rowSize] = Vector( static_cast((src.r + dst.r * (1.0f - src.a)) / outA), static_cast((src.g + dst.g * (1.0f - src.a)) / outA), static_cast((src.b + dst.b * (1.0f - src.a)) / outA), static_cast(outA * 255) ); } break; } else { std::uint16_t src_x = rowSize - element->scaled.position.x; using VectorType = VectorF16L<4, VectorF16L<1, 1, 1>::MaxElement / 4, VectorF16L<1, 1, 1>::MaxSize/((VectorF16L<1, 1, 1>::MaxElement / 4)*4)>; VectorType src(src_buffer[src_y * src_width + src_x].v); VectorType dst(buffer[frame][y * this->sizeX + rowSize].v); VectorType srcA = src.ShufflePacked<3,3,3,3>(); VectorType dstA = dst.ShufflePacked<3,3,3,3>(); VectorType srcANeg = -srcA; VectorType outA = VectorType::MulitplyAdd(dstA, srcANeg, srcA); VectorType result = src + dst * srcANeg / outA; result = VectorType::BlendPacked<0,0,0,1>(dst, outA); result.Store(buffer[frame][y * this->sizeX + rowSize].v); rowSize -= elementsPerVector; } } } } else { for (std::uint16_t y = dirty.top; y < dirty.bottom; y++) { std::uint16_t src_y = y - element->scaled.position.y; for (std::uint16_t x = dirty.left; x < dirty.right; x++) { std::uint16_t src_x = x - element->scaled.position.x; Vector src = src_buffer[src_y * src_width + src_x]; Vector dst = buffer[frame][y * this->sizeX + x]; float srcA = src.a / 255.0f; float dstA = dst.a / 255.0f; float outA = srcA + dstA * (1.0f - srcA); this->buffer[frame][y * this->sizeX + x] = Vector( static_cast((src.r + dst.r * (1.0f - srcA)) / outA), static_cast((src.g + dst.g * (1.0f - srcA)) / outA), static_cast((src.b + dst.b * (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, 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::int32_t(0)), std::min(element->scaled.position.x + element->scaled.size.x, this->sizeX), std::max(element->scaled.position.y, std::int32_t(0)), std::min(element->scaled.position.y + element->scaled.size.y, this->sizeY)); clipRects.emplace_back(std::max(element->oldScale[frame].position.x, std::int32_t(0)), std::min(element->oldScale[frame].position.x + element->oldScale[frame].size.x, this->sizeX), std::max(element->oldScale[frame].position.y, std::int32_t(0)), std::min(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::int32_t(0)), std::min(element->scaled.position.x + element->scaled.size.x, this->sizeX), std::max(element->scaled.position.y, std::int32_t(0)), std::min(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; } } }; }