Rendering images
Create deterministic off-screen PNG output from a semantic scene.
Renderer opens a compatible native graphics device and renders a scene to an Image. Use converged() when a saved image matters more than interactive adaptation.
import molframe
import molgfx
structure = molframe.read("4hhb.cif").infer_bonds()
scene = molgfx.Scene(structure)
renderer = molgfx.Renderer(profile=molgfx.profile.converged())
image = renderer.render_image(scene, size=(1920, 1080))
image.save("figure.png")Image exposes width, height, RGBA pixels(), and png_bytes() when you need to pass the output to another system instead of saving it.
Fixed quality and completed exposures
molgfx.profile.highest_fixed(target_fps=120.0) holds the highest tier independently of scene size or timing. The target is a scheduling goal, not a performance guarantee. Unlike converged(), it keeps realtime presentation settings; neither profile lowers analytic molecular detail through LOD.
Converged images and the default camera sequence complete all 64 temporal samples for each output. Explicit progressive sequences submit one sample and must not be counted as converged outputs. Rust Image::quality() reports physical extent, selected detail, submitted samples and completion observed after readback; EffectiveQuality::complete() derives completion from those observations. Indirect illumination currently reports zero transport bounces.
Python image.quality (a dict; image.quality_json() is the same as a string) exposes the same canonical fields and
derived complete value without copying pixels. Rust HDR images also retain
their exposure quality. Browser renderer.frameReportJSON() is absent before
the first render, then returns the latest report as JSON; read last_completed
for fence-observed settings rather than treating the current submission as done.
Converged outputs freeze scene, camera and time once per exposure. A persistent three-bank uniform arena retains all 64 distinct jittered payloads in aligned ranges with cached bindings; one encoder and one tracked submission record the exposure. Banks cannot be reused before their final fence completes. Native saturation waits for the oldest bank; browser saturation reports explicit backpressure.
The native frame_time benchmark measures completed outputs, not temporal subsamples. It emits unavailable GPU timing as null with an explicit reason, keeps cold and warmed measurements separate, and reports allocator activity separately from residency events. Timestamp support is optional: completion is proved by the tracked submission fence even without usable timestamp queries. GPU timestamp intervals bracket the scheduled graph and are not sums of individual pass execution times. Real process RSS is measured by isolated resources child runs, not inferred from retained allocator bytes.
Profiles record each actual render/compute pass occurrence with its temporal sample, raw timestamp ticks, duration or absence reason. Renderer::last_pass_timings() borrows the latest completed profile without copying engine storage. Capture holds at most 2048 occurrences per exposure; overflow is explicit and prevents a whole-GPU timing certificate. The whole interval uses the earliest beginning and latest ending of all resolved occurrences, so overlapping GPU passes do not imply CPU recording order. Any unavailable pass leaves the whole duration unavailable. Host stages separately report preparation, recording, submission, completion wait and timestamp readback; these are elapsed wall intervals, not exclusive CPU utilization.
Presentation reports retain last_completed settings captured at submission, including submission identity. A later camera change or resize cannot retroactively change the completed output's metadata.
Profiling cancellation retains the target, query storage and latest fence in
the engine. The next profile waits before reuse. Timestamp resolution runs in
a separate submission after exposure completion: on the verified Metal path,
same-command-buffer resolution returned stale or unwritten final-pass values.
Exact older same-slot tick reuse is reported as stale, not a new GPU duration.
The additional resolve/wait/map interval belongs to timestamp readback overhead,
not the rendered exposure GPU interval.
Camera
render_image uses a framing camera. For a repeatable viewpoint, set a validated camera on the scene first:
scene.set_camera(molgfx.Camera(
position=(40.0, 30.0, 80.0),
target=(0.0, 0.0, 0.0),
aspect=16 / 9,
))Choose coordinates from your structure and keep near, far, and camera vectors physically meaningful. The camera constructor rejects degenerate and invalid values.
Camera paths
A CameraPath interpolates a camera between keyframes at strictly increasing
times. It orbits the target, so the in-between cameras keep the target in view
instead of cutting a straight line through it, and it clamps at both ends.
render_camera_path renders one fully converged frame per tick, covering the
path's time range inclusively; encoding the frames into a movie is left to the
caller.
def at(x):
return molgfx.Camera(position=(x, 20.0, 60.0), target=(0.0, 0.0, 0.0))
path = molgfx.CameraPath([(0.0, at(-40.0)), (2.0, at(40.0))])
frames = molgfx.Renderer().render_camera_path(scene, path, size=(1280, 720), fps=30)Repeated renders of the same scene on the same adapter are byte-identical, and a camera path repeats exactly as well; frames can differ between adapters.
Device availability
Renderer initialization can fail when the platform has no compatible graphics device. Use molgfx.system_info() to inspect compiled backends and visible adapters when diagnosing a native environment.