Plan depth export through document renderer

This commit is contained in:
2026-06-05 21:03:27 +02:00
parent 3c36be4b43
commit 3be7171010
10 changed files with 443 additions and 14 deletions

View File

@@ -37,10 +37,20 @@ struct DocumentCubeFaceExportTarget {
std::size_t face_count = 0;
};
struct DocumentDepthExportTarget {
std::string image_path;
std::string depth_path;
};
struct DocumentCubeFaceExportPayload {
std::span<const std::byte> bytes;
};
struct DocumentDepthExportPayload {
std::span<const std::byte> image_bytes;
std::span<const std::byte> depth_bytes;
};
struct DocumentExportCollectionPngPayload {
std::string path_suffix;
std::span<const std::byte> bytes;
@@ -195,6 +205,16 @@ public:
virtual void publish_exported_image(std::string_view path) = 0;
};
class DocumentDepthExportWriteServices {
public:
virtual ~DocumentDepthExportWriteServices() = default;
virtual pp::foundation::Status write_binary_file(
std::string_view path,
std::span<const std::byte> bytes) = 0;
virtual void publish_exported_image(std::string_view path) = 0;
};
class DocumentExportCollectionWriteServices {
public:
virtual ~DocumentExportCollectionWriteServices() = default;
@@ -623,6 +643,34 @@ document_cube_face_export_names() noexcept
return pp::foundation::Result<DocumentCubeFaceExportTarget>::success(std::move(target));
}
[[nodiscard]] inline pp::foundation::Result<DocumentDepthExportTarget> make_document_depth_export_target(
std::string_view work_directory,
std::string_view document_name)
{
if (work_directory.empty()) {
return pp::foundation::Result<DocumentDepthExportTarget>::failure(
pp::foundation::Status::invalid_argument("work directory must not be empty"));
}
if (document_name.empty()) {
return pp::foundation::Result<DocumentDepthExportTarget>::failure(
pp::foundation::Status::invalid_argument("document name must not be empty"));
}
DocumentDepthExportTarget target;
target.image_path.reserve(work_directory.size() + document_name.size() + 5U);
target.image_path += work_directory;
target.image_path += "/";
target.image_path += document_name;
target.image_path += ".png";
target.depth_path.reserve(work_directory.size() + document_name.size() + 11U);
target.depth_path += work_directory;
target.depth_path += "/";
target.depth_path += document_name;
target.depth_path += "_depth.png";
return pp::foundation::Result<DocumentDepthExportTarget>::success(std::move(target));
}
[[nodiscard]] inline std::string document_export_two_digit_index(std::size_t index)
{
auto value = std::to_string(index);
@@ -706,6 +754,33 @@ document_cube_face_export_names() noexcept
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_document_depth_export_write(
const DocumentDepthExportTarget& target,
DocumentDepthExportPayload payload,
DocumentDepthExportWriteServices& services)
{
if (target.image_path.empty() || target.depth_path.empty()) {
return pp::foundation::Status::invalid_argument("depth export target requires image and depth paths");
}
if (payload.image_bytes.empty() || payload.depth_bytes.empty()) {
return pp::foundation::Status::invalid_argument("depth export payload requires image and depth bytes");
}
const auto image_status = services.write_binary_file(target.image_path, payload.image_bytes);
if (!image_status.ok()) {
return image_status;
}
services.publish_exported_image(target.image_path);
const auto depth_status = services.write_binary_file(target.depth_path, payload.depth_bytes);
if (!depth_status.ok()) {
return depth_status;
}
services.publish_exported_image(target.depth_path);
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_document_export_collection_write(
const DocumentExportCollectionTarget& target,
std::span<const DocumentExportCollectionPngPayload> payloads,

View File

@@ -67,6 +67,7 @@ pp::foundation::Status write_export_binary_file(std::string_view path, std::span
class LegacyExportWriteServices final
: public pp::app::DocumentCubeFaceExportWriteServices
, public pp::app::DocumentDepthExportWriteServices
, public pp::app::DocumentExportCollectionWriteServices {
public:
explicit LegacyExportWriteServices(App& app) noexcept
@@ -519,6 +520,48 @@ public:
void export_depth(std::string_view document_name) override
{
auto* app = &app_;
#if !__WEB__
const auto target = pp::app::make_document_depth_export_target(app_.work_path, document_name);
if (target) {
LOG(
"export-depth document export target: image=%s depth=%s",
target.value().image_path.c_str(),
target.value().depth_path.c_str());
} else {
LOG("export-depth document export target planning failed: %s", target.status().message);
}
const auto prepared = prepare_legacy_document_export_snapshot(app_, "export-depth");
if (prepared) {
const auto plan = pp::paint_renderer::plan_document_depth_export_render(
pp::paint_renderer::DocumentDepthExportRenderPlanRequest {
.document = &prepared.value().snapshot.document,
.frame_index = prepared.value().snapshot.document.active_frame_index(),
});
if (plan) {
LOG(
"export-depth document export render plan: output=%ux%u mergedFaceDraws=%zu layerDepthDraws=%zu visitedLayers=%zu visibleLayers=%zu facePayloads=%zu requiresRendererReadback=%s",
plan.value().output_extent.width,
plan.value().output_extent.height,
plan.value().merged_face_draw_count,
plan.value().layer_depth_draw_count,
plan.value().visited_layer_count,
plan.value().visible_layer_count,
plan.value().face_payload_count,
plan.value().requires_renderer_readback ? "true" : "false");
} else {
LOG(
"export-depth document export render plan retained legacy export after failure: %s",
plan.status().message);
}
} else {
LOG(
"export-depth document export snapshot bridge retained legacy export after failure: %s",
prepared.status().message);
}
#else
prepare_legacy_document_export_snapshot_or_continue(app_, "export-depth");
#endif
app_.canvas->m_canvas->export_depth(std::string(document_name), [app] {
show_export_success_dialog(
*app,

View File

@@ -683,6 +683,57 @@ export_document_frame_equirectangular_png(DocumentFrameCompositeRequest request)
return export_document_frame_equirectangular_png(composite.value());
}
pp::foundation::Result<DocumentDepthExportRenderPlan> plan_document_depth_export_render(
DocumentDepthExportRenderPlanRequest request) noexcept
{
if (request.document == nullptr) {
return pp::foundation::Result<DocumentDepthExportRenderPlan>::failure(
pp::foundation::Status::invalid_argument("document depth export request requires a document"));
}
if (request.frame_index >= request.document->frames().size()) {
return pp::foundation::Result<DocumentDepthExportRenderPlan>::failure(
pp::foundation::Status::out_of_range("document depth export frame index is outside the document"));
}
const auto output_pixels = expected_pixel_count(request.output_extent);
if (!output_pixels) {
return pp::foundation::Result<DocumentDepthExportRenderPlan>::failure(output_pixels.status());
}
DocumentDepthExportRenderPlan plan;
plan.output_extent = request.output_extent;
plan.merged_face_draw_count = pp::document::cube_face_count;
plan.visited_layer_count = request.document->layers().size();
for (const auto& layer : request.document->layers()) {
if (!layer.visible || layer.opacity == 0.0F || request.frame_index >= layer.frames.size()) {
continue;
}
++plan.visible_layer_count;
std::array<bool, pp::document::cube_face_count> layer_faces {};
const auto& frame = layer.frames[request.frame_index];
for (const auto& payload : frame.face_pixels) {
if (payload.face_index >= pp::document::cube_face_count) {
return pp::foundation::Result<DocumentDepthExportRenderPlan>::failure(
pp::foundation::Status::out_of_range("document depth export face index is outside the cube"));
}
layer_faces[payload.face_index] = true;
++plan.face_payload_count;
}
for (const auto has_payload : layer_faces) {
if (has_payload) {
++plan.layer_depth_draw_count;
}
}
}
return pp::foundation::Result<DocumentDepthExportRenderPlan>::success(plan);
}
pp::foundation::Result<DocumentLayerEquirectangularPngExportResult>
export_document_layers_equirectangular_pngs(DocumentLayerEquirectangularPngExportRequest request)
{

View File

@@ -156,6 +156,29 @@ struct DocumentFrameEquirectangularPngExportResult {
std::size_t composited_layer_face_count = 0;
};
struct DocumentDepthExportRenderPlanRequest {
const pp::document::CanvasDocument* document = nullptr;
std::size_t frame_index = 0;
pp::renderer::Extent2D output_extent {
.width = 1024,
.height = 1024,
};
};
struct DocumentDepthExportRenderPlan {
pp::renderer::Extent2D output_extent {
.width = 1024,
.height = 1024,
};
std::size_t merged_face_draw_count = 0;
std::size_t layer_depth_draw_count = 0;
std::size_t visited_layer_count = 0;
std::size_t visible_layer_count = 0;
std::size_t face_payload_count = 0;
bool uses_perspective_camera = true;
bool requires_renderer_readback = true;
};
struct DocumentLayerEquirectangularPngExportRequest {
const pp::document::CanvasDocument* document = nullptr;
std::size_t frame_index = 0;
@@ -232,6 +255,9 @@ export_document_frame_equirectangular_png(const DocumentFrameCompositeResult& co
[[nodiscard]] pp::foundation::Result<DocumentFrameEquirectangularPngExportResult>
export_document_frame_equirectangular_png(DocumentFrameCompositeRequest request);
[[nodiscard]] pp::foundation::Result<DocumentDepthExportRenderPlan> plan_document_depth_export_render(
DocumentDepthExportRenderPlanRequest request) noexcept;
[[nodiscard]] pp::foundation::Result<DocumentLayerEquirectangularPngExportResult>
export_document_layers_equirectangular_pngs(DocumentLayerEquirectangularPngExportRequest request);