Render-path containers survive running out of memory: std::map for QHash (12.8e)
This commit is contained in:
1 parent
56af936a7b
commit
bb8129e8d5
8 files changed
+210
-48
No files matched your search
@@ -6,10 +6,10 @@
|
||||
#include "IO/ImageExporter.h"
|
||||
#include "Logging.h"
|
||||
#include "Rendering/DownsampleCache.h"
|
||||
#include <QHash>
|
||||
#include "IO/ProjectStore.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
LayerMask::LayerMask(ImportedImage asset, bool isEnabled, std::optional<LayerTransform> placement, bool isLinked)
|
||||
@@ -220,12 +220,12 @@ void FolderMaskClip::draw(const std::vector<QUuid> &ids, const std::function<std
|
||||
const std::function<void(QUuid, const QImage &clip)> &drawLayer)
|
||||
{
|
||||
// Coverage through a folder and its ancestors; null for none.
|
||||
QHash<QUuid, QImage> covered;
|
||||
std::map<QUuid, QImage> covered;
|
||||
std::function<QImage(std::optional<QUuid>, int)> through = [&](std::optional<QUuid> folder, int depth) -> QImage {
|
||||
if (!folder || depth >= 64)
|
||||
return QImage();
|
||||
if (const auto found = covered.constFind(*folder); found != covered.constEnd())
|
||||
return *found;
|
||||
if (const auto found = covered.find(*folder); found != covered.end())
|
||||
return found->second;
|
||||
QImage coverage = through(parent(*folder), depth + 1);
|
||||
if (const std::optional<Applier> applier = clip(*folder)) {
|
||||
if (coverage.isNull()) {
|
||||
@@ -236,7 +236,7 @@ void FolderMaskClip::draw(const std::vector<QUuid> &ids, const std::function<std
|
||||
}
|
||||
(*applier)(context, coverage);
|
||||
}
|
||||
covered.insert(*folder, coverage);
|
||||
covered.insert_or_assign(*folder, coverage);
|
||||
return coverage;
|
||||
};
|
||||
for (const QUuid &id : ids)
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
#include "Rendering/AdjustmentSurface.h"
|
||||
#include "Rendering/LayerRenderer.h"
|
||||
#include "Rendering/LiveMaskRenderer.h"
|
||||
#include <QHash>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QSet>
|
||||
#include <QtConcurrent>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
|
||||
void EditorSession::drawLiveComposite(const CanvasDocument &document, QPainter &context, bool onSurface) const
|
||||
@@ -73,27 +73,31 @@ void EditorSession::drawLiveComposite(const CanvasDocument &document, QPainter &
|
||||
|
||||
void LiveMaskGraph::validate(const std::vector<ProjectLayerRecord> &layers)
|
||||
{
|
||||
QHash<QUuid, const ProjectLayerRecord *> records;
|
||||
// Standard containers: a worker's render may run out of memory.
|
||||
std::map<QUuid, const ProjectLayerRecord *> records;
|
||||
for (const ProjectLayerRecord &layer : layers) {
|
||||
if (records.contains(layer.id))
|
||||
if (!records.emplace(layer.id, &layer).second)
|
||||
throw ProjectError(ProjectError::Kind::invalid);
|
||||
records.insert(layer.id, &layer);
|
||||
}
|
||||
const auto record = [&records](QUuid id) -> const ProjectLayerRecord * {
|
||||
const auto found = records.find(id);
|
||||
return found != records.end() ? found->second : nullptr;
|
||||
};
|
||||
for (const ProjectLayerRecord &layer : layers) {
|
||||
QSet<QUuid> path;
|
||||
std::set<QUuid> path;
|
||||
std::optional<QUuid> current = layer.id;
|
||||
while (current) {
|
||||
const ProjectLayerRecord *record = records.value(*current);
|
||||
if (path.size() >= 256 || path.contains(*current) || !record)
|
||||
const ProjectLayerRecord *found = record(*current);
|
||||
if (path.size() >= 256 || path.contains(*current) || !found)
|
||||
throw ProjectError(ProjectError::Kind::invalid);
|
||||
path.insert(*current);
|
||||
if (record->maskSourceID) {
|
||||
const ProjectLayerRecord *source = records.value(*record->maskSourceID);
|
||||
if (found->maskSourceID) {
|
||||
const ProjectLayerRecord *source = record(*found->maskSourceID);
|
||||
// Folders never clip; nothing clips to an adjustment.
|
||||
if (record->isGroup == true || !source || source->isGroup == true || source->adjustment)
|
||||
if (found->isGroup == true || !source || source->isGroup == true || source->adjustment)
|
||||
throw ProjectError(ProjectError::Kind::invalid);
|
||||
}
|
||||
current = record->maskSourceID;
|
||||
current = found->maskSourceID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,21 +114,20 @@ std::optional<ImportedImage> LiveMaskBaker::bake(const ProjectSnapshot &snapshot
|
||||
QImage surface = BrushRaster::context(width, height, false);
|
||||
const QTransform inverse = BrushRaster::pixelToDocument(layers[found].transform, width, height).inverted();
|
||||
// Swift's dictionary traps on an id that repeats.
|
||||
QHash<QUuid, const ProjectLayerRecord *> records;
|
||||
std::map<QUuid, const ProjectLayerRecord *> records;
|
||||
for (const ProjectLayerRecord &layer : layers) {
|
||||
if (records.contains(layer.id))
|
||||
if (!records.emplace(layer.id, &layer).second)
|
||||
throw std::logic_error("a snapshot to bake repeats a layer id");
|
||||
records.insert(layer.id, &layer);
|
||||
}
|
||||
// A source dims with its folders, as exported.
|
||||
const auto folded = [&records](const ProjectLayerRecord &layer) {
|
||||
return LayerOpacity::effective(layer.opacity.value_or(1), layer.parentID, [&records](QUuid id) -> std::optional<std::pair<double, std::optional<QUuid>>> {
|
||||
if (!records.contains(id))
|
||||
return std::nullopt;
|
||||
return std::pair(records.value(id)->opacity.value_or(1), records.value(id)->parentID);
|
||||
return std::pair(records.at(id)->opacity.value_or(1), records.at(id)->parentID);
|
||||
});
|
||||
};
|
||||
LiveMaskRenderer live([&](QUuid id) { return records.contains(id) ? records.value(id)->maskSourceID : std::nullopt; },
|
||||
LiveMaskRenderer live([&](QUuid id) { return records.contains(id) ? records.at(id)->maskSourceID : std::nullopt; },
|
||||
[&](QUuid id, QPainter &context, const QImage &clip) {
|
||||
// `at` is checked: end() would read wild memory.
|
||||
if (!records.contains(id) || !snapshot.images.contains(id))
|
||||
@@ -136,7 +139,7 @@ std::optional<ImportedImage> LiveMaskBaker::bake(const ProjectSnapshot &snapshot
|
||||
context, {.clip = clip});
|
||||
return;
|
||||
}
|
||||
const ProjectLayerRecord &layer = *records.value(id);
|
||||
const ProjectLayerRecord &layer = *records.at(id);
|
||||
const std::optional<LayerMask> mask = snapshot.mask(layer);
|
||||
const std::optional<QImage> shown =
|
||||
mask ? mask->clipImage(mask->placement, layer.transform, pixels.width(), pixels.height()) : std::nullopt;
|
||||
|
||||
@@ -16,7 +16,7 @@ void LiveMaskRenderer::prepareStacks(const std::vector<QUuid> &ids, const std::f
|
||||
const std::function<LayerBlendMode(QUuid)> &blend)
|
||||
{
|
||||
for (const QUuid &id : ids)
|
||||
m_modes.insert(id, blend(id));
|
||||
m_modes.insert_or_assign(id, blend(id));
|
||||
for (size_t index = 0; index < ids.size(); ++index) {
|
||||
const QUuid base = ids[index];
|
||||
if (m_source(base))
|
||||
@@ -29,9 +29,9 @@ void LiveMaskRenderer::prepareStacks(const std::vector<QUuid> &ids, const std::f
|
||||
}
|
||||
if (children.empty())
|
||||
continue;
|
||||
m_stackModes.insert(base, blend(base));
|
||||
m_stacked.unite(QSet<QUuid>(children.begin(), children.end()));
|
||||
m_stacks.insert(base, std::move(children));
|
||||
m_stackModes.insert_or_assign(base, blend(base));
|
||||
m_stacked.insert(children.begin(), children.end());
|
||||
m_stacks.insert_or_assign(base, std::move(children));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,10 +45,10 @@ void LiveMaskRenderer::drawComposite(QUuid id, QPainter &context, const QImage &
|
||||
adjust(id, context, clip);
|
||||
return;
|
||||
}
|
||||
const auto children = m_stacks.constFind(id);
|
||||
const auto children = m_stacks.find(id);
|
||||
const QSize device(context.device()->width(), context.device()->height());
|
||||
QImage group, alpha;
|
||||
if (children != m_stacks.constEnd() && fits(context)) {
|
||||
if (children != m_stacks.end() && fits(context)) {
|
||||
group = QImage(device, QImage::Format_RGBA8888_Premultiplied);
|
||||
alpha = QImage(device, QImage::Format_Grayscale8);
|
||||
if (group.isNull() || alpha.isNull())
|
||||
@@ -56,9 +56,9 @@ void LiveMaskRenderer::drawComposite(QUuid id, QPainter &context, const QImage &
|
||||
}
|
||||
if (group.isNull() || alpha.isNull()) {
|
||||
// Without a group each child draws alone, through the base.
|
||||
if (children != m_stacks.constEnd()) {
|
||||
for (const QUuid &child : *children)
|
||||
m_stacked.remove(child);
|
||||
if (children != m_stacks.end()) {
|
||||
for (const QUuid &child : children->second)
|
||||
m_stacked.erase(child);
|
||||
}
|
||||
draw(id, context, clip);
|
||||
return;
|
||||
@@ -76,7 +76,7 @@ void LiveMaskRenderer::drawComposite(QUuid id, QPainter &context, const QImage &
|
||||
{
|
||||
QPainter painter(&group);
|
||||
painter.setTransform(context.deviceTransform());
|
||||
for (const QUuid &child : *children) {
|
||||
for (const QUuid &child : children->second) {
|
||||
if (adjustment(child))
|
||||
adjust(child, painter, QImage());
|
||||
else
|
||||
@@ -87,7 +87,7 @@ void LiveMaskRenderer::drawComposite(QUuid id, QPainter &context, const QImage &
|
||||
size_t(device.width()), size_t(device.height()));
|
||||
// Device pixels already: one to one, at the painter's opacity.
|
||||
LayerRenderer::composite(context, context.deviceTransform().inverted(), QRectF(group.rect()), LayerSampling::nearest,
|
||||
InterpolationQuality::none, {.opacity = context.opacity(), .blendMode = m_stackModes.value(id), .clip = clip}, QRectF(),
|
||||
InterpolationQuality::none, {.opacity = context.opacity(), .blendMode = m_stackModes.at(id), .clip = clip}, QRectF(),
|
||||
[&](QPainter &aside) { aside.drawImage(QRectF(group.rect()), group); });
|
||||
}
|
||||
|
||||
@@ -127,16 +127,16 @@ bool LiveMaskRenderer::fits(const QPainter &context) const
|
||||
|
||||
std::optional<QImage> LiveMaskRenderer::coverage(QUuid id, const QPainter &context)
|
||||
{
|
||||
if (const auto found = m_cache.constFind(id); found != m_cache.constEnd())
|
||||
return *found;
|
||||
if (const auto found = m_cache.find(id); found != m_cache.end())
|
||||
return found->second;
|
||||
if (!fits(context))
|
||||
qCWarning(lcRendering) << "clip coverage passes its pixel budget:" << context.device()->width() << "x" << context.device()->height();
|
||||
if (m_visiting.contains(id) || m_visiting.size() >= 256 || !fits(context))
|
||||
return std::nullopt;
|
||||
struct Visit {
|
||||
QSet<QUuid> &visiting;
|
||||
std::set<QUuid> &visiting;
|
||||
QUuid id;
|
||||
~Visit() { visiting.remove(id); }
|
||||
~Visit() { visiting.erase(id); }
|
||||
} visit{m_visiting, id};
|
||||
m_visiting.insert(id);
|
||||
const QSize device(context.device()->width(), context.device()->height());
|
||||
@@ -153,7 +153,7 @@ std::optional<QImage> LiveMaskRenderer::coverage(QUuid id, const QPainter &conte
|
||||
}
|
||||
layer_extract_alpha(pixels.constBits(), size_t(pixels.bytesPerLine()), gray.bits(), size_t(gray.bytesPerLine()),
|
||||
size_t(device.width()), size_t(device.height()));
|
||||
m_cache.insert(id, gray);
|
||||
m_cache.insert_or_assign(id, gray);
|
||||
return gray;
|
||||
}
|
||||
|
||||
@@ -217,7 +217,8 @@ void LiveMaskRenderer::adjust(QUuid id, QPainter &context, const QImage &clip)
|
||||
throw ExportError(ExportError::Kind::render);
|
||||
original.setDevicePixelRatio(1);
|
||||
QImage adjusted = adjustment(id).value().apply(original, region);
|
||||
const LayerBlendMode mode = m_modes.value(id, LayerBlendMode::normal);
|
||||
const auto found = m_modes.find(id);
|
||||
const LayerBlendMode mode = found != m_modes.end() ? found->second : LayerBlendMode::normal;
|
||||
if (mode != LayerBlendMode::normal)
|
||||
adjusted = blended(original, adjusted, mode);
|
||||
QImage coverage(device->size(), QImage::Format_Alpha8);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
#include "Document/LayerAdjustment.h"
|
||||
#include "Document/LayerAppearance.h"
|
||||
#include <QHash>
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QSet>
|
||||
#include <QUuid>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
// Per-render dependency cache: a layer shows through its source's alpha.
|
||||
@@ -39,11 +39,12 @@ private:
|
||||
const Source m_source;
|
||||
const DrawOwn m_drawOwn;
|
||||
const qint64 m_pixelBudget;
|
||||
QHash<QUuid, QImage> m_cache;
|
||||
QSet<QUuid> m_visiting;
|
||||
QHash<QUuid, std::vector<QUuid>> m_stacks;
|
||||
QSet<QUuid> m_stacked;
|
||||
QHash<QUuid, LayerBlendMode> m_stackModes;
|
||||
// Standard containers: Qt 6.4's hash inserts terminate out of memory.
|
||||
std::map<QUuid, QImage> m_cache;
|
||||
std::set<QUuid> m_visiting;
|
||||
std::map<QUuid, std::vector<QUuid>> m_stacks;
|
||||
std::set<QUuid> m_stacked;
|
||||
std::map<QUuid, LayerBlendMode> m_stackModes;
|
||||
// Every prepared layer's mode, as Swift's `blendMode`.
|
||||
QHash<QUuid, LayerBlendMode> m_modes;
|
||||
std::map<QUuid, LayerBlendMode> m_modes;
|
||||
};
|
||||
Reference in new issue
Block a user