Interface ReadableSurface
- All Superinterfaces:
GpuSurface
- All Known Subinterfaces:
RenderTarget
GpuSurface whose pixels can be brought back to the CPU. Rendering into a surface and
compositing it into the scene needs none of this; reading one is what an export, a thumbnail or a
reference image for a rendering test needs, and it is a capability rather than a promise of
GpuSurface; a surface that wraps memory it does not own (a decoder's picture, say) can
composite without being able to hand its pixels over.
Which of the two reads you want is a colour-space decision, and it is the one thing here that is easy to get silently wrong. An offscreen target holds scene-referred linear light; the display transform (exposure, tonemap, sRGB encode) runs later, when the surface is composited. So the numbers in the surface and the picture a person saw are two different images, and the methods are named apart rather than separated by a flag so that neither can be reached without choosing it:
readDisplayReferred(): what was on screen. Exposure, tonemap and sRGB encode applied, alpha un-premultiplied, 8 bits per channel. For anything a person will look at.readSceneReferred(): what the renderer wrote. Linear, premultiplied, float, no transform. For further processing, or for a test that asserts on the pass's own output.
Both are synchronous GPU reads: they stall the pipeline until everything queued has
finished, which on a frame path shows up as a frame-time cliff that profiles as "the renderer got
slow" rather than as this call. Read outside the frames that matter, or accept the cost
knowingly; the encode that usually follows can move off the UI thread (Images.saveAsync(limn.graphics.Image, limn.graphics.ImageEncodeOptions, java.nio.file.Path)),
but the read itself cannot.
Both must be called on the UI thread with the owning window's GL context current, i.e. from
inside a frame, like every other GpuSurface operation.
Coordinates are device pixels with the origin at the top-left, matching Image
and the rest of this package, not the bottom-up convention of the underlying graphics API. The
returned rows are top-down for the same reason. Whatever flip that costs belongs here: an encoder
takes Image as it finds it, and a second flip at encode time would cancel this one for
one path and not another.
-
Method Summary
Modifier and TypeMethodDescriptiondefault ImageReads the whole surface as the display saw it.readDisplayReferred(int x, int y, int widthPx, int heightPx) Reads a rectangle of this surface as the display saw it: the surface's display transform (exposure,RenderTarget.exposure()where the surface has one, then tonemap, then sRGB encode) applied once, alpha divided back out to straight, quantized to 8 bits per channel.default ScenePixelsReads the whole surface as the renderer wrote it.readSceneReferred(int x, int y, int widthPx, int heightPx) Reads a rectangle of this surface exactly as the renderer wrote it: linear light, premultiplied, float, no exposure and no tonemap.Methods inherited from interface limn.graphics.GpuSurface
dispose, heightPx, resize, widthPx
-
Method Details
-
readDisplayReferred
Reads a rectangle of this surface as the display saw it: the surface's display transform (exposure,RenderTarget.exposure()where the surface has one, then tonemap, then sRGB encode) applied once, alpha divided back out to straight, quantized to 8 bits per channel.Applied once is the contract, and the mirror-image mistake is worth naming: a surface whose contents are already display-referred must return them unchanged rather than transforming them again. Reading a window's framebuffer, which has already been through the composite, is not this method; it is
GpuRenderer.captureFramebuffer.Where alpha is 0 the result is a fully transparent pixel with all colour channels 0. Un-premultiplying is a division by alpha, so it loses precision as alpha approaches 0 and has no answer at all when alpha is exactly 0; this picks the one answer that composites back to the same picture.
- Parameters:
x- left edge in device pixels, 0 at the left of the surfacey- top edge in device pixels, 0 at the top of the surfacewidthPx- rectangle width, at least 1heightPx- rectangle height, at least 1- Returns:
- a new image of exactly
widthPx * heightPx, straight alpha, top-down - Throws:
IllegalArgumentException- if the rectangle is empty or reaches outside the surfaceIllegalStateException- if the surface has been disposed
-
readDisplayReferred
Reads the whole surface as the display saw it. SeereadDisplayReferred(int, int, int, int). -
readSceneReferred
Reads a rectangle of this surface exactly as the renderer wrote it: linear light, premultiplied, float, no exposure and no tonemap. Values above 1 survive, which is why this does not return anImage.A multisampled surface is resolved first; one sample per pixel is not what the surface shows, and a read that quietly returned sample 0 would differ from the composite by exactly the antialiasing.
- Parameters:
x- left edge in device pixels, 0 at the left of the surfacey- top edge in device pixels, 0 at the top of the surfacewidthPx- rectangle width, at least 1heightPx- rectangle height, at least 1- Returns:
- new pixels of exactly
widthPx * heightPx, premultiplied, top-down - Throws:
IllegalArgumentException- if the rectangle is empty or reaches outside the surfaceIllegalStateException- if the surface has been disposed
-
readSceneReferred
Reads the whole surface as the renderer wrote it. SeereadSceneReferred(int, int, int, int).
-