GSplatSorter

class GSplatSorter

Bases: TypedReferenceCount

The back-to-front draw order of a set of Gaussian splats, as a native counting sort.

Alpha-blended splats composite correctly only drawn far-to-near, and a scan has millions of them, so every view change wants a fresh order and the sort is on the frame’s critical path. This class owns three arrays sized to the splat count – depths (one view-space depth per splat, filled by the caller – Python writes straight into it through the buffer protocol), visible (one byte per splat, 1 = draw; the culled tail is left out of the order entirely, which is what keeps a telephoto view over a large scan cheap), and order (the result: splat indices as float32, farthest first, which is the format an R32F order texture wants) – and sort() fills the third from the first two.

The sort quantises the depth range to 2^bits buckets (bits = clamp( log2(n / 4), 10, 20) unless set) and counting-sorts on the key, stable, so ties keep index order. It is O(n) and releases the GIL (BLOCKING), so a Python worker thread can call it while the frame loop renders.

load_depths() fills depths and visible from a “depth plane”: an RGBA32F texture with one texel per splat (texel i = (i % width, i / width)) whose red channel is the splat’s view depth and whose green channel is 1 for a splat worth drawing – the plane a GPU decode pass writes, so the depths of splats the GPU has deformed never have to be recomputed on the CPU. The texture must hold a RAM image (a render target copied to RAM). load_depth_image() reads the same texels from a copy of such an image (a snapshot taken while a sort was running).

Indices travel as float32 (the R32F order texture), exact up to 2^24, so a sorter and an order texture hold at most get_max_count() = 2^24 splats (16.7 M): the constructor clamps to it and make_order_texture() refuses more. The arrays are handed out as PTAs so Python can write them in place; every method checks their sizes against the count before touching them, so a resized array (push_back / pop_back) cannot be over-read. Resizing one while sort() runs on another thread is not supported.

This is the only sort: the engine always builds it, so there is no fallback in the Python tier.

Inheritance diagram

digraph inheritance84d508b83c { bgcolor=transparent; rankdir=BT; size="8.0, 12.0"; "DTOOL_SUPER_BASE" [fillcolor=white,fontname="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans",fontsize=11,height=0.25,shape=box,style=""]; "GSplatSorter" [URL="acron.gsplat.GSplatSorter.html#acron.gsplat.GSplatSorter",fillcolor=white,fontname="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans",fontsize=11,height=0.25,shape=box,style="",target="_top",tooltip="/**"]; "TypedReferenceCount" -> "GSplatSorter" [arrowsize=0.75,style=""]; "ReferenceCount" [URL="acron.core.ReferenceCount.html#acron.core.ReferenceCount",fillcolor=white,fontname="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans",fontsize=11,height=0.25,shape=box,style="",target="_top",tooltip="/**"]; "DTOOL_SUPER_BASE" -> "ReferenceCount" [arrowsize=0.75,style=""]; "TypedObject" [URL="acron.core.TypedObject.html#acron.core.TypedObject",fillcolor=white,fontname="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans",fontsize=11,height=0.25,shape=box,style="",target="_top",tooltip="/**"]; "DTOOL_SUPER_BASE" -> "TypedObject" [arrowsize=0.75,style=""]; "TypedReferenceCount" [URL="acron.core.TypedReferenceCount.html#acron.core.TypedReferenceCount",fillcolor=white,fontname="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans",fontsize=11,height=0.25,shape=box,style="",target="_top",tooltip="/**"]; "TypedObject" -> "TypedReferenceCount" [arrowsize=0.75,style=""]; "ReferenceCount" -> "TypedReferenceCount" [arrowsize=0.75,style=""]; }
GSplatSorter.__init__()

Allocates the three per-splat arrays for count splats. visible starts all-ones (everything drawn), depths and order zero.

static GSplatSorter.depth_along()

The plain view-space depth of undeformed splats: for every centre (xyz triplets), dot(centre - origin, direction). depths receives one value per centre and must be at least that long.

GSplatSorter.get_bits()
static GSplatSorter.get_class_type()
GSplatSorter.get_count()
GSplatSorter.get_depths()
static GSplatSorter.get_max_count()
GSplatSorter.get_num_sorted()
GSplatSorter.get_order()
GSplatSorter.get_visible()
GSplatSorter.load_depth_image()

As load_depths(), from a depth plane’s RAM image (BGRA float texels) – typically a copy taken while the sorter was busy. Returns false if the image holds fewer texels than the splat count.

GSplatSorter.load_depths()

Reads depths and visible from a depth plane (see the class description): texel i’s red channel is the depth, its green channel > 0.5 marks it visible. Returns false if the texture has no RAM image, is not a four-component float image, or holds fewer texels than the splat count.

Acron keeps RGBA RAM images in BGRA order, so red is the third float of a texel and green the second.

static GSplatSorter.make_order_texture()

An R32F order texture of width x height texels holding the identity order (texel i = i), nearest-filtered: what a draw uses until its first sort. Null for an empty size or more than get_max_count() texels (float32 holds every index exactly only up to 2^24).

GSplatSorter.set_bits()
GSplatSorter.sort()

Fills order with the indices of the visible splats (all of them when use_visible is false), farthest depth first, ties in index order. Returns how many indices were written; the rest of order is untouched.

GSplatSorter.write_order()

Copies the sorted indices into the texture’s RAM image as float32 (an R32F order texture of at least num_sorted texels). Returns false if the texture has no RAM image or is too small.