rendering improvements

This commit is contained in:
Jorijn van der Graaf 2026-03-12 01:07:46 +01:00
commit 7f46ac13fa
14 changed files with 296 additions and 179 deletions

View file

@ -32,7 +32,7 @@ export namespace Crafter {
Transform2D transform;
std::int32_t sizeX;
std::int32_t sizeY;
std::vector<RenderingElement2DBase*> elements;
std::vector<Transform2D*> elements;
std::vector<ClipRect> dirtyRects;
RendertargetBase() = default;
RendertargetBase(std::int16_t sizeX, std::int16_t sizeY) : sizeX(sizeX), sizeY(sizeY), transform({0, 0, 1, 1, 0, 0, 0}){
@ -42,13 +42,7 @@ export namespace Crafter {
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);
dirtyRects.emplace_back(std::max(scale.position.x, std::int32_t(0)), std::min(scale.position.x + scale.size.x, sizeX), std::max(scale.position.y, std::int32_t(0)), std::min(scale.position.y + scale.size.y, sizeY));
}
};
@ -59,89 +53,99 @@ export namespace Crafter {
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
void RenderElement(Transform2D* elementTransform) {
RenderingElement2DBase* element = dynamic_cast<RenderingElement2DBase*>(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<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;
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
}
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);
}
#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() {
elements.erase(std::remove(elements.begin(), elements.end(), static_cast<Transform2D*>(nullptr)), elements.end());
std::sort(elements.begin(), elements.end(), [](Transform2D* a, Transform2D* b){ return a->anchor.z < b->anchor.z; });
//std::vector<ClipRect> newClip;
// for (std::uint32_t i = 0; i < dirtyRects.size(); i++) {
// ClipRect rect = dirtyRects[i];
@ -250,7 +254,7 @@ export namespace Crafter {
}
}
for(RenderingElement2DBase* child : elements) {
for(Transform2D* child : elements) {
RenderElement(child);
}
dirtyRects.clear();