Render captured canvas snapshots through renderer boundary

This commit is contained in:
2026-06-05 18:31:39 +02:00
parent ba5c3069e1
commit 9cafc39788
6 changed files with 78 additions and 8 deletions

View File

@@ -268,7 +268,11 @@ powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.p
plus the save-readiness report now consumed before retained live saves. For
payload-complete or metadata-only snapshots, the same app-core boundary now
exports through the pure `pp_document` PPI writer and reports generated byte
counts plus decoded dirty-face counts in `ppiExport` JSON. It is covered by
counts plus decoded dirty-face counts in `ppiExport` JSON. Payload-complete
snapshots also feed the active frame through the `pp_paint_renderer`
document-frame compositor and renderer-neutral recording upload path,
reporting texture, transition, command, byte, and active-frame payload counts
in `rendererUpload` JSON. It is covered by
`pano_cli_plan_canvas_document_snapshot_smoke` plus the payload-bearing
snapshot smoke.
- `pano_cli save-document-project` writes that pure document export to a PPI

View File

@@ -11,7 +11,7 @@ and validation command.
| Capability | Current Area | Target Owner | Required Tests |
| --- | --- | --- | --- |
| PPI open/save | `Canvas`, serializer, dialogs | `pp_document`, `pp_assets`, `pano_cli` | Round-trip tiny project, old-version fixture, corrupt/truncated fixture, live-canvas-to-`pp_document` snapshot projection with captured RGBA8 payloads, pending renderer-readback counts, save-readiness reporting before retained live saves, and pure PPI export from payload-complete snapshots |
| PPI open/save | `Canvas`, serializer, dialogs | `pp_document`, `pp_assets`, `pano_cli` | Round-trip tiny project, old-version fixture, corrupt/truncated fixture, live-canvas-to-`pp_document` snapshot projection with captured RGBA8 payloads, pending renderer-readback counts, save-readiness reporting before retained live saves, pure PPI export from payload-complete snapshots, and renderer-neutral active-frame upload from the same snapshot |
| Open-document routing | `App::open_document` | `pp_app_core`, `pano_cli`, `pp_panopainter_ui`, `pp_document`, `pp_assets` | Project/ABR/PPBR route tests, malformed path tests, open-action plan tests, CLI route/action smoke, app open smoke |
| Document session decisions | `App::open_document`, `App::request_close`, save hotkeys, file menu, dialogs | `pp_app_core`, `pano_cli`, `pp_panopainter_ui` | Clean/dirty/prompt-open/save/save-as/save-version/save-before-workflow/name/new-document resolution/overwrite/version-target decision tests, CLI session, new-document, document-file, and document-version smoke, app close/open/save/new/browse smoke |
| Version metadata | `scripts/pre-build.py`, `version.*` | build system, `pp_foundation` | Generated header smoke test, missing-tag behavior |
@@ -26,7 +26,7 @@ and validation command.
| PNG/JPEG import | `Image`, `Canvas` import paths | `pp_assets`, `pp_document` | Fixture import, malformed file |
| PNG/JPEG export | `Canvas`, `Image`, export dialogs | `pp_assets`, `pp_paint_renderer`, `pp_app_core` | Golden output tolerance, export start/target planning tests |
| Equirectangular import/export | `Canvas`, shaders, RTT, export dialogs | `pp_paint_renderer`, `pp_app_core` | Tiny cube/equirect golden, app-core file target tests |
| Cube face export | `Canvas` | `pp_paint_renderer` | Pure six-face document frame composite, renderer texture-upload bridge, OpenGL command-plan coverage, six-face golden set |
| Cube face export | `Canvas` | `pp_paint_renderer` | Pure six-face document frame composite, renderer texture-upload bridge, payload-complete canvas-snapshot renderer-upload automation, OpenGL command-plan coverage, six-face golden set |
| Depth export | `Canvas`, grid tools | `pp_paint_renderer` | Float/readback validation |
## Brush And Painting

File diff suppressed because one or more lines are too long

View File

@@ -674,7 +674,10 @@ prepare and log a payload-completeness report from that snapshot before
delegating to retained `Canvas::project_save`; the app-core snapshot boundary
also has a tested pure PPI export helper, and
`pano_cli plan-canvas-document-snapshot` runs that helper for payload-complete
snapshots and reports generated byte/dirty-face summaries. Live save writer
snapshots and reports generated byte/dirty-face summaries. The same automation
now feeds payload-complete snapshots through the `pp_paint_renderer`
document-frame compositor and renderer-neutral recording upload path, reporting
texture, transition, byte, payload, and command counts. Live save writer
replacement, export adoption, and renderer-owned readback remain under
`DEBT-0010`/`DEBT-0013`/`DEBT-0036`.
`pano_cli plan-image-import` exposes app-core planning for File > Import image
@@ -2219,6 +2222,10 @@ Results:
report (`payloadComplete` and `canExportPpi`) used by the live save bridge,
and payload-complete snapshots now run the pure `pp_document` PPI exporter
and decoded-project summary before emitting `ppiExport` JSON.
- The same payload-complete snapshot automation now uploads the active document
frame through `pp_paint_renderer::upload_document_frame_faces` and the
`RecordingRenderDevice`, emitting `rendererUpload` JSON with texture,
transition, command, byte, and active-frame payload counts.
- `pp_app_core_document_import_tests` passed, covering wide equirectangular,
legacy vertical cube strip, regular transform-placement, and invalid-dimension
import route decisions, equirectangular service dispatch, transform import
@@ -2497,6 +2504,11 @@ Results:
document-canvas tests decode the generated bytes, and
`pano_cli plan-canvas-document-snapshot` reports `ppiExport` readiness,
byte count, and dirty-face count for agent automation.
- Payload-complete canvas snapshot automation also crosses the renderer
boundary now: `pano_cli plan-canvas-document-snapshot` records the same
snapshot through the pure document-frame compositor and renderer-neutral
texture upload stream, so agents can validate document/canvas payloads moving
into renderer commands before live canvas export/save writer replacement.
- Snapshot creation now rejects invalid embedded RGBA8 face payloads before
document export or history can persist malformed state.
- Package-smoke wrappers validate the Windows CMake app executable/runtime

View File

@@ -1474,13 +1474,13 @@ if(TARGET pano_cli)
COMMAND pano_cli plan-canvas-document-snapshot --width 128 --height 64 --layers 3 --frames 2 --current-layer 2 --current-frame 1 --hidden-layer 0 --alpha-locked-layer 2 --opacity 0.5 --blend-mode 4 --pending-face-payloads-per-layer 6)
set_tests_properties(pano_cli_plan_canvas_document_snapshot_smoke PROPERTIES
LABELS "app;document;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-document-snapshot\".*\"width\":128.*\"height\":64.*\"layers\":3.*\"frames\":2.*\"activeLayer\":2.*\"activeFrame\":1.*\"activeLayerName\":\"Layer 3\".*\"activeLayerOpacity\":0.5.*\"activeLayerBlend\":\"overlay\".*\"activeLayerAlphaLocked\":true.*\"pendingFacePayloads\":18.*\"metadataOnly\":true.*\"requiresRendererPayloadReadback\":true.*\"documentFacePayloads\":0.*\"saveReport\":\\{\"payloadComplete\":false,\"canExportPpi\":false\\}.*\"ppiExport\":\\{\"ready\":false,\"bytes\":0,\"dirtyFaces\":0\\}")
PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-document-snapshot\".*\"width\":128.*\"height\":64.*\"layers\":3.*\"frames\":2.*\"activeLayer\":2.*\"activeFrame\":1.*\"activeLayerName\":\"Layer 3\".*\"activeLayerOpacity\":0.5.*\"activeLayerBlend\":\"overlay\".*\"activeLayerAlphaLocked\":true.*\"pendingFacePayloads\":18.*\"metadataOnly\":true.*\"requiresRendererPayloadReadback\":true.*\"documentFacePayloads\":0.*\"saveReport\":\\{\"payloadComplete\":false,\"canExportPpi\":false\\}.*\"ppiExport\":\\{\"ready\":false,\"bytes\":0,\"dirtyFaces\":0\\}.*\"rendererUpload\":\\{\"ready\":false,\"textures\":0,\"bytes\":0,\"transitions\":0,\"facePayloads\":0,\"compositedLayerFaces\":0,\"commands\":0,\"uploadCommands\":0,\"transitionCommands\":0\\}")
add_test(NAME pano_cli_plan_canvas_document_snapshot_payload_smoke
COMMAND pano_cli plan-canvas-document-snapshot --width 128 --height 64 --layers 2 --frames 2 --current-layer 1 --current-frame 1 --pending-face-payloads-per-layer 2 --captured-face-payloads-per-layer 2)
set_tests_properties(pano_cli_plan_canvas_document_snapshot_payload_smoke PROPERTIES
LABELS "app;document;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-document-snapshot\".*\"layers\":2.*\"frames\":2.*\"activeLayer\":1.*\"activeFrame\":1.*\"pendingFacePayloads\":4.*\"capturedFacePayloads\":4.*\"metadataOnly\":false.*\"requiresRendererPayloadReadback\":false.*\"documentFacePayloads\":4.*\"saveReport\":\\{\"payloadComplete\":true,\"canExportPpi\":true\\}.*\"ppiExport\":\\{\"ready\":true,\"bytes\":[1-9][0-9]*,\"dirtyFaces\":4\\}")
PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-document-snapshot\".*\"layers\":2.*\"frames\":2.*\"activeLayer\":1.*\"activeFrame\":1.*\"pendingFacePayloads\":4.*\"capturedFacePayloads\":4.*\"metadataOnly\":false.*\"requiresRendererPayloadReadback\":false.*\"documentFacePayloads\":4.*\"saveReport\":\\{\"payloadComplete\":true,\"canExportPpi\":true\\}.*\"ppiExport\":\\{\"ready\":true,\"bytes\":[1-9][0-9]*,\"dirtyFaces\":4\\}.*\"rendererUpload\":\\{\"ready\":true,\"textures\":6,\"bytes\":[1-9][0-9]*,\"transitions\":6,\"facePayloads\":2,\"compositedLayerFaces\":2,\"commands\":12,\"uploadCommands\":6,\"transitionCommands\":6\\}")
add_test(NAME pano_cli_plan_canvas_document_snapshot_no_canvas
COMMAND pano_cli plan-canvas-document-snapshot --no-canvas)

View File

@@ -5998,6 +5998,15 @@ int plan_canvas_document_snapshot(int argc, char** argv)
bool ppi_export_ready = false;
std::size_t ppi_export_bytes = 0;
std::uint32_t ppi_export_dirty_faces = 0;
bool renderer_upload_ready = false;
std::size_t renderer_texture_count = 0;
std::size_t renderer_transition_count = 0;
std::uint64_t renderer_uploaded_bytes = 0;
std::size_t renderer_face_payloads = 0;
std::size_t renderer_composited_layer_faces = 0;
std::size_t renderer_command_count = 0;
std::size_t renderer_upload_command_count = 0;
std::size_t renderer_transition_command_count = 0;
if (save_report.can_export_ppi) {
const auto exported = pp::app::export_document_canvas_save_snapshot_to_ppi(value);
if (!exported) {
@@ -6014,6 +6023,37 @@ int plan_canvas_document_snapshot(int argc, char** argv)
ppi_export_ready = true;
ppi_export_bytes = exported.value().bytes.size();
ppi_export_dirty_faces = decoded.value().project.body.summary.dirty_face_count;
constexpr pp::paint::Rgba clear_color {};
pp::renderer::RecordingRenderDevice render_device;
const auto uploaded = pp::paint_renderer::upload_document_frame_faces(
render_device,
pp::paint_renderer::DocumentFrameUploadRequest {
.document = &document,
.frame_index = document.active_frame_index(),
.clear_color = clear_color,
});
if (!uploaded) {
print_error("plan-canvas-document-snapshot", uploaded.status().message);
return 2;
}
renderer_upload_ready = true;
renderer_texture_count = uploaded.value().texture_count;
renderer_transition_count = uploaded.value().transition_count;
renderer_uploaded_bytes = uploaded.value().uploaded_bytes;
renderer_face_payloads = uploaded.value().composite.face_payload_count;
renderer_composited_layer_faces = uploaded.value().composite.composited_layer_face_count;
const auto commands = render_device.commands();
renderer_command_count = commands.size();
for (const auto& command : commands) {
if (command.kind == pp::renderer::RecordedRenderCommandKind::upload_texture) {
++renderer_upload_command_count;
}
if (command.kind == pp::renderer::RecordedRenderCommandKind::transition_texture) {
++renderer_transition_command_count;
}
}
}
std::cout << "{\"ok\":true,\"command\":\"plan-canvas-document-snapshot\""
@@ -6045,6 +6085,15 @@ int plan_canvas_document_snapshot(int argc, char** argv)
<< "},\"ppiExport\":{\"ready\":" << json_bool(ppi_export_ready)
<< ",\"bytes\":" << ppi_export_bytes
<< ",\"dirtyFaces\":" << ppi_export_dirty_faces
<< "},\"rendererUpload\":{\"ready\":" << json_bool(renderer_upload_ready)
<< ",\"textures\":" << renderer_texture_count
<< ",\"bytes\":" << renderer_uploaded_bytes
<< ",\"transitions\":" << renderer_transition_count
<< ",\"facePayloads\":" << renderer_face_payloads
<< ",\"compositedLayerFaces\":" << renderer_composited_layer_faces
<< ",\"commands\":" << renderer_command_count
<< ",\"uploadCommands\":" << renderer_upload_command_count
<< ",\"transitionCommands\":" << renderer_transition_command_count
<< "}"
<< "}}\n";
return 0;