OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
geometry_port.hpp
1#pragma once
2#include <ossia/detail/config.hpp>
3
4#include <ossia/dataflow/texture_port.hpp>
5#include <ossia/detail/flat_map.hpp>
6#include <ossia/detail/small_vector.hpp>
7#include <ossia/detail/variant.hpp>
8#include <ossia/network/value/value.hpp>
9
10#include <cstdint>
11#include <limits>
12#include <memory>
13#include <span>
14#include <string>
15#include <string_view>
16#include <variant>
17#include <vector>
18
19namespace ossia
20{
21
22// clang-format off
23// Semantic identification for geometry attributes.
24// Custom attributes use attribute_semantic::custom + a string name.
25enum class attribute_semantic : uint16_t
26{
27 // Core geometry
28 position = 0, // vec3. Object-space position.
29 normal = 1, // vec3. Surface normal.
30 tangent = 2, // vec4. xyz=tangent, w=handedness (±1). [glTF TANGENT]
31 bitangent = 3, // vec3. cross(N, T.xyz) * T.w.
32
33 // Basic materials
34 texcoord0 = 100, // vec2/3. Primary UV. [glTF TEXCOORD_0]
35 texcoord1 = texcoord0 + 1, // vec2. Secondary UV (lightmaps). [glTF TEXCOORD_1]
36 texcoord2 = texcoord0 + 2, // vec2. Secondary UV. [glTF TEXCOORD_2]
37 texcoord3 = texcoord0 + 3, // vec2. Secondary UV. [glTF TEXCOORD_3]
38 texcoord4 = texcoord0 + 4, // vec2. Secondary UV. [glTF TEXCOORD_4]
39 texcoord5 = texcoord0 + 5, // vec2. Secondary UV.
40 texcoord6 = texcoord0 + 6, // vec2. Secondary UV.
41 texcoord7 = texcoord0 + 7, // vec2. Secondary UV.
42
43 color0 = 200, // vec4. Vertex color RGBA. [glTF COLOR_0]
44 color1 = color0 + 1, // vec4. Secondary vertex color. [glTF COLOR_1]
45 color2 = color0 + 2, // vec4. Secondary vertex color.
46 color3 = color0 + 3, // vec4. Secondary vertex color.
47
48 // Skinning / skeletal animation
49 joints0 = 300, // uvec4. Bone indices, set 0. [glTF JOINTS_0]
50 joints1 = joints0 + 1, // uvec4. Bone indices, set 1. [glTF JOINTS_1]
51
52 weights0 = 400, // vec4. Bone weights, set 0. [glTF WEIGHTS_0]
53 weights1 = weights0 + 1, // vec4. Bone weights, set 1. [glTF WEIGHTS_1]
54
55 // Morph targets / blend shapes
56 morph_position = 500, // vec3. Position delta for morph target.
57 morph_normal = morph_position + 1, // vec3. Normal delta for morph target.
58 morph_tangent = morph_position + 2, // vec3. Tangent delta (no w). [glTF morph TANGENT]
59 morph_texcoord = morph_position + 3, // vec2. UV delta for morph target.
60 morph_color = morph_position + 4, // vec3/4. Color delta for morph target.
61
62 // Transform / instancing
63 rotation = 600, // vec4. Quaternion (x,y,z,w).
64 rotation_extra = rotation + 1, // vec4. Post-orient rotation.
65 scale = rotation + 2, // vec3. Non-uniform scale.
66 uniform_scale = rotation + 3, // float. Uniform scale.
67 up = rotation + 4, // vec3. Up vector for LookAt.
68 pivot = rotation + 5, // vec3. Local pivot point.
69 transform_matrix = rotation + 6, // mat4. Full transform, overrides TRS. (note: remember that mat4 takes 4 lanes of attributes)
70 translation = rotation + 7, // vec3. Additional translation offset.
71
72 // Particle dynamics
73 velocity = 1000, // vec3. Velocity in units/sec.
74 acceleration = velocity + 1, // vec3. Current acceleration.
75 force = velocity + 2, // vec3. Accumulated force this frame.
76 mass = velocity + 3, // float.
77 age = velocity + 4, // float. Time since birth, seconds.
78 lifetime = velocity + 5, // float. Max age before death.
79 birth_time = velocity + 6, // float. Absolute time of birth.
80 particle_id = velocity + 7, // int. Stable unique ID.
81 drag = velocity + 8, // float. Per-particle drag coefficient.
82 angular_velocity = velocity + 9, // vec3. Rotation speed rad/sec.
83 previous_position = velocity + 10, // vec3. For Verlet / motion blur.
84 rest_position = velocity + 11, // vec3. Undeformed position.
85 target_position = velocity + 12, // vec3. Goal position for constraints.
86 previous_velocity = velocity + 13, // vec3. Velocity at previous frame.
87 state = velocity + 14, // int. alive/dying/dead/collided enum.
88 collision_count = velocity + 15, // int. Number of collisions.
89 collision_normal = velocity + 16, // vec3. Normal at last collision.
90 sleep = velocity + 17, // int. Dormant flag (skip simulation).
91
92 // Rendering hints
93 sprite_size = 1100, // vec2. Billboard width/height.
94 sprite_rotation = sprite_size + 1, // float. Billboard screen-space rotation.
95 sprite_facing = sprite_size + 2, // vec3. Custom billboard facing direction.
96 sprite_index = sprite_size + 3, // int/float. Sub-image index for sprite sheets.
97 width = sprite_size + 4, // float. Curve/ribbon thickness.
98 opacity = sprite_size + 5, // float. Separate from color alpha.
99 emissive = sprite_size + 6, // vec3. Self-illumination color.
100 emissive_strength = sprite_size + 7, // float. Emissive intensity multiplier.
101
102 // Material / PBR
103 roughness = 1200, // float. PBR roughness [0-1].
104 metallic = roughness + 1, // float. PBR metalness [0-1].
105 ambient_occlusion = roughness + 2, // float. Baked AO [0-1].
106 specular = roughness + 3, // float. Specular factor.
107 subsurface = roughness + 4, // float. SSS intensity.
108 clearcoat = roughness + 5, // float. Clearcoat factor.
109 clearcoat_roughness = roughness + 6, // float. Clearcoat roughness.
110 anisotropy = roughness + 7, // float. Anisotropic reflection.
111 anisotropy_direction = roughness + 8, // vec3. Anisotropy tangent direction.
112 ior = roughness + 9, // float. Index of refraction.
113 transmission = roughness + 10, // float. Transmission factor (glass-like).
114 thickness = roughness + 11, // float. Volume thickness for transmission.
115 material_id = roughness + 22, // int. Index into material array.
116
117 // Gaussian splatting
118 sh_dc = 1300, // vec3. SH degree-0 (DC) color.
119 sh_coeffs = sh_dc + 1, // float[N]. SH coefficients for higher degrees.
120 covariance_3d = sh_dc + 2, // vec6 or mat3. 3D covariance (6 unique floats).
121 sh_degree = sh_dc + 3, // int. Active SH degree for this splat (0-3).
122
123 // Volumetric / field data
124 density = 1400, // float. Scalar density.
125 temperature = density + 1, // float.
126 fuel = density + 2, // float.
127 pressure = density + 3, // float.
128 divergence = density + 4, // float.
129 sdf_distance = density + 5, // float. Signed distance field value.
130 voxel_color = density + 6, // vec4. Per-voxel RGBA.
131
132 // Topology / connectivity
133 name = 1600, // string. Piece/group identifier.
134 piece_id = name + 1, // int. Numeric piece/group index.
135 line_id = name + 2, // int. Which line strip this point belongs to.
136 prim_id = name + 3, // int. Source primitive index.
137 point_id = name + 4, // int. Stable point ID (distinct from array index).
138 group_mask = name + 5, // int. Bitfield for group membership.
139 instance_id = name + 6, // int. Which instance this element belongs to.
140
141 // UI
142 selection = 1700, // float. Soft selection weight [0-1].
143
144 instance_color0 = 1800, // vec4. Per-instance broadcast color, set 0.
145 instance_color1 = instance_color0 + 1, // vec4.
146 instance_color2 = instance_color0 + 2, // vec4.
147 instance_color3 = instance_color0 + 3, // vec4.
148 instance_custom0 = instance_color0 + 4, // vec4. Per-instance user data, set 0.
149 instance_custom1 = instance_color0 + 5, // vec4.
150 instance_custom2 = instance_color0 + 6, // vec4.
151 instance_custom3 = instance_color0 + 7, // vec4.
152 instance_draw_id = instance_color0 + 8, // uint.
153 // User / general purpose
154 fx0 = 2000, // float. General-purpose effect control.
155 fx1 = fx0 + 1, // float. General-purpose effect control.
156 fx2 = fx0 + 2, // float. General-purpose effect control.
157 fx3 = fx0 + 3, // float. General-purpose effect control.
158 fx4 = fx0 + 4, // float. General-purpose effect control.
159 fx5 = fx0 + 5, // float. General-purpose effect control.
160 fx6 = fx0 + 6, // float. General-purpose effect control.
161 fx7 = fx0 + 7, // float. General-purpose effect control.
162
163 // Custom (string name lookup)
164 custom = 0xFFFF
165};
166
167// Returns a display name for well-known semantics, empty for custom.
168OSSIA_EXPORT std::string_view semantic_to_name(attribute_semantic s) noexcept;
169
170// Returns the semantic for a well-known name, or custom if not recognized.
171OSSIA_EXPORT attribute_semantic name_to_semantic(std::string_view name) noexcept;
172
173struct geometry
174{
175 struct cpu_buffer
176 {
177 std::shared_ptr<void> raw_data;
178 int64_t byte_size{};
179 };
180
181 struct gpu_buffer
182 {
183 void* handle{}; // Can be casted to e.g. a QRhiBuffer
184 int64_t byte_size{};
185 };
186
187 struct buffer
188 {
189 ossia::variant<cpu_buffer, gpu_buffer> data;
190 bool dirty{};
191 int64_t active_element_count{-1}; // -1 = use full buffer; else first N elements valid
192 };
193
194 struct binding
195 {
196 uint32_t byte_stride{};
197 enum
198 {
199 per_vertex,
200 per_instance
201 } classification{};
202 int step_rate{};
203 };
204
205 struct attribute
206 {
207 int binding = 0;
208 int location = 0;
209
210 enum
211 {
212 float4,
213 float3,
214 float2,
215 float1,
216 unormbyte4,
217 unormbyte2,
218 unormbyte1,
219 uint4,
220 uint3,
221 uint2,
222 uint1,
223 sint4,
224 sint3,
225 sint2,
226 sint1,
227 half4,
228 half3,
229 half2,
230 half1,
231 ushort4,
232 ushort3,
233 ushort2,
234 ushort1,
235 sshort4,
236 sshort3,
237 sshort2,
238 sshort1,
239 user_struct,
240 } format
241 = float4;
242
243 uint32_t byte_offset = 0;
244 // Semantic identification for this attribute.
245 // For well-known semantics, name is empty (derivable from the enum).
246 // For custom semantics, name holds the user-defined attribute name.
247 attribute_semantic semantic = attribute_semantic::custom;
248 std::string name;
249
250 // Used only when format == user_struct: byte size of one element
251 // (sizeof of the user-defined struct) and its GLSL type name.
252 // element_byte_size doubles as the per-vertex stride when packed
253 // tightly with no padding.
254 uint32_t element_byte_size = 0;
255 std::string user_type_name;
256 };
257
258 struct input
259 {
260 int buffer{};
261 int64_t byte_offset{};
262 };
263
264 ossia::small_vector<buffer, 2> buffers;
265 ossia::small_vector<binding, 2> bindings;
266 ossia::small_vector<attribute, 2> attributes;
267 ossia::small_vector<input, 2> input;
268
269 int vertices{}, indices{}, instances{1};
270 enum
271 {
272 triangles,
273 triangle_strip,
274 triangle_fan,
275 lines,
276 line_strip,
277 points
278 } topology;
279 enum
280 {
281 none,
282 front,
283 back
284 } cull_mode;
285 enum
286 {
287 counter_clockwise,
288 clockwise
289 } front_face;
290
291 enum blend_mode : uint8_t
292 {
293 blend_opaque,
294 blend_premultiplied_alpha
295 } blend{blend_opaque};
296 bool depth_write{true};
297 struct
298 {
299 int buffer{-1};
300 int64_t byte_offset{};
301 enum
302 {
303 uint16,
304 uint32
305 } format{};
306 } index;
307 struct
308 {
309 float min[3]{};
310 float max[3]{};
311 } bounds;
312 uint32_t filter_tag{};
313 uint32_t filter_material_index{};
314 gpu_buffer indirect_count;
315 struct draw_command
316 {
317 uint32_t index_or_vertex_count{};
318 uint32_t instance_count{1};
319 uint32_t first_index_or_vertex{};
320 int32_t base_vertex{};
321 uint32_t first_instance{};
322 };
323 ossia::small_vector<draw_command, 0> cpu_draw_commands;
324 struct auxiliary_buffer
325 {
326 std::string name; // Shader-visible name for matching (e.g. "particle_aux")
327 int buffer{-1}; // Index into the buffers array
328 int64_t byte_offset{}; // Offset within the buffer
329 int64_t byte_size{}; // Size of the auxiliary data region
330 };
331 ossia::small_vector<auxiliary_buffer, 1> auxiliary;
332 struct auxiliary_texture
333 {
334 std::string name; // Shader-visible name (e.g. "base_color_array", "skybox")
335 void* native_handle{}; // Backend-owned texture handle
336 // Optional sampler handle to bind alongside the texture. When non-
337 // null, the renderer uses this sampler instead of its own.
338 void* sampler_handle{};
339 };
340 ossia::small_vector<auxiliary_texture, 2> auxiliary_textures;
341 const auxiliary_buffer* find_auxiliary(std::string_view name) const noexcept
342 {
343 for(auto& a : auxiliary)
344 if(a.name == name)
345 return &a;
346 return nullptr;
347 }
348 const auxiliary_texture* find_auxiliary_texture(std::string_view name) const noexcept
349 {
350 for(auto& a : auxiliary_textures)
351 if(a.name == name)
352 return &a;
353 return nullptr;
354 }
355 const attribute* find(attribute_semantic sem) const noexcept
356 {
357 for(auto& a : attributes)
358 if(a.semantic == sem)
359 return &a;
360 return nullptr;
361 }
362
363 const attribute* find(std::string_view attr_name) const noexcept
364 {
365 for(auto& a : attributes)
366 if(a.semantic == attribute_semantic::custom && a.name == attr_name)
367 return &a;
368 return nullptr;
369 }
370 static std::string_view display_name(const attribute& a) noexcept
371 {
372 if(a.semantic != attribute_semantic::custom)
373 return semantic_to_name(a.semantic);
374 return a.name;
375 }
376};
377
378struct mesh_list
379{
380 std::vector<geometry> meshes;
381 int64_t dirty_index{};
382};
383using mesh_list_ptr = std::shared_ptr<mesh_list>;
384struct transform3d
385{
386 float matrix[16]{
387 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.,
388 };
389};
390
391struct geometry_filter
392{
393 int64_t node_id{}; // which node is responsible for initalizing the UBO
394 int64_t filter_id{}; // unique index for this filter instance
408 std::string shader;
409 int64_t dirty_index{};
410};
411
412struct geometry_filter_list
413{
414 std::vector<geometry_filter> filters;
415 int64_t dirty_index{};
416};
417using geometry_filter_list_ptr = std::shared_ptr<geometry_filter_list>;
418
419struct geometry_spec
420{
421 mesh_list_ptr meshes;
422 geometry_filter_list_ptr filters;
423
424 operator bool() const noexcept { return meshes && filters; }
425 bool operator==(const geometry_spec&) const noexcept = default;
426 bool operator<(const geometry_spec& rhs) const noexcept
427 {
428 return (meshes < rhs.meshes) || (meshes == rhs.meshes && filters < rhs.filters);
429 }
430};
431
432struct OSSIA_EXPORT geometry_port
433{
434 static const constexpr int which = 4;
435 enum dirt_flags
436 {
437 dirty_transform = 0x1,
438 dirty_meshes = 0x2
439 };
440
441 void clear();
442
443 geometry_spec geometry;
444 transform3d transform;
445 uint8_t flags{};
446};
447
448struct geometry_delay_line
449{
450 std::vector<geometry_spec> geometries;
451};
452
453struct gpu_slot_ref
454{
455 uint32_t arena{};
456 uint32_t offset{};
457 uint32_t size{};
458 uint32_t internal_index{}; // registry-private slot identity for isLive()
459 uint32_t generation{}; // bumped on allocate/free — compared by isLive()
460
461 bool valid() const noexcept { return size != 0; }
462};
463OSSIA_EXPORT uint64_t mint_stable_id() noexcept;
464struct scene_transform
465{
466 float translation[3]{0.0f, 0.0f, 0.0f};
467 float rotation[4]{0.0f, 0.0f, 0.0f, 1.0f}; // quaternion (x,y,z,w)
468 float scale[3]{1.0f, 1.0f, 1.0f};
469
470 // GPU arena slot stamped by the producer. See gpu_slot_ref for the
471 // liveness contract. Opaque to CPU paths.
472 gpu_slot_ref raw_slot;
473
474 // Stable id across producer rebuilds. 0 = unset (anonymous transform).
475 uint64_t stable_id{};
476};
477
480{
481 void* native_handle{};
482 int64_t byte_size{};
483 int64_t byte_offset{};
484 uint32_t binding_index{};
485
486 bool valid() const noexcept { return native_handle != nullptr; }
487 bool operator==(const gpu_buffer_handle&) const noexcept = default;
488};
491{
492 void* native_handle{};
493 uint32_t bindless_index{};
494
495 struct sampler_state
496 {
497 enum class filter : uint8_t { nearest, linear };
498 enum class address : uint8_t { repeat, clamp_to_edge, mirror };
499 enum class mipmap : uint8_t { none, nearest, linear };
500
501 filter mag : 2 = filter::linear;
502 filter min : 2 = filter::linear;
503 mipmap mip : 2 = mipmap::linear;
504 address u : 2 = address::repeat;
505 address v : 2 = address::repeat;
506 address w : 2 = address::repeat;
507
508 bool operator==(const sampler_state&) const noexcept = default;
509 } sampler{};
510
511 bool valid() const noexcept { return native_handle != nullptr; }
512 bool operator==(const gpu_texture_handle&) const noexcept = default;
513};
514struct buffer_data
515{
516 std::shared_ptr<const void> data;
517 int64_t byte_size{};
518
519 enum class usage : uint8_t
520 {
521 vertex_buffer,
522 index_buffer,
523 uniform_buffer,
524 storage_buffer,
525 indirect_buffer
526 } usage_hint{usage::vertex_buffer};
527};
528
529struct buffer_resource
530{
531 ossia::variant<buffer_data, gpu_buffer_handle> resource;
532 uint64_t content_hash{};
533 int64_t dirty_index{};
534
535 bool is_gpu_resident() const noexcept
536 {
537 return ossia::get_if<gpu_buffer_handle>(&resource) != nullptr;
538 }
539};
540using buffer_resource_ptr = std::shared_ptr<const buffer_resource>;
541
542enum class vertex_format : uint8_t
543{
544 float1, float2, float3, float4,
545 half1, half2, half3, half4,
546 unorm8x1, unorm8x2, unorm8x4,
547 snorm8x1, snorm8x2, snorm8x4,
548 uint8x1, uint8x2, uint8x4,
549 uint16x1, uint16x2, uint16x4,
550 uint32x1, uint32x2, uint32x3, uint32x4,
551 sint8x1, sint8x2, sint8x4,
552 sint16x1, sint16x2, sint16x4,
553 sint32x1, sint32x2, sint32x3, sint32x4,
554 rgb10a2_unorm, rg11b10_float,
555 user_struct
556};
557
558struct vertex_attribute
559{
560 attribute_semantic semantic{}; // reuses the existing attribute_semantic enum
561 vertex_format format{vertex_format::float3};
562 uint32_t buffer_index{};
563 uint32_t byte_offset{};
564 uint32_t byte_stride{};
565 enum class input_rate : uint8_t { per_vertex, per_instance } rate{};
566
567 // Used only when format == vertex_format::user_struct. element_byte_size
568 // equals sizeof of the user-defined struct (typically == byte_stride
569 // for tightly-packed per-vertex data); user_type_name is its GLSL
570 // type name.
571 uint32_t element_byte_size = 0;
572 std::string user_type_name;
573};
574
575enum class primitive_topology : uint8_t
576{
577 points, lines, line_strip, triangles, triangle_strip, triangle_fan,
578 patches, meshlets
579};
580
581enum class index_format : uint8_t { none, uint16, uint32 };
582
583
584struct aabb
585{
586 float min[3]{};
587 float max[3]{};
588 bool operator==(const aabb&) const noexcept = default;
589
590 // Empty/uninitialised sentinel: an inverted box (min > max on every axis).
591 // Used as the default so renderers can detect "no bounds computed" and
592 // fall back to an infinite AABB (never-culled) instead of a degenerate
593 // point at origin.
594 constexpr bool empty() const noexcept
595 {
596 return min[0] > max[0] || min[1] > max[1] || min[2] > max[2];
597 }
598
599 constexpr void expand(float x, float y, float z) noexcept
600 {
601 if(empty())
602 {
603 min[0] = max[0] = x;
604 min[1] = max[1] = y;
605 min[2] = max[2] = z;
606 return;
607 }
608 if(x < min[0]) min[0] = x; else if(x > max[0]) max[0] = x;
609 if(y < min[1]) min[1] = y; else if(y > max[1]) max[1] = y;
610 if(z < min[2]) min[2] = z; else if(z > max[2]) max[2] = z;
611 }
612};
613
614inline aabb compute_aabb_from_positions(
615 const float* positions, std::size_t vertex_count) noexcept
616{
617 aabb out{};
618 // Mark empty: inverted box. expand() bootstraps on the first vertex.
619 out.min[0] = out.min[1] = out.min[2] = 1.f;
620 out.max[0] = out.max[1] = out.max[2] = -1.f;
621 if(!positions || vertex_count == 0)
622 return out;
623 for(std::size_t i = 0; i < vertex_count; ++i)
624 {
625 out.expand(positions[i * 3 + 0], positions[i * 3 + 1], positions[i * 3 + 2]);
626 }
627 return out;
628}
629
630struct material_component;
631using material_component_ptr = std::shared_ptr<const material_component>;
632
633struct skeleton_component;
634using skeleton_component_ptr = std::shared_ptr<const skeleton_component>;
635
636struct mesh_primitive
637{
638 ossia::small_vector<buffer_resource_ptr, 4> vertex_buffers;
639 buffer_resource_ptr index_buffer;
640 ossia::small_vector<vertex_attribute, 8> attributes;
641
642 primitive_topology topology{primitive_topology::triangles};
643 index_format index_type{index_format::none};
644
645 uint32_t vertex_count{};
646 uint32_t index_count{};
647 uint32_t first_vertex{};
648 uint32_t first_index{};
649 int32_t vertex_offset{};
650
651 material_component_ptr material;
652 aabb bounds{};
653 float line_width{1.0f};
654 gpu_slot_ref raw_slot;
655 uint64_t stable_id{};
656 ossia::small_vector<material_component_ptr, 0> material_variants;
657};
658
659struct morph_target
660{
661 buffer_resource_ptr position_deltas;
662 buffer_resource_ptr normal_deltas;
663 buffer_resource_ptr tangent_deltas;
664 float default_weight{0.0f};
665};
666
667using scene_property_map = ossia::flat_map<std::string, ossia::value>;
668
669struct mesh_component
670{
671 ossia::small_vector<mesh_primitive, 4> primitives;
672 ossia::small_vector<morph_target, 8> morph_targets;
673 ossia::small_vector<float, 8> morph_weights;
674
675 skeleton_component_ptr skin;
676 aabb bounds{};
677
678 uint8_t lod_level{0};
679 float lod_screen_coverage{0.0f};
680
681 scene_property_map properties;
682 geometry_spec legacy_geometry;
683
684 int64_t dirty_index{};
685};
686using mesh_component_ptr = std::shared_ptr<const mesh_component>;
687struct texture_source
688{
689 std::string file_path; // Filesystem path (empty = use embedded_data)
690 std::shared_ptr<const std::vector<uint8_t>> embedded_data; // Inline image bytes (glTF bufferView, FBX blob)
691 std::string mime_type; // "image/png", "image/jpeg", "image/ktx2", etc.
692
693 uint64_t content_hash{};
694};
695struct texture_sampler_config
696{
697 texture_address_mode wrap_s : 2 = texture_address_mode::REPEAT;
698 texture_address_mode wrap_t : 2 = texture_address_mode::REPEAT;
699 texture_filter mag_filter : 2 = texture_filter::LINEAR;
700 texture_filter min_filter : 2 = texture_filter::LINEAR;
701 texture_filter mipmap_mode : 2 = texture_filter::LINEAR;
702
703 constexpr uint32_t hash() const noexcept
704 {
705 return uint32_t(wrap_s)
706 | (uint32_t(wrap_t) << 2)
707 | (uint32_t(mag_filter) << 4)
708 | (uint32_t(min_filter) << 6)
709 | (uint32_t(mipmap_mode) << 8);
710 }
711 bool operator==(const texture_sampler_config&) const noexcept = default;
712};
713struct texture_ref
714{
715 gpu_texture_handle texture{}; // GPU-resident handle (filled by renderer after upload)
716 uint32_t texcoord_set{0};
717
718 std::shared_ptr<const texture_source> source;
719
720 struct transform
721 {
722 float offset[2]{0.0f, 0.0f};
723 float scale[2]{1.0f, 1.0f};
724 float rotation{0.0f};
725 } uv_transform{};
726 texture_sampler_config sampler{};
727
728 bool valid() const noexcept { return texture.valid() || source; }
729};
730enum class alpha_mode : uint8_t { opaque_, mask, blend };
731
732struct material_component
733{
734 float base_color_factor[4]{1.0f, 1.0f, 1.0f, 1.0f};
735 texture_ref base_color_texture;
736
737 float metallic_factor{1.0f};
738 float roughness_factor{1.0f};
739 texture_ref metallic_roughness_texture;
740
741 texture_ref normal_texture;
742 float normal_scale{1.0f};
743
744 texture_ref occlusion_texture;
745 float occlusion_strength{1.0f};
746
747 float emissive_factor[3]{0.0f, 0.0f, 0.0f};
748 texture_ref emissive_texture;
749 float emissive_strength{1.0f};
750
751 alpha_mode alpha{alpha_mode::opaque_};
752 float alpha_cutoff{0.5f};
753 bool double_sided{false};
754
755 struct { float factor{0.0f}; texture_ref texture; } transmission{};
756
757 struct {
758 float thickness_factor{0.0f};
759 texture_ref thickness_texture;
760 float attenuation_distance{std::numeric_limits<float>::infinity()};
761 float attenuation_color[3]{1.0f, 1.0f, 1.0f};
762 } volume{};
763
764 float ior{1.5f};
765
766 struct {
767 float factor{1.0f};
768 texture_ref texture;
769 float color_factor[3]{1.0f, 1.0f, 1.0f};
770 texture_ref color_texture;
771 } specular{};
772
773 struct {
774 float factor{0.0f};
775 texture_ref texture;
776 float roughness_factor{0.0f};
777 texture_ref roughness_texture;
778 texture_ref normal_texture;
779 } clearcoat{};
780
781 struct {
782 float color_factor[3]{0.0f, 0.0f, 0.0f};
783 texture_ref color_texture;
784 float roughness_factor{0.0f};
785 texture_ref roughness_texture;
786 } sheen{};
787
788 struct {
789 float factor{0.0f};
790 texture_ref texture;
791 float ior{1.3f};
792 float thickness_min{100.0f};
793 float thickness_max{400.0f};
794 texture_ref thickness_texture;
795 } iridescence{};
796
797 struct {
798 float strength{0.0f};
799 float rotation{0.0f};
800 texture_ref texture;
801 } anisotropy{};
802
803 struct {
804 float factor{0.0f};
805 float color_factor[3]{1.0f, 1.0f, 1.0f};
806 texture_ref texture;
807 texture_ref color_texture;
808 } diffuse_transmission{};
809
810 bool unlit{false};
811
812 bool shadow_caster{true}; // false → skipped by shadow-map passes.
813 bool shadow_receiver{true}; // false → lit as if no shadow hits it.
814 bool reflection_caster{true}; // false → skipped by planar / cubemap reflection passes.
815
816 std::string tag;
817
818 scene_property_map properties;
819
820 int64_t dirty_index{};
821 gpu_slot_ref raw_slot;
822 uint64_t stable_id{};
823};
824using material_component_ptr = std::shared_ptr<const material_component>;
825
826enum class light_type : uint8_t
827{
828 directional, point, spot,
829 rect_area, disk_area, sphere_area, cylinder_area,
830 dome
831};
832
833enum class light_decay : uint8_t
834{
835 none, // constant intensity regardless of distance
836 linear, // 1/d
837 quadratic, // 1/d² (physically correct)
838 cubic // 1/d³
839};
840struct light_component
841{
842 light_type type{light_type::point};
843 light_decay decay{light_decay::quadratic};
844
845 float color[3]{1.0f, 1.0f, 1.0f};
846 float intensity{1.0f};
847 float range{0.0f}; // 0 = infinite (or renderer default)
848
849 float inner_cone_angle{0.0f};
850 float outer_cone_angle{0.7853981f}; // pi/4
851
852 // Area light dimensions.
853 float width{1.0f};
854 float height{1.0f};
855 float radius{0.5f};
856
857 buffer_resource_ptr ies_profile;
858 std::shared_ptr<const texture_source> environment_texture;
859
860 struct {
861 bool enabled{true};
862 float bias{0.001f};
863 float normal_bias{0.01f};
864 uint32_t map_resolution{1024};
865 float near_plane{0.1f};
866 float far_plane{100.0f};
867 } shadow{};
868
869 int64_t dirty_index{};
870 gpu_slot_ref raw_slot;
871 uint64_t stable_id{};
872};
873using light_component_ptr = std::shared_ptr<const light_component>;
874
875enum class camera_projection : uint8_t { perspective, orthographic, fulldome };
876
877struct camera_component
878{
879 camera_projection projection{camera_projection::perspective};
880
881 float yfov{0.7853981f}; // 45° in radians
882 float aspect_ratio{1.0f};
883
884 float xmag{1.0f};
885 float ymag{1.0f};
886
887 float znear{0.1f};
888 float zfar{1000.0f};
889
890 struct {
891 float focal_length{50.0f}; // mm
892 float focus_distance{10.0f}; // m
893 float fstop{5.6f};
894 float horizontal_aperture{36.0f}; // mm
895 float vertical_aperture{24.0f}; // mm
896 } physical{};
897
898 bool enable_dof{false};
899
900 int64_t dirty_index{};
901 gpu_slot_ref raw_slot;
902 uint64_t stable_id{};
903};
904using camera_component_ptr = std::shared_ptr<const camera_component>;
905
906struct OSSIA_EXPORT scene_node_id
907{
908 uint64_t value{};
909
910 bool operator==(const scene_node_id&) const noexcept = default;
911 bool operator<(const scene_node_id& rhs) const noexcept { return value < rhs.value; }
912
913 static scene_node_id from_path(std::string_view path) noexcept;
914 static scene_node_id from_parent(scene_node_id parent, std::string_view name) noexcept;
915};
916
917struct skeleton_joint
918{
919 std::string name; // Bone name from source file (for retargeting)
920 int32_t parent_index{-1};
921 float inverse_bind_matrix[16]{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
922
923 float translation[3]{0, 0, 0};
924 float rotation[4]{0, 0, 0, 1}; // quaternion (x, y, z, w)
925 float scale[3]{1, 1, 1};
926};
927
928enum class skinning_method : uint8_t
929{
930 linear_blend, // Standard LBS
931 dual_quaternion // DQ skinning (avoids candy-wrapper artifacts)
932};
933
934struct skeleton_component
935{
936 ossia::small_vector<skeleton_joint, 64> joints;
937 buffer_resource_ptr joint_matrices_buffer;
938
939 ossia::small_vector<scene_node_id, 64> joint_node_ids;
940
941 skinning_method method{skinning_method::linear_blend};
942
943 int64_t dirty_index{};
944
945 int32_t find_joint(std::string_view name) const noexcept
946 {
947 for(int32_t i = 0; i < (int32_t)joints.size(); i++)
948 if(joints[i].name == name)
949 return i;
950 return -1;
951 }
952};
953using skeleton_component_ptr = std::shared_ptr<const skeleton_component>;
954
955enum class animation_interpolation : uint8_t { step, linear, cubic_spline };
956enum class animation_target : uint8_t { translation, rotation, scale, weights, custom };
957
958struct animation_channel
959{
960 uint64_t target_node_id{};
961 animation_target target_path{};
962 std::string custom_path;
963
964 animation_interpolation interpolation{animation_interpolation::linear};
965
966 std::shared_ptr<const std::vector<float>> times;
967 std::shared_ptr<const std::vector<float>> values;
968};
969
970struct animation_component
971{
972 ossia::small_vector<animation_channel, 16> channels;
973
974 float duration{0.0f};
975 bool loop{false};
976
977 int64_t dirty_index{};
978};
979using animation_component_ptr = std::shared_ptr<const animation_component>;
980
981
982struct gaussian_splat_component
983{
984 buffer_resource_ptr positions;
985 buffer_resource_ptr covariances;
986 buffer_resource_ptr sh_coefficients;
987 buffer_resource_ptr opacities;
988
989 uint32_t splat_count{};
990 uint8_t sh_degree{3};
991
992 struct {
993 buffer_resource_ptr cluster_bounds;
994 buffer_resource_ptr cluster_indices;
995 uint32_t cluster_count{};
996 float lod_bias{0.0f};
997 } clustering{};
998
999 float splat_scale{1.0f};
1000 bool use_sh{true};
1001
1002 int64_t dirty_index{};
1003};
1004using gaussian_splat_component_ptr = std::shared_ptr<const gaussian_splat_component>;
1005
1006
1007struct voxel_field_component
1008{
1009 buffer_resource_ptr morton_codes;
1010 buffer_resource_ptr densities;
1011 buffer_resource_ptr colors;
1012 buffer_resource_ptr emissions;
1013
1014 uint32_t voxel_count{};
1015 uint32_t resolution[3]{256, 256, 256};
1016
1017 float bounds_min[3]{};
1018 float bounds_max[3]{};
1019
1020 float density_scale{1.0f};
1021 float step_size{0.01f};
1022
1023 int64_t dirty_index{};
1024};
1025using voxel_field_component_ptr = std::shared_ptr<const voxel_field_component>;
1026
1027// Generic, format-agnostic point-cloud / splat container.
1028// Carries N primitives whose schema lives entirely in the consumer
1029// shader — no fixed fields like positions / colors / SH coefficients
1030// live here. The parser hands us the raw row data verbatim; the
1031// consumer shader tells the GPU how to read it.
1032struct primitive_cloud_component
1033{
1034 // Primary raw payload: the verbatim per-row data (PLY header stripped)
1035 // for a .ply, or the post-decode bytes for binary formats.
1036 buffer_resource_ptr raw_data;
1037
1038 // Stride in bytes between consecutive primitives in raw_data. 0 means
1039 // raw_data isn't a flat row buffer (e.g. format with separate indexes).
1040 uint32_t row_stride{};
1041
1042 // Optional named extra buffers for formats that need more than one
1043 // CPU-side input array (e.g. quantized SH codebook + per-primitive
1044 // indices).
1045 struct named_buffer
1046 {
1047 std::string name;
1048 buffer_resource_ptr data;
1049 };
1050 ossia::small_vector<named_buffer, 2> extra_buffers;
1051
1052 uint64_t primitive_count{};
1053 primitive_topology topology{primitive_topology::points};
1054
1055 // Format identity. Empty = unrouted. When set (e.g. "3dgs.classic"),
1056 // clouds with matching format_id can be batched together.
1057 std::string format_id;
1058 scene_property_map format_params;
1059
1060 // GLSL type name for the per-primitive row payload. When non-empty
1061 // (e.g. "Splat3DGS"), raw_data is exposed as a per-vertex attribute
1062 // of format=user_struct with this type name; when empty, it is
1063 // exposed as a raw auxiliary block.
1064 std::string struct_type_name;
1065
1066 aabb bounds{};
1067
1068 // Producer-stamped identifier, stable across the producer's lifetime
1069 // so delta upload tracking can identify the same cloud frame-to-frame
1070 // even if raw_data is rebuilt.
1071 uint64_t stable_id{};
1072
1073 int64_t dirty_index{};
1074 gpu_slot_ref raw_slot;
1075};
1076using primitive_cloud_component_ptr = std::shared_ptr<const primitive_cloud_component>;
1077
1078struct point_cloud_component
1079{
1080 buffer_resource_ptr positions;
1081 buffer_resource_ptr colors;
1082 buffer_resource_ptr normals;
1083 buffer_resource_ptr intensities;
1084
1085 uint64_t point_count{};
1086
1087 struct {
1088 buffer_resource_ptr hilbert_keys;
1089 buffer_resource_ptr range_bounds;
1090 uint32_t range_count{};
1091 } spatial_index{};
1092
1093 float point_size{1.0f};
1094 enum class size_mode : uint8_t { fixed, adaptive } point_size_mode{};
1095 bool enable_edl{true};
1096
1097 int64_t dirty_index{};
1098};
1099using point_cloud_component_ptr = std::shared_ptr<const point_cloud_component>;
1100
1101struct volume_channel
1102{
1103 buffer_resource_ptr data;
1104 enum class type : uint8_t { scalar_float, vector_float, scalar_half } data_type{};
1105};
1106
1107struct volume_component
1108{
1109 ossia::small_vector<volume_channel, 4> channels;
1110 buffer_resource_ptr active_voxel_indices;
1111
1112 uint32_t resolution[3]{};
1113 float voxel_size{0.1f};
1114 float bounds_min[3]{};
1115 float bounds_max[3]{};
1116
1117 int64_t dirty_index{};
1118};
1119using volume_component_ptr = std::shared_ptr<const volume_component>;
1120
1121struct instance_component
1122{
1123 mesh_component_ptr prototype; // Mesh to draw for each instance
1124
1125 // Per-instance data buffers, interpreted according to transform_type.
1126 buffer_resource_ptr instance_transforms; // Per-instance transforms
1127 buffer_resource_ptr instance_colors; // Optional per-instance color (vec4)
1128 buffer_resource_ptr instance_custom; // Optional per-instance application data
1129
1130 uint32_t instance_count{};
1131
1132 enum class transform_format : uint8_t
1133 {
1134 mat4, // 16 floats per instance (full 4x4 matrix)
1135 trs, // 10 floats: vec3 translation + vec4 rotation(quat) + vec3 scale
1136 translation // 3 floats: vec3 position only
1137 } transform_type{transform_format::mat4};
1138
1139 int64_t dirty_index{};
1140
1141 gpu_slot_ref raw_slot;
1142};
1143using instance_component_ptr = std::shared_ptr<const instance_component>;
1144struct scene_data
1145{
1146 std::string name; // Shader-visible name for AUXILIARY matching.
1147 buffer_resource_ptr data; // CPU data or GPU handle.
1148 int64_t dirty_index{};
1149};
1150using scene_data_ptr = std::shared_ptr<const scene_data>;
1151
1152struct scene_node;
1153using scene_node_ptr = std::shared_ptr<const scene_node>;
1154
1155using scene_payload = ossia::variant<
1156 scene_node_ptr, // Complete subtree
1157 mesh_component_ptr, // Mesh
1158 material_component_ptr, // Standalone material
1159 light_component_ptr, // Light
1160 camera_component_ptr, // Camera
1161 skeleton_component_ptr, // Skeleton
1162 animation_component_ptr, // Animation clip
1163 instance_component_ptr, // GPU-instanced mesh
1164 scene_data_ptr, // Generic named scene-level buffer
1165 gaussian_splat_component_ptr,
1166 voxel_field_component_ptr,
1167 point_cloud_component_ptr,
1168 volume_component_ptr,
1169 primitive_cloud_component_ptr, // Format-agnostic point-cloud / splat
1170 scene_transform // Just a transform
1171 >;
1172enum class scene_purpose : uint8_t
1173{
1174 default_ = 0, // Always visible regardless of purpose filter.
1175 render = 1, // Final-quality render; skipped by proxy-only passes.
1176 proxy = 2, // Viewport-quality fallback; skipped by render passes.
1177 guide = 3, // UI helpers / gizmos; usually hidden outside editors.
1178};
1179
1180struct scene_node
1181{
1182 scene_node_id id;
1183 std::string name; // Human-readable label (for UI / debugging)
1184 bool visible{true}; // Toggle rendering without removing from graph
1185 bool active{true}; // USD-style: false → prim + subtree skipped by
1186 scene_purpose purpose{scene_purpose::default_};
1187 std::shared_ptr<const std::vector<scene_payload>> children;
1188
1189 scene_property_map properties;
1190
1191 int64_t dirty_index{};
1192
1193 bool has_children() const noexcept { return children && !children->empty(); }
1194
1195 template <typename T>
1196 const T* get_component() const noexcept;
1197 template <typename T>
1198 bool is() const noexcept;
1199};
1200struct scene_environment
1201{
1202 std::shared_ptr<const texture_source> skybox_source;
1203
1204 gpu_texture_handle skybox_texture;
1205 gpu_texture_handle irradiance_map;
1206 gpu_texture_handle prefiltered_map;
1207 gpu_texture_handle brdf_lut;
1208
1209 uint32_t params_set{0};
1210 enum params_bits : uint32_t
1211 {
1212 params_exposure_gamma = 1u << 0,
1213 params_ambient = 1u << 1,
1214 params_fog = 1u << 2,
1215 params_render_target_size = 1u << 3,
1216 };
1217
1218 float exposure{1.0f};
1219 float gamma{2.2f};
1220
1221 struct {
1222 bool enabled{false};
1223 float color[3]{0.8f, 0.8f, 0.8f};
1224 float density{0.01f};
1225 float start{10.0f};
1226 float end{100.0f};
1227 enum class type : uint8_t { linear, exponential, exponential_squared } mode{};
1228 } fog{};
1229 float ambient_color[3]{0.03f, 0.03f, 0.03f};
1230 float ambient_intensity{1.0f};
1231 uint32_t render_target_size[2]{0, 0};
1232 gpu_slot_ref raw_slot;
1233};
1234struct shadow_cascades_info
1235{
1236 static constexpr uint32_t max_cascades = 8;
1237 float split_view_depths[max_cascades + 1]{};
1238 float light_view_proj[max_cascades][16]{};
1239 gpu_texture_handle shadow_map_array{};
1240 uint32_t cascade_count{0};
1241 float shadow_distance{100.f};
1242 float light_direction[3]{0.f, -1.f, 0.f};
1243};
1244
1245struct aux_inject_buffer
1246{
1247 std::string name;
1248 void* native_handle{}; // QRhiBuffer*
1249 int64_t byte_size{};
1250};
1251struct aux_inject_texture
1252{
1253 std::string name;
1254 void* native_handle{}; // QRhiTexture*
1255};
1256struct scene_collection
1257{
1258 std::string name;
1259 ossia::small_vector<std::string, 8> paths;
1260 ossia::small_vector<std::string, 4> tags; // optional, free-form
1261};
1262using scene_collection_ptr = std::shared_ptr<const scene_collection>;
1263
1264struct scene_state
1265{
1266 std::shared_ptr<const std::vector<scene_node_ptr>> roots;
1267 std::shared_ptr<const std::vector<material_component_ptr>> materials;
1268 std::shared_ptr<const std::vector<animation_component_ptr>> animations;
1269 std::shared_ptr<const std::vector<skeleton_component_ptr>> skeletons;
1270 std::shared_ptr<const std::vector<camera_component_ptr>> cameras;
1271
1272 std::shared_ptr<const std::vector<scene_collection_ptr>> collections;
1273
1274 scene_environment environment{};
1275 shadow_cascades_info shadow_cascades{};
1276
1277 ossia::small_vector<aux_inject_buffer, 4> inject_buffers;
1278 ossia::small_vector<aux_inject_texture, 4> inject_textures;
1279
1280 scene_node_id active_camera_id{};
1281 double time_seconds{0.0};
1282
1283 int32_t active_variant_index{-1};
1284 ossia::small_vector<std::string, 0> variant_names;
1285
1286 int64_t version{};
1287 int64_t dirty_index{};
1288
1289 struct {
1290 uint64_t total_nodes{};
1291 uint64_t total_triangles{};
1292 uint64_t total_vertices{};
1293 uint64_t total_lights{};
1294 uint64_t total_materials{};
1295 } statistics{};
1296
1297 bool empty() const noexcept { return !roots || roots->empty(); }
1298};
1299using scene_state_ptr = std::shared_ptr<const scene_state>;
1300
1301
1302struct scene_spec
1303{
1304 scene_state_ptr state;
1305
1306 struct scene_delta
1307 {
1308 ossia::small_vector<scene_node_id, 16> added_nodes;
1309 ossia::small_vector<scene_node_id, 16> removed_nodes;
1310 ossia::small_vector<scene_node_id, 16> modified_nodes;
1311 };
1312 std::shared_ptr<const scene_delta> delta;
1313
1314 explicit operator bool() const noexcept { return state != nullptr; }
1315 bool operator==(const scene_spec& rhs) const noexcept { return state == rhs.state; }
1316 bool operator<(const scene_spec& rhs) const noexcept { return state < rhs.state; }
1317};
1318
1319struct OSSIA_EXPORT scene_port
1320{
1321 static const constexpr int which = 4;
1322
1323 enum dirt_flags : uint8_t
1324 {
1325 dirty_transform = 0x01,
1326 dirty_geometry = 0x02,
1327 dirty_materials = 0x04,
1328 dirty_lights = 0x08,
1329 dirty_animation = 0x10,
1330 dirty_environment = 0x20,
1331 dirty_structure = 0x40,
1332 dirty_all = 0xFF
1333 };
1334
1335 void clear();
1336
1337 scene_spec scene;
1338 scene_transform transform{};
1339 uint8_t flags{};
1340};
1341
1342struct scene_delay_line
1343{
1344 std::vector<scene_spec> scenes;
1345 size_t max_frames{60};
1346 size_t write_index{0};
1347};
1348OSSIA_EXPORT scene_spec wrap_geometry_as_scene(const geometry_spec& geom);
1349OSSIA_EXPORT geometry_spec extract_first_geometry(const scene_spec& scene);
1350OSSIA_EXPORT scene_spec merge_scenes(std::span<const scene_spec> scenes);
1351// clang-format on
1352}
Definition git_info.h:7
OSSIA_INLINE constexpr auto min(const T a, const U b) noexcept -> typename std::conditional<(sizeof(T) > sizeof(U)), T, U >::type
min function tailored for values
Definition math.hpp:125
OSSIA_INLINE constexpr auto max(const T a, const U b) noexcept -> typename std::conditional<(sizeof(T) > sizeof(U)), T, U >::type
max function tailored for values
Definition math.hpp:96
std::vector< std::string > tags
Tags applied to a node: {"model", "interesting", ...}.
Definition node_attributes.hpp:71
Handle to a GPU buffer (vertex, index, uniform, storage)
Definition geometry_port.hpp:480
Handle to a GPU texture with sampler state.
Definition geometry_port.hpp:491