/* 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(); } }