/* SPDX-FileCopyrightText: 2018 Blender Foundation * * SPDX-License-Identifier: GPL-2.0-or-later * * Author: Sergey Sharybin. */ #include #ifdef _MSC_VER # include #endif #include #include #include #include #include #include #include "internal/evaluator/eval_output_cpu.h" #include "internal/evaluator/eval_output_gpu.h" #include "internal/evaluator/evaluator_cache_impl.h" #include "internal/evaluator/patch_map.h" #include "opensubdiv_evaluator.hh" #include "opensubdiv_evaluator_capi.hh" #include "opensubdiv_topology_refiner.hh" using OpenSubdiv::Far::PatchTable; using OpenSubdiv::Far::PatchTableFactory; using OpenSubdiv::Far::StencilTable; using OpenSubdiv::Far::StencilTableFactory; using OpenSubdiv::Far::StencilTableReal; using OpenSubdiv::Far::TopologyRefiner; using OpenSubdiv::Osd::PatchArray; using OpenSubdiv::Osd::PatchCoord; namespace blender::opensubdiv { // Array implementation which stores small data on stack (or, rather, in the class itself). template class StackOrHeapArray { public: StackOrHeapArray() : num_elements_(0), heap_elements_(nullptr), num_heap_elements_(0), effective_elements_(nullptr) { } explicit StackOrHeapArray(int size) : StackOrHeapArray() { resize(size); } ~StackOrHeapArray() { delete[] heap_elements_; } int size() const { return num_elements_; }; T *data() { return effective_elements_; } void resize(int num_elements) { const int old_num_elements = num_elements_; num_elements_ = num_elements; // Early output if allcoation size did not change, or allocation size is smaller. // We never re-allocate, sacrificing some memory over performance. if (old_num_elements >= num_elements) { return; } // Simple case: no previously allocated buffer, can simply do one allocation. if (effective_elements_ == nullptr) { effective_elements_ = allocate(num_elements); return; } // Make new allocation, and copy elements if needed. T *old_buffer = effective_elements_; effective_elements_ = allocate(num_elements); if (old_buffer != effective_elements_) { memcpy( effective_elements_, old_buffer, sizeof(T) * std::min(old_num_elements, num_elements)); } if (old_buffer != stack_elements_) { delete[] old_buffer; } } protected: T *allocate(int num_elements) { if (num_elements < kNumMaxElementsOnStack) { return stack_elements_; } heap_elements_ = new T[num_elements]; return heap_elements_; } // Number of elements in the buffer. int num_elements_; // Elements which are allocated on a stack (or, rather, in the same allocation as the buffer // itself). // Is used as long as buffer is smaller than kNumMaxElementsOnStack. T stack_elements_[kNumMaxElementsOnStack]; // Heap storage for buffer larger than kNumMaxElementsOnStack. T *heap_elements_; int num_heap_elements_; // Depending on the current buffer size points to rither stack_elements_ or heap_elements_. T *effective_elements_; }; // 32 is a number of inner vertices along the patch size at subdivision level 6. using StackOrHeapPatchCoordArray = StackOrHeapArray; static void convertPatchCoordsToArray(const OpenSubdiv_PatchCoord *patch_coords, const int num_patch_coords, const PatchMap *patch_map, StackOrHeapPatchCoordArray *array) { array->resize(num_patch_coords); for (int i = 0; i < num_patch_coords; ++i) { const PatchTable::PatchHandle *handle = patch_map->FindPatch( patch_coords[i].ptex_face, patch_coords[i].u, patch_coords[i].v); (array->data())[i] = PatchCoord(*handle, patch_coords[i].u, patch_coords[i].v); } } //////////////////////////////////////////////////////////////////////////////// // Evaluator wrapper for anonymous API. EvalOutputAPI::EvalOutputAPI(EvalOutput *implementation, PatchMap *patch_map) : patch_map_(patch_map), implementation_(implementation) { } EvalOutputAPI::~EvalOutputAPI() { delete implementation_; } void EvalOutputAPI::setSettings(const OpenSubdiv_EvaluatorSettings *settings) { implementation_->updateSettings(settings); } void EvalOutputAPI::setCoarsePositions(const float *positions, const int start_vertex_index, const int num_vertices) { // TODO(sergey): Add sanity check on indices. implementation_->updateData(positions, start_vertex_index, num_vertices); } void EvalOutputAPI::setVaryingData(const float *varying_data, const int start_vertex_index, const int num_vertices) { // TODO(sergey): Add sanity check on indices. implementation_->updateVaryingData(varying_data, start_vertex_index, num_vertices); } void EvalOutputAPI::setVertexData(const float *vertex_data, const int start_vertex_index, const int num_vertices) { // TODO(sergey): Add sanity check on indices. implementation_->updateVertexData(vertex_data, start_vertex_index, num_vertices); } void EvalOutputAPI::setFaceVaryingData(const int face_varying_channel, const float *face_varying_data, const int start_vertex_index, const int num_vertices) { // TODO(sergey): Add sanity check on indices. implementation_->updateFaceVaryingData( face_varying_channel, face_varying_data, start_vertex_index, num_vertices); } void EvalOutputAPI::setCoarsePositionsFromBuffer(const void *buffer, const int start_offset, const int stride, const int start_vertex_index, const int num_vertices) { // TODO(sergey): Add sanity check on indices. const unsigned char *current_buffer = (unsigned char *)buffer; current_buffer += start_offset; for (int i = 0; i < num_vertices; ++i) { const int current_vertex_index = start_vertex_index + i; implementation_->updateData( reinterpret_cast(current_buffer), current_vertex_index, 1); current_buffer += stride; } } void EvalOutputAPI::setVaryingDataFromBuffer(const void *buffer, const int start_offset, const int stride, const int start_vertex_index, const int num_vertices) { // TODO(sergey): Add sanity check on indices. const unsigned char *current_buffer = (unsigned char *)buffer; current_buffer += start_offset; for (int i = 0; i < num_vertices; ++i) { const int current_vertex_index = start_vertex_index + i; implementation_->updateVaryingData( reinterpret_cast(current_buffer), current_vertex_index, 1); current_buffer += stride; } } void EvalOutputAPI::setFaceVaryingDataFromBuffer(const int face_varying_channel, const void *buffer, const int start_offset, const int stride, const int start_vertex_index, const int num_vertices) { // TODO(sergey): Add sanity check on indices. const unsigned char *current_buffer = (unsigned char *)buffer; current_buffer += start_offset; for (int i = 0; i < num_vertices; ++i) { const int current_vertex_index = start_vertex_index + i; implementation_->updateFaceVaryingData(face_varying_channel, reinterpret_cast(current_buffer), current_vertex_index, 1); current_buffer += stride; } } void EvalOutputAPI::refine() { implementation_->refine(); } void EvalOutputAPI::evaluateLimit(const int ptex_face_index, float face_u, float face_v, float P[3], float dPdu[3], float dPdv[3]) { assert(face_u >= 0.0f); assert(face_u <= 1.0f); assert(face_v >= 0.0f); assert(face_v <= 1.0f); const PatchTable::PatchHandle *handle = patch_map_->FindPatch(ptex_face_index, face_u, face_v); PatchCoord patch_coord(*handle, face_u, face_v); if (dPdu != nullptr || dPdv != nullptr) { implementation_->evalPatchesWithDerivatives(&patch_coord, 1, P, dPdu, dPdv); } else { implementation_->evalPatches(&patch_coord, 1, P); } } void EvalOutputAPI::evaluateVarying(const int ptex_face_index, float face_u, float face_v, float varying[3]) { assert(face_u >= 0.0f); assert(face_u <= 1.0f); assert(face_v >= 0.0f); assert(face_v <= 1.0f); const PatchTable::PatchHandle *handle = patch_map_->FindPatch(ptex_face_index, face_u, face_v); PatchCoord patch_coord(*handle, face_u, face_v); implementation_->evalPatchesVarying(&patch_coord, 1, varying); } void EvalOutputAPI::evaluateVertexData(const int ptex_face_index, float face_u, float face_v, float vertex_data[]) { assert(face_u >= 0.0f); assert(face_u <= 1.0f); assert(face_v >= 0.0f); assert(face_v <= 1.0f); const PatchTable::PatchHandle *handle = patch_map_->FindPatch(ptex_face_index, face_u, face_v); PatchCoord patch_coord(*handle, face_u, face_v); implementation_->evalPatchesVertexData(&patch_coord, 1, vertex_data); } void EvalOutputAPI::evaluateFaceVarying(const int face_varying_channel, const int ptex_face_index, float face_u, float face_v, float face_varying[2]) { assert(face_u >= 0.0f); assert(face_u <= 1.0f); assert(face_v >= 0.0f); assert(face_v <= 1.0f); const PatchTable::PatchHandle *handle = patch_map_->FindPatch(ptex_face_index, face_u, face_v); PatchCoord patch_coord(*handle, face_u, face_v); implementation_->evalPatchesFaceVarying(face_varying_channel, &patch_coord, 1, face_varying); } void EvalOutputAPI::evaluatePatchesLimit(const OpenSubdiv_PatchCoord *patch_coords, const int num_patch_coords, float *P, float *dPdu, float *dPdv) { StackOrHeapPatchCoordArray patch_coords_array; convertPatchCoordsToArray(patch_coords, num_patch_coords, patch_map_, &patch_coords_array); if (dPdu != nullptr || dPdv != nullptr) { implementation_->evalPatchesWithDerivatives( patch_coords_array.data(), num_patch_coords, P, dPdu, dPdv); } else { implementation_->evalPatches(patch_coords_array.data(), num_patch_coords, P); } } void EvalOutputAPI::getPatchMap(blender::gpu::VertBuf *patch_map_handles, blender::gpu::VertBuf *patch_map_quadtree, int *min_patch_face, int *max_patch_face, int *max_depth, int *patches_are_triangular) { *min_patch_face = patch_map_->getMinPatchFace(); *max_patch_face = patch_map_->getMaxPatchFace(); *max_depth = patch_map_->getMaxDepth(); *patches_are_triangular = patch_map_->getPatchesAreTriangular(); const std::vector &handles = patch_map_->getHandles(); // TODO(jbakker): should these be SSBO's they are never bound as vertex buffers. GPU_vertbuf_data_alloc(*patch_map_handles, handles.size()); MutableSpan buffer_handles = patch_map_handles->data(); memcpy(buffer_handles.data(), handles.data(), sizeof(PatchTable::PatchHandle) * handles.size()); const std::vector &quadtree = patch_map_->nodes(); GPU_vertbuf_data_alloc(*patch_map_quadtree, quadtree.size()); MutableSpan buffer_nodes = patch_map_quadtree->data(); memcpy(buffer_nodes.data(), quadtree.data(), sizeof(PatchMap::QuadNode) * quadtree.size()); } GPUStorageBuf *EvalOutputAPI::create_patch_arrays_buf() { return implementation_->create_patch_arrays_buf(); } GPUStorageBuf *EvalOutputAPI::get_patch_index_buf() { return implementation_->get_patch_index_buf(); } GPUStorageBuf *EvalOutputAPI::get_patch_param_buf() { return implementation_->get_patch_param_buf(); } gpu::VertBuf *EvalOutputAPI::get_source_buf() { return implementation_->get_source_buf(); } gpu::VertBuf *EvalOutputAPI::get_source_data_buf() { return implementation_->get_source_data_buf(); } GPUStorageBuf *EvalOutputAPI::create_face_varying_patch_array_buf(const int face_varying_channel) { return implementation_->create_face_varying_patch_array_buf(face_varying_channel); } GPUStorageBuf *EvalOutputAPI::get_face_varying_patch_index_buf(const int face_varying_channel) { return implementation_->get_face_varying_patch_index_buf(face_varying_channel); } GPUStorageBuf *EvalOutputAPI::get_face_varying_patch_param_buf(const int face_varying_channel) { return implementation_->get_face_varying_patch_param_buf(face_varying_channel); } gpu::VertBuf *EvalOutputAPI::get_face_varying_source_buf(const int face_varying_channel) { return implementation_->get_face_varying_source_buf(face_varying_channel); } int EvalOutputAPI::get_face_varying_source_offset(const int face_varying_channel) const { return implementation_->get_face_varying_source_offset(face_varying_channel); } bool EvalOutputAPI::hasVertexData() const { return implementation_->hasVertexData(); } } // namespace blender::opensubdiv OpenSubdiv_Evaluator::OpenSubdiv_Evaluator() : eval_output(nullptr), patch_map(nullptr), patch_table(nullptr) { } OpenSubdiv_Evaluator::~OpenSubdiv_Evaluator() { delete eval_output; delete patch_map; delete patch_table; } OpenSubdiv_Evaluator *openSubdiv_createEvaluatorFromTopologyRefiner( blender::opensubdiv::TopologyRefinerImpl *topology_refiner, eOpenSubdivEvaluator evaluator_type, OpenSubdiv_EvaluatorCache *evaluator_cache_descr) { TopologyRefiner *refiner = topology_refiner->topology_refiner; if (refiner == nullptr) { // Happens on bad topology. return nullptr; } // TODO(sergey): Base this on actual topology. const bool has_varying_data = false; const int num_face_varying_channels = refiner->GetNumFVarChannels(); const bool has_face_varying_data = (num_face_varying_channels != 0); const int level = topology_refiner->settings.level; const bool is_adaptive = topology_refiner->settings.is_adaptive; // Common settings for stencils and patches. const bool stencil_generate_intermediate_levels = is_adaptive; const bool stencil_generate_offsets = true; const bool use_inf_sharp_patch = true; // Refine the topology with given settings. // TODO(sergey): What if topology is already refined? if (is_adaptive) { TopologyRefiner::AdaptiveOptions options(level); options.considerFVarChannels = has_face_varying_data; options.useInfSharpPatch = use_inf_sharp_patch; refiner->RefineAdaptive(options); } else { TopologyRefiner::UniformOptions options(level); refiner->RefineUniform(options); } // Work around ASAN warnings, due to OpenSubdiv pretending to have an actual StencilTable // instance while it's really its base class. auto delete_stencil_table = [](const StencilTable *table) { static_assert(std::is_base_of_v, StencilTable>); delete reinterpret_cast *>(table); }; // Generate stencil table to update the bi-cubic patches control vertices // after they have been re-posed (both for vertex & varying interpolation). // // Vertex stencils. StencilTableFactory::Options vertex_stencil_options; vertex_stencil_options.generateOffsets = stencil_generate_offsets; vertex_stencil_options.generateIntermediateLevels = stencil_generate_intermediate_levels; const StencilTable *vertex_stencils = StencilTableFactory::Create(*refiner, vertex_stencil_options); // Varying stencils. // // TODO(sergey): Seems currently varying stencils are always required in // OpenSubdiv itself. const StencilTable *varying_stencils = nullptr; if (has_varying_data) { StencilTableFactory::Options varying_stencil_options; varying_stencil_options.generateOffsets = stencil_generate_offsets; varying_stencil_options.generateIntermediateLevels = stencil_generate_intermediate_levels; varying_stencil_options.interpolationMode = StencilTableFactory::INTERPOLATE_VARYING; varying_stencils = StencilTableFactory::Create(*refiner, varying_stencil_options); } // Face warying stencil. std::vector all_face_varying_stencils; all_face_varying_stencils.reserve(num_face_varying_channels); for (int face_varying_channel = 0; face_varying_channel < num_face_varying_channels; ++face_varying_channel) { StencilTableFactory::Options face_varying_stencil_options; face_varying_stencil_options.generateOffsets = stencil_generate_offsets; face_varying_stencil_options.generateIntermediateLevels = stencil_generate_intermediate_levels; face_varying_stencil_options.interpolationMode = StencilTableFactory::INTERPOLATE_FACE_VARYING; face_varying_stencil_options.fvarChannel = face_varying_channel; all_face_varying_stencils.push_back( StencilTableFactory::Create(*refiner, face_varying_stencil_options)); } // Generate bi-cubic patch table for the limit surface. PatchTableFactory::Options patch_options(level); patch_options.SetEndCapType(PatchTableFactory::Options::ENDCAP_GREGORY_BASIS); patch_options.useInfSharpPatch = use_inf_sharp_patch; patch_options.generateFVarTables = has_face_varying_data; patch_options.generateFVarLegacyLinearPatches = false; const PatchTable *patch_table = PatchTableFactory::Create(*refiner, patch_options); // Append local points stencils. // Point stencils. const StencilTable *local_point_stencil_table = patch_table->GetLocalPointStencilTable(); if (local_point_stencil_table != nullptr) { const StencilTable *table = StencilTableFactory::AppendLocalPointStencilTable( *refiner, vertex_stencils, local_point_stencil_table); delete_stencil_table(vertex_stencils); vertex_stencils = table; } // Varying stencils. if (has_varying_data) { const StencilTable *local_point_varying_stencil_table = patch_table->GetLocalPointVaryingStencilTable(); if (local_point_varying_stencil_table != nullptr) { const StencilTable *table = StencilTableFactory::AppendLocalPointStencilTable( *refiner, varying_stencils, local_point_varying_stencil_table); delete_stencil_table(varying_stencils); varying_stencils = table; } } for (int face_varying_channel = 0; face_varying_channel < num_face_varying_channels; ++face_varying_channel) { const StencilTable *table = StencilTableFactory::AppendLocalPointStencilTableFaceVarying( *refiner, all_face_varying_stencils[face_varying_channel], patch_table->GetLocalPointFaceVaryingStencilTable(face_varying_channel), face_varying_channel); if (table != nullptr) { delete_stencil_table(all_face_varying_stencils[face_varying_channel]); all_face_varying_stencils[face_varying_channel] = table; } } // Create OpenSubdiv's CPU side evaluator. blender::opensubdiv::EvalOutputAPI::EvalOutput *eval_output = nullptr; const bool use_gpu_evaluator = evaluator_type == OPENSUBDIV_EVALUATOR_GPU; if (use_gpu_evaluator) { blender::opensubdiv::GpuEvalOutput::EvaluatorCache *evaluator_cache = nullptr; if (evaluator_cache_descr) { evaluator_cache = static_cast( evaluator_cache_descr->impl->eval_cache); } eval_output = new blender::opensubdiv::GpuEvalOutput(vertex_stencils, varying_stencils, all_face_varying_stencils, 2, patch_table, evaluator_cache); } else { eval_output = new blender::opensubdiv::CpuEvalOutput( vertex_stencils, varying_stencils, all_face_varying_stencils, 2, patch_table); } blender::opensubdiv::PatchMap *patch_map = new blender::opensubdiv::PatchMap(*patch_table); // Wrap everything we need into an object which we control from our side. OpenSubdiv_Evaluator *evaluator = new OpenSubdiv_Evaluator(); evaluator->type = evaluator_type; evaluator->eval_output = new blender::opensubdiv::EvalOutputAPI(eval_output, patch_map); evaluator->patch_map = patch_map; evaluator->patch_table = patch_table; // TODO(sergey): Look into whether we've got duplicated stencils arrays. delete_stencil_table(vertex_stencils); delete_stencil_table(varying_stencils); for (const StencilTable *table : all_face_varying_stencils) { delete_stencil_table(table); } return evaluator; }