# Graphics objects (gobj) This cluster is the **device-independent representation of everything that gets drawn**: geometry (`Geom`, `GeomPrimitive`), the vertex-data layer (`GeomVertexData` / `GeomVertexFormat` / `GeomVertexArrayData`), textures (`Texture`, `TextureStage`, `SamplerState`), shaders (`Shader`, `ShaderBuffer`), materials, lenses, and the GPU-resource bookkeeping (`PreparedGraphicsObjects` and the per-GSG `*Context` handles). Everything here lives first in system RAM as renderer-agnostic data; it is converted to a backend-specific layout by `GeomMunger` and uploaded to the GPU lazily through the abstract `GraphicsStateGuardianBase` (gsgbase) interface on first render. The two directories are deliberately separate Panda packages: `gobj` (`libp3gobj`) holds the concrete data classes; `gsgbase` (`libp3gsgbase`) holds only the abstract GSG/output interfaces, kept in their own package "so we can avoid circular build dependency problems" (`graphicsStateGuardianBase.h`). Almost every class in `gobj` is built around the copy-on-write + pipeline-cycler concurrency pattern so a render thread can read geometry lock-free while an app thread mutates it. ## gobj **What it is.** `panda/src/gobj` is the largest leaf package in Panda's graphics stack. It defines the in-memory data model for renderable content: a `Geom` bundles one `GeomVertexData` (the vertex table) with one or more `GeomPrimitive`s (index lists describing how vertices form triangles/lines/points). Separately it defines textures, shaders, materials, lenses, the format/registry machinery, the munger that bridges to a specific renderer, and the LRU/paging systems that keep both vertex data and GPU resources within a memory budget. It has no knowledge of OpenGL/DirectX/Vulkan — those backends (`panda/src/glstuff`, `dxgsg9`, etc.) consume these classes through the gsgbase double-dispatch interface. ### The geometry core: Geom, GeomPrimitive, GeomVertexData - **`Geom`** (`geom.h` / `geom.cxx`, ~61 KB of impl) — `class Geom : public CopyOnWriteObject, public GeomEnums`. A container associating one `COWPT(GeomVertexData)` with a `pvector`. All primitives in a Geom share the same vertex table and the same render state. Key methods: `set_vertex_data`/`modify_vertex_data`, `add_primitive`, `draw()` (the public entry into rendering, takes a `GraphicsStateGuardianBase *`), `prepare_now()`, and a family of in-place transforms (`decompose_in_place`, `unify_in_place`, `make_points_in_place`, `doubleside_in_place`). It maintains a per-format munge cache (`Geom::CacheEntry`, keyed by `(GeomVertexData*, GeomMunger*)`) and a per-GSG context map (`Contexts _contexts`). - **`GeomPrimitive`** (`geomPrimitive.h` / `.cxx`, ~75 KB) — `class GeomPrimitive : public CopyOnWriteObject, public GeomEnums`. Abstract base for an ordered list of vertex indices. The index list itself is stored as a `GeomVertexArrayData` (so index buffers reuse the same paging/upload machinery as vertex buffers). Concrete subclasses (all `: public GeomPrimitive`): `GeomTriangles`, `GeomTristrips`, `GeomTrifans`, `GeomPatches`, `GeomLines`, `GeomLinestrips`, `GeomPoints`, plus adjacency variants. `make_copy()`, `get_primitive_type()`, and `draw()` are pure virtual; each subclass dispatches to the matching `GraphicsStateGuardianBase::draw_triangles/draw_tristrips/...`. Notable: indices can be non-indexed (a `first_vertex`/`num_vertices` range) or indexed (`uint8`/`uint16`/`uint32`, auto-elevated via `consider_elevate_index_type`); composite types (strips/fans) use a `_ends` array; `_mins`/`_maxs` cache the index range per sub-primitive. - **`GeomVertexData`** (`geomVertexData.h` / `.cxx`, ~90 KB) — `class GeomVertexData : public CopyOnWriteObject, public GeomEnums`. The vertex table: a list of `GeomVertexArrayData` arrays interpreted through a `GeomVertexFormat`. Holds optional animation tables (`TransformTable`, `TransformBlendTable`, `SliderTable`) and a cached `_animated_vertices` result. Key methods: `convert_to(new_format)`, `animate_vertices(force, thread)` (runs CPU skinning/morphing), `transform_vertices(mat)`, `scale_color`/`set_color`, `set_num_rows`. App code should not poke arrays directly — it goes through reader/writer cursors (below). ### The vertex-data subsystem (format, arrays, cursors, animation, paging) - **`GeomVertexFormat`** (`geomVertexFormat.h` / `.cxx`) — `final : public TypedWritableReferenceCount, public GeomEnums`. Describes the physical layout: a list of `GeomVertexArrayFormat`s, each a list of `GeomVertexColumn`s (name + numeric type + components). Formats must be **registered** before use (`GeomVertexFormat::register_format`); registration deduplicates through a global `Registry` guarded by `LightReMutex _lock`, so two logically equal formats return the *same pointer* and can be compared by identity. Provides standard prebuilt formats (`get_v3n3t2`, `get_v3c4`, etc.) and the all-important `get_post_animated_format()` / `get_union_format()` used by the munger. - **`GeomVertexArrayFormat`** (`geomVertexArrayFormat.h`) and **`GeomVertexColumn`** (`geomVertexColumn.h`, ~100 KB of `.cxx` — it contains every numeric-type read/write packer) define one array's columns. `GeomVertexColumn : public GeomEnums`. - **`GeomVertexArrayData`** (`geomVertexArrayData.h` / `.cxx`) — `class GeomVertexArrayData : public CopyOnWriteObject, public SimpleLruPage, public GeomEnums`. One contiguous byte buffer ("stream"/vertex buffer in DX/GL terms). The raw bytes live in a `VertexDataBuffer`, which can be paged out (see below). Inheriting `SimpleLruPage` is what enrolls each array in the global vertex-memory LRU. Access goes through `GeomVertexArrayDataHandle`. - **Cursors:** `GeomVertexReader` / `GeomVertexWriter` (both `: public GeomEnums`) and `GeomVertexRewriter : public GeomVertexWriter, public GeomVertexReader`. These are the high-level, type-safe way to read/write columns by `InternalName`. The `.I` files are enormous (`geomVertexWriter.I` ~37 KB) because they inline a specialized path per numeric type. - **Animation:** `GeomVertexAnimationSpec` records whether a format animates on CPU (`AT_panda`) or GPU (`AT_hardware`). The transform pipeline is `VertexTransform` (abstract; `get_matrix`/`accumulate_matrix`) → `TransformBlend` (a weighted set of transforms for one vertex) → `TransformBlendTable` → referenced from `GeomVertexData`. `VertexSlider`/`SliderTable` drive morph targets. `UserVertexTransform`/`UserVertexSlider` are app-settable leaves. - **Paging (vertex data to disk):** `VertexDataBuffer` (`vertexDataBuffer.h`) holds the bytes; `VertexDataPage : public SimpleAllocator, public SimpleLruPage` (`vertexDataPage.h`) packs many buffers onto a page that can transition `RC_resident → RC_compressed → RC_disk` under memory pressure; `VertexDataBook` is the allocator-of-pages; `VertexDataSaveFile` is the on-disk backing store. Background threads (config `vertex-data-page-threads`) do the compress/spill work. ### The texture subsystem - **`Texture`** (`texture.h`, 50 KB header; `texture.cxx`, 339 KB — the single biggest file in the cluster) — `class Texture : public TypedWritableReferenceCount, public Namable`. Holds a CPU RAM image plus all the metadata (`TextureType` 1d/2d/3d/array/cube/buffer, `ComponentType`, `Format` — dozens of formats incl. sRGB, integer, packed-float, depth). It is pipelined (`CData`/`PipelineCycler`) and tracks a per-GSG `TextureContext`. **Gotcha (community-confirmed):** Panda frees the system-RAM image once the texture is uploaded to the GPU; if the GPU copy is later lost (e.g. cube map mipmaps), reload can fail — set `Texture.keep_ram_image = True` or rely on `keep-texture-ram`. See GitHub issue #1091 "Loading/unloading cubemap texture with manual mipmaps needs TexturePool.release()", https://github.com/panda3d/panda3d/issues/1091 ("Panda unloads the texture from RAM once it's been uploaded to the GPU... Texture.keep_ram_image = True works and solves the issue"). - **`TextureStage`** (`textureStage.h`) — names a slot in the fixed-function multitexture pipeline and its combine mode; pooled via `TextureStagePool`. - **`SamplerState`** (`samplerState.h`) — `class SamplerState : public MemoryBase`. A value object (not ref-counted) describing wrap modes (`WM_clamp/repeat/mirror/...`), min/mag `FilterType`, anisotropy, LOD bias, border color. Decoupled from `Texture` since rdb's 2014 rework so the same image can be sampled differently in different places; the GSG prepares a `SamplerContext` for it. - **`TexturePool`** (`texturePool.h`, ~45 KB impl) — process-global registry/cache keyed by filename, with a filter chain (`TexturePoolFilter`, `PythonTexturePoolFilter`). `VideoTexture : public Texture, public AnimInterface` is the base for movie textures. `TexturePeeker` reads pixels back from a RAM image; `TextureCollection` is a list type. `TextureReloadRequest` is the async background reload task (threads: `texture-reload-num-threads`). ### Shaders and materials - **`Shader`** (`shader.h`; `shader.cxx` ~120 KB) — `class Shader : public TypedWritableReferenceCount`. Holds source/metadata for `SL_Cg / SL_GLSL / SL_HLSL / SL_SPIR_V`, split by `ShaderType` (vertex/fragment/geometry/tess/compute). `Shader::load`/`make`/`make_compute` are the factories. The big `ShaderMatInput` enum (`SMO_model_to_view`, `SMO_attr_colorscale`, …) enumerates the auto-bound shader inputs Panda can compute from render state. `prepare()` returns a `PT(AsyncFuture)` (compilation can be async). `ShaderBuffer : public TypedWritableReferenceCount, public Namable, public GeomEnums` (`shaderBuffer.h`) is GPU storage for compute (SSBO). - **`Material`** (`material.h`) — `class Material : public TypedWritableReferenceCount, public Namable`. Lighting properties; supports both classic (ambient/diffuse/specular) and metalness (base color + metallic + roughness) PBR workflows. `MaterialPool` deduplicates. - **`Lens`** (`lens.h`; `lens.cxx` ~64 KB) — `class Lens : public TypedWritableReferenceCount`. Camera/projection abstraction with `extrude`/`project`. Subclasses: `PerspectiveLens`, `OrthographicLens`, `MatrixLens`. Lives in gobj (not display) because a `Spotlight` also uses a `Lens`. `ParamTextureSampler`/`ParamTextureImage` (`paramTexture.h`, both `: public ParamValueBase`) wrap a texture+sampler (or image) as a shader-input parameter. ### Munging, caching, and GPU preparation - **`GeomMunger`** (`geomMunger.h` / `.cxx`) — `class GeomMunger : public TypedReferenceCount, public GeomEnums`. The pivotal bridge: converts a `GeomVertexData` from its authoring format into one the backend can consume, and applies render-state-driven vertex changes (e.g. baking a `ColorScaleAttrib` into vertex colors). Each backend subclasses it (`CLP(GeomMunger)`, `DXGeomMunger`). Registered mungers are deduplicated like formats (same operation ⇒ same pointer). The GSG hands one out via `GraphicsStateGuardianBase::get_geom_munger(state, thread)`. **Performance note (trusted forum):** "'munge' is the time required to massage the GVD into a format suitable for the backend, and is what fills up the geom-vertex-cache" — https://discourse.panda3d.org/t/8211. Matching your authoring format to the GSG's preferred format avoids munge cost. - **`GeomCacheManager` / `GeomCacheEntry`** (`geomCacheManager.h`, `geomCacheEntry.h`) — a global LRU bounding the total munge/decompose cache (`geom-cache-size`, `geom-cache-min-frames`). `AnimateVerticesRequest` runs CPU vertex animation off-thread. - **`PreparedGraphicsObjects`** (`preparedGraphicsObjects.h`; `.cxx` ~54 KB) — `class PreparedGraphicsObjects : public ReferenceCount`. The per-GSG (or shared-context) registry of GPU resources. For each resource kind (texture, sampler, geom, shader, vertex buffer, index buffer, shader buffer) it offers `enqueue_*` / `dequeue_*` / `is_*_prepared` / `release_*` / `prepare_*_now`. It owns the `*Context` objects and enforces the graphics-memory budget through `BufferResidencyTracker` + `AdaptiveLru`. Protected by a `ReMutex`. - **Context objects** (per-GSG opaque handles): `SavedContext : public TypedObject` ← `BufferContext : public SavedContext, private LinkedListNode` ← `TextureContext : public BufferContext, public AdaptiveLruPage`; also `VertexBufferContext`, `IndexBufferContext`, `GeomContext`, `ShaderContext`, `SamplerContext`, `BufferContextChain`. Backends subclass these to stash the real API handle (e.g. a GL texture id). - **Memory machinery:** `SimpleAllocator` (block sub-allocation), `SimpleLru` + `SimpleLruPage` (basic LRU; vertex arrays/pages are pages), `AdaptiveLru` + `AdaptiveLruPage` (frequency-weighted LRU used for GPU residency; textures are pages). Config `graphics-memory-limit`, `adaptive-lru-weight`. ### Shared enums and naming - **`GeomEnums`** (`geomEnums.h`) — pure scoping base providing `UsageHint` (`UH_client/stream/dynamic/static/unspecified`), `GeomRendering` (capability bitmask — the diff between a Geom's required bits and the GSG's `get_supported_geom_rendering()` tells the munger what to fix up), `ShadeModel`, `PrimitiveType`, `NumericType`, `Contents` (semantic: `C_point`, `C_normal`, `C_color`, `C_texcoord`, `C_morph_delta`, …), and `AnimationType`. Inherited by virtually every class here. - **`InternalName`** (`internalName.h` / `.cxx`) — `final : public TypedWritableReferenceCount`. Interned, hierarchical ('.'-separated) immutable name tokens used for vertex columns, texture stages, and shader inputs. Interning gives O(1) pointer comparison; predefined singletons (`get_vertex()`, `get_normal()`, `get_color()`, `get_texcoord()`, `get_transform_blend()`, …) name the standard columns. **How it plugs into the rest of the engine.** Up the stack, `GeomNode` (panda/src/pgraph) holds `Geom`s in the scene graph; the cull traversal collects them into `CullableObject`s, and `CullBin`/`GraphicsEngine` eventually call `Geom::draw()`. `Geom::draw()` and the `*PipelineReader` classes call **down** into a concrete `GraphicsStateGuardian` (panda/src/display + a backend like glgsg) exclusively through the abstract `GraphicsStateGuardianBase` vtable. Textures/shaders/materials are referenced by `RenderAttrib`s (`TextureAttrib`, `ShaderAttrib`, `MaterialAttrib`) in pgraph but the objects themselves live here. Bam serialization (panda/src/putil) reads/writes these via the `write_datagram`/`fillin`/`make_from_bam` hooks present on most classes. **Where to start reading (entry points).** - Adding/altering a vertex feature → `geomVertexFormat.cxx` (registration, standard formats, `get_post_animated_format`) and `geomVertexData.cxx` (`convert_to`, `animate_vertices`). - A primitive/index bug → `geomPrimitive.cxx` (`do_make_indexed`, `consider_elevate_index_type`, `decompose_impl`) and the relevant `geomTriangles.cxx` / `geomTristrips.cxx`. - A "wrong colors / unexpected reformatting" bug → `geomMunger.cxx` (`munge_format_impl`, `munge_data_impl`) and the backend's munger subclass. - A texture upload/format/memory bug → `texture.cxx` (`do_reload_ram_image`, `consider_auto_process_ram_image`, format tables) and `preparedGraphicsObjects.cxx`. - GPU-resource lifecycle/leak → `preparedGraphicsObjects.cxx` (`enqueue_*`/`release_*`/`prepare_*_now`). **Gotchas / design rationale / config.** - *COW + pipeline:* Each core class has a private `CData : public CycleData` plus a `PipelineCycler`, and exposes `*PipelineReader`/`*PipelineWriter` helpers. The render thread reads a frozen cycle stage while the app thread writes a copy — never bypass this by holding raw pointers across frames. - *Format/munger/pool identity:* Because registered formats, mungers, and pooled objects deduplicate to a single pointer, code routinely compares them by pointer; if you build a format and forget to `register_format` it, identity comparisons silently break. - *Direct buffer writes:* You *can* write raw vertex bytes via `GeomVertexArrayDataHandle::set_data` / `get_write_pointer` (trusted forum thread https://discourse.panda3d.org/t/3646), but you must `set_num_rows` first and keep the format in sync. - *Texture RAM eviction* (issue #1091, above) is the most common texture footgun. - Selected config vars (all in `config_gobj.cxx`): `vertex-buffers`, `vertex-arrays`, `hardware-animated-vertices`, `matrix-palette`, `vertices-float64`, `vertex-column-alignment`, `vertex-animation-align-16`, `vertex-colors-prefer-packed`, `geom-cache-size`, `geom-cache-min-frames`, `released-vbuffer-cache-size`, `released-ibuffer-cache-size`, `graphics-memory-limit`, `sampler-object-limit`, `adaptive-lru-weight`, `vertex-data-small-size`, `vertex-data-page-threads`, `vertex-save-file-directory`, `keep-texture-ram`, `driver-compress-textures`, `driver-generate-mipmaps`, `textures-power-2`, `textures-square`, `texture-reload-num-threads`, `max-texture-dimension`, `dump-generated-shaders`, `cache-generated-shaders`, `glsl-preprocess`, `default-near`/`default-far`/`default-fov`, `lens-geom-segments`. ## gsgbase **What it is.** `panda/src/gsgbase` is a tiny package (two real classes) that exists purely to break a build-dependency cycle. The renderable data in `gobj` needs to call into a GSG to draw, but a concrete GSG (in `display`/backends) depends on `gobj`. The fix is to put the *abstract* GSG interface here, in a package that `gobj` can depend on, so `Geom`/`GeomPrimitive` can invoke `gsg->draw_triangles(...)` without a circular link. As the header states: "It lives in a separate class in its own package so we can avoid circular build dependency problems" (`graphicsStateGuardianBase.h`). **Key classes.** - **`GraphicsStateGuardianBase`** (`graphicsStateGuardianBase.h` / `.cxx`) — `class GraphicsStateGuardianBase : public TypedWritableReferenceCount`. A pure-virtual "device driver" interface enumerating everything a renderer must implement: capability queries (`get_max_texture_dimension`, `get_supported_geom_rendering`, `prefers_triangle_strips`, `get_supports_compressed_texture_format`, `get_supports_hlsl`), resource preparation (`prepare_texture`/`prepare_geom`/`prepare_shader`/`prepare_vertex_buffer`/`prepare_index_buffer`/`prepare_sampler`/`prepare_shader_buffer` and their `release_*`), state binding (`set_state_and_transform`, `get_geom_munger`, `bind_light`), and the **double-dispatch draw protocol**: `begin_draw_primitives` → one of `draw_triangles / draw_triangles_adj / draw_tristrips / draw_tristrips_adj / draw_trifans / draw_patches / draw_lines / draw_lines_adj / draw_linestrips / draw_linestrips_adj / draw_points` → `end_draw_primitives`. Each `GeomPrimitive` subclass picks the matching method, so the primitive type is resolved without RTTI or switch statements. It also keeps the global GSG registry (`add_gsg`/`get_gsg`/`get_default_gsg`) used so a `Texture`/`Geom` can be released from all GSGs. - **`GraphicsOutputBase`** (`graphicsOutputBase.h` / `.cxx`) — `class GraphicsOutputBase : public TypedWritableReferenceCount`. A two-method abstract base (`set_sort`, `get_texture`) for `GraphicsOutput` (windows/buffers), again split out so lower packages can refer to an output without depending on `display`. **How it plugs in.** Concrete subclass `GraphicsStateGuardian` (panda/src/display) fleshes most of these out; backend GSGs (`GLGraphicsStateGuardian`, `DXGraphicsStateGuardian9`, Vulkan) subclass that. `gobj` (and `pgraph`, via cull) only ever sees the base type — `Geom::draw(GraphicsStateGuardianBase *gsg, ...)`. The forward-declaration block at the top of `graphicsStateGuardianBase.h` (every `Geom*`, `Texture`, attrib, light type) documents exactly which engine objects participate in the GSG dispatch. **Where to start.** To add a capability flag or a new draw entry point, edit `graphicsStateGuardianBase.h` (add the pure-virtual), then implement it in `panda/src/display/graphicsStateGuardian.cxx` and each backend. `config_gsgbase.cxx` only does `init_type()` registration — there are no gsgbase config vars. Community pointer on profiling: time attributed to `begin_draw_primitives` in PStats is the GSG draw-setup cost (Discord, https://discord.com/channels/524691714909274162/533306680406966273/1126767468858331156). ## Where to start (this cluster) A new contributor should orient through these files, in order: 1. **`panda/src/gobj/geom.h`** — the top-level container; from `Geom::draw()` you can trace the whole render path. 2. **`panda/src/gobj/geomEnums.h`** — small, and decodes the vocabulary (`UsageHint`, `GeomRendering`, `NumericType`, `Contents`) used everywhere else. 3. **`panda/src/gobj/geomVertexFormat.h` + `geomVertexData.h`** — how vertices are described and stored; read alongside `geomVertexReader.h`/`geomVertexWriter.h` for the cursor API. 4. **`panda/src/gobj/geomPrimitive.h`** — index/primitive model and the `draw_*` dispatch tie-in. 5. **`panda/src/gobj/geomMunger.h`** — the format-conversion bridge; the single most important class for understanding the gobj↔backend boundary. 6. **`panda/src/gsgbase/graphicsStateGuardianBase.h`** — the abstract driver interface every backend implements; the forward-declare block is a map of who talks to the GSG. 7. **`panda/src/gobj/preparedGraphicsObjects.h`** — GPU-resource lifecycle and the memory budget. 8. **`panda/src/gobj/texture.h`** and **`shader.h`** — the other two big resource types; large, so start from the published factory methods (`Texture::read`, `Shader::load`/`make`).