caliper.geometry.v1 / v1_1 / v1_2 / v1_3¶
Service ids caliper.geometry.v1 (instanced points) and its additive revisions
caliper.geometry.v1_1 (general primitives), caliper.geometry.v1_2
(textures on meshes), and caliper.geometry.v1_3
(instanced transforms). Imported 3-D geometry: an applet
writes vertices/indices/normals/attributes into device memory, exports them once
through caliper.tensor_bridge.v1_2, and the host draws
them in place — zero copies of the geometry data — into an offscreen view
texture you show with ImGui::Image. This page embeds the headers verbatim; the
docs build fails if any embedded file moves.
Deliberately a new service, not a tensor-bridge revision: the bridge's frozen
identity is "a tensor becomes an image"; cameras and draw calls are a different
vocabulary. The two share id spaces on purpose — a view is a CaliperTextureId
in the same table the bridge uses (drawable with ImGui::Image like any texture),
and geometry sources are addressed as (CaliperAllocId, byte offset), reusing the
v1.2 import machinery, caches, gates, and lifecycle as-is.
Platform status (honest, stated once)
Points (v1): Metal + Vulkan + GL-fallback ladder. Primitives
(v1_1): shipped on both backends (Metal and Vulkan), byte-exact
against one CPU reference on real hardware on each. Textures on meshes
(v1_2): run-proven byte-exact on both backends — Vulkan/RTX 500 Ada
and Metal/Apple Silicon (both 2026-07-10, the macOS hardware pass), against
the same shared CPU reference. Instanced transforms
(v1_3): run-proven byte-exact on both backends — Metal on Apple
Silicon and Vulkan/CUDA on Windows (both 2026-07-11), against the same
shared CPU reference. On a backend without
the path (the GL fallback, or a host that doesn't vend the revision) the
matching caps bit is unset and every
entry point is inert — ship your fallback (see the worked example
below). This is the degradation ladder, not a bug: absent capability →
CPU path, never a wrong image.
#pragma once
/* caliper.geometry.v1 — imported 3-D geometry: draw instanced points DIRECTLY
* from an applet-exported device allocation (tensor_bridge.v1_2's imported
* blocks) into an offscreen view texture. Zero copies of the point data: the
* vertex stage reads simulation memory in place, per frame.
*
* Deliberately a NEW service, not a tensor_bridge revision: the bridge's
* frozen identity is "a tensor becomes an image"; cameras and draw calls are
* a different vocabulary. The two share id spaces on purpose:
* - create_view returns a CaliperTextureId in the SAME table the bridge
* uses — a view is drawable with ImGui::Image like any other texture;
* - point data is addressed as (CaliperAllocId, byte offset) — the v1.2
* import machinery, caches, gates, and lifecycle are reused as-is.
*
* v1 scope: instanced points only (built for particle clouds — additive
* blending, no depth). Meshes/lines are a later additive revision.
* IMMUTABLE once published; violations return 0/false and emit a
* caliper.log.v1 line — never a wrong image (the degradation ladder). */
#include <stdint.h>
#include <stdbool.h>
#include <caliper/services/tensor_bridge_v1.h> /* CaliperTextureId */
#include <caliper/services/tensor_bridge_v1_2.h> /* CaliperAllocId */
#define CALIPER_GEOMETRY_V1 "caliper.geometry.v1"
/* caps() bit 0: create_view/draw_points are live (renderer has the imported-
* geometry path — Vulkan with a UUID-paired CUDA device today). Absent bit:
* every entry point is inert and the applet keeps its CPU fallback. */
#define CALIPER_GEOM_CAP_IMPORTED_POINTS (1u << 0)
#ifdef __cplusplus
extern "C" {
#endif
/* Column-major 4x4 view and projection, applet-owned math (the service does
* no camera logic — orbit/zoom/ray-casting are UI and live in the applet). */
typedef struct CaliperGeomCamera {
float view[16];
float proj[16];
} CaliperGeomCamera;
typedef struct CaliperGeometryV1 {
uint32_t struct_size;
uint32_t (*caps)(void);
/* Offscreen 3-D render target. The returned id lives in the tensor-bridge
* texture table: cast it to ImTextureID for ImGui::Image, release it here
* (not via the bridge). 0 on failure. */
CaliperTextureId (*create_view)(uint32_t width, uint32_t height);
void (*release_view)(CaliperTextureId view);
/* Render ONE frame of `view`, atomically: clear to clear_rgba (packed
* little-endian r|g<<8|b<<16|a<<24), then draw `count` points whose
* positions are a contiguous (count,3) f32 array at pos_offset inside the
* imported allocation pos_alloc. count == 0 is a pure clear.
*
* attr_alloc != 0 selects a contiguous (count,) f32 scalar per point at
* attr_offset, colormapped through the tensor-bridge LUTs over
* [vmin,vmax] (same index rule as texture_from_tensor_mapped);
* attr_alloc == 0 draws flat white and ignores attr_offset/colormap.
*
* size_px: point size in pixels (clamped to device limits). Points blend
* ADDITIVELY with no depth test (v1 — built for particle clouds; order-
* independent, no sort).
*
* Memory-stability contract (same as update_texture_from_alloc) — TWO
* halves the caller owns, both load-bearing (a new worker->frame publish
* path must honor both):
* (1) SPATIAL — the addressed bytes are read IN PLACE and must not be
* rewritten until this view's next draw. Applets satisfy this by
* triple-buffering the render slots: the worker only ever writes a
* slot that is neither the just-published one nor the one the frame
* thread is drawing, so it never overwrites bytes mid-read.
* (2) TEMPORAL — the producer must FINISH its device writes to those
* bytes BEFORE this call is issued. This ABI carries NO producer-
* stream channel (unlike the CaliperTensor texture path's
* STREAM_ORDERED handshake — there is no field for a CUstream /
* MTLCommandQueue here), so the geometry path is PERMANENTLY the
* drain rung: the worker drains its device
* (torch::cuda::synchronize / MPS synchronize) before publishing the
* slot the frame thread draws from. The renderer only makes those
* already-complete writes visible to its own vertex stage (Vulkan
* MEMORY_WRITE->SHADER_READ barrier / Metal same-queue commit order);
* it does NOT GPU-order against an in-flight producer. Publishing a
* slot whose writes are still in flight therefore RACES the vertex
* read — the barrier is renderer-internal and cannot order an
* external producer. The triple-buffer alone is NOT sufficient; the
* drain is what the (absent) STREAM_ORDERED gate would otherwise buy.
* Gates: live view/allocations only, 4-byte-aligned offsets, overflow-safe
* bounds. false = nothing drawn, the view keeps its prior pixels. */
bool (*draw_points)(CaliperTextureId view,
const CaliperGeomCamera* cam,
CaliperAllocId pos_alloc, uint64_t pos_offset,
uint64_t count,
CaliperAllocId attr_alloc, uint64_t attr_offset,
int32_t colormap, float vmin, float vmax,
float size_px, uint32_t clear_rgba);
} CaliperGeometryV1;
#ifdef __cplusplus
}
#endif
#pragma once
/* caliper.geometry.v1_1 — additive general-primitives revision of
* caliper.geometry.v1. The v1 prefix is frozen and unchanged: views still
* live in the tensor-bridge texture id table, and geometry sources are
* imported allocations from tensor_bridge.v1_2. v1_1 appends a single atomic
* multi-draw entry point for points, lines, and triangles, with optional depth
* and a fixed shading/blending menu. The ABI remains graphics-API-neutral. */
#include <caliper/services/geometry_v1.h>
#define CALIPER_GEOMETRY_V1_1 "caliper.geometry.v1_1"
/* caps() bit 1: create_view_ex / draw_primitives are live. */
#define CALIPER_GEOM_CAP_PRIMITIVES (1u << 1)
/* create_view_ex flags */
#define CALIPER_GEOM_VIEW_DEPTH (1u << 0)
/* CaliperGeomDraw.topology */
#define CALIPER_GEOM_TOPO_POINTS 0u
#define CALIPER_GEOM_TOPO_LINES 1u
#define CALIPER_GEOM_TOPO_LINE_STRIP 2u
#define CALIPER_GEOM_TOPO_TRIANGLES 3u
#define CALIPER_GEOM_TOPO_TRIANGLE_STRIP 4u
/* CaliperGeomDraw.color_mode */
#define CALIPER_GEOM_COLOR_FLAT 0u
#define CALIPER_GEOM_COLOR_COLORMAP 1u
#define CALIPER_GEOM_COLOR_VERTEX_RGBA 2u
/* CaliperGeomDraw.shade_mode */
#define CALIPER_GEOM_SHADE_UNLIT 0u
#define CALIPER_GEOM_SHADE_LAMBERT 1u
/* CaliperGeomDraw.blend_mode */
#define CALIPER_GEOM_BLEND_OPAQUE 0u
#define CALIPER_GEOM_BLEND_ALPHA 1u
#define CALIPER_GEOM_BLEND_ADDITIVE 2u
/* CaliperGeomDraw.depth_flags */
#define CALIPER_GEOM_DEPTH_TEST (1u << 0)
#define CALIPER_GEOM_DEPTH_WRITE (1u << 1)
#ifdef __cplusplus
extern "C" {
#endif
/* Clip-space convention for applet-owned camera math: +Y up, Z in [0,1]. */
typedef struct CaliperGeomDraw {
/* Sources are (imported alloc id, byte offset) pairs. Positions and
* normals are contiguous (vertex_count,3) f32 arrays. Indices are u32
* bit patterns. Attributes are either f32 scalar values for COLORMAP or
* packed little-endian RGBA8 u32 values for VERTEX_RGBA. */
CaliperAllocId pos_alloc; uint64_t pos_offset;
uint64_t vertex_count;
CaliperAllocId index_alloc; uint64_t index_offset;
uint64_t index_count;
CaliperAllocId normal_alloc; uint64_t normal_offset;
CaliperAllocId attr_alloc; uint64_t attr_offset;
uint32_t topology;
uint32_t color_mode;
uint32_t shade_mode;
uint32_t blend_mode;
uint32_t depth_flags;
uint32_t flat_rgba;
int32_t colormap;
float vmin;
float vmax;
float size_px;
/* Column-major model transform. Applets should use an identity matrix for
* world-space vertices; the C++ helper caliper::geom_draw_defaults() sets
* that up. */
float model[16];
uint32_t reserved[2]; /* must be zero */
} CaliperGeomDraw;
typedef struct CaliperGeometryV1_1 {
uint32_t struct_size;
/* v1-identical prefix. */
uint32_t (*caps)(void);
CaliperTextureId (*create_view)(uint32_t width, uint32_t height);
void (*release_view)(CaliperTextureId view);
bool (*draw_points)(CaliperTextureId view,
const CaliperGeomCamera* cam,
CaliperAllocId pos_alloc, uint64_t pos_offset,
uint64_t count,
CaliperAllocId attr_alloc, uint64_t attr_offset,
int32_t colormap, float vmin, float vmax,
float size_px, uint32_t clear_rgba);
/* v1_1 additions. */
CaliperTextureId (*create_view_ex)(uint32_t width, uint32_t height,
uint32_t flags);
/* Render one frame of `view` atomically. Every source (pos/index/normal/
* attr, incl. the per-vertex COLORMAP attr) obeys the same two-half
* memory-stability contract as draw_points (see geometry_v1.h): SPATIAL
* (bytes read in place — don't rewrite a drawn slot) + TEMPORAL (drain the
* producer BEFORE publishing — this ABI has no producer-stream channel, so
* it is always the drain rung, never STREAM_ORDERED). Any future added
* source (e.g. an instanced (N,16) pose stream) inherits both halves.
* draw_stride = the caller's sizeof(CaliperGeomDraw). */
bool (*draw_primitives)(CaliperTextureId view,
const CaliperGeomCamera* cam,
const CaliperGeomDraw* draws, uint32_t draw_count,
uint32_t draw_stride,
uint32_t clear_rgba);
void (*reserved0)(void); /* NULL in v1_1; reserved for a future revision. */
} CaliperGeometryV1_1;
#ifdef __cplusplus
}
static_assert(sizeof(CaliperGeomDraw) == 192,
"CaliperGeomDraw ABI size is frozen");
#endif
#pragma once
/* caliper.geometry.v1_2 - textured imported geometry. The v1.1 draw record is
* a frozen 192-byte ABI prefix; v1.2 appends UV and bridge-texture sources in
* a new record carried by the existing draw_primitives + draw_stride slot. */
#include <caliper/services/geometry_v1_1.h>
#include <stddef.h>
#define CALIPER_GEOMETRY_V1_2 "caliper.geometry.v1_2"
/* caps() bit 2: COLOR_TEXTURE draws are live. */
#define CALIPER_GEOM_CAP_TEXTURED (1u << 2)
/* CaliperGeomDrawV1_2.base.color_mode */
#define CALIPER_GEOM_COLOR_TEXTURE 3u
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CaliperGeomDrawV1_2 {
CaliperGeomDraw base; /* frozen v1.1 prefix */
CaliperAllocId uv_alloc; /* contiguous (vertex_count,2) f32 */
uint64_t uv_offset;
CaliperTextureId texture; /* bridge texture id; views are refused */
} CaliperGeomDrawV1_2;
typedef struct CaliperGeometryV1_2 {
uint32_t struct_size;
/* v1-identical prefix. */
uint32_t (*caps)(void);
CaliperTextureId (*create_view)(uint32_t width, uint32_t height);
void (*release_view)(CaliperTextureId view);
bool (*draw_points)(CaliperTextureId view,
const CaliperGeomCamera* cam,
CaliperAllocId pos_alloc, uint64_t pos_offset,
uint64_t count,
CaliperAllocId attr_alloc, uint64_t attr_offset,
int32_t colormap, float vmin, float vmax,
float size_px, uint32_t clear_rgba);
/* Same slots as v1.1; only the draw record type and minimum stride grow. */
CaliperTextureId (*create_view_ex)(uint32_t width, uint32_t height,
uint32_t flags);
bool (*draw_primitives)(CaliperTextureId view,
const CaliperGeomCamera* cam,
const CaliperGeomDrawV1_2* draws,
uint32_t draw_count, uint32_t draw_stride,
uint32_t clear_rgba);
void (*reserved0)(void); /* remains NULL */
} CaliperGeometryV1_2;
#ifdef __cplusplus
}
static_assert(sizeof(CaliperGeomDrawV1_2) == 216,
"CaliperGeomDrawV1_2 ABI size is frozen");
static_assert(offsetof(CaliperGeomDrawV1_2, uv_alloc) == 192,
"v1.2 fields must follow the frozen v1.1 prefix");
#endif
#pragma once
/* caliper.geometry.v1_3 - instanced imported geometry. The v1.1 draw record is
* a frozen 192-byte ABI prefix and v1.2 a frozen 216-byte record; v1.3 appends
* an instance tail ((N,16) f32 poses + optional (N,) f32 tint) in a new record
* carried by the existing draw_primitives + draw_stride slot. Pure additive
* struct growth in the exact shape v1.2 used to grow from v1.1. */
#include <caliper/services/geometry_v1_2.h>
#include <stddef.h>
#define CALIPER_GEOMETRY_V1_3 "caliper.geometry.v1_3"
/* caps() bit 3: instanced draws are live. */
#define CALIPER_GEOM_CAP_INSTANCED (1u << 3)
/* G14 rigidity tolerance (spec §5.1), relative/dimensionless: an instanced
* LAMBERT draw is refused unless every instance upper-3x3 is orthogonal-up-to-
* uniform-scale within this bound (the §4.4 normal chain is only exact-compose
* under that class). Part of the byte-exact contract — pinned here next to the
* caps bit, asserted in the G14 gate, never a tunable. Both backends run the
* identical comparison in the identical float order against it. */
#define CALIPER_GEOM_RIGID_TOL 1e-4f
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CaliperGeomDrawV1_3 {
CaliperGeomDrawV1_2 base; /* frozen 216-byte v1.2 record */
CaliperAllocId instance_alloc; /* (N,16) f32 column-major model matrices */
uint64_t instance_offset; /* bytes, 4-byte aligned */
uint64_t instance_count; /* N; 0 or instance_alloc==0 -> non-instanced */
CaliperAllocId instance_attr_alloc; /* optional (N,) f32; 0 = no per-instance tint */
uint64_t instance_attr_offset;/* bytes, 4-byte aligned */
} CaliperGeomDrawV1_3;
typedef struct CaliperGeometryV1_3 {
uint32_t struct_size;
/* v1-identical prefix. */
uint32_t (*caps)(void);
CaliperTextureId (*create_view)(uint32_t width, uint32_t height);
void (*release_view)(CaliperTextureId view);
bool (*draw_points)(CaliperTextureId view,
const CaliperGeomCamera* cam,
CaliperAllocId pos_alloc, uint64_t pos_offset,
uint64_t count,
CaliperAllocId attr_alloc, uint64_t attr_offset,
int32_t colormap, float vmin, float vmax,
float size_px, uint32_t clear_rgba);
/* Same slots as v1.2; only the draw record type and minimum stride grow. */
CaliperTextureId (*create_view_ex)(uint32_t width, uint32_t height,
uint32_t flags);
bool (*draw_primitives)(CaliperTextureId view,
const CaliperGeomCamera* cam,
const CaliperGeomDrawV1_3* draws,
uint32_t draw_count, uint32_t draw_stride,
uint32_t clear_rgba);
void (*reserved0)(void); /* remains NULL */
} CaliperGeometryV1_3;
#ifdef __cplusplus
}
static_assert(sizeof(CaliperGeomDrawV1_2) == 216,
"v1.2 prefix drift would break the v1.3 tail offsets");
static_assert(sizeof(CaliperGeomDrawV1_3) == 256,
"CaliperGeomDrawV1_3 ABI size is frozen");
static_assert(offsetof(CaliperGeomDrawV1_3, base) == 0,
"v1.3 record opens with the frozen v1.2 prefix");
static_assert(offsetof(CaliperGeomDrawV1_3, instance_alloc) == 216,
"v1.3 instance tail must follow the frozen v1.2 record");
#endif
caliper.geometry.v1 — instanced points¶
Caps bit 0 (CALIPER_GEOM_CAP_IMPORTED_POINTS) set means create_view /
draw_points are live. Absent bit → both are inert, the applet keeps its CPU
fallback.
The offscreen-view pattern¶
create_view(width, height)returns aCaliperTextureIdin the tensor-bridge texture table.0= failure. Sizes are physical (framebuffer) pixels; recreate the view when the content region changes by more than a few pixels.- Each frame,
draw_points(view, cam, …)renders one frame of the view atomically: clear toclear_rgba(packed little-endianr | g<<8 | b<<16 | a<<24), then drawcountpoints whose positions are a contiguous(count,3)f32 array atpos_offsetinside the imported allocationpos_alloc.count == 0is a pure clear. caliper::Bridge::imtex(view)casts the id toImTextureID; display it withImGui::Imageat logical size (physical / DisplayFramebufferScale) so one texel maps to one framebuffer pixel.release_view(view)frees it — release it here, on the frame thread, not through the bridge.
attr_alloc != 0 selects a contiguous (count,) f32 scalar per point at
attr_offset, colormapped through the shared LUTs over [vmin, vmax] (same index
rule as texture_from_tensor_mapped, see tensor-bridge);
attr_alloc == 0 draws flat white and ignores attr_offset/colormap. size_px
is point size in pixels (clamped to device limits). Points blend additively with
no depth test — v1 is built for particle clouds: order-independent, no sort.
cam is a CaliperGeomCamera — column-major 4×4 view and proj, applet-owned
math. The service does no camera logic; orbit/zoom/ray-casting are UI and live in
the applet.
Memory-stability contract¶
The addressed bytes are read in place and must not be rewritten until this
view's next draw. On the worker side, drain the producer stream/queue once at the
handoff before publishing the slot (this is the triple-buffer / ready-slot
discipline the exemplars use with caliper.jobs.v1); the frame
thread does every draw and never touches the learner's tensors.
caliper.geometry.v1_1 — general primitives¶
Additive revision of v1: the first five members are prefix-identical
(struct_size, caps, create_view, release_view, draw_points); three
members are appended (create_view_ex, draw_primitives, a reserved slot). No ABI
epoch bump, v1 untouched and frozen.
Caps bit 1 (CALIPER_GEOM_CAP_PRIMITIVES) set means create_view_ex /
draw_primitives are live. It implies nothing about bit 0; hosts set both when the
primitives path exists.
Depth views¶
create_view_ex(width, height, flags) is create_view plus flags. The one flag,
CALIPER_GEOM_VIEW_DEPTH, attaches a D32-float depth buffer (renderer-internal —
never sampled, never an id). Same texture table, released via release_view. A
plain v1 view has no depth: setting any depth_flags bit against it is
refused, never silently ignored (degradation ladder — never a silently-wrong
image).
One atomic gated frame¶
draw_primitives(view, cam, draws, draw_count, draw_stride, clear_rgba) renders
one frame of the view, atomically:
- Gate every draw first (see below).
- Clear color to
clear_rgba(and depth to1.0when the view has depth). - Encode
draws[0..draw_count)in array order into a single render pass. Later draws paint over earlier ones subject to their own depth/blend state.
draw_count == 0 is a pure clear. If any gate fails, nothing is drawn or
cleared — prior pixels intact — and the host emits one caliper.log.v1 line
naming the gate. This is why the API is one atomic call with a descriptor array
rather than a stateful begin/end: a begin/end can fail after the clear, which
would violate pixels-untouched.
draw_stride is the applet's compiled sizeof(CaliperGeomDraw), so the struct can
grow additively later; the host reads min(stride, its own sizeof). A stride below
the host's known minimum is refused. The C++ sugar passes sizeof
for you.
The CaliperGeomDraw descriptor¶
One fixed-layout struct per draw call. static_assert(sizeof == 192) in every
consumer — 8-byte fields first, no packing surprises.
Geometry sources — each an (alloc, byte offset) pair into a bridge-v1.2
import; alloc id 0 = source absent; all offsets 4-byte aligned:
| Field(s) | Meaning |
|---|---|
pos_alloc, pos_offset |
positions: contiguous (vertex_count, 3) f32 |
vertex_count |
vertex count (must be > 0) |
index_alloc, index_offset, index_count |
indices: (index_count,) u32 bit patterns; consumed only when index_alloc != 0 |
normal_alloc, normal_offset |
normals: (vertex_count, 3) f32; required for LAMBERT |
attr_alloc, attr_offset |
per-vertex attribute; meaning set by color_mode |
topology — how vertices assemble:
| Constant | |
|---|---|
CALIPER_GEOM_TOPO_POINTS |
0u |
CALIPER_GEOM_TOPO_LINES |
1u |
CALIPER_GEOM_TOPO_LINE_STRIP |
2u |
CALIPER_GEOM_TOPO_TRIANGLES |
3u |
CALIPER_GEOM_TOPO_TRIANGLE_STRIP |
4u |
color_mode — where each vertex's colour comes from:
| Constant | attr layout |
Colour |
|---|---|---|
CALIPER_GEOM_COLOR_FLAT (0u) |
(none) | flat_rgba for every vertex, unpacked LE bytes /255 |
CALIPER_GEOM_COLOR_COLORMAP (1u) |
(vertex_count,) f32 |
attr value through the LUT colormap over [vmin, vmax] |
CALIPER_GEOM_COLOR_VERTEX_RGBA (2u) |
(vertex_count,) u32 |
packed LE r\|g<<8\|b<<16\|a<<24 per vertex, unpacked /255 |
The COLORMAP index rule is byte-identical to the tensor-bridge's
texture_from_tensor_mapped / map_f32_to_rgba8:
idx = uint(clamp((v - vmin) / (vmax - vmin), 0, 1) * 255.0 + 0.5)
with NaN → 0, a degenerate vmin == vmax range → 0, and the shared 256-entry
table. colormap is a bridge LUT id (CALIPER_CMAP_VIRIDIS = 0, _MAGMA = 1,
_RDBU = 2).
shade_mode:
| Constant | |
|---|---|
CALIPER_GEOM_SHADE_UNLIT (0u) |
colour as computed above |
CALIPER_GEOM_SHADE_LAMBERT (1u) |
single headlight, Gouraud; requires normal_alloc != 0 |
LAMBERT is a single headlight — view-space light direction (0,0,1) — shaded
per-vertex and interpolated:
n_vs = normalize(nmat * n); // nmat: host-computed 3x3
lit = 0.30 + 0.70 * max(dot(n_vs, vec3(0,0,1)), 0.0);
color.rgb *= lit; // alpha untouched
nmat = transpose(inverse(upper3x3(view * model))), computed on the CPU per draw —
no shader inverses. The 0.30 ambient is a spec constant, not a parameter. Faceted
shading = the applet duplicates vertices with face normals (applet choice, zero
host work); per-pixel (Phong) lighting is not offered.
blend_mode:
| Constant | Equation |
|---|---|
CALIPER_GEOM_BLEND_OPAQUE (0u) |
blending disabled |
CALIPER_GEOM_BLEND_ALPHA (1u) |
SRC_ALPHA / ONE_MINUS_SRC_ALPHA |
CALIPER_GEOM_BLEND_ADDITIVE (2u) |
ONE / ONE, both channels (byte-identical to the v1 points look) |
Transparency is not sorted — the applet orders its draw array; unsorted alpha artifacts are documented, not fixed.
depth_flags (bits) — valid only on a CALIPER_GEOM_VIEW_DEPTH view:
| Constant | |
|---|---|
CALIPER_GEOM_DEPTH_TEST (1u << 0) |
depth test on (LESS_OR_EQUAL compare) |
CALIPER_GEOM_DEPTH_WRITE (1u << 1) |
write depth |
The LESS_OR_EQUAL compare is fixed; it lets coplanar edges win, so a
wireframe-over-mesh overlay is draw 0 = triangles (TEST | WRITE), draw 1 = lines
(TEST only).
Remaining scalar/state fields: flat_rgba (packed LE, used when COLOR_FLAT),
colormap / vmin / vmax (used when COLOR_COLORMAP), size_px (point size;
ignored for non-point topologies), and reserved[2] which must be zero
(nonzero is refused).
model[16] — per-draw column-major model transform. Applets pass an identity
matrix for world-space vertices; mvp = proj * view * model and (for LAMBERT)
the normal matrix are premultiplied host-side. An all-zero model renders
nothing — the classic footgun; the sugar's geom_draw_defaults() sets identity
so you never hit it.
Clip-space convention (applet-owned camera math): +Y up, clip Z in [0, 1]
(Vulkan/Metal/D3D, not GL's [-1,1]).
Gates and refusals¶
All gates run before any encoding (on Metal, before the encoder exists —
creating it performs the clear). Any failure refuses the whole frame: false,
pixels untouched, one caliper.log.v1 line.
| Scope | Gate |
|---|---|
| Per frame | live view; cam != NULL; draw_stride >= host minimum |
| Per draw | topology / color_mode / shade_mode / blend_mode in range; reserved zero |
pos_alloc live; pos_offset % 4 == 0; vertex_count > 0; overflow-safe bounds |
|
indexed (index_alloc != 0): alloc live, index_offset % 4 == 0, index_count > 0, bounds-checked |
|
LINE_* need ≥2 consumed vertices, TRIANGLE* need ≥3 (consumed = index_count when indexed, else vertex_count) |
|
LAMBERT requires normal_alloc != 0, live, aligned, bounds-checked |
|
color_mode != FLAT requires attr_alloc != 0, live, aligned, bounds-checked, and a resolvable colormap (COLORMAP) |
|
any depth_flags bit against a depthless view → refuse |
The index-clamp contract¶
Index values live in device memory and cannot be gated host-side. Both
backends' vertex shaders clamp: vi = min(index[i], vertex_count - 1). An
out-of-range index therefore produces a wrong-looking but defined image — never
an out-of-bounds read or UB. This is the applet's contract: a bad index is a visual
bug in your data, not a crash.
caliper.geometry.v1_2 — textures on meshes¶
Additive revision of v1_1: CaliperGeomDrawV1_2 = the frozen 192-byte
CaliperGeomDraw record (base) plus an appended texture tail
(uv_alloc, uv_offset, texture), carried by the same draw_primitives +
draw_stride slot. No new entry point, reserved0 still NULL, the v1.1 prefix
untouched and frozen. static_assert(sizeof(CaliperGeomDrawV1_2) == 216) and
offsetof(uv_alloc) == 192 pin the tail. Caps bit 2
(CALIPER_GEOM_CAP_TEXTURED, 1u << 2) set means COLOR_TEXTURE draws are
live; absent → the textured path is inert and the draw behaves exactly as v1_1.
The host vends both revisions side by side: calls through the v1.1 table accept
a minimum stride of 192 and cannot request textured colour; calls through the
v1.2 table accept a minimum stride of 216. Old binaries keep working while a
v1.2 caller cannot expose an absent tail (short stride → refused).
The COLOR_TEXTURE color mode¶
A fourth color_mode value, CALIPER_GEOM_COLOR_TEXTURE (3u), extends the
v1_1 FLAT / COLORMAP / VERTEX_RGBA set. Each vertex carries a
(vertex_count, 2) f32 UV pair at (uv_alloc, uv_offset) — another
(alloc, byte offset) into a bridge-v1.2 import, pulled by the same final
vertex index as positions/normals/attributes — and the fragment samples a
bridge CaliperTextureId (texture) at that coordinate. The sampled RGBA
value is the draw colour. attr_alloc is ignored under COLOR_TEXTURE and
need not be present.
Sampling is a fixed sampler — no configurable materials or sampler state:
- normalized coordinates;
- bilinear minification and magnification (linear);
- clamp-to-edge on U and V (UVs outside
[0, 1]are defined, not wrapped); - base level only, no mipmaps.
SHADE_LAMBERT multiplies the sampled RGB by the existing
0.30 + 0.70 * max(dot(n_vs, headlight), 0.0) headlight term and leaves alpha
unchanged; blend and depth modes then apply exactly as in v1_1.
The render-target-view refusal¶
texture names a bridge texture, never a geometry view. Naming any geometry
view — including the view currently being drawn — is refused whole-frame
(pixels untouched, one caliper.log.v1 line), not silently sampled. A geometry
view is never sampleable by construction, which forecloses a read-after-write
feedback loop against the target you are painting. This is the degradation
ladder's usual shape: a wrong request refuses, never renders a wrong image.
Gates and refusals¶
COLOR_TEXTURE adds these to the v1_1 gate set (all before any encoding; any
failure refuses the whole frame):
| Scope | Gate |
|---|---|
| Per draw | uv_alloc live; uv_offset % 4 == 0; uv_offset + vertex_count·2·sizeof(f32) overflow-safe and in-bounds |
texture resolves to a live tensor-bridge texture entry |
|
texture is not a geometry view (including the target view) → refuse |
UV values are finite shader inputs like any other imported bytes; values
outside [0, 1] are defined by the clamp-to-edge sampler, not a gate.
Honest ladder¶
When caps bit 2 is absent (the GL fallback, or a host vending only v1/v1_1),
has_textured() is false and the textured path is inert — the applet ladders
down to a per-vertex colour fallback (COLORMAP or VERTEX_RGBA over the same
field, the field sampled at vertex resolution instead of texture resolution) and
says so; never a wrong image, never a false status line. The caliper::Geometry
sugar exposes has_textured() (caps bit 2) and a draw_primitives overload
taking CaliperGeomDrawV1_2*; seed descriptors so the texture tail is zero over
the v1_1 defaults (a zero-tail v1_2 record renders byte-identically to the
equivalent v1_1 draw). TwinScope's surface twin — a learned thermal field
draped on a heatsink housing at texture resolution — is the exemplar: the twin's
state painted on its shape.
caliper.geometry.v1_3 — instanced transforms¶
Additive revision of v1_1/v1_2: CaliperGeomDrawV1_3 = the frozen 216-byte
CaliperGeomDrawV1_2 record (base) plus an appended instance tail, carried
by the same draw_primitives + draw_stride slot. No new entry point, reserved0
still NULL, the v1.1/v1.2 prefixes untouched. Caps bit 3
(CALIPER_GEOM_CAP_INSTANCED, 1u << 3) set means the instance tail is live;
absent → the tail is inert and the draw behaves exactly as v1_2.
One imported mesh drawn N times in one call: an imported (N,16) f32
column-major pose tensor (instance_alloc/instance_offset, instance_count = N)
supplies each instance's model matrix, pulled by instance index in the vertex
shader and applied to the world position first (mvp · (M_i · v)). An optional
imported (N,) f32 per-instance scalar (instance_attr_alloc/instance_attr_offset)
tints each unit through the draw's existing colormap/vmin/vmax LUT — same
index rule as every other COLORMAP path on this page. instance_attr_alloc == 0
leaves coloring to the base color_mode.
Additive default (zero tail == a v1_2 draw). instance_count == 0 or
instance_alloc == 0 takes the exact non-instanced path — same shader code path,
same draw arity — so a v1_3 record with a zero instance tail is byte-identical at
the pixel level to the equivalent v1_2 record. Widening a v1_1/v1_2 record up to
v1_3 (zero tail) draws the same pixels.
LAMBERT is restricted to rigid + uniform-scale instance transforms
(rotation, uniform scale, translation — no shear, no non-uniform scale). Under that
class the correct normal map is the instance rotation up to a scalar normalize
removes, so the shader composes the instance upper-3×3 with the per-draw normal
matrix and stays a ±2-LSB tolerance row. Out-of-class instance matrices on a
LAMBERT-instanced draw are refused whole-frame (never silently mis-lit) by gate
G14, whose tolerance is pinned in the header as CALIPER_GEOM_RIGID_TOL
(1e-4f) — part of the byte-exact contract, not a tunable. UNLIT instanced draws
never touch normals and are unrestricted.
Honest ladder. When caps bit 3 is absent (GL fallback, or a host vending only
v1/v1_1/v1_2), has_instanced() is false and the instanced path is inert — the
applet ladders down to a non-instanced fallback (draw the hero unit alone, or N
plain draws) and says so; never a wrong image, never a false status line. The
caliper::Geometry sugar exposes has_instanced() (caps bit 3) and a
draw_primitives overload taking CaliperGeomDrawV1_3*; seed descriptors from
caliper::geom_draw_v1_3_defaults() (a zero instance tail over the v1_2 defaults).
C++ sugar¶
caliper::Geometry (in caliper.hpp) wraps every geometry revision. Construct it
from the
Host; it is falsy when the host vends no geometry service, and every method
null-guards so it stays inert on hosts without the path. Frame-thread only, same
as caliper::Bridge.
caliper::Geometry geometry(host);
uint32_t caps = geometry.caps(); // 0 when absent
bool have_prims = geometry.has_primitives(); // caps bit 1
CaliperTextureId view = geometry.create_view_ex(w, h, CALIPER_GEOM_VIEW_DEPTH);
CaliperGeomDraw draws[3] = { /* … */ };
bool drew = geometry.draw_primitives(view, cam, draws, 3, 0xff05050au);
geometry.release_view(view);
draw_primitives passes sizeof(CaliperGeomDraw) as the stride for you.
Always seed a descriptor from caliper::geom_draw_defaults() — it returns a
zero-initialised CaliperGeomDraw with flat_rgba = 0xffffffff, vmin/vmax =
0/1, size_px = 1, and model = identity, removing the all-zero-model footgun at
the source. Set only the fields your draw needs.
Torch layout notes¶
- positions:
.contiguous()f32(N, 3). - indices: int32 tensors with non-negative values — torch has no uint32; the bit pattern is exactly what the u32 shader reads.
- normals: f32
(N, 3). - packed colours (
VERTEX_RGBA,flat_rgba): int32 bit patterns.
Worked example — a learned surface, three draws¶
Modelled on applets/mesh_scope (the v1_1 exemplar): a small net's prediction over
a grid is written into imported device tensors each training step by a
jobs.v1 worker and drawn the same frame as Lambert-lit
triangles + a wireframe overlay + the training minibatch as additive points. The
worker publishes into triple-buffered slots and drains the device once before
flipping the ready slot; the frame thread snapshots the display slot under one
mutex and does every draw.
// Frame thread. `geometry`, `bridge`, `pool` are set up at init; `draw_pos`,
// `draw_normal`, `draw_attr`, `draw_sample`, `tri_idx`, `line_idx` are the
// worker-published slot tensors snapshotted under the mutex.
const bool geom_live = geometry.has_primitives();
// Honest fallback ladder: no caps / no pool / no view / no surface -> CPU heatmap.
if (!geom_live || view == 0 || !pool || !draw_pos.defined()) {
// …input-locked ImPlot heatmap of the same per-vertex error; never a blank
// rectangle. See mesh_scope.cpp for the full fallback.
return;
}
CaliperGeomCamera cam{};
look_at(eye, {0,0,0}, {0,1,0}, cam.view); // applet-owned math
perspective(fovy, aspect, 0.05f, 50.f, cam.proj);
// Import-once per pool block (cached); resolves to (alloc, offset).
auto pref = pool->to_bridge(bridge, draw_pos);
auto nref = pool->to_bridge(bridge, draw_normal);
auto aref = pool->to_bridge(bridge, draw_attr);
auto tref = pool->to_bridge(bridge, tri_idx);
auto lref = pool->to_bridge(bridge, line_idx);
if (!(pref && nref && aref && tref && lref)) return; // fall back
// Draw 0: the learned surface — indexed triangles, MAGMA over squared error,
// Lambert-lit from finite-difference normals, opaque, depth read+write.
CaliperGeomDraw surf = caliper::geom_draw_defaults();
surf.pos_alloc = pref->alloc; surf.pos_offset = pref->offset;
surf.vertex_count = vertex_count;
surf.index_alloc = tref->alloc; surf.index_offset = tref->offset;
surf.index_count = tri_index_count;
surf.normal_alloc = nref->alloc; surf.normal_offset = nref->offset;
surf.attr_alloc = aref->alloc; surf.attr_offset = aref->offset;
surf.topology = CALIPER_GEOM_TOPO_TRIANGLES;
surf.color_mode = CALIPER_GEOM_COLOR_COLORMAP;
surf.shade_mode = CALIPER_GEOM_SHADE_LAMBERT;
surf.blend_mode = CALIPER_GEOM_BLEND_OPAQUE;
surf.depth_flags = CALIPER_GEOM_DEPTH_TEST | CALIPER_GEOM_DEPTH_WRITE;
surf.colormap = CALIPER_CMAP_MAGMA;
surf.vmin = 0.0f; surf.vmax = color_vmax;
// Draw 1: coplanar wireframe overlay — indexed lines, flat white at low alpha,
// depth-TESTed only. LESS_OR_EQUAL lets the edges win over the surface.
CaliperGeomDraw wire = caliper::geom_draw_defaults();
wire.pos_alloc = pref->alloc; wire.pos_offset = pref->offset;
wire.vertex_count = vertex_count;
wire.index_alloc = lref->alloc; wire.index_offset = lref->offset;
wire.index_count = line_index_count;
wire.topology = CALIPER_GEOM_TOPO_LINES;
wire.color_mode = CALIPER_GEOM_COLOR_FLAT;
wire.blend_mode = CALIPER_GEOM_BLEND_ALPHA;
wire.depth_flags = CALIPER_GEOM_DEPTH_TEST;
wire.flat_rgba = 0x59ffffffu; // white, alpha ~0.35
CaliperGeomDraw draws[2] = { surf, wire };
bool drew = geometry.draw_primitives(view, cam, draws, 2, 0xff05050au);
if (drew)
ImGui::Image(caliper::Bridge::imtex(view),
ImVec2(view_w / fb_scale, view_h / fb_scale));
The status line reports "zero-copy (imported geometry)" only when
draw_primitives actually drew this frame — the honest-provenance discipline the
exemplars follow verbatim; on a non-primitives backend has_primitives() is false
and the CPU fallback runs instead.
See also: caliper.tensor_bridge.v1 for the import
machinery geometry draws from, and caliper.jobs.v1 for the
worker/frame threading the exemplars use to feed it.