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{1e38f};
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 float normal_scale{1.0f};
780 } clearcoat{};
781
782 struct {
783 float color_factor[3]{0.0f, 0.0f, 0.0f};
784 texture_ref color_texture;
785 float roughness_factor{0.0f};
786 texture_ref roughness_texture;
787 } sheen{};
788
789 struct {
790 float factor{0.0f};
791 texture_ref texture;
792 float ior{1.3f};
793 float thickness_min{100.0f};
794 float thickness_max{400.0f};
795 texture_ref thickness_texture;
796 } iridescence{};
797
798 struct {
799 float strength{0.0f};
800 float rotation{0.0f};
801 texture_ref texture;
802 } anisotropy{};
803
804 struct {
805 float factor{0.0f};
806 float color_factor[3]{1.0f, 1.0f, 1.0f};
807 texture_ref texture;
808 texture_ref color_texture;
809 } diffuse_transmission{};
810
811 bool unlit{false};
812
813 bool shadow_caster{true}; // false → skipped by shadow-map passes.
814 bool shadow_receiver{true}; // false → lit as if no shadow hits it.
815 bool reflection_caster{true}; // false → skipped by planar / cubemap reflection passes.
816
817 std::string tag;
818
819 scene_property_map properties;
820
821 int64_t dirty_index{};
822 gpu_slot_ref raw_slot;
823 uint64_t stable_id{};
824};
825using material_component_ptr = std::shared_ptr<const material_component>;
826
827enum class light_type : uint8_t
828{
829 directional, point, spot,
830 rect_area, disk_area, sphere_area, cylinder_area,
831 dome
832};
833
834enum class light_decay : uint8_t
835{
836 none, // constant intensity regardless of distance
837 linear, // 1/d
838 quadratic, // 1/d² (physically correct)
839 cubic // 1/d³
840};
841struct light_component
842{
843 light_type type{light_type::point};
844 light_decay decay{light_decay::quadratic};
845
846 float color[3]{1.0f, 1.0f, 1.0f};
847 float intensity{1.0f};
848 float range{0.0f}; // 0 = infinite (or renderer default)
849
850 float inner_cone_angle{0.0f};
851 float outer_cone_angle{0.7853981f}; // pi/4
852
853 // Area light dimensions.
854 float width{1.0f};
855 float height{1.0f};
856 float radius{0.5f};
857
858 buffer_resource_ptr ies_profile;
859 std::shared_ptr<const texture_source> environment_texture;
860
861 struct {
862 bool enabled{true};
863 float bias{0.001f};
864 float normal_bias{0.01f};
865 uint32_t map_resolution{1024};
866 float near_plane{0.1f};
867 float far_plane{100.0f};
868 } shadow{};
869
870 int64_t dirty_index{};
871 gpu_slot_ref raw_slot;
872 uint64_t stable_id{};
873};
874using light_component_ptr = std::shared_ptr<const light_component>;
875
876enum class camera_projection : uint8_t { perspective, orthographic, fulldome };
877
878struct camera_component
879{
880 camera_projection projection{camera_projection::perspective};
881
882 float yfov{0.7853981f}; // 45° in radians
883 float aspect_ratio{1.0f};
884 // false: the projection follows the aspect of the target it renders into.
885 bool aspect_ratio_set{false};
886
887 float xmag{1.0f};
888 float ymag{1.0f};
889
890 float znear{0.1f};
891 float zfar{1000.0f};
892
893 struct {
894 float focal_length{50.0f}; // mm
895 float focus_distance{10.0f}; // m
896 float fstop{5.6f};
897 float horizontal_aperture{36.0f}; // mm
898 float vertical_aperture{24.0f}; // mm
899 } physical{};
900
901 bool enable_dof{false};
902
903 int64_t dirty_index{};
904 gpu_slot_ref raw_slot;
905 uint64_t stable_id{};
906};
907using camera_component_ptr = std::shared_ptr<const camera_component>;
908
909struct OSSIA_EXPORT scene_node_id
910{
911 uint64_t value{};
912
913 bool operator==(const scene_node_id&) const noexcept = default;
914 bool operator<(const scene_node_id& rhs) const noexcept { return value < rhs.value; }
915
916 static scene_node_id from_path(std::string_view path) noexcept;
917 static scene_node_id from_parent(scene_node_id parent, std::string_view name) noexcept;
918};
919
920struct skeleton_joint
921{
922 std::string name; // Bone name from source file (for retargeting)
923 int32_t parent_index{-1};
924 float inverse_bind_matrix[16]{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
925
926 float translation[3]{0, 0, 0};
927 float rotation[4]{0, 0, 0, 1}; // quaternion (x, y, z, w)
928 float scale[3]{1, 1, 1};
929};
930
931enum class skinning_method : uint8_t
932{
933 linear_blend, // Standard LBS
934 dual_quaternion // DQ skinning (avoids candy-wrapper artifacts)
935};
936
937struct skeleton_component
938{
939 ossia::small_vector<skeleton_joint, 64> joints;
940 buffer_resource_ptr joint_matrices_buffer;
941
942 ossia::small_vector<scene_node_id, 64> joint_node_ids;
943
944 skinning_method method{skinning_method::linear_blend};
945
946 int64_t dirty_index{};
947
948 int32_t find_joint(std::string_view name) const noexcept
949 {
950 for(int32_t i = 0; i < (int32_t)joints.size(); i++)
951 if(joints[i].name == name)
952 return i;
953 return -1;
954 }
955};
956using skeleton_component_ptr = std::shared_ptr<const skeleton_component>;
957
958enum class animation_interpolation : uint8_t { step, linear, cubic_spline };
959enum class animation_target : uint8_t { translation, rotation, scale, weights, custom };
960
961struct animation_channel
962{
963 uint64_t target_node_id{};
964 animation_target target_path{};
965 std::string custom_path;
966
967 animation_interpolation interpolation{animation_interpolation::linear};
968
969 std::shared_ptr<const std::vector<float>> times;
970 std::shared_ptr<const std::vector<float>> values;
971};
972
973struct animation_component
974{
975 ossia::small_vector<animation_channel, 16> channels;
976
977 float duration{0.0f};
978 bool loop{false};
979
980 int64_t dirty_index{};
981};
982using animation_component_ptr = std::shared_ptr<const animation_component>;
983
984
985struct gaussian_splat_component
986{
987 buffer_resource_ptr positions;
988 buffer_resource_ptr covariances;
989 buffer_resource_ptr sh_coefficients;
990 buffer_resource_ptr opacities;
991
992 uint32_t splat_count{};
993 uint8_t sh_degree{3};
994
995 struct {
996 buffer_resource_ptr cluster_bounds;
997 buffer_resource_ptr cluster_indices;
998 uint32_t cluster_count{};
999 float lod_bias{0.0f};
1000 } clustering{};
1001
1002 float splat_scale{1.0f};
1003 bool use_sh{true};
1004
1005 int64_t dirty_index{};
1006};
1007using gaussian_splat_component_ptr = std::shared_ptr<const gaussian_splat_component>;
1008
1009
1010struct voxel_field_component
1011{
1012 buffer_resource_ptr morton_codes;
1013 buffer_resource_ptr densities;
1014 buffer_resource_ptr colors;
1015 buffer_resource_ptr emissions;
1016
1017 uint32_t voxel_count{};
1018 uint32_t resolution[3]{256, 256, 256};
1019
1020 float bounds_min[3]{};
1021 float bounds_max[3]{};
1022
1023 float density_scale{1.0f};
1024 float step_size{0.01f};
1025
1026 int64_t dirty_index{};
1027};
1028using voxel_field_component_ptr = std::shared_ptr<const voxel_field_component>;
1029
1030// Generic, format-agnostic point-cloud / splat container.
1031// Carries N primitives whose schema lives entirely in the consumer
1032// shader — no fixed fields like positions / colors / SH coefficients
1033// live here. The parser hands us the raw row data verbatim; the
1034// consumer shader tells the GPU how to read it.
1035struct primitive_cloud_component
1036{
1037 // Primary raw payload: the verbatim per-row data (PLY header stripped)
1038 // for a .ply, or the post-decode bytes for binary formats.
1039 buffer_resource_ptr raw_data;
1040
1041 // Stride in bytes between consecutive primitives in raw_data. 0 means
1042 // raw_data isn't a flat row buffer (e.g. format with separate indexes).
1043 uint32_t row_stride{};
1044
1045 // Optional named extra buffers for formats that need more than one
1046 // CPU-side input array (e.g. quantized SH codebook + per-primitive
1047 // indices).
1048 struct named_buffer
1049 {
1050 std::string name;
1051 buffer_resource_ptr data;
1052 };
1053 ossia::small_vector<named_buffer, 2> extra_buffers;
1054
1055 uint64_t primitive_count{};
1056 primitive_topology topology{primitive_topology::points};
1057
1058 // Format identity. Empty = unrouted. When set (e.g. "3dgs.classic"),
1059 // clouds with matching format_id can be batched together.
1060 std::string format_id;
1061 scene_property_map format_params;
1062
1063 // GLSL type name for the per-primitive row payload. When non-empty
1064 // (e.g. "Splat3DGS"), raw_data is exposed as a per-vertex attribute
1065 // of format=user_struct with this type name; when empty, it is
1066 // exposed as a raw auxiliary block.
1067 std::string struct_type_name;
1068
1069 aabb bounds{};
1070
1071 // Producer-stamped identifier, stable across the producer's lifetime
1072 // so delta upload tracking can identify the same cloud frame-to-frame
1073 // even if raw_data is rebuilt.
1074 uint64_t stable_id{};
1075
1076 int64_t dirty_index{};
1077 gpu_slot_ref raw_slot;
1078};
1079using primitive_cloud_component_ptr = std::shared_ptr<const primitive_cloud_component>;
1080
1081struct point_cloud_component
1082{
1083 buffer_resource_ptr positions;
1084 buffer_resource_ptr colors;
1085 buffer_resource_ptr normals;
1086 buffer_resource_ptr intensities;
1087
1088 uint64_t point_count{};
1089
1090 struct {
1091 buffer_resource_ptr hilbert_keys;
1092 buffer_resource_ptr range_bounds;
1093 uint32_t range_count{};
1094 } spatial_index{};
1095
1096 float point_size{1.0f};
1097 enum class size_mode : uint8_t { fixed, adaptive } point_size_mode{};
1098 bool enable_edl{true};
1099
1100 int64_t dirty_index{};
1101};
1102using point_cloud_component_ptr = std::shared_ptr<const point_cloud_component>;
1103
1104struct volume_channel
1105{
1106 buffer_resource_ptr data;
1107 enum class type : uint8_t { scalar_float, vector_float, scalar_half } data_type{};
1108};
1109
1110struct volume_component
1111{
1112 ossia::small_vector<volume_channel, 4> channels;
1113 buffer_resource_ptr active_voxel_indices;
1114
1115 uint32_t resolution[3]{};
1116 float voxel_size{0.1f};
1117 float bounds_min[3]{};
1118 float bounds_max[3]{};
1119
1120 int64_t dirty_index{};
1121};
1122using volume_component_ptr = std::shared_ptr<const volume_component>;
1123
1124struct instance_component
1125{
1126 mesh_component_ptr prototype; // Mesh to draw for each instance
1127
1128 // Per-instance data buffers, interpreted according to transform_type.
1129 buffer_resource_ptr instance_transforms; // Per-instance transforms
1130 buffer_resource_ptr instance_colors; // Optional per-instance color (vec4)
1131 buffer_resource_ptr instance_custom; // Optional per-instance application data
1132
1133 uint32_t instance_count{};
1134
1135 enum class transform_format : uint8_t
1136 {
1137 mat4, // 16 floats per instance (full 4x4 matrix)
1138 trs, // 10 floats: vec3 translation + vec4 rotation(quat) + vec3 scale
1139 translation // 3 floats: vec3 position only
1140 } transform_type{transform_format::mat4};
1141
1142 int64_t dirty_index{};
1143
1144 gpu_slot_ref raw_slot;
1145};
1146using instance_component_ptr = std::shared_ptr<const instance_component>;
1147struct scene_data
1148{
1149 std::string name; // Shader-visible name for AUXILIARY matching.
1150 buffer_resource_ptr data; // CPU data or GPU handle.
1151 int64_t dirty_index{};
1152};
1153using scene_data_ptr = std::shared_ptr<const scene_data>;
1154
1155struct scene_node;
1156using scene_node_ptr = std::shared_ptr<const scene_node>;
1157
1158using scene_payload = ossia::variant<
1159 scene_node_ptr, // Complete subtree
1160 mesh_component_ptr, // Mesh
1161 material_component_ptr, // Standalone material
1162 light_component_ptr, // Light
1163 camera_component_ptr, // Camera
1164 skeleton_component_ptr, // Skeleton
1165 animation_component_ptr, // Animation clip
1166 instance_component_ptr, // GPU-instanced mesh
1167 scene_data_ptr, // Generic named scene-level buffer
1168 gaussian_splat_component_ptr,
1169 voxel_field_component_ptr,
1170 point_cloud_component_ptr,
1171 volume_component_ptr,
1172 primitive_cloud_component_ptr, // Format-agnostic point-cloud / splat
1173 scene_transform // Just a transform
1174 >;
1175enum class scene_purpose : uint8_t
1176{
1177 default_ = 0, // Always visible regardless of purpose filter.
1178 render = 1, // Final-quality render; skipped by proxy-only passes.
1179 proxy = 2, // Viewport-quality fallback; skipped by render passes.
1180 guide = 3, // UI helpers / gizmos; usually hidden outside editors.
1181};
1182
1183struct scene_node
1184{
1185 scene_node_id id;
1186 std::string name; // Human-readable label (for UI / debugging)
1187 bool visible{true}; // Toggle rendering without removing from graph
1188 bool active{true}; // USD-style: false → prim + subtree skipped by
1189 scene_purpose purpose{scene_purpose::default_};
1190 std::shared_ptr<const std::vector<scene_payload>> children;
1191
1192 scene_property_map properties;
1193
1194 int64_t dirty_index{};
1195
1196 bool has_children() const noexcept { return children && !children->empty(); }
1197
1198 template <typename T>
1199 const T* get_component() const noexcept;
1200 template <typename T>
1201 bool is() const noexcept;
1202};
1203struct scene_environment
1204{
1205 std::shared_ptr<const texture_source> skybox_source;
1206
1207 gpu_texture_handle skybox_texture;
1208 gpu_texture_handle irradiance_map;
1209 gpu_texture_handle prefiltered_map;
1210 gpu_texture_handle brdf_lut;
1211
1212 uint32_t params_set{0};
1213 enum params_bits : uint32_t
1214 {
1215 params_exposure_gamma = 1u << 0,
1216 params_ambient = 1u << 1,
1217 params_fog = 1u << 2,
1218 params_render_target_size = 1u << 3,
1219 };
1220
1221 float exposure{1.0f};
1222 float gamma{2.2f};
1223
1224 struct {
1225 bool enabled{false};
1226 float color[3]{0.8f, 0.8f, 0.8f};
1227 float density{0.01f};
1228 float start{10.0f};
1229 float end{100.0f};
1230 enum class type : uint8_t { linear, exponential, exponential_squared } mode{};
1231 } fog{};
1232 float ambient_color[3]{0.f, 0.f, 0.f};
1233 float ambient_intensity{1.0f};
1234 uint32_t render_target_size[2]{0, 0};
1235 gpu_slot_ref raw_slot;
1236};
1237struct shadow_cascades_info
1238{
1239 static constexpr uint32_t max_cascades = 8;
1240 float split_view_depths[max_cascades + 1]{};
1241 float light_view_proj[max_cascades][16]{};
1242 gpu_texture_handle shadow_map_array{};
1243 uint32_t cascade_count{0};
1244 float shadow_distance{100.f};
1245 float light_direction[3]{0.f, -1.f, 0.f};
1246 // RawLight slot of the directional light the cascades belong to; size 0
1247 // when no such light was found.
1248 gpu_slot_ref light_slot{};
1249};
1250
1251struct aux_inject_buffer
1252{
1253 std::string name;
1254 void* native_handle{}; // QRhiBuffer*
1255 int64_t byte_size{};
1256};
1257struct aux_inject_texture
1258{
1259 std::string name;
1260 void* native_handle{}; // QRhiTexture*
1261};
1262struct scene_collection
1263{
1264 std::string name;
1265 ossia::small_vector<std::string, 8> paths;
1266 ossia::small_vector<std::string, 4> tags; // optional, free-form
1267};
1268using scene_collection_ptr = std::shared_ptr<const scene_collection>;
1269
1270struct scene_state
1271{
1272 std::shared_ptr<const std::vector<scene_node_ptr>> roots;
1273 std::shared_ptr<const std::vector<material_component_ptr>> materials;
1274 std::shared_ptr<const std::vector<animation_component_ptr>> animations;
1275 std::shared_ptr<const std::vector<skeleton_component_ptr>> skeletons;
1276 std::shared_ptr<const std::vector<camera_component_ptr>> cameras;
1277
1278 std::shared_ptr<const std::vector<scene_collection_ptr>> collections;
1279
1280 scene_environment environment{};
1281 shadow_cascades_info shadow_cascades{};
1282
1283 ossia::small_vector<aux_inject_buffer, 4> inject_buffers;
1284 ossia::small_vector<aux_inject_texture, 4> inject_textures;
1285
1286 scene_node_id active_camera_id{};
1287 double time_seconds{0.0};
1288
1289 int32_t active_variant_index{-1};
1290 ossia::small_vector<std::string, 0> variant_names;
1291
1292 int64_t version{};
1293 int64_t dirty_index{};
1294
1295 struct {
1296 uint64_t total_nodes{};
1297 uint64_t total_triangles{};
1298 uint64_t total_vertices{};
1299 uint64_t total_lights{};
1300 uint64_t total_materials{};
1301 } statistics{};
1302
1303 bool empty() const noexcept { return !roots || roots->empty(); }
1304};
1305using scene_state_ptr = std::shared_ptr<const scene_state>;
1306
1307
1308struct scene_spec
1309{
1310 scene_state_ptr state;
1311
1312 struct scene_delta
1313 {
1314 ossia::small_vector<scene_node_id, 16> added_nodes;
1315 ossia::small_vector<scene_node_id, 16> removed_nodes;
1316 ossia::small_vector<scene_node_id, 16> modified_nodes;
1317 };
1318 std::shared_ptr<const scene_delta> delta;
1319
1320 explicit operator bool() const noexcept { return state != nullptr; }
1321 bool operator==(const scene_spec& rhs) const noexcept { return state == rhs.state; }
1322 bool operator<(const scene_spec& rhs) const noexcept { return state < rhs.state; }
1323};
1324
1325struct OSSIA_EXPORT scene_port
1326{
1327 static const constexpr int which = 4;
1328
1329 enum dirt_flags : uint8_t
1330 {
1331 dirty_transform = 0x01,
1332 dirty_geometry = 0x02,
1333 dirty_materials = 0x04,
1334 dirty_lights = 0x08,
1335 dirty_animation = 0x10,
1336 dirty_environment = 0x20,
1337 dirty_structure = 0x40,
1338 dirty_all = 0xFF
1339 };
1340
1341 void clear();
1342
1343 scene_spec scene;
1344 scene_transform transform{};
1345 uint8_t flags{};
1346};
1347
1348struct scene_delay_line
1349{
1350 std::vector<scene_spec> scenes;
1351 size_t max_frames{60};
1352 size_t write_index{0};
1353};
1354OSSIA_EXPORT scene_spec wrap_geometry_as_scene(const geometry_spec& geom);
1355OSSIA_EXPORT geometry_spec extract_first_geometry(const scene_spec& scene);
1356OSSIA_EXPORT scene_spec merge_scenes(std::span<const scene_spec> scenes);
1357// clang-format on
1358}
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