#include "hydra_bridge.h"
#include <pxr/base/gf/frustum.h>
#include <pxr/base/gf/half.h>
#include <pxr/base/gf/vec3d.h>
#include <pxr/base/gf/vec4d.h>
#include <pxr/base/vt/value.h>
#include <pxr/imaging/hd/aov.h>
#include <pxr/imaging/hd/driver.h>
#include <pxr/imaging/hd/renderBuffer.h>
#include <pxr/imaging/hd/rendererPluginRegistry.h>
#include <pxr/imaging/hd/types.h>
#include <pxr/imaging/hdx/tokens.h>
#include <pxr/imaging/hf/pluginDesc.h>
#include <pxr/imaging/hgi/tokens.h>
#include <pxr/usd/sdf/assetPath.h>
#include <pxr/usd/sdf/layer.h>
#include <pxr/usd/usd/editContext.h>
#include <pxr/usd/usd/primRange.h>
#include <pxr/usd/usd/timeCode.h>
#include <pxr/usd/usdGeom/mesh.h>
#include <pxr/usd/usdGeom/xformable.h>
#include <pxr/usd/usdGeom/xformCommonAPI.h>
#include <pxr/base/gf/rotation.h>
#include <pxr/usd/usdLux/distantLight.h>
#include <pxr/usd/usdLux/domeLight.h>
#include <pxr/usd/usdLux/shapingAPI.h>
#include <pxr/usd/usdLux/sphereLight.h>
#include <pxr/usd/usdShade/connectableAPI.h>
#include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/materialBindingAPI.h>
#include <pxr/usd/usdShade/shader.h>
#include <pxr/usdImaging/usdImagingGL/renderParams.h>
#include <algorithm>
#include <cstring>
#include <functional>
#include <stdexcept>
namespace hydra_rs {
std::unique_ptr<SceneIndex> populate_from_path(rust::Str usd_path) {
std::string p(usd_path.data(), usd_path.size());
pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(p);
if (!stage) {
throw std::runtime_error("UsdStage::Open returned null for: " + p);
}
auto si = pxr::UsdImagingStageSceneIndex::New();
si->SetStage(stage);
si->SetTime(pxr::UsdTimeCode::Default());
auto wrapped = std::make_unique<SceneIndex>();
wrapped->stage_owner = stage;
wrapped->scene_index = si;
return wrapped;
}
rust::String SceneIndex::stage_root() const {
if (!stage_owner) return rust::String();
auto root = stage_owner->GetRootLayer();
if (!root) return rust::String();
return rust::String(root->GetIdentifier());
}
size_t SceneIndex::prim_count() const {
if (!scene_index) return 0;
size_t count = 0;
std::function<void(const pxr::SdfPath&)> walk = [&](const pxr::SdfPath& p) {
++count;
for (const auto& child : scene_index->GetChildPrimPaths(p)) {
walk(child);
}
};
walk(pxr::SdfPath::AbsoluteRootPath());
return count;
}
std::unique_ptr<std::vector<std::string>> SceneIndex::prim_paths() const {
auto vec = std::make_unique<std::vector<std::string>>();
if (!scene_index) return vec;
std::function<void(const pxr::SdfPath&)> walk = [&](const pxr::SdfPath& p) {
vec->push_back(p.GetString());
for (const auto& child : scene_index->GetChildPrimPaths(p)) {
walk(child);
}
};
walk(pxr::SdfPath::AbsoluteRootPath());
return vec;
}
std::unique_ptr<std::vector<std::string>> list_render_delegate_ids() {
auto vec = std::make_unique<std::vector<std::string>>();
auto& reg = pxr::HdRendererPluginRegistry::GetInstance();
pxr::HfPluginDescVector descs;
reg.GetPluginDescs(&descs);
vec->reserve(descs.size());
for (const auto& d : descs) {
vec->push_back(d.id.GetString());
}
return vec;
}
namespace {
pxr::GfMatrix4d matrix_from_slice(rust::Slice<const float> s) {
if (s.size() != 16) {
throw std::runtime_error(
"matrix slice must have exactly 16 elements (row-major 4x4)");
}
double m[4][4];
for (size_t r = 0; r < 4; ++r) {
for (size_t c = 0; c < 4; ++c) {
m[r][c] = static_cast<double>(s[r * 4 + c]);
}
}
return pxr::GfMatrix4d(m);
}
void apply_camera_default(pxr::GfMatrix4d& view, pxr::GfMatrix4d& proj,
uint32_t w, uint32_t h) {
view.SetLookAt(
pxr::GfVec3d(5.0, 5.0, 5.0),
pxr::GfVec3d(0.0, 0.0, 0.0),
pxr::GfVec3d(0.0, 1.0, 0.0));
pxr::GfFrustum frustum;
const double aspect = static_cast<double>(w) / static_cast<double>(h);
frustum.SetPerspective(45.0, aspect, 0.1, 1000.0);
proj = frustum.ComputeProjectionMatrix();
}
}
std::unique_ptr<Renderer> create_renderer(rust::Str usd_path,
rust::Str render_delegate_id) {
auto r = std::make_unique<Renderer>();
std::string p(usd_path.data(), usd_path.size());
r->stage = pxr::UsdStage::Open(p);
if (!r->stage) {
throw std::runtime_error("UsdStage::Open returned null for: " + p);
}
r->hgi = pxr::Hgi::CreatePlatformDefaultHgi();
if (!r->hgi) {
throw std::runtime_error(
"Hgi::CreatePlatformDefaultHgi returned null — no usable GPU "
"backend. On macOS, expected HgiMetal; on Linux, HgiGL/Vulkan.");
}
pxr::HdDriver hd_driver;
hd_driver.name = pxr::HgiTokens->renderDriver;
hd_driver.driver = pxr::VtValue(r->hgi.get());
r->engine = std::make_unique<pxr::UsdImagingGLEngine>(hd_driver);
r->engine->SetEnablePresentation(false);
std::string delegate_str(render_delegate_id.data(), render_delegate_id.size());
if (!delegate_str.empty()) {
if (!r->engine->SetRendererPlugin(pxr::TfToken(delegate_str))) {
throw std::runtime_error("SetRendererPlugin failed: " + delegate_str);
}
r->engine->SetEnablePresentation(false);
}
apply_camera_default(r->view_matrix, r->proj_matrix, r->width, r->height);
r->material.SetAmbient(pxr::GfVec4f(0.2f, 0.2f, 0.2f, 1.0f));
r->material.SetDiffuse(pxr::GfVec4f(1.0f, 1.0f, 1.0f, 1.0f));
r->material.SetSpecular(pxr::GfVec4f(0.5f, 0.5f, 0.5f, 1.0f));
r->material.SetShininess(32.0f);
return r;
}
void Renderer::set_size(uint32_t w, uint32_t h) {
width = w;
height = h;
pxr::GfFrustum frustum;
const double aspect = static_cast<double>(w) / static_cast<double>(h);
frustum.SetPerspective(45.0, aspect, 0.1, 1000.0);
proj_matrix = frustum.ComputeProjectionMatrix();
}
void Renderer::set_camera_matrices(rust::Slice<const float> view,
rust::Slice<const float> projection) {
view_matrix = matrix_from_slice(view);
proj_matrix = matrix_from_slice(projection);
}
void Renderer::set_time(double time) {
frame = time;
default_frame = false;
}
void Renderer::use_default_time() {
default_frame = true;
}
void Renderer::clear_lights() {
explicit_lights.clear();
use_default_lighting = false;
}
void Renderer::use_default_light() {
explicit_lights.clear();
use_default_lighting = true;
}
void Renderer::add_distant_light(float dx, float dy, float dz,
float r, float g, float b, float intensity) {
pxr::GlfSimpleLight light;
light.SetPosition(pxr::GfVec4f(dx, dy, dz, 0.0f));
light.SetDiffuse(pxr::GfVec4f(r * intensity, g * intensity, b * intensity, 1.0f));
light.SetSpecular(pxr::GfVec4f(r * intensity, g * intensity, b * intensity, 1.0f));
light.SetAmbient(pxr::GfVec4f(0.0f, 0.0f, 0.0f, 1.0f));
explicit_lights.push_back(light);
use_default_lighting = false;
}
void Renderer::add_positional_light(float px, float py, float pz,
float r, float g, float b, float intensity) {
pxr::GlfSimpleLight light;
light.SetPosition(pxr::GfVec4f(px, py, pz, 1.0f)); light.SetDiffuse(pxr::GfVec4f(r * intensity, g * intensity, b * intensity, 1.0f));
light.SetSpecular(pxr::GfVec4f(r * intensity, g * intensity, b * intensity, 1.0f));
light.SetAmbient(pxr::GfVec4f(0.0f, 0.0f, 0.0f, 1.0f));
explicit_lights.push_back(light);
use_default_lighting = false;
}
void Renderer::set_clear_color(float r, float g, float b, float a) {
clear_color = pxr::GfVec4f(r, g, b, a);
}
void Renderer::set_scene_ambient(float r, float g, float b, float a) {
scene_ambient = pxr::GfVec4f(r, g, b, a);
}
namespace {
const pxr::SdfPath kDomeLightPath("/_hydraDomeLight");
}
void Renderer::set_dome_light(rust::Str hdri_path,
float intensity,
float exposure,
float rotation_y_degrees) {
if (!stage) {
throw std::runtime_error(
"Renderer::set_dome_light called before stage was opened");
}
pxr::UsdEditContext ctx(stage, stage->GetSessionLayer());
pxr::UsdLuxDomeLight dome =
pxr::UsdLuxDomeLight::Define(stage, kDomeLightPath);
if (!dome) {
throw std::runtime_error(
"UsdLuxDomeLight::Define returned an invalid prim at " +
kDomeLightPath.GetString());
}
std::string p(hdri_path.data(), hdri_path.size());
dome.CreateTextureFileAttr().Set(pxr::SdfAssetPath(p));
dome.CreateIntensityAttr().Set(intensity);
dome.CreateExposureAttr().Set(exposure);
dome.CreateTextureFormatAttr().Set(pxr::TfToken("latlong"));
pxr::UsdGeomXformCommonAPI xform_api(dome.GetPrim());
xform_api.SetRotate(pxr::GfVec3f(0.0f, rotation_y_degrees, 0.0f));
dome_light_active = true;
}
void Renderer::clear_dome_light() {
if (!stage) return;
pxr::UsdEditContext ctx(stage, stage->GetSessionLayer());
stage->RemovePrim(kDomeLightPath);
dome_light_active = false;
}
namespace {
const pxr::SdfPath kPaintMaterialRoot("/_hydraPaintMaterial");
const pxr::TfToken kPaintMaterialBindingName("_hydraPaintMaterial");
void connect_texture_input(
const pxr::UsdStageRefPtr& stage,
const pxr::SdfPath& material_root,
pxr::UsdShadeShader& surface,
pxr::UsdShadeOutput& st_output,
const std::string& asset_path,
const char* tex_prim_name,
const pxr::TfToken& surface_input,
const pxr::SdfValueTypeName& surface_input_type,
const pxr::TfToken& tex_output_name,
const pxr::SdfValueTypeName& tex_output_type,
const char* source_color_type)
{
if (asset_path.empty()) return;
pxr::SdfPath tex_path = material_root.AppendChild(pxr::TfToken(tex_prim_name));
auto tex = pxr::UsdShadeShader::Define(stage, tex_path);
tex.CreateIdAttr().Set(pxr::TfToken("UsdUVTexture"));
tex.CreateInput(pxr::TfToken("file"), pxr::SdfValueTypeNames->Asset)
.Set(pxr::SdfAssetPath(asset_path));
tex.CreateInput(pxr::TfToken("sourceColorSpace"), pxr::SdfValueTypeNames->Token)
.Set(pxr::TfToken(source_color_type));
tex.CreateInput(pxr::TfToken("st"), pxr::SdfValueTypeNames->Float2)
.ConnectToSource(st_output);
auto tex_out = tex.CreateOutput(tex_output_name, tex_output_type);
surface.CreateInput(surface_input, surface_input_type).ConnectToSource(tex_out);
}
}
void Renderer::set_painted_material(rust::Str base_color_asset_path,
rust::Str roughness_asset_path,
rust::Str metallic_asset_path,
rust::Str normal_asset_path)
{
if (!stage) {
throw std::runtime_error(
"Renderer::set_painted_material called before stage was opened");
}
pxr::UsdEditContext ctx(stage, stage->GetSessionLayer());
if (stage->GetPrimAtPath(kPaintMaterialRoot)) {
stage->RemovePrim(kPaintMaterialRoot);
}
auto mat = pxr::UsdShadeMaterial::Define(stage, kPaintMaterialRoot);
auto surface = pxr::UsdShadeShader::Define(
stage, kPaintMaterialRoot.AppendChild(pxr::TfToken("Surface")));
surface.CreateIdAttr().Set(pxr::TfToken("UsdPreviewSurface"));
auto surface_out = surface.CreateOutput(
pxr::TfToken("surface"), pxr::SdfValueTypeNames->Token);
auto st_reader = pxr::UsdShadeShader::Define(
stage, kPaintMaterialRoot.AppendChild(pxr::TfToken("StReader")));
st_reader.CreateIdAttr().Set(pxr::TfToken("UsdPrimvarReader_float2"));
st_reader.CreateInput(pxr::TfToken("varname"), pxr::SdfValueTypeNames->Token)
.Set(pxr::TfToken("st"));
auto st_out = st_reader.CreateOutput(
pxr::TfToken("result"), pxr::SdfValueTypeNames->Float2);
auto str_to_string = [](rust::Str s) {
return std::string(s.data(), s.size());
};
std::string bc = str_to_string(base_color_asset_path);
std::string ro = str_to_string(roughness_asset_path);
std::string me = str_to_string(metallic_asset_path);
std::string nm = str_to_string(normal_asset_path);
connect_texture_input(
stage, kPaintMaterialRoot, surface, st_out, bc,
"BaseColorTex",
pxr::TfToken("diffuseColor"), pxr::SdfValueTypeNames->Color3f,
pxr::TfToken("rgb"), pxr::SdfValueTypeNames->Float3,
"sRGB");
connect_texture_input(
stage, kPaintMaterialRoot, surface, st_out, ro,
"RoughnessTex",
pxr::TfToken("roughness"), pxr::SdfValueTypeNames->Float,
pxr::TfToken("r"), pxr::SdfValueTypeNames->Float,
"raw");
connect_texture_input(
stage, kPaintMaterialRoot, surface, st_out, me,
"MetallicTex",
pxr::TfToken("metallic"), pxr::SdfValueTypeNames->Float,
pxr::TfToken("r"), pxr::SdfValueTypeNames->Float,
"raw");
connect_texture_input(
stage, kPaintMaterialRoot, surface, st_out, nm,
"NormalTex",
pxr::TfToken("normal"), pxr::SdfValueTypeNames->Normal3f,
pxr::TfToken("rgb"), pxr::SdfValueTypeNames->Float3,
"raw");
mat.CreateSurfaceOutput().ConnectToSource(
surface.ConnectableAPI(), pxr::TfToken("surface"));
pxr::UsdPrimRange range(stage->GetPseudoRoot());
for (const auto& prim : range) {
if (prim.IsA<pxr::UsdGeomMesh>()) {
auto bind = pxr::UsdShadeMaterialBindingAPI::Apply(prim);
bind.Bind(mat);
}
}
}
void Renderer::set_show_render(bool show) { show_render_purpose = show; }
void Renderer::set_show_proxy(bool show) { show_proxy_purpose = show; }
void Renderer::set_show_guides(bool show) { show_guides_purpose = show; }
namespace {
pxr::SdfPath user_light_path(uint32_t index) {
return pxr::SdfPath("/_hydraLight" + std::to_string(index));
}
void place_light_at(const pxr::UsdPrim& prim,
pxr::GfVec3f dir,
pxr::GfVec3f position) {
const float mag = dir.GetLength();
if (mag < 1e-6f) {
dir = pxr::GfVec3f(0.0f, -1.0f, 0.0f);
} else {
dir /= mag;
}
const pxr::GfRotation rot(pxr::GfVec3d(0.0, 0.0, -1.0),
pxr::GfVec3d(dir[0], dir[1], dir[2]));
pxr::GfMatrix4d xform;
xform.SetRotate(rot);
xform.SetTranslateOnly(pxr::GfVec3d(position[0], position[1], position[2]));
pxr::UsdGeomXformable xformable(prim);
xformable.ClearXformOpOrder();
pxr::UsdGeomXformOp op = xformable.AddTransformOp();
op.Set(xform);
}
}
namespace {
const pxr::SdfPath kExternalMaterialPath("/_hydraExternalMaterial");
const pxr::TfToken kExternalMatBindingName("_hydraExternalMaterial");
}
void Renderer::set_external_material(rust::Str source_usd_path,
rust::Str prim_path) {
if (!stage) {
throw std::runtime_error(
"Renderer::set_external_material called before stage was opened");
}
const std::string src(source_usd_path.data(), source_usd_path.size());
const std::string sub_prim(prim_path.data(), prim_path.size());
pxr::UsdEditContext ctx(stage, stage->GetSessionLayer());
if (stage->GetPrimAtPath(kExternalMaterialPath)) {
stage->RemovePrim(kExternalMaterialPath);
}
if (src.empty()) {
pxr::UsdPrimRange range(stage->GetPseudoRoot());
for (const auto& prim : range) {
if (prim.IsA<pxr::UsdGeomMesh>()) {
auto bind = pxr::UsdShadeMaterialBindingAPI(prim);
if (bind) bind.UnbindAllBindings();
}
}
return;
}
auto mat = pxr::UsdShadeMaterial::Define(stage, kExternalMaterialPath);
pxr::SdfPath ref_prim_path = sub_prim.empty()
? pxr::SdfPath()
: pxr::SdfPath(sub_prim);
mat.GetPrim().GetReferences().AddReference(src, ref_prim_path);
pxr::UsdPrimRange range(stage->GetPseudoRoot());
for (const auto& prim : range) {
if (prim.IsA<pxr::UsdGeomMesh>()) {
auto bind = pxr::UsdShadeMaterialBindingAPI::Apply(prim);
bind.Bind(mat);
}
}
}
void Renderer::clear_external_material() {
if (!stage) return;
pxr::UsdEditContext ctx(stage, stage->GetSessionLayer());
pxr::UsdPrimRange range(stage->GetPseudoRoot());
for (const auto& prim : range) {
if (prim.IsA<pxr::UsdGeomMesh>()) {
auto bind = pxr::UsdShadeMaterialBindingAPI(prim);
if (bind) bind.UnbindAllBindings();
}
}
if (stage->GetPrimAtPath(kExternalMaterialPath)) {
stage->RemovePrim(kExternalMaterialPath);
}
}
void Renderer::set_user_lights(rust::Slice<const float> data) {
if (!stage) {
throw std::runtime_error(
"Renderer::set_user_lights called before stage was opened");
}
if (data.size() % 16 != 0) {
throw std::runtime_error(
"Renderer::set_user_lights: payload length must be a multiple of 16");
}
pxr::UsdEditContext ctx(stage, stage->GetSessionLayer());
const uint32_t new_count = static_cast<uint32_t>(data.size() / 16);
for (uint32_t i = 0; i < new_count; ++i) {
const float* p = data.data() + i * 16;
const pxr::GfVec3f direction(p[0], p[1], p[2]);
const float type_tag = p[3];
const pxr::GfVec3f position(p[4], p[5], p[6]);
const float enabled = p[7];
const pxr::GfVec3f color(p[8], p[9], p[10]);
const float intensity = p[11];
const float cos_inner = p[12];
const float cos_outer = p[13];
const pxr::SdfPath path = user_light_path(i);
if (enabled < 0.5f) {
if (stage->GetPrimAtPath(path)) {
stage->RemovePrim(path);
}
continue;
}
const bool is_spot = type_tag > 0.5f;
if (stage->GetPrimAtPath(path)) {
const auto& existing = stage->GetPrimAtPath(path);
const bool was_spot = existing.IsA<pxr::UsdLuxSphereLight>();
if (was_spot != is_spot) {
stage->RemovePrim(path);
}
}
if (is_spot) {
auto light = pxr::UsdLuxSphereLight::Define(stage, path);
light.CreateRadiusAttr().Set(0.01f);
light.CreateColorAttr().Set(color);
light.CreateIntensityAttr().Set(intensity);
place_light_at(light.GetPrim(), direction, position);
auto shaping = pxr::UsdLuxShapingAPI::Apply(light.GetPrim());
const auto clamp_cos = [](float v) {
if (v < -1.0f) return -1.0f;
if (v > 1.0f) return 1.0f;
return v;
};
const float inner_deg = std::acos(clamp_cos(cos_inner)) * 180.0f / 3.14159265f;
const float outer_deg = std::acos(clamp_cos(cos_outer)) * 180.0f / 3.14159265f;
const float angle = outer_deg;
const float softness = (outer_deg > 1e-3f)
? ((outer_deg - inner_deg) / outer_deg)
: 0.0f;
shaping.CreateShapingConeAngleAttr().Set(angle);
shaping.CreateShapingConeSoftnessAttr().Set(softness);
} else {
auto light = pxr::UsdLuxDistantLight::Define(stage, path);
light.CreateColorAttr().Set(color);
light.CreateIntensityAttr().Set(intensity);
place_light_at(light.GetPrim(), direction, pxr::GfVec3f(0.0f));
}
}
for (uint32_t i = new_count; i < user_light_count; ++i) {
const pxr::SdfPath path = user_light_path(i);
if (stage->GetPrimAtPath(path)) {
stage->RemovePrim(path);
}
}
user_light_count = new_count;
}
void Renderer::clear_user_lights() {
if (!stage) return;
pxr::UsdEditContext ctx(stage, stage->GetSessionLayer());
for (uint32_t i = 0; i < user_light_count; ++i) {
const pxr::SdfPath path = user_light_path(i);
if (stage->GetPrimAtPath(path)) {
stage->RemovePrim(path);
}
}
user_light_count = 0;
}
void Renderer::clear_painted_material() {
if (!stage) return;
pxr::UsdEditContext ctx(stage, stage->GetSessionLayer());
pxr::UsdPrimRange range(stage->GetPseudoRoot());
for (const auto& prim : range) {
if (prim.IsA<pxr::UsdGeomMesh>()) {
auto bind = pxr::UsdShadeMaterialBindingAPI(prim);
if (bind) bind.UnbindAllBindings();
}
}
if (stage->GetPrimAtPath(kPaintMaterialRoot)) {
stage->RemovePrim(kPaintMaterialRoot);
}
}
rust::String Renderer::current_renderer() const {
if (!engine) return rust::String();
return rust::String(engine->GetCurrentRendererId().GetString());
}
bool Renderer::is_converged() const {
if (!engine) return false;
return engine->IsConverged();
}
bool Renderer::set_renderer_plugin(rust::Str plugin_id) const {
if (!engine) return false;
std::string id(plugin_id.data(), plugin_id.size());
bool ok = engine->SetRendererPlugin(pxr::TfToken(id));
if (ok) {
engine->SetEnablePresentation(false);
}
return ok;
}
std::unique_ptr<std::vector<uint8_t>> Renderer::render_color() const {
if (!engine || !stage) {
throw std::runtime_error("Renderer not initialized");
}
if (width == 0 || height == 0) {
throw std::runtime_error("Renderer width and height must be > 0");
}
engine->SetCameraState(view_matrix, proj_matrix);
engine->SetRenderViewport(pxr::GfVec4d(0.0, 0.0,
static_cast<double>(width),
static_cast<double>(height)));
engine->SetRendererAov(pxr::HdAovTokens->color);
pxr::GlfSimpleLightVector lights = explicit_lights;
if (lights.empty() && use_default_lighting) {
pxr::GlfSimpleLight default_light;
default_light.SetPosition(pxr::GfVec4f(2.5f, 4.0f, 5.0f, 1.0f));
default_light.SetDiffuse(pxr::GfVec4f(1.0f, 1.0f, 1.0f, 1.0f));
default_light.SetSpecular(pxr::GfVec4f(1.0f, 1.0f, 1.0f, 1.0f));
default_light.SetAmbient(pxr::GfVec4f(0.0f, 0.0f, 0.0f, 1.0f));
lights.push_back(default_light);
}
engine->SetLightingState(lights, material, scene_ambient);
pxr::UsdImagingGLRenderParams params;
params.frame = default_frame ? pxr::UsdTimeCode::Default()
: pxr::UsdTimeCode(frame);
params.complexity = 1.0f;
const bool any_user_lux = dome_light_active || user_light_count > 0;
params.enableLighting = !lights.empty()
|| use_default_lighting
|| any_user_lux;
params.enableSceneLights = any_user_lux;
params.enableSceneMaterials = true;
params.showRender = show_render_purpose;
params.showProxy = show_proxy_purpose;
params.showGuides = show_guides_purpose;
params.clearColor = clear_color;
engine->Render(stage->GetPseudoRoot(), params);
pxr::HdRenderBuffer* color = engine->GetAovRenderBuffer(pxr::HdAovTokens->color);
if (!color) {
throw std::runtime_error("UsdImagingGLEngine has no color render buffer");
}
color->Resolve();
const uint32_t bw = color->GetWidth();
const uint32_t bh = color->GetHeight();
const pxr::HdFormat fmt = color->GetFormat();
void* mapped = color->Map();
if (!mapped) {
throw std::runtime_error("HdRenderBuffer::Map returned null");
}
auto out = std::make_unique<std::vector<uint8_t>>();
out->resize(static_cast<size_t>(bw) * bh * 4);
if (fmt == pxr::HdFormatFloat32Vec4) {
const float* src = static_cast<const float*>(mapped);
for (size_t i = 0; i < static_cast<size_t>(bw) * bh; ++i) {
for (int c = 0; c < 4; ++c) {
float v = src[i * 4 + c];
v = std::max(0.0f, std::min(1.0f, v));
(*out)[i * 4 + c] = static_cast<uint8_t>(v * 255.0f + 0.5f);
}
}
} else if (fmt == pxr::HdFormatFloat16Vec4) {
const pxr::GfHalf* src = static_cast<const pxr::GfHalf*>(mapped);
for (size_t i = 0; i < static_cast<size_t>(bw) * bh; ++i) {
for (int c = 0; c < 4; ++c) {
float v = static_cast<float>(src[i * 4 + c]);
v = std::max(0.0f, std::min(1.0f, v));
(*out)[i * 4 + c] = static_cast<uint8_t>(v * 255.0f + 0.5f);
}
}
} else if (fmt == pxr::HdFormatUNorm8Vec4) {
std::memcpy(out->data(), mapped, out->size());
} else {
color->Unmap();
throw std::runtime_error(
"Unsupported render buffer format from delegate (HdFormat=" +
std::to_string(static_cast<int>(fmt)) + ")");
}
color->Unmap();
const size_t row_bytes = static_cast<size_t>(bw) * 4;
if (bh > 1 && out->size() >= row_bytes * bh) {
std::vector<uint8_t> tmp(row_bytes);
for (uint32_t y = 0; y < bh / 2; ++y) {
uint8_t* top = out->data() + static_cast<size_t>(y) * row_bytes;
uint8_t* bot = out->data() + static_cast<size_t>(bh - 1 - y) * row_bytes;
std::memcpy(tmp.data(), top, row_bytes);
std::memcpy(top, bot, row_bytes);
std::memcpy(bot, tmp.data(), row_bytes);
}
}
return out;
}
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)
{
auto r = create_renderer(usd_path, render_delegate_id);
r->set_size(width, height);
std::unique_ptr<std::vector<uint8_t>> out;
for (int i = 0; i < 256; ++i) {
out = r->render_color();
if (r->is_converged()) break;
}
return out;
}
}