render target improvements

This commit is contained in:
Jorijn van der Graaf 2026-03-10 22:32:50 +01:00
commit 789bb307d5
12 changed files with 368 additions and 428 deletions

View file

@ -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;
};
}

View file

@ -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

View file

@ -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<Vector<std::uint8_t, 4>> 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<std::uint8_t, 4>* scalingBuffer;
std::uint32_t bufferWidth;
std::uint32_t bufferHeight;
bool bufferUpdated = true;
RenderElement2DScalingNonOwning() = default;
RenderElement2DScalingNonOwning(Vector<std::uint8_t, 4>* 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<bool Scaling, bool Owning>
using ScalingBase =
std::conditional_t<
Scaling,
std::conditional_t<Owning,
RenderElement2DScalingOwning,
RenderElement2DScalingNonOwning>,
EmptyScalingBase
>;
template<bool Rotating>
using RotatingBase =
std::conditional_t<
Rotating,
RenderElement2DRotating,
EmptyRotatingBase
>;
struct RenderingElement2DBase : Transform2D {
std::vector<Vector<std::uint8_t, 4>> 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<bool Scaling, bool Owning, bool Rotating> requires ((!Rotating || Scaling) && (!Owning || Scaling))
struct RenderingElement2D : RenderingElement2DBase, ScalingBase<Scaling, Owning>, RotatingBase<Rotating> {
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);

View file

@ -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<Vector<std::uint8_t, 4>> 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<std::uint8_t, 4>* scalingBuffer;
std::uint32_t bufferWidth;
std::uint32_t bufferHeight;
bool bufferUpdated = true;
RenderElement2DScalingNonOwning() = default;
RenderElement2DScalingNonOwning(Vector<std::uint8_t, 4>* 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<bool Scaling, bool Owning>
using ScalingBase =
std::conditional_t<
Scaling,
std::conditional_t<Owning,
RenderElement2DScalingOwning,
RenderElement2DScalingNonOwning>,
EmptyScalingBase
>;
template<bool Rotating>
using RotatingBase =
std::conditional_t<
Rotating,
RenderElement2DRotating,
EmptyRotatingBase
>;
struct RenderingElement2DBase : Transform2D {
std::vector<Vector<std::uint8_t, 4>> 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;
}
};
}

View file

@ -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<std::uint8_t, 4>* buffer;
struct RendertargetBase {
#ifdef CRAFTER_TIMING
std::vector<std::tuple<const Transform*, std::uint32_t, std::uint32_t, std::chrono::nanoseconds>> renderTimings;
#endif
@ -35,9 +34,227 @@ export namespace Crafter {
std::int32_t sizeY;
std::vector<RenderingElement2DBase*> elements;
std::vector<ClipRect> 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<typename T, std::uint8_t Channels, std::uint8_t Alignment>
struct Rendertarget : RendertargetBase {
Vector<T, Channels, Alignment>* 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<std::uint8_t, 4>* 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<std::uint8_t, 4> 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<T, Channels, Alignment> src = src_buffer[src_y * src_width + src_x];
Vector<T, Channels, Alignment> 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<T, Channels, Alignment>(
static_cast<T>((src.r * srcA + dst.r * dstA * (1.0f - srcA)) / outA),
static_cast<T>((src.g * srcA + dst.g * dstA * (1.0f - srcA)) / outA),
static_cast<T>((src.b * srcA + dst.b * dstA * (1.0f - srcA)) / outA),
static_cast<T>(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<ClipRect> 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<Vector<std::uint8_t, 4>> 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<std::uint8_t, 4>& 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();
}
}
};
}

View file

@ -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<Transform2D*> 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);
};
}

View file

@ -125,7 +125,7 @@ export namespace Crafter {
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
float scale;
#ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE
Rendertarget renderer;
Rendertarget<std::uint8_t, 4, 4> renderer;
#endif
bool configured = false;
xdg_toplevel* xdgToplevel = nullptr;

View file

@ -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;