* Add SubdAttributeInterpolation class for linear attribute interpolation. * Dicing computes ptex UV and face ID for interpolation. * Simplify mesh storage of subd primitive counts * Remove kernel code for subd attribute interpolation * Remove patch table packing and upload The old optimization adds a fair amount of complexity to the kernel, affecting performance even when not using the feature. It's also not that useful as it does not work for UVs that needs special interpolation. With this simpler code it should be easier to make it feature complete. Pull Request: https://projects.blender.org/blender/blender/pulls/135681
517 lines
17 KiB
C++
517 lines
17 KiB
C++
/* SPDX-FileCopyrightText: 2022 NVIDIA Corporation
|
|
* SPDX-FileCopyrightText: 2022 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#include "hydra/mesh.h"
|
|
#include "hydra/attribute.h"
|
|
#include "hydra/geometry.inl"
|
|
#include "scene/mesh.h"
|
|
|
|
#include <pxr/base/gf/vec2f.h>
|
|
#include <pxr/imaging/hd/extComputationUtils.h>
|
|
|
|
HDCYCLES_NAMESPACE_OPEN_SCOPE
|
|
|
|
namespace {
|
|
|
|
template<typename T>
|
|
VtValue ComputeTriangulatedUniformPrimvar(VtValue value, const VtIntArray &primitiveParams)
|
|
{
|
|
T output;
|
|
output.reserve(primitiveParams.size());
|
|
const T &input = value.Get<T>();
|
|
|
|
for (size_t i = 0; i < primitiveParams.size(); ++i) {
|
|
const int faceIndex = HdMeshUtil::DecodeFaceIndexFromCoarseFaceParam(primitiveParams[i]);
|
|
|
|
output.push_back(input[faceIndex]);
|
|
}
|
|
|
|
return VtValue(output);
|
|
}
|
|
|
|
VtValue ComputeTriangulatedUniformPrimvar(VtValue value,
|
|
const HdType valueType,
|
|
const VtIntArray &primitiveParams)
|
|
{
|
|
switch (valueType) {
|
|
case HdTypeFloat:
|
|
return ComputeTriangulatedUniformPrimvar<VtFloatArray>(value, primitiveParams);
|
|
case HdTypeFloatVec2:
|
|
return ComputeTriangulatedUniformPrimvar<VtVec2fArray>(value, primitiveParams);
|
|
case HdTypeFloatVec3:
|
|
return ComputeTriangulatedUniformPrimvar<VtVec3fArray>(value, primitiveParams);
|
|
case HdTypeFloatVec4:
|
|
return ComputeTriangulatedUniformPrimvar<VtVec4fArray>(value, primitiveParams);
|
|
default:
|
|
TF_RUNTIME_ERROR("Unsupported attribute type %d", static_cast<int>(valueType));
|
|
return VtValue();
|
|
}
|
|
}
|
|
|
|
VtValue ComputeTriangulatedFaceVaryingPrimvar(VtValue value,
|
|
const HdType valueType,
|
|
HdMeshUtil &meshUtil)
|
|
{
|
|
if (meshUtil.ComputeTriangulatedFaceVaryingPrimvar(
|
|
HdGetValueData(value), value.GetArraySize(), valueType, &value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return VtValue();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Transform convert_transform(const GfMatrix4d &matrix)
|
|
{
|
|
return make_transform(matrix[0][0],
|
|
matrix[1][0],
|
|
matrix[2][0],
|
|
matrix[3][0],
|
|
matrix[0][1],
|
|
matrix[1][1],
|
|
matrix[2][1],
|
|
matrix[3][1],
|
|
matrix[0][2],
|
|
matrix[1][2],
|
|
matrix[2][2],
|
|
matrix[3][2]);
|
|
}
|
|
|
|
HdCyclesMesh::HdCyclesMesh(const SdfPath &rprimId
|
|
#if PXR_VERSION < 2102
|
|
,
|
|
const SdfPath &instancerId
|
|
#endif
|
|
)
|
|
: HdCyclesGeometry(rprimId
|
|
#if PXR_VERSION < 2102
|
|
,
|
|
instancerId
|
|
#endif
|
|
),
|
|
_util(&_topology, rprimId)
|
|
{
|
|
}
|
|
|
|
HdCyclesMesh::~HdCyclesMesh() = default;
|
|
|
|
HdDirtyBits HdCyclesMesh::GetInitialDirtyBitsMask() const
|
|
{
|
|
HdDirtyBits bits = HdCyclesGeometry::GetInitialDirtyBitsMask();
|
|
bits |= HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyNormals |
|
|
HdChangeTracker::DirtyPrimvar | HdChangeTracker::DirtyTopology |
|
|
HdChangeTracker::DirtyDisplayStyle | HdChangeTracker::DirtySubdivTags;
|
|
return bits;
|
|
}
|
|
|
|
HdDirtyBits HdCyclesMesh::_PropagateDirtyBits(HdDirtyBits bits) const
|
|
{
|
|
if (bits & (HdChangeTracker::DirtyMaterialId)) {
|
|
// Update used shaders from geometry subsets if any exist in the topology
|
|
bits |= HdChangeTracker::DirtyTopology;
|
|
}
|
|
|
|
if (bits & (HdChangeTracker::DirtyTopology | HdChangeTracker::DirtyDisplayStyle |
|
|
HdChangeTracker::DirtySubdivTags))
|
|
{
|
|
// Do full topology update when display style or subdivision changes
|
|
bits |= HdChangeTracker::DirtyTopology | HdChangeTracker::DirtyDisplayStyle |
|
|
HdChangeTracker::DirtySubdivTags;
|
|
}
|
|
|
|
if (bits & (HdChangeTracker::DirtyTopology)) {
|
|
// Changing topology clears the geometry, so need to populate everything again
|
|
bits |= HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyNormals |
|
|
HdChangeTracker::DirtyPrimvar;
|
|
}
|
|
|
|
return bits;
|
|
}
|
|
|
|
void HdCyclesMesh::Populate(HdSceneDelegate *sceneDelegate, HdDirtyBits dirtyBits, bool &rebuild)
|
|
{
|
|
if (HdChangeTracker::IsTopologyDirty(dirtyBits, GetId())) {
|
|
PopulateTopology(sceneDelegate);
|
|
}
|
|
|
|
if (dirtyBits & HdChangeTracker::DirtyPoints) {
|
|
PopulatePoints(sceneDelegate);
|
|
}
|
|
|
|
// Must happen after topology update, so that normals attribute size can be calculated
|
|
if (dirtyBits & HdChangeTracker::DirtyNormals) {
|
|
PopulateNormals(sceneDelegate);
|
|
}
|
|
|
|
// Must happen after topology update, so that appropriate attribute set can be selected
|
|
if (dirtyBits & HdChangeTracker::DirtyPrimvar) {
|
|
PopulatePrimvars(sceneDelegate);
|
|
}
|
|
|
|
rebuild = (_geom->triangles_is_modified()) || (_geom->subd_start_corner_is_modified()) ||
|
|
(_geom->subd_num_corners_is_modified()) || (_geom->subd_shader_is_modified()) ||
|
|
(_geom->subd_smooth_is_modified()) || (_geom->subd_ptex_offset_is_modified()) ||
|
|
(_geom->subd_face_corners_is_modified());
|
|
}
|
|
|
|
void HdCyclesMesh::PopulatePoints(HdSceneDelegate *sceneDelegate)
|
|
{
|
|
VtValue value;
|
|
|
|
for (const HdExtComputationPrimvarDescriptor &desc :
|
|
sceneDelegate->GetExtComputationPrimvarDescriptors(GetId(), HdInterpolationVertex))
|
|
{
|
|
if (desc.name == HdTokens->points) {
|
|
auto valueStore = HdExtComputationUtils::GetComputedPrimvarValues({desc}, sceneDelegate);
|
|
const auto valueStoreIt = valueStore.find(desc.name);
|
|
if (valueStoreIt != valueStore.end()) {
|
|
value = std::move(valueStoreIt->second);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (value.IsEmpty()) {
|
|
value = GetPoints(sceneDelegate);
|
|
}
|
|
|
|
if (!value.IsHolding<VtVec3fArray>()) {
|
|
TF_WARN("Invalid points data for %s", GetId().GetText());
|
|
return;
|
|
}
|
|
|
|
const auto &points = value.UncheckedGet<VtVec3fArray>();
|
|
|
|
TF_VERIFY(points.size() >= static_cast<size_t>(_topology.GetNumPoints()));
|
|
|
|
array<float3> pointsDataCycles;
|
|
pointsDataCycles.reserve(points.size());
|
|
for (const GfVec3f &point : points) {
|
|
pointsDataCycles.push_back_reserved(make_float3(point[0], point[1], point[2]));
|
|
}
|
|
|
|
_geom->set_verts(pointsDataCycles);
|
|
}
|
|
|
|
void HdCyclesMesh::PopulateNormals(HdSceneDelegate *sceneDelegate)
|
|
{
|
|
_geom->attributes.remove(ATTR_STD_VERTEX_NORMAL);
|
|
|
|
// Authored normals should only exist on triangle meshes
|
|
if (_geom->get_subdivision_type() != Mesh::SUBDIVISION_NONE) {
|
|
return;
|
|
}
|
|
|
|
VtValue value;
|
|
HdInterpolation interpolation = HdInterpolationCount;
|
|
|
|
for (int i = 0; i < HdInterpolationCount && interpolation == HdInterpolationCount; ++i) {
|
|
for (const HdExtComputationPrimvarDescriptor &desc :
|
|
sceneDelegate->GetExtComputationPrimvarDescriptors(GetId(),
|
|
static_cast<HdInterpolation>(i)))
|
|
{
|
|
if (desc.name == HdTokens->normals) {
|
|
auto valueStore = HdExtComputationUtils::GetComputedPrimvarValues({desc}, sceneDelegate);
|
|
const auto valueStoreIt = valueStore.find(desc.name);
|
|
if (valueStoreIt != valueStore.end()) {
|
|
value = std::move(valueStoreIt->second);
|
|
interpolation = static_cast<HdInterpolation>(i);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (value.IsEmpty()) {
|
|
interpolation = GetPrimvarInterpolation(sceneDelegate, HdTokens->normals);
|
|
if (interpolation == HdInterpolationCount) {
|
|
return; // Ignore missing normals
|
|
}
|
|
|
|
value = GetNormals(sceneDelegate);
|
|
}
|
|
|
|
if (!value.IsHolding<VtVec3fArray>()) {
|
|
TF_WARN("Invalid normals data for %s", GetId().GetText());
|
|
return;
|
|
}
|
|
|
|
const auto &normals = value.UncheckedGet<VtVec3fArray>();
|
|
|
|
if (interpolation == HdInterpolationConstant) {
|
|
TF_VERIFY(normals.size() == 1);
|
|
|
|
const GfVec3f constantNormal = normals[0];
|
|
|
|
float3 *const N = _geom->attributes.add(ATTR_STD_VERTEX_NORMAL)->data_float3();
|
|
for (size_t i = 0; i < _geom->get_verts().size(); ++i) {
|
|
N[i] = make_float3(constantNormal[0], constantNormal[1], constantNormal[2]);
|
|
}
|
|
}
|
|
else if (interpolation == HdInterpolationUniform) {
|
|
TF_VERIFY(normals.size() == static_cast<size_t>(_topology.GetNumFaces()));
|
|
/* Nothing to do, face normals are computed on demand in the kernel. */
|
|
}
|
|
else if (interpolation == HdInterpolationVertex || interpolation == HdInterpolationVarying) {
|
|
TF_VERIFY(normals.size() == static_cast<size_t>(_topology.GetNumPoints()) &&
|
|
static_cast<size_t>(_topology.GetNumPoints()) == _geom->get_verts().size());
|
|
|
|
float3 *const N = _geom->attributes.add(ATTR_STD_VERTEX_NORMAL)->data_float3();
|
|
for (size_t i = 0; i < _geom->get_verts().size(); ++i) {
|
|
N[i] = make_float3(normals[i][0], normals[i][1], normals[i][2]);
|
|
}
|
|
}
|
|
else if (interpolation == HdInterpolationFaceVarying) {
|
|
TF_VERIFY(normals.size() == static_cast<size_t>(_topology.GetNumFaceVaryings()));
|
|
|
|
// TODO: Cycles has no per-corner normals, so ignore until supported.
|
|
#if 0
|
|
if (!_util.ComputeTriangulatedFaceVaryingPrimvar(
|
|
normals.data(), normals.size(), HdTypeFloatVec3, &value))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const auto &normalsTriangulated = value.UncheckedGet<VtVec3fArray>();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void HdCyclesMesh::PopulatePrimvars(HdSceneDelegate *sceneDelegate)
|
|
{
|
|
Scene *const scene = (Scene *)_geom->get_owner();
|
|
|
|
const bool subdivision = _geom->get_subdivision_type() != Mesh::SUBDIVISION_NONE;
|
|
AttributeSet &attributes = subdivision ? _geom->subd_attributes : _geom->attributes;
|
|
|
|
const std::pair<HdInterpolation, AttributeElement> interpolations[] = {
|
|
std::make_pair(HdInterpolationFaceVarying, ATTR_ELEMENT_CORNER),
|
|
std::make_pair(HdInterpolationUniform, ATTR_ELEMENT_FACE),
|
|
std::make_pair(HdInterpolationVertex, ATTR_ELEMENT_VERTEX),
|
|
std::make_pair(HdInterpolationVarying, ATTR_ELEMENT_VERTEX),
|
|
std::make_pair(HdInterpolationConstant, ATTR_ELEMENT_OBJECT),
|
|
};
|
|
|
|
for (const auto &interpolation : interpolations) {
|
|
for (const HdPrimvarDescriptor &desc :
|
|
GetPrimvarDescriptors(sceneDelegate, interpolation.first))
|
|
{
|
|
// Skip special primvars that are handled separately
|
|
if (desc.name == HdTokens->points || desc.name == HdTokens->normals) {
|
|
continue;
|
|
}
|
|
|
|
VtValue value = GetPrimvar(sceneDelegate, desc.name);
|
|
if (value.IsEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
const ustring name(desc.name.GetString());
|
|
|
|
AttributeStandard std = ATTR_STD_NONE;
|
|
if (desc.role == HdPrimvarRoleTokens->textureCoordinate) {
|
|
std = ATTR_STD_UV;
|
|
}
|
|
else if (interpolation.first == HdInterpolationVertex) {
|
|
if (desc.name == HdTokens->displayColor || desc.role == HdPrimvarRoleTokens->color) {
|
|
std = ATTR_STD_VERTEX_COLOR;
|
|
}
|
|
else if (desc.name == HdTokens->normals) {
|
|
std = ATTR_STD_VERTEX_NORMAL;
|
|
}
|
|
}
|
|
else if (desc.name == HdTokens->displayColor &&
|
|
interpolation.first == HdInterpolationConstant)
|
|
{
|
|
if (value.IsHolding<VtVec3fArray>() && value.GetArraySize() == 1) {
|
|
const GfVec3f color = value.UncheckedGet<VtVec3fArray>()[0];
|
|
_instances[0]->set_color(make_float3(color[0], color[1], color[2]));
|
|
}
|
|
}
|
|
|
|
// Skip attributes that are not needed
|
|
if ((std != ATTR_STD_NONE && _geom->need_attribute(scene, std)) ||
|
|
_geom->need_attribute(scene, name))
|
|
{
|
|
const HdType valueType = HdGetValueTupleType(value).type;
|
|
|
|
if (!subdivision) {
|
|
// Adjust attributes for polygons that were triangulated
|
|
if (interpolation.first == HdInterpolationUniform) {
|
|
value = ComputeTriangulatedUniformPrimvar(value, valueType, _primitiveParams);
|
|
if (value.IsEmpty()) {
|
|
continue;
|
|
}
|
|
}
|
|
else if (interpolation.first == HdInterpolationFaceVarying) {
|
|
value = ComputeTriangulatedFaceVaryingPrimvar(value, valueType, _util);
|
|
if (value.IsEmpty()) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
ApplyPrimvars(attributes, name, value, interpolation.second, std);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HdCyclesMesh::PopulateTopology(HdSceneDelegate *sceneDelegate)
|
|
{
|
|
// Clear geometry before populating it again with updated topology
|
|
_geom->clear(true);
|
|
|
|
const HdDisplayStyle displayStyle = GetDisplayStyle(sceneDelegate);
|
|
_topology = HdMeshTopology(GetMeshTopology(sceneDelegate), displayStyle.refineLevel);
|
|
|
|
const TfToken subdivScheme = _topology.GetScheme();
|
|
if (subdivScheme == PxOsdOpenSubdivTokens->bilinear && _topology.GetRefineLevel() > 0) {
|
|
_geom->set_subdivision_type(Mesh::SUBDIVISION_LINEAR);
|
|
}
|
|
else if (subdivScheme == PxOsdOpenSubdivTokens->catmullClark && _topology.GetRefineLevel() > 0) {
|
|
_geom->set_subdivision_type(Mesh::SUBDIVISION_CATMULL_CLARK);
|
|
}
|
|
else {
|
|
_geom->set_subdivision_type(Mesh::SUBDIVISION_NONE);
|
|
}
|
|
|
|
const bool smooth = !displayStyle.flatShadingEnabled;
|
|
const bool subdivision = _geom->get_subdivision_type() != Mesh::SUBDIVISION_NONE;
|
|
|
|
// Initialize lookup table from polygon face to material shader index
|
|
VtIntArray faceShaders(_topology.GetNumFaces(), 0);
|
|
|
|
const HdGeomSubsets &geomSubsets = _topology.GetGeomSubsets();
|
|
if (!geomSubsets.empty()) {
|
|
array<Node *> usedShaders = std::move(_geom->get_used_shaders());
|
|
// Remove any previous materials except for the material assigned to the prim
|
|
usedShaders.resize(1);
|
|
|
|
std::unordered_map<SdfPath, int, SdfPath::Hash> materials;
|
|
|
|
for (const HdGeomSubset &geomSubset : geomSubsets) {
|
|
TF_VERIFY(geomSubset.type == HdGeomSubset::TypeFaceSet);
|
|
|
|
int shader = 0;
|
|
const auto it = materials.find(geomSubset.materialId);
|
|
if (it != materials.end()) {
|
|
shader = it->second;
|
|
}
|
|
else {
|
|
const auto *const material = static_cast<const HdCyclesMaterial *>(
|
|
sceneDelegate->GetRenderIndex().GetSprim(HdPrimTypeTokens->material,
|
|
geomSubset.materialId));
|
|
|
|
if (material && material->GetCyclesShader()) {
|
|
shader = static_cast<int>(usedShaders.size());
|
|
usedShaders.push_back_slow(material->GetCyclesShader());
|
|
|
|
materials.emplace(geomSubset.materialId, shader);
|
|
}
|
|
}
|
|
|
|
for (const int face : geomSubset.indices) {
|
|
faceShaders[face] = shader;
|
|
}
|
|
}
|
|
|
|
_geom->set_used_shaders(usedShaders);
|
|
}
|
|
|
|
const VtIntArray vertIndx = _topology.GetFaceVertexIndices();
|
|
const VtIntArray vertCounts = _topology.GetFaceVertexCounts();
|
|
|
|
if (!subdivision) {
|
|
VtVec3iArray triangles;
|
|
_util.ComputeTriangleIndices(&triangles, &_primitiveParams);
|
|
|
|
_geom->reserve_mesh(_topology.GetNumPoints(), triangles.size());
|
|
|
|
for (size_t i = 0; i < _primitiveParams.size(); ++i) {
|
|
const int faceIndex = HdMeshUtil::DecodeFaceIndexFromCoarseFaceParam(_primitiveParams[i]);
|
|
|
|
const GfVec3i triangle = triangles[i];
|
|
_geom->add_triangle(triangle[0], triangle[1], triangle[2], faceShaders[faceIndex], smooth);
|
|
}
|
|
}
|
|
else {
|
|
const PxOsdSubdivTags subdivTags = GetSubdivTags(sceneDelegate);
|
|
_topology.SetSubdivTags(subdivTags);
|
|
|
|
size_t numCorners = 0;
|
|
for (const int vertCount : vertCounts) {
|
|
numCorners += vertCount;
|
|
}
|
|
|
|
_geom->reserve_subd_faces(_topology.GetNumFaces(), numCorners);
|
|
|
|
// TODO: Handle hole indices
|
|
size_t faceIndex = 0;
|
|
size_t indexOffset = 0;
|
|
for (const int vertCount : vertCounts) {
|
|
_geom->add_subd_face(&vertIndx[indexOffset], vertCount, faceShaders[faceIndex], smooth);
|
|
|
|
faceIndex++;
|
|
indexOffset += vertCount;
|
|
}
|
|
|
|
const VtIntArray creaseLengths = subdivTags.GetCreaseLengths();
|
|
if (!creaseLengths.empty()) {
|
|
size_t numCreases = 0;
|
|
for (const int creaseLength : creaseLengths) {
|
|
numCreases += creaseLength - 1;
|
|
}
|
|
|
|
_geom->reserve_subd_creases(numCreases);
|
|
|
|
const VtIntArray creaseIndices = subdivTags.GetCreaseIndices();
|
|
const VtFloatArray creaseWeights = subdivTags.GetCreaseWeights();
|
|
|
|
indexOffset = 0;
|
|
size_t creaseLengthOffset = 0;
|
|
size_t createWeightOffset = 0;
|
|
for (const int creaseLength : creaseLengths) {
|
|
for (int j = 0; j < creaseLength - 1; ++j, ++createWeightOffset) {
|
|
const int v0 = creaseIndices[indexOffset + j];
|
|
const int v1 = creaseIndices[indexOffset + j + 1];
|
|
|
|
const float weight = creaseWeights.size() == creaseLengths.size() ?
|
|
creaseWeights[creaseLengthOffset] :
|
|
creaseWeights[createWeightOffset];
|
|
|
|
_geom->add_edge_crease(v0, v1, weight);
|
|
}
|
|
|
|
indexOffset += creaseLength;
|
|
creaseLengthOffset++;
|
|
}
|
|
|
|
const VtIntArray cornerIndices = subdivTags.GetCornerIndices();
|
|
const VtFloatArray cornerWeights = subdivTags.GetCornerWeights();
|
|
|
|
for (size_t i = 0; i < cornerIndices.size(); ++i) {
|
|
_geom->add_vertex_crease(cornerIndices[i], cornerWeights[i]);
|
|
}
|
|
}
|
|
|
|
_geom->set_subd_dicing_rate(1.0f);
|
|
_geom->set_subd_max_level(_topology.GetRefineLevel());
|
|
_geom->set_subd_objecttoworld(_instances[0]->get_tfm());
|
|
}
|
|
}
|
|
|
|
void HdCyclesMesh::Finalize(PXR_NS::HdRenderParam *renderParam)
|
|
{
|
|
_topology = HdMeshTopology();
|
|
_primitiveParams.clear();
|
|
|
|
HdCyclesGeometry<PXR_NS::HdMesh, Mesh>::Finalize(renderParam);
|
|
}
|
|
|
|
HDCYCLES_NAMESPACE_CLOSE_SCOPE
|