Skip to content

perf: sampled shading function stream is decoded once per grid point #999

Description

@tobocop2

Problem

render_mesh_shading (src/rendering/mesh_shading.rs) resolves a shading's /Function once, but evaluates it through the to_rgba closure — once per domain grid point for a Type 1 function-based shading (render_function_based walks a grid of up to 129×129 = 16,641 points), and once per vertex for the mesh types.

For a Type 0 (sampled) function, eval_type0 calls decode_stream_data() at the top of every evaluation. The sample stream is therefore decoded up to 16,641 times per shading, and render time scales with stream size × grid area instead of paying the decode once per shading.

Measured on a 600×600 page painting one Type 1 shading over a Type 0 function (median of 5 renders, three interleaved rounds, one pinned core on an otherwise-idle 32-core box):

sample stream render time
16 KiB 62.1 / 62.7 / 62.7 ms
64 KiB 81.8 / 81.6 / 81.9 ms
256 KiB 153.6 / 163.7 / 166.5 ms

Render time tracks the stream size even though the output is identical — which only happens if the stream is decoded per evaluation. Root cause confirmed by that scaling, not by reading.

Proposed direction

Decode the stream where the function is resolved, in render_mesh_shading, and pass the bytes down to eval_type0. Output is byte-identical — the change is purely where the decode happens. With the decode hoisted, the same three cases render in ~56 ms flat (null experiment spread on the same box: 154.5–164.0 ms for base-vs-base at 256 KiB, so the 64 KiB and 256 KiB deltas are far outside the floor; the 16 KiB delta is inside it and not claimed):

sample stream base fix
64 KiB ~82 ms ~56 ms (~1.5×)
256 KiB ~154–167 ms ~57 ms (~2.8×)

Not covered, deliberately: a top-level array of functions and the children of a Type 3 stitching function are reached from the same per-grid-point path and would still decode per call. Same defect class, unmeasured: eval_type4 also re-decodes and re-parses its PostScript program on every call even though functions::Program is compile-once/evaluate-many — typical Type 4 programs are small, so the impact is likely minor; worth its own measurement before touching.

Acceptance criteria

  • Byte-identity across a real-PDF corpus regression sweep, base vs fix: per-page render digests plus text/table/image extraction digests, zero differing documents.
  • The targeted benchmark above with its base-vs-base null experiment; claim only deltas outside the floor.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions