1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#pragma once
#include "rust/cxx.h"
// Forward-declared structs that cxx generates from the bridge module
// have to be available here so the C++ Renderer signatures below
// reference them by name. cxx emits them in this header automatically
// (the file is included downstream of the rust-side bridge module).
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/vec4f.h>
#include <pxr/imaging/glf/simpleLight.h>
#include <pxr/imaging/glf/simpleMaterial.h>
#include <pxr/imaging/hd/sceneIndex.h>
#include <pxr/imaging/hgi/hgi.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usdImaging/usdImaging/stageSceneIndex.h>
#include <pxr/usdImaging/usdImagingGL/engine.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
namespace hydra_rs {
struct SceneIndex {
// Owns the UsdStageRefPtr that backs the scene index — the imaging
// delegate keeps a non-owning reference, so the stage must outlive it.
pxr::UsdStageRefPtr stage_owner;
pxr::HdSceneIndexBaseRefPtr scene_index;
rust::String stage_root() const;
size_t prim_count() const;
std::unique_ptr<std::vector<std::string>> prim_paths() const;
};
std::unique_ptr<SceneIndex> populate_from_path(rust::Str usd_path);
// Render delegate plugin IDs registered in this process.
std::unique_ptr<std::vector<std::string>> list_render_delegate_ids();
// Stateful renderer. Holds a stage, an HGI, and a UsdImagingGLEngine across
// render() calls so a viewport can re-render at interactive rates without
// rebuilding any of that scaffolding. Construction order in the struct is
// load-bearing: hgi MUST destruct after engine, because engine references it.
struct Renderer {
pxr::UsdStageRefPtr stage;
pxr::HgiUniquePtr hgi;
std::unique_ptr<pxr::UsdImagingGLEngine> engine;
pxr::GfMatrix4d view_matrix;
pxr::GfMatrix4d proj_matrix;
pxr::GlfSimpleLightVector explicit_lights;
pxr::GlfSimpleMaterial material;
pxr::GfVec4f scene_ambient = pxr::GfVec4f(0.05f, 0.05f, 0.05f, 1.0f);
pxr::GfVec4f clear_color = pxr::GfVec4f(0.1f, 0.1f, 0.15f, 1.0f);
bool use_default_lighting = true;
uint32_t width = 256;
uint32_t height = 256;
double frame = 0.0;
bool default_frame = true;
void set_size(uint32_t w, uint32_t h);
void set_camera_matrices(rust::Slice<const float> view,
rust::Slice<const float> projection);
void set_time(double time);
void use_default_time();
void clear_lights();
void use_default_light();
void add_distant_light(float dx, float dy, float dz,
float r, float g, float b, float intensity);
void add_positional_light(float px, float py, float pz,
float r, float g, float b, float intensity);
void set_clear_color(float r, float g, float b, float a);
// Scene ambient — a constant additive RGB applied to every shaded
// surface, regardless of light direction. Storm uses it as the
// ambient term inside `GlfSimpleLightingContext`. Default is the
// 0.05 grey set in the struct initializer; bump it (~0.15-0.25)
// when the consumer has no IBL and wants the shadow side of the
// model to read instead of going to black.
void set_scene_ambient(float r, float g, float b, float a);
// Image-based environment lighting — authors a `UsdLuxDomeLight`
// into the stage's session layer pointing at `hdri_path`. Storm
// renders it both as the dome background behind geometry and as
// an IBL contributor (importance-sampled). `intensity` and
// `exposure` map to the corresponding UsdLux attrs; `rotation_y`
// is applied as an `xformOp:rotateY` in degrees (positive =
// counter-clockwise looking down -Y, matching the UsdGeomXformOp
// sign convention).
//
// Idempotent: replaces the existing dome's attributes on each
// call rather than spawning a new prim, so calling it every frame
// is cheap. Session-layer authoring means the underlying USD file
// on disk is never modified. Enables scene lights in the render
// params automatically — explicit `add_distant_light` lights stay
// active and stack with the dome.
void set_dome_light(rust::Str hdri_path,
float intensity,
float exposure,
float rotation_y_degrees);
// Tear down any dome authored by `set_dome_light` and turn off
// scene-lights routing. Explicit GlfSimpleLights are unaffected.
void clear_dome_light();
// Author a `UsdPreviewSurface` material into the stage's session
// layer and bind it to every `UsdGeomMesh` under the pseudo-root.
// Each non-empty asset path becomes a `UsdUVTexture` shader
// wired into the matching PBR input (`diffuseColor` /
// `roughness` / `metallic` / `normal`). The four texture nodes
// share one `UsdPrimvarReader_float2` reading the `st` primvar.
//
// Asset paths may contain the `<UDIM>` token; USD's resolver
// substitutes the per-tile id (1001, 1002, …) at sample time, so
// a single material binding covers multi-tile UDIM layouts. Pass
// an empty string to skip a channel — the corresponding PBR
// input keeps its UsdPreviewSurface default.
//
// Idempotent: replaces the previous painted material rather than
// stacking, so the consumer can call it every time it re-exports
// its paint targets without leaking prim trees.
void set_painted_material(rust::Str base_color_asset_path,
rust::Str roughness_asset_path,
rust::Str metallic_asset_path,
rust::Str normal_asset_path);
// Unbind the painted material from every mesh that carries it
// and remove the material prim from the session layer. After
// this the stage's originally-authored material bindings (if
// any) drive shading again.
void clear_painted_material();
// Reference an external material defined in another USD layer
// (the library file under `assets/materials/`) into the session
// layer at `/_hydraExternalMaterial`, and bind it to every mesh
// under the pseudo-root. `source_usd_path` is the on-disk USD
// file (or any URI the asset resolver can chase). `prim_path` is
// the SdfPath of the `UsdShade.Material` inside that file (e.g.
// `/Materials/Brick`). Empty `source_usd_path` is treated as
// `clear_external_material`.
//
// Idempotent. Overwrites the previous reference + re-binds to
// every mesh on each call, so re-pointing at a different
// material is one bridge call.
void set_external_material(rust::Str source_usd_path,
rust::Str prim_path);
// Drop the external material reference and unbind from every
// mesh. The stage's original material bindings (if any) take
// over again. Doesn't touch the painted-material binding —
// those are different prim paths.
void clear_external_material();
// Author analytic user lights into the stage's session layer
// from a packed flat-float payload. 16 floats per light — see
// the Rust-side `set_user_lights` doc for the field layout. Each
// light becomes either a `UsdLuxDistantLight` (type tag 0) or a
// `UsdLuxSphereLight` with `shaping:cone:*` (type tag 1) at
// `/_hydraLight<i>`. Throws on a payload whose length isn't a
// multiple of 16.
//
// Idempotent: prim attributes overwrite in place when the slot
// is already populated; previously-authored slots past the new
// count get removed. Triggers `enableSceneLights` next render.
void set_user_lights(rust::Slice<const float> data);
// Remove every previously-authored user-light prim. Dome light
// is untouched.
void clear_user_lights();
rust::String current_renderer() const;
bool set_renderer_plugin(rust::Str plugin_id) const;
// `UsdGeomImageable::purpose` filters: anything marked `default`
// always draws; the three optional purposes opt-in via these
// flags. Defaults match what consumers usually want for a
// "show me the asset" preview (render + proxy on, guides off).
// Pipeline assets that wrap their detail in a
// `Scope { purpose = "render" }` only show up when render is on.
void set_show_render(bool show);
void set_show_proxy(bool show);
void set_show_guides(bool show);
std::unique_ptr<std::vector<uint8_t>> render_color() const;
// True once the active render delegate considers the current
// frame fully resolved. Rasterising delegates (Storm) return
// true after a single `render_color` call; sampling delegates
// (hdNSI, Embree, Arnold, …) return true after their sample
// budget is hit. Headless one-shots that want a converged image
// call `render_color` in a loop until this flips on; interactive
// viewports ignore it and let the continuous-repaint loop drive
// progressive convergence on its own.
bool is_converged() const;
// Tracks whether `set_dome_light` was called since the last
// `clear_dome_light`. Drives `enableSceneLights` in render
// params so the UsdLux prim we authored is consumed by Storm.
bool dome_light_active = false;
// `UsdImagingGLRenderParams::show*` mirrors. Toggled per-frame
// by `set_show_render` / `_proxy` / `_guides`; consumed inside
// `render_color`.
bool show_render_purpose = true;
bool show_proxy_purpose = true;
bool show_guides_purpose = false;
// Number of `/_hydraLight<i>` prims currently authored by
// `set_user_lights`. Used to clean up slots past the new count
// on each re-author, and to drive `enableSceneLights` (the dome
// flag is OR'd with whether any user light is present).
uint32_t user_light_count = 0;
};
std::unique_ptr<Renderer> create_renderer(rust::Str usd_path,
rust::Str render_delegate_id);
// Convenience wrapper: spin up a renderer, render once, throw away.
std::unique_ptr<std::vector<uint8_t>> render_to_rgba(
rust::Str usd_path,
rust::Str render_delegate_id,
uint32_t width,
uint32_t height);
} // namespace hydra_rs