render/geometry.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | /// @file geometry.h | ||
| 3 | /// @brief The geometry the device scene traces: meshes as triangles, and UsdGeom's implicit | ||
| 4 | /// shapes as unit shapes in procedural boxes, intersected exactly at any size. | ||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "ernie/device/arena.h" | ||
| 8 | #include "ernie/device/column.h" | ||
| 9 | #include "ernie/device/staging.h" | ||
| 10 | #include "ernie/graph/graph.h" | ||
| 11 | #include "ernie/render/prim_rows.h" | ||
| 12 | #include "ernie/scene/scene.h" | ||
| 13 | #include "ernie/shaders/scene/geometry.slang" | ||
| 14 | |||
| 15 | #include <glm/glm.hpp> | ||
| 16 | |||
| 17 | #include <cstdint> | ||
| 18 | #include <optional> | ||
| 19 | #include <span> | ||
| 20 | #include <vector> | ||
| 21 | |||
| 22 | namespace ernie { | ||
| 23 | |||
| 24 | /// Triangles, or the unit shapes procedural primitives stand for: a unit sphere and cube, a unit | ||
| 25 | /// cylinder and cone about +z with z in [-1, 1] (the cone's apex at +1), a capsule of radius 1 | ||
| 26 | /// about a segment of half-length `Geometry::halfLength`, and a unit square in z = 0 facing +z | ||
| 27 | /// (planes, and ground planes 200 km across). | ||
| 28 | enum class GeometryKind : std::uint8_t { | ||
| 29 | Triangles = shaders::kTriangles, | ||
| 30 | Sphere = shaders::kSphere, | ||
| 31 | Cube = shaders::kCube, | ||
| 32 | Cylinder = shaders::kCylinder, | ||
| 33 | Cone = shaders::kCone, | ||
| 34 | Capsule = shaders::kCapsule, | ||
| 35 | Plane = shaders::kPlane, | ||
| 36 | }; | ||
| 37 | |||
| 38 | using GeometryId = std::uint32_t; | ||
| 39 | |||
| 40 | /// @brief A piece of geometry on the device, shared by every prim and world that draws it. | ||
| 41 | struct Geometry { | ||
| 42 | GeometryKind kind = GeometryKind::Triangles; | ||
| 43 | /// Triangles' float x, y, z positions and uint32 indices; or the one procedural box. | ||
| 44 | BufferRange vertices; | ||
| 45 | BufferRange indices; | ||
| 46 | BufferRange box; | ||
| 47 | std::uint32_t vertexCount = 0; | ||
| 48 | std::uint32_t triangleCount = 0; | ||
| 49 | /// A capsule's segment half-length over its radius. | ||
| 50 | float halfLength = 0.0f; | ||
| 51 | }; | ||
| 52 | |||
| 53 | /// @brief A geometry as kernels read it from `GeometryPool::Records`, as geometry.slang defines it. | ||
| 54 | using GeometryRecord = shaders::GeometryRecord; | ||
| 55 | |||
| 56 | /// @brief What a prim draws: its geometry, and the transform from the geometry's frame to the | ||
| 57 | /// prim's, which sizes and turns a unit shape. | ||
| 58 | struct PrimGeometry { | ||
| 59 | PrimId prim; | ||
| 60 | GeometryId geometry = 0; | ||
| 61 | glm::dmat4 shape{1.0}; | ||
| 62 | /// Its albedo in linear RGB, from `primvars:displayColor`, until materials bind one. | ||
| 63 | glm::vec3 colour{0.5f}; | ||
| 64 | }; | ||
| 65 | |||
| 66 | /// @brief The scene's gprims as device geometry. Implicit shapes share one unit geometry a kind | ||
| 67 | /// (capsules one each, as their caps stay round), so a thousand spheres need one bottom level. | ||
| 68 | /// Updates follow the scene's changes and cost what changed: new points of the same count are | ||
| 69 | /// rewritten in place for a refit, new topology is a rebuild, and shape attributes change only | ||
| 70 | /// the prim's shape transform. | ||
| 71 | class GeometryPool { | ||
| 72 | public: | ||
| 73 | /// @brief What an update changed, for the bottom levels and instances that follow it. | ||
| 74 | struct Changes { | ||
| 75 | /// Prims added, or whose geometry, shape or colour changed. | ||
| 76 | std::vector<PrimGeometry> placed; | ||
| 77 | std::vector<PrimId> removed; | ||
| 78 | /// Geometries new or of new topology, whose bottom levels are built anew. | ||
| 79 | std::vector<GeometryId> rebuilt; | ||
| 80 | /// Geometries whose points moved in place, whose bottom levels may be refitted. | ||
| 81 | std::vector<GeometryId> refitted; | ||
| 82 | /// Geometries no prim draws any more. | ||
| 83 | std::vector<GeometryId> freed; | ||
| 84 | }; | ||
| 85 | |||
| 86 | /// @param arenas Where the geometry lives. | ||
| 87 | GeometryPool(DeviceContext& context, Arenas& arenas, const Scene& scene); | ||
| 88 | |||
| 89 | /// @brief Follows the scene's gprims since the last update, staging their data's uploads. | ||
| 90 | /// @throws std::invalid_argument for a mesh with malformed topology, or points not on the host. | ||
| 91 | Changes Update(); | ||
| 92 | /// @brief Adds the pass that uploads what the last update staged, before the passes that | ||
| 93 | /// import `Buffers` to build from them. | ||
| 94 | void AddUploads(graph::Graph& graph); | ||
| 95 | |||
| 96 | 75 | const Geometry& Get(GeometryId geometry) const { | |
| 97 | 75 | return geometries_[geometry]; | |
| 98 | } | ||
| 99 | /// @return What the prim draws, or none for a prim that is no gprim Ernie traces. | ||
| 100 | std::optional<PrimGeometry> Find(PrimId prim) const; | ||
| 101 | /// @return The buffers geometry lives in, as a ranges' arena indexes them. | ||
| 102 | 54 | const Buffer& Storage(const BufferRange& range) const { | |
| 103 | 48 | return arenas_.Storage(range); | |
| 104 | } | ||
| 105 | /// @brief Imports every buffer geometry lives in, for passes that read it. | ||
| 106 | std::vector<graph::Buffer> Import(graph::Graph& graph) const; | ||
| 107 | /// @return Each geometry's `GeometryRecord`, by geometry id, in the buffers `Import` imports. | ||
| 108 | 28 | const DeviceColumn& Records() const { | |
| 109 | 28 | return *records_; | |
| 110 | } | ||
| 111 | |||
| 112 | private: | ||
| 113 | /// Places a prim: its geometry, built or shared, its shape transform and its colour. A mesh's | ||
| 114 | /// points are uploaded when it is new or `points`, its triangles when `topology`. | ||
| 115 | void Place(PrimId prim, bool points, bool topology, Changes& changes); | ||
| 116 | /// @return The unit shape geometry of a kind, made the first time it is asked for. | ||
| 117 | GeometryId UnitShape(GeometryKind kind); | ||
| 118 | /// @return A new or recycled geometry id. | ||
| 119 | GeometryId Allocate(const Geometry& geometry); | ||
| 120 | void Free(GeometryId geometry, Changes& changes); | ||
| 121 | void UploadBox(const Geometry& geometry, const glm::vec3& low, const glm::vec3& high); | ||
| 122 | /// Uploads a mesh's points, and its triangles when `topology`; a refit when only points move. | ||
| 123 | void UploadMesh(PrimId prim, GeometryId geometry, bool topology, Changes& changes); | ||
| 124 | /// Marks a geometry's record for the next upload. | ||
| 125 | void Touch(GeometryId geometry); | ||
| 126 | |||
| 127 | DeviceContext& context_; | ||
| 128 | Arenas& arenas_; | ||
| 129 | const Scene& scene_; | ||
| 130 | Uploader uploader_; | ||
| 131 | std::uint64_t cursor_ = 0; | ||
| 132 | PrimRows rows_; | ||
| 133 | std::vector<Geometry> geometries_; | ||
| 134 | std::vector<GeometryId> unused_; | ||
| 135 | /// What each prim draws, by its id's slot. | ||
| 136 | std::vector<std::optional<PrimGeometry>> prims_; | ||
| 137 | std::vector<std::optional<GeometryId>> unitShapes_; | ||
| 138 | /// The records' host copies, and the geometries whose records changed. | ||
| 139 | std::vector<GeometryRecord> hostRecords_; | ||
| 140 | std::vector<std::uint32_t> dirtyRecords_; | ||
| 141 | std::unique_ptr<DeviceColumn> records_; | ||
| 142 | }; | ||
| 143 | |||
| 144 | } // namespace ernie | ||
| 145 |