rendertarget multi frame rewrite

This commit is contained in:
Jorijn van der Graaf 2026-03-12 21:13:53 +01:00
commit 2b22c16ce7
13 changed files with 225 additions and 276 deletions

View file

@ -29,31 +29,31 @@ import :Types;
import :Window;
export namespace Crafter {
template<bool Scaling, bool Owning, bool Rotating, std::uint8_t Alignment = 0> requires ((!Rotating || Scaling) && (!Owning || Scaling))
struct RenderingElement2D : RenderingElement2DBase, ScalingBase<Scaling, Owning, Alignment>, RotatingBase<Rotating> {
template<bool Scaling, bool Owning, bool Rotating, std::uint8_t Alignment = 0, std::uint8_t Frames = 1> requires ((!Rotating || Scaling) && (!Owning || Scaling))
struct RenderingElement2D : RenderingElement2DBase<Frames>, ScalingBase<Scaling, Owning, Alignment>, RotatingBase<Rotating> {
RenderingElement2D() = default;
RenderingElement2D(Anchor2D anchor, OpaqueType opaque) : RenderingElement2DBase(anchor, opaque) {
RenderingElement2D(Anchor2D anchor, OpaqueType opaque) : RenderingElement2DBase<Frames>(anchor, opaque) {
}
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t rotation) requires(Rotating) : RenderingElement2DBase(anchor, opaque), RotatingBase<Rotating>(rotation) {
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t rotation) requires(Rotating) : RenderingElement2DBase<Frames>(anchor, opaque), RotatingBase<Rotating>(rotation) {
}
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, Vector<std::uint8_t, 4, Alignment>* scalingBuffer) requires(Scaling && !Owning) : RenderingElement2DBase(anchor, opaque), ScalingBase<Scaling, Owning, Alignment>(bufferWidth, bufferHeight, scalingBuffer) {
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, Vector<std::uint8_t, 4, Alignment>* scalingBuffer) requires(Scaling && !Owning) : RenderingElement2DBase<Frames>(anchor, opaque), ScalingBase<Scaling, Owning, Alignment>(bufferWidth, bufferHeight, scalingBuffer) {
}
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, Vector<std::uint8_t, 4, Alignment>* scalingBuffer, std::uint32_t rotation) requires(Scaling && !Owning && Rotating) : RenderingElement2DBase(anchor, opaque), ScalingBase<Scaling, Owning, Alignment>(bufferWidth, bufferHeight, scalingBuffer), RotatingBase<Rotating>(rotation) {
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, Vector<std::uint8_t, 4, Alignment>* scalingBuffer, std::uint32_t rotation) requires(Scaling && !Owning && Rotating) : RenderingElement2DBase<Frames>(anchor, opaque), ScalingBase<Scaling, Owning, Alignment>(bufferWidth, bufferHeight, scalingBuffer), RotatingBase<Rotating>(rotation) {
}
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight) requires(Owning) : RenderingElement2DBase(anchor, opaque), ScalingBase<Scaling, Owning, Alignment>(bufferWidth, bufferHeight) {
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight) requires(Owning) : RenderingElement2DBase<Frames>(anchor, opaque), ScalingBase<Scaling, Owning, Alignment>(bufferWidth, bufferHeight) {
}
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, std::uint32_t rotation) requires(Owning && Rotating) : RenderingElement2DBase(anchor, opaque), ScalingBase<Scaling, Owning, Alignment>(bufferWidth, bufferHeight) , RotatingBase<Rotating>(rotation) {
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, std::uint32_t rotation) requires(Owning && Rotating) : RenderingElement2DBase<Frames>(anchor, opaque), ScalingBase<Scaling, Owning, Alignment>(bufferWidth, bufferHeight) , RotatingBase<Rotating>(rotation) {
}
RenderingElement2D(Anchor2D anchor, TextureAsset<Vector<std::uint8_t, 4, Alignment>>& texture) requires(!Owning && Scaling) : RenderingElement2DBase(anchor, texture.opaque), ScalingBase<Scaling, Owning, Alignment>(texture.pixels.data(), texture.sizeX, texture.sizeY) {
RenderingElement2D(Anchor2D anchor, TextureAsset<Vector<std::uint8_t, 4, Alignment>>& texture) requires(!Owning && Scaling) : RenderingElement2DBase<Frames>(anchor, texture.opaque), ScalingBase<Scaling, Owning, Alignment>(texture.pixels.data(), texture.sizeX, texture.sizeY) {
}
RenderingElement2D(Anchor2D anchor, TextureAsset<Vector<std::uint8_t, 4, Alignment>>& texture, std::uint32_t rotation) requires(!Owning && Scaling && Rotating) : RenderingElement2DBase(anchor, texture.opaque), ScalingBase<Scaling, Owning, Alignment>(texture.pixels.data(), texture.sizeX, texture.sizeY), RotatingBase<Rotating>(rotation) {
RenderingElement2D(Anchor2D anchor, TextureAsset<Vector<std::uint8_t, 4, Alignment>>& texture, std::uint32_t rotation) requires(!Owning && Scaling && Rotating) : RenderingElement2DBase<Frames>(anchor, texture.opaque), ScalingBase<Scaling, Owning, Alignment>(texture.pixels.data(), texture.sizeX, texture.sizeY), RotatingBase<Rotating>(rotation) {
}
@ -61,18 +61,18 @@ export namespace Crafter {
RenderingElement2D& operator=(RenderingElement2D&) = delete;
void ScaleNearestNeighbor() requires(Scaling) {
for (std::uint32_t y = 0; y < scaled.size.y; y++) {
std::uint32_t srcY = y * ScalingBase<true, Owning, Alignment>::bufferHeight / scaled.size.y;
for (std::uint32_t x = 0; x < scaled.size.x; x++) {
std::uint32_t srcX = x * ScalingBase<true, Owning, Alignment>::bufferWidth / scaled.size.x;
buffer[y * scaled.size.x + x] = ScalingBase<true, Owning, Alignment>::scalingBuffer[srcY * ScalingBase<true, Owning, Alignment>::bufferWidth + srcX];
for (std::uint32_t y = 0; y < this->scaled.size.y; y++) {
std::uint32_t srcY = y * ScalingBase<true, Owning, Alignment>::bufferHeight / this->scaled.size.y;
for (std::uint32_t x = 0; x < this->scaled.size.x; x++) {
std::uint32_t srcX = x * ScalingBase<true, Owning, Alignment>::bufferWidth / this->scaled.size.x;
this->buffer[y * this->scaled.size.x + x] = ScalingBase<true, Owning, Alignment>::scalingBuffer[srcY * ScalingBase<true, Owning, Alignment>::bufferWidth + srcX];
}
}
}
void ScaleRotating() requires(Scaling) {
const std::uint32_t dstWidth = scaled.size.x;
const std::uint32_t dstHeight = scaled.size.y;
const float dstWidth = this->scaled.size.x;
const float dstHeight = this->scaled.size.y;
const float c2 = std::abs(std::cos(RotatingBase<true>::rotation));
const float s2 = std::abs(std::sin(RotatingBase<true>::rotation));
@ -80,25 +80,25 @@ export namespace Crafter {
const float rotatedWidth = dstWidth * c2 + dstHeight * s2;
const float rotatedHeight = dstWidth * s2 + dstHeight * c2;
const std::uint32_t diffX = static_cast<std::uint32_t>(std::ceil((rotatedWidth - dstWidth) * 0.5));
const std::uint32_t diffY = static_cast<std::uint32_t>(std::ceil((rotatedHeight - dstHeight) * 0.5));
const float diffX = std::ceil((rotatedWidth - dstWidth) * 0.5);
const float diffY = std::ceil((rotatedHeight - dstHeight) * 0.5);
scaled.size.x += diffX + diffX;
scaled.size.y += diffY + diffY;
this->scaled.size.x += diffX + diffX;
this->scaled.size.y += diffY + diffY;
scaled.position.x -= diffX;
scaled.position.y -= diffY;
this->scaled.position.x -= diffX;
this->scaled.position.y -= diffY;
buffer.clear();
buffer.resize(scaled.size.x * scaled.size.y);
this->buffer.clear();
this->buffer.resize(this->scaled.size.x * this->scaled.size.y);
// Destination center
const float dstCx = (static_cast<float>(scaled.size.x) - 1.0) * 0.5;
const float dstCy = (static_cast<float>(scaled.size.y) - 1.0) * 0.5;
const float dstCx = (this->scaled.size.x - 1.0) * 0.5;
const float dstCy = (this->scaled.size.y - 1.0) * 0.5;
// Source center
const float srcCx = (static_cast<float>(ScalingBase<true, Owning, Alignment>::bufferWidth) - 1.0) * 0.5;
const float srcCy = (static_cast<float>(ScalingBase<true, Owning, Alignment>::bufferHeight) - 1.0) * 0.5;
const float srcCx = (ScalingBase<true, Owning, Alignment>::bufferWidth - 1.0) * 0.5;
const float srcCy = (ScalingBase<true, Owning, Alignment>::bufferHeight - 1.0) * 0.5;
const float c = std::cos(RotatingBase<true>::rotation);
const float s = std::sin(RotatingBase<true>::rotation);
@ -107,8 +107,8 @@ export namespace Crafter {
const float scaleX = static_cast<float>(ScalingBase<true, Owning, Alignment>::bufferWidth) / dstWidth;
const float scaleY = static_cast<float>(ScalingBase<true, Owning, Alignment>::bufferHeight) / dstHeight;
for (std::uint32_t yB = 0; yB < scaled.size.y; ++yB) {
for (std::uint32_t xB = 0; xB < scaled.size.x; ++xB) {
for (std::uint32_t yB = 0; yB < this->scaled.size.y; ++yB) {
for (std::uint32_t xB = 0; xB < this->scaled.size.x; ++xB) {
// ---- Destination pixel relative to center ----
const float dx = (static_cast<float>(xB) - dstCx) * scaleX;
@ -123,74 +123,37 @@ export namespace Crafter {
const std::int32_t srcY = static_cast<std::int32_t>(std::round(sy));
if (srcX >= 0 && srcX < ScalingBase<true, Owning, Alignment>::bufferWidth && srcY >= 0 && srcY < ScalingBase<true, Owning, Alignment>::bufferHeight) {
buffer[yB * scaled.size.x + xB] = ScalingBase<true, Owning, Alignment>::scalingBuffer[srcY * ScalingBase<true, Owning, Alignment>::bufferWidth + srcX];
this->buffer[yB * this->scaled.size.x + xB] = ScalingBase<true, Owning, Alignment>::scalingBuffer[srcY * ScalingBase<true, Owning, Alignment>::bufferWidth + srcX];
}
}
}
}
void UpdatePosition(RendertargetBase& renderer, ScaleData2D oldScale) {
void UpdatePosition(RendertargetBase<Frames>& window, Transform2D& parent) override {
ScaleData2D oldScale = this->scaled;
this->ScaleElement(parent);
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);
if(oldScale.size.x != this->scaled.size.x || oldScale.size.y != this->scaled.size.y) {
this->buffer.resize(this->scaled.size.x * this->scaled.size.y);
ScaleNearestNeighbor();
renderer.AddDirtyRect(oldScale);
renderer.AddDirtyRect(scaled);
} else if(oldScale.position.x != scaled.position.x || oldScale.position.y != scaled.position.y) {
renderer.AddDirtyRect(oldScale);
renderer.AddDirtyRect(scaled);
if(ScalingBase<true, Owning, Alignment>::bufferUpdated) {
ScaleNearestNeighbor();
ScalingBase<true, Owning, Alignment>::bufferUpdated = false;
}
} else if(ScalingBase<true, Owning, Alignment>::bufferUpdated) {
ScaleNearestNeighbor();
ScalingBase<true, Owning, Alignment>::bufferUpdated = false;
renderer.AddDirtyRect(scaled);
}
} else if constexpr(Rotating) {
if(oldScale.size.x != scaled.size.x || oldScale.size.y != scaled.size.y) {
buffer.resize(scaled.size.x * scaled.size.y);
if(oldScale.size.x != this->scaled.size.x || oldScale.size.y != this->scaled.size.y) {
this->buffer.resize(this->scaled.size.x * this->scaled.size.y);
ScaleRotating();
renderer.AddDirtyRect(oldScale);
renderer.AddDirtyRect(scaled);
} else if(oldScale.position.x != scaled.position.x || oldScale.position.y != scaled.position.y) {
renderer.AddDirtyRect(oldScale);
renderer.AddDirtyRect(scaled);
if(ScalingBase<true, Owning, Alignment>::bufferUpdated || RotatingBase<true>::rotationUpdated) {
ScaleRotating();
ScalingBase<true, Owning, Alignment>::bufferUpdated = false;
RotatingBase<true>::rotationUpdated = false;
}
} else if(ScalingBase<true, Owning, Alignment>::bufferUpdated || RotatingBase<true>::rotationUpdated) {
ScaleRotating();
ScalingBase<true, Owning, Alignment>::bufferUpdated = false;
RotatingBase<true>::rotationUpdated = false;
renderer.AddDirtyRect(scaled);
}
} else {
if(oldScale.size.x != scaled.size.x || oldScale.size.y != scaled.size.y) {
buffer.resize(scaled.size.x * scaled.size.y);
renderer.AddDirtyRect(oldScale);
renderer.AddDirtyRect(scaled);
}
if(oldScale.position.x != scaled.position.x || oldScale.position.y != scaled.position.y) {
renderer.AddDirtyRect(oldScale);
renderer.AddDirtyRect(scaled);
if(oldScale.size.x != this->scaled.size.x || oldScale.size.y != this->scaled.size.y) {
this->buffer.resize(this->scaled.size.x * this->scaled.size.y);
}
}
}
void UpdatePosition(RendertargetBase& window, Transform2D& parent) override {
ScaleData2D oldScale = scaled;
ScaleElement(parent);
UpdatePosition(window, oldScale);
for(Transform2D* child : children) {
for(Transform2D* child : this->children) {
child->UpdatePosition(window, *this);
}
}
std::vector<std::string_view> ResizeText(RendertargetBase& window, Transform2D& parent, const std::string_view text, float& size, Font& font, TextOverflowMode overflowMode = TextOverflowMode::Clip, TextScaleMode scaleMode = TextScaleMode::None) {
std::vector<std::string_view> ResizeText(RendertargetBase<Frames>& window, Transform2D& parent, const std::string_view text, float& size, Font& font, std::uint8_t frame, TextOverflowMode overflowMode = TextOverflowMode::Clip, TextScaleMode scaleMode = TextScaleMode::None) {
float scale = stbtt_ScaleForPixelHeight(&font.font, size);
int baseline = (int)(font.ascent * scale);
@ -225,40 +188,29 @@ export namespace Crafter {
}
}
if(scaleMode == TextScaleMode::Element) {
std::int32_t logicalPerPixelY = anchor.height / scaled.size.y;
std::int32_t oldHeight = anchor.height;
std::int32_t logicalPerPixelX = anchor.width / scaled.size.x;
std::int32_t oldwidth = anchor.width;
anchor.height = lineHeight * logicalPerPixelY;
anchor.width = maxWidth * logicalPerPixelX;
if(oldHeight != anchor.height || oldwidth != anchor.width) {
std::int32_t logicalPerPixelY = this->anchor.height / this->scaled.size.y;
std::int32_t oldHeight = this->anchor.height;
std::int32_t logicalPerPixelX = this->anchor.width / this->scaled.size.x;
std::int32_t oldwidth = this->anchor.width;
this->anchor.height = lineHeight * logicalPerPixelY;
this->anchor.width = maxWidth * logicalPerPixelX;
if(oldHeight != this->anchor.height || oldwidth != this->anchor.width) {
UpdatePosition(window, parent);
}
} else if(scaleMode == TextScaleMode::Font) {
float lineHeightPerFont = lineHeight / size;
float lineWidthPerFont = maxWidth / size;
float maxFontHeight = scaled.size.y / lineHeightPerFont;
float maxFontWidth = scaled.size.x / lineWidthPerFont;
float maxFontHeight = this->scaled.size.y / lineHeightPerFont;
float maxFontWidth = this->scaled.size.x / lineWidthPerFont;
size = std::min(maxFontHeight, maxFontWidth);
} else if(scaleMode == TextScaleMode::Buffer) {
if constexpr(Scaling && Owning) {
std::uint32_t neededHeight = lines.size() * lineHeight;
if(neededHeight != ScalingBase<true, true>::bufferHeight || maxWidth != ScalingBase<true, true>::bufferWidth) {
ScalingBase<true, true>::bufferHeight = neededHeight;
ScalingBase<true, true>::bufferWidth = maxWidth;
ScalingBase<true, true>::bufferUpdated = true;
ScalingBase<true, Owning, Alignment>::scalingBuffer.resize(neededHeight*maxWidth);
}
}
} else {
if constexpr(Scaling) {
lines.resize(ScalingBase<true, Owning, Alignment>::bufferHeight / lines.size());
} else {
lines.resize(scaled.size.y / lines.size());
lines.resize(this->scaled.size.y / lines.size());
}
}
} else {
@ -291,7 +243,7 @@ export namespace Crafter {
}
// if line exceeds width, wrap
if (lineWidth > scaled.size.x) {
if (lineWidth > this->scaled.size.x) {
std::size_t wrapPos;
if (lastWrapPos > startPos) {
wrapPos = lastWrapPos; // wrap at last space
@ -321,36 +273,27 @@ export namespace Crafter {
}
if(scaleMode == TextScaleMode::Element) {
float logicalPerPixelY = anchor.height / scaled.size.y;
float oldHeight = anchor.height;
anchor.height = lineHeight * logicalPerPixelY;
if(oldHeight != anchor.height) {
float logicalPerPixelY = this->anchor.height / this->scaled.size.y;
float oldHeight = this->anchor.height;
this->anchor.height = lineHeight * logicalPerPixelY;
if(oldHeight != this->anchor.height) {
UpdatePosition(window, parent);
}
} else if(scaleMode == TextScaleMode::Font) {
float lineHeightPerFont = lineHeight / size;
size = scaled.size.y / lineHeightPerFont;
} else if(scaleMode == TextScaleMode::Buffer) {
if constexpr(Scaling && Owning) {
float neededHeight = lines.size() * lineHeight;
if(neededHeight != ScalingBase<true, true>::bufferHeight) {
ScalingBase<true, true>::bufferHeight = neededHeight;
ScalingBase<true, true>::bufferUpdated = true;
ScalingBase<true, Owning, Alignment>::scalingBuffer.resize(neededHeight*ScalingBase<true, true>::bufferWidth);
}
}
size = this->scaled.size.y / lineHeightPerFont;
} else {
if constexpr(Scaling) {
lines.resize(ScalingBase<true, Owning, Alignment>::bufferHeight / lines.size());
} else {
lines.resize(scaled.size.y / lines.size());
lines.resize(this->scaled.size.y / lines.size());
}
}
}
return lines;
}
void RenderText(std::span<const std::string_view> lines, float size, Vector<std::uint8_t, 4> color, Font& font, TextAlignment alignment = TextAlignment::Left, std::uint32_t offsetX = 0, std::uint32_t offsetY = 0, OpaqueType opaque = OpaqueType::FullyOpaque) {
void RenderText(std::span<const std::string_view> lines, float size, Vector<std::uint8_t, 4> color, Font& font, std::uint8_t frame, TextAlignment alignment = TextAlignment::Left, std::uint32_t offsetX = 0, std::uint32_t offsetY = 0, OpaqueType opaque = OpaqueType::FullyOpaque) {
float scale = stbtt_ScaleForPixelHeight(&font.font, size);
int baseline = (int)(font.ascent * scale);
std::uint32_t lineHeight = (font.ascent - font.descent) * scale;
@ -370,10 +313,10 @@ export namespace Crafter {
x = 0;
break;
case TextAlignment::Center:
x = (scaled.size.x - lineWidth) / 2;
x = (this->scaled.size.x - lineWidth) / 2;
break;
case TextAlignment::Right:
x = scaled.size.x - lineWidth;
x = this->scaled.size.x - lineWidth;
break;
}
@ -407,8 +350,8 @@ export namespace Crafter {
ScalingBase<true, Owning, Alignment>::scalingBuffer[bufferY * ScalingBase<true, Owning, Alignment>::bufferWidth + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]};
}
} else {
if (bufferX >= 0 && bufferX < (int)scaled.size.x && bufferY >= 0 && bufferY < (int)scaled.size.y) {
buffer[bufferY * scaled.size.x + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]};
if (bufferX >= 0 && bufferX < (int)this->scaled.size.x && bufferY >= 0 && bufferY < (int)this->scaled.size.y) {
this->buffer[bufferY * this->scaled.size.x + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]};
}
}
}
@ -427,10 +370,10 @@ export namespace Crafter {
ScalingBase<true, Owning, Alignment>::scalingBuffer[bufferY * ScalingBase<true, Owning, Alignment>::bufferWidth + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]};
}
} else {
if (bufferX >= 0 && bufferX < (int)scaled.size.x && bufferY >= 0 && bufferY < (int)scaled.size.y) {
if (bufferX >= 0 && bufferX < (int)this->scaled.size.x && bufferY >= 0 && bufferY < (int)this->scaled.size.y) {
std::uint8_t alpha = bitmap[j * w + i];
if(alpha != 0) {
buffer[bufferY * scaled.size.x + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]};
this->buffer[bufferY * this->scaled.size.x + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]};
}
}
}
@ -450,20 +393,20 @@ export namespace Crafter {
ScalingBase<true, Owning, Alignment>::scalingBuffer[bufferY * ScalingBase<true, Owning, Alignment>::bufferWidth + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]};
}
} else {
if (bufferX >= 0 && bufferX < (int)scaled.size.x && bufferY >= 0 && bufferY < (int)scaled.size.y) {
if (bufferX >= 0 && bufferX < (int)this->scaled.size.x && bufferY >= 0 && bufferY < (int)this->scaled.size.y) {
std::uint8_t alpha = bitmap[j * w + i];
if(alpha == 0) {
continue;
}
Vector<std::uint8_t, 4, Alignment> dst = buffer[bufferY * scaled.size.x + bufferX];
Vector<std::uint8_t, 4, Alignment> dst = this->buffer[bufferY * this->scaled.size.x + bufferX];
float srcA = (alpha / 255.0f) * (color.a / 255.0f);
float dstA = dst.a / 255.0f;
float outA = srcA + dstA * (1.0f - srcA);
buffer[bufferY * scaled.size.x + bufferX] = Vector<std::uint8_t, 4, Alignment>(
this->buffer[bufferY * this->scaled.size.x + bufferX] = Vector<std::uint8_t, 4, Alignment>(
static_cast<std::uint8_t>((color.r * srcA + dst.r * dstA * (1.0f - srcA)) / outA),
static_cast<std::uint8_t>((color.g * srcA + dst.g * dstA * (1.0f - srcA)) / outA),
static_cast<std::uint8_t>((color.b * srcA + dst.b * dstA * (1.0f - srcA)) / outA),