Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,160 @@
# SPDX-FileCopyrightText: 2013 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
.
../../source/blender/gpu/intern
)
set(INC_SYS
)
set(SRC
opensubdiv_capi.hh
opensubdiv_capi_type.hh
opensubdiv_converter_capi.hh
opensubdiv_evaluator_capi.hh
opensubdiv_evaluator.hh
opensubdiv_topology_refiner.hh
)
set(LIB
PRIVATE bf::blenlib
PRIVATE bf::intern::guardedalloc
PRIVATE bf::gpu
)
if(WITH_OPENSUBDIV)
function(OPENSUBDIV_DEFINE_COMPONENT component)
if(${${component}})
add_definitions(-D${component})
endif()
endfunction()
list(APPEND SRC
# Base.
internal/base/memory.h
internal/base/opensubdiv_capi.cc
internal/base/type_convert.cc
internal/base/type_convert.h
internal/base/util.cc
internal/base/util.h
# Evaluator.
internal/evaluator/eval_output.cc
internal/evaluator/eval_output.h
internal/evaluator/eval_output_cpu.cc
internal/evaluator/eval_output_cpu.h
internal/evaluator/evaluator_cache_impl.cc
internal/evaluator/evaluator_cache_impl.h
internal/evaluator/evaluator_capi.cc
internal/evaluator/evaluator_impl.cc
internal/evaluator/patch_map.cc
internal/evaluator/patch_map.h
# Topology.
internal/topology/mesh_topology.cc
internal/topology/mesh_topology_compare.cc
internal/topology/mesh_topology.h
internal/topology/topology_refiner_factory.cc
internal/topology/topology_refiner_impl.cc
internal/topology/topology_refiner_impl_compare.cc
)
list(APPEND LIB
bf::dependencies::optional::opensubdiv
)
if(NOT WITH_WEB)
list(APPEND SRC
internal/evaluator/eval_output_gpu.cc
internal/evaluator/eval_output_gpu.h
internal/evaluator/gpu_compute_evaluator.cc
internal/evaluator/gpu_compute_evaluator.h
internal/evaluator/gpu_patch_table.cc
internal/evaluator/gpu_patch_table.hh
internal/evaluator/gpu_vertex_buffer_wrapper.hh
)
list(APPEND LIB bf::dependencies::epoxy)
endif()
if(WITH_METAL_BACKEND)
add_definitions(-DWITH_METAL_BACKEND)
endif()
if(WITH_OPENGL_BACKEND)
add_definitions(-DWITH_OPENGL_BACKEND)
endif()
if(WITH_VULKAN_BACKEND)
add_definitions(-DWITH_VULKAN_BACKEND)
endif()
if(NOT WITH_WEB)
set(GLSL_SRC
internal/evaluator/shaders/osd_eval_stencils_comp.glsl
internal/evaluator/shaders/osd_eval_patches_comp.glsl
)
set(GLSL_INC
# For osd_patch_basis.glsl
../../source/blender/draw/intern/shaders
internal/evaluator/shaders
)
set(GLSL_C "")
foreach(GLSL_FILE ${GLSL_SRC})
glsl_to_c(${GLSL_FILE} GLSL_C GLSL_INC)
endforeach()
blender_add_lib(bf_osd_shaders "${GLSL_C}" "" "" "")
list(APPEND LIB
bf_osd_shaders
)
set(GLSL_SOURCE_CONTENT "")
set(GLSL_METADATA_CONTENT "")
set(GLSL_INFOS_CONTENT "")
foreach(GLSL_FILE ${GLSL_SRC})
get_filename_component(GLSL_FILE_NAME ${GLSL_FILE} NAME)
string(REPLACE "." "_" GLSL_FILE_NAME_UNDERSCORES ${GLSL_FILE_NAME})
string(APPEND GLSL_FILE_NAME_UNDERSCORES "_tmp")
string(APPEND GLSL_SOURCE_CONTENT "SHADER_SOURCE\(${GLSL_FILE_NAME_UNDERSCORES}, \"${GLSL_FILE_NAME}\", \"${GLSL_FILE}\"\)\n")
string(APPEND GLSL_METADATA_CONTENT "#include \"${GLSL_FILE}.hh\"\n")
string(APPEND GLSL_INFOS_CONTENT "#include \"${GLSL_FILE}.info\"\n")
endforeach()
set(glsl_source_list_file "${CMAKE_CURRENT_BINARY_DIR}/glsl_osd_source_list.h")
file(GENERATE OUTPUT ${glsl_source_list_file} CONTENT "${GLSL_SOURCE_CONTENT}")
list(APPEND SRC ${glsl_source_list_file})
set(glsl_metadata_list_file "${CMAKE_CURRENT_BINARY_DIR}/glsl_osd_metadata_list.hh")
file(GENERATE OUTPUT ${glsl_metadata_list_file} CONTENT "${GLSL_METADATA_CONTENT}")
list(APPEND SRC ${glsl_metadata_list_file})
set(glsl_infos_list_file "${CMAKE_CURRENT_BINARY_DIR}/glsl_osd_infos_list.hh")
file(GENERATE OUTPUT ${glsl_infos_list_file} CONTENT "${GLSL_INFOS_CONTENT}")
list(APPEND SRC ${glsl_infos_list_file})
list(APPEND INC ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(bf_osd_shaders PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
endif()
else()
list(APPEND SRC
stub/opensubdiv_stub.cc
stub/opensubdiv_evaluator_stub.cc
)
endif()
blender_add_lib(bf_intern_opensubdiv "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# Tests.
if(WITH_GTESTS AND WITH_OPENSUBDIV)
set(TEST_SRC
internal/topology/mesh_topology_test.cc
)
blender_add_test_suite_lib(intern_opensubdiv "${TEST_SRC}" "${INC}" "${INC_SYS}" "${LIB};bf_intern_opensubdiv")
endif()

View File

@@ -0,0 +1,14 @@
/* SPDX-FileCopyrightText: 2020 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef OPENSUBDIV_BASE_MEMORY_H_
#define OPENSUBDIV_BASE_MEMORY_H_
#include "MEM_guardedalloc.h"
namespace blender::opensubdiv {
} // namespace blender::opensubdiv
#endif // OPENSUBDIV_BASE_MEMORY_H_

View File

@@ -0,0 +1,39 @@
/* SPDX-FileCopyrightText: 2013 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "opensubdiv_capi.hh"
#include "opensubdiv/version.h"
#ifdef _MSC_VER
# include <iso646.h>
#endif
void openSubdiv_init() {}
void openSubdiv_cleanup() {}
int openSubdiv_getVersionHex()
{
#if defined(OPENSUBDIV_VERSION_NUMBER)
return OPENSUBDIV_VERSION_NUMBER;
#elif defined(OPENSUBDIV_VERSION_MAJOR)
return OPENSUBDIV_VERSION_MAJOR * 10000 + OPENSUBDIV_VERSION_MINOR * 100 +
OPENSUBDIV_VERSION_PATCH;
#elif defined(OPENSUBDIV_VERSION)
const char *version = STRINGIFY(OPENSUBDIV_VERSION);
if (version[0] == 'v') {
version += 1;
}
int major = 0, minor = 0, patch = 0;
vector<string> tokens;
blender::opensubdiv::stringSplit(&tokens, version, "_", true);
if (tokens.size() == 3) {
major = atoi(tokens[0].c_str());
minor = atoi(tokens[1].c_str());
patch = atoi(tokens[2].c_str());
}
return major * 10000 + minor * 100 + patch;
#else
return 0;
#endif
}

View File

@@ -0,0 +1,93 @@
/* SPDX-FileCopyrightText: 2018 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#ifdef _MSC_VER
# include <iso646.h>
#endif
#include "internal/base/type_convert.h"
#include <cassert>
#include <opensubdiv/sdc/crease.h>
namespace blender::opensubdiv {
OpenSubdiv::Sdc::SchemeType getSchemeTypeFromCAPI(OpenSubdiv_SchemeType type)
{
switch (type) {
case OSD_SCHEME_BILINEAR:
return OpenSubdiv::Sdc::SCHEME_BILINEAR;
case OSD_SCHEME_CATMARK:
return OpenSubdiv::Sdc::SCHEME_CATMARK;
case OSD_SCHEME_LOOP:
return OpenSubdiv::Sdc::SCHEME_LOOP;
}
assert(!"Unknown scheme type passed via C-API");
return OpenSubdiv::Sdc::SCHEME_CATMARK;
}
OpenSubdiv::Sdc::Options::FVarLinearInterpolation getFVarLinearInterpolationFromCAPI(
OpenSubdiv_FVarLinearInterpolation linear_interpolation)
{
using Options = OpenSubdiv::Sdc::Options;
switch (linear_interpolation) {
case OSD_FVAR_LINEAR_INTERPOLATION_NONE:
return Options::FVAR_LINEAR_NONE;
case OSD_FVAR_LINEAR_INTERPOLATION_CORNERS_ONLY:
return Options::FVAR_LINEAR_CORNERS_ONLY;
case OSD_FVAR_LINEAR_INTERPOLATION_CORNERS_PLUS1:
return Options::FVAR_LINEAR_CORNERS_PLUS1;
case OSD_FVAR_LINEAR_INTERPOLATION_CORNERS_PLUS2:
return Options::FVAR_LINEAR_CORNERS_PLUS2;
case OSD_FVAR_LINEAR_INTERPOLATION_BOUNDARIES:
return Options::FVAR_LINEAR_BOUNDARIES;
case OSD_FVAR_LINEAR_INTERPOLATION_ALL:
return Options::FVAR_LINEAR_ALL;
}
assert(!"Unknown fvar linear interpolation passed via C-API");
return Options::FVAR_LINEAR_NONE;
}
OpenSubdiv_FVarLinearInterpolation getCAPIFVarLinearInterpolationFromOSD(
OpenSubdiv::Sdc::Options::FVarLinearInterpolation linear_interpolation)
{
using Options = OpenSubdiv::Sdc::Options;
switch (linear_interpolation) {
case Options::FVAR_LINEAR_NONE:
return OSD_FVAR_LINEAR_INTERPOLATION_NONE;
case Options::FVAR_LINEAR_CORNERS_ONLY:
return OSD_FVAR_LINEAR_INTERPOLATION_CORNERS_ONLY;
case Options::FVAR_LINEAR_CORNERS_PLUS1:
return OSD_FVAR_LINEAR_INTERPOLATION_CORNERS_PLUS1;
case Options::FVAR_LINEAR_CORNERS_PLUS2:
return OSD_FVAR_LINEAR_INTERPOLATION_CORNERS_PLUS2;
case Options::FVAR_LINEAR_BOUNDARIES:
return OSD_FVAR_LINEAR_INTERPOLATION_BOUNDARIES;
case Options::FVAR_LINEAR_ALL:
return OSD_FVAR_LINEAR_INTERPOLATION_ALL;
}
assert(!"Unknown fvar linear interpolation passed via C-API");
return OSD_FVAR_LINEAR_INTERPOLATION_NONE;
}
OpenSubdiv::Sdc::Options::VtxBoundaryInterpolation getVtxBoundaryInterpolationFromCAPI(
OpenSubdiv_VtxBoundaryInterpolation boundary_interpolation)
{
using OpenSubdiv::Sdc::Options;
switch (boundary_interpolation) {
case OSD_VTX_BOUNDARY_NONE:
return Options::VTX_BOUNDARY_NONE;
case OSD_VTX_BOUNDARY_EDGE_ONLY:
return Options::VTX_BOUNDARY_EDGE_ONLY;
case OSD_VTX_BOUNDARY_EDGE_AND_CORNER:
return Options::VTX_BOUNDARY_EDGE_AND_CORNER;
}
assert(!"Unknown veretx boundary interpolation.");
return Options::VTX_BOUNDARY_EDGE_ONLY;
}
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,40 @@
/* SPDX-FileCopyrightText: 2018 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#ifndef OPENSUBDIV_BASE_TYPE_CONVERT_H_
#define OPENSUBDIV_BASE_TYPE_CONVERT_H_
#ifdef _MSC_VER
# include <iso646.h>
#endif
#include <opensubdiv/sdc/options.h>
#include <opensubdiv/sdc/types.h>
#include "opensubdiv_capi_type.hh"
struct OpenSubdiv_Converter;
namespace blender::opensubdiv {
// Convert scheme type from C-API enum to an OpenSubdiv native enum.
OpenSubdiv::Sdc::SchemeType getSchemeTypeFromCAPI(OpenSubdiv_SchemeType type);
// Convert face-varying interpolation type from C-API to an OpenSubdiv
// native enum.
OpenSubdiv::Sdc::Options::FVarLinearInterpolation getFVarLinearInterpolationFromCAPI(
OpenSubdiv_FVarLinearInterpolation linear_interpolation);
// Similar to above, just other way around.
OpenSubdiv_FVarLinearInterpolation getCAPIFVarLinearInterpolationFromOSD(
OpenSubdiv::Sdc::Options::FVarLinearInterpolation linear_interpolation);
OpenSubdiv::Sdc::Options::VtxBoundaryInterpolation getVtxBoundaryInterpolationFromCAPI(
OpenSubdiv_VtxBoundaryInterpolation boundary_interpolation);
} // namespace blender::opensubdiv
#endif // OPENSUBDIV_BASE_TYPE_CONVERT_H_

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2013 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "internal/base/util.h"
namespace blender::opensubdiv {
void stringSplit(std::vector<std::string> *tokens,
const std::string &str,
const std::string &separators,
bool skip_empty)
{
size_t token_start = 0, token_length = 0;
for (size_t i = 0; i < str.length(); ++i) {
const char ch = str[i];
if (separators.find(ch) == std::string::npos) {
// Append non-separator char to a token.
++token_length;
}
else {
// Append current token to the list (if any).
if (token_length > 0 || !skip_empty) {
std::string token = str.substr(token_start, token_length);
tokens->push_back(token);
}
// Re-set token pointers.
token_start = i + 1;
token_length = 0;
}
}
// Append token which might be at the end of the std::string.
if ((token_length != 0) || (!skip_empty && token_start > 0 &&
separators.find(str[token_start - 1]) != std::string::npos))
{
std::string token = str.substr(token_start, token_length);
tokens->push_back(token);
}
}
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,20 @@
/* SPDX-FileCopyrightText: 2013 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef OPENSUBDIV_BASE_UTIL_H_
#define OPENSUBDIV_BASE_UTIL_H_
#include <string>
#include <vector>
namespace blender::opensubdiv {
void stringSplit(std::vector<std::string> *tokens,
const std::string &str,
const std::string &separators,
bool skip_empty);
} // namespace blender::opensubdiv
#endif // OPENSUBDIV_BASE_UTIL_H_

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2021 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#include "internal/evaluator/eval_output.h"
namespace blender::opensubdiv {
bool is_adaptive(const CpuPatchTable *patch_table)
{
return patch_table->GetPatchArrayBuffer()[0].GetDescriptor().IsAdaptive();
}
#ifndef WITH_WEB
bool is_adaptive(const GPUPatchTable *patch_table)
{
return patch_table->GetPatchArrays()[0].GetDescriptor().IsAdaptive();
}
#endif
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,668 @@
/* SPDX-FileCopyrightText: 2021 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#ifndef OPENSUBDIV_EVAL_OUTPUT_H_
#define OPENSUBDIV_EVAL_OUTPUT_H_
#include <opensubdiv/osd/cpuPatchTable.h>
#include <opensubdiv/osd/mesh.h>
#include <opensubdiv/osd/types.h>
#include "opensubdiv_evaluator.hh"
#include "opensubdiv_evaluator_capi.hh"
#ifndef WITH_WEB
# include "gpu_patch_table.hh"
#endif
using OpenSubdiv::Far::PatchTable;
using OpenSubdiv::Far::StencilTable;
using OpenSubdiv::Osd::BufferDescriptor;
using OpenSubdiv::Osd::CpuPatchTable;
using OpenSubdiv::Osd::PatchCoord;
namespace blender::opensubdiv {
// Base class for the implementation of the evaluators.
class EvalOutputAPI::EvalOutput {
public:
virtual ~EvalOutput() = default;
virtual void updateSettings(const OpenSubdiv_EvaluatorSettings *settings) = 0;
virtual void updateData(const float *src, int start_vertex, int num_vertices) = 0;
virtual void updateVaryingData(const float *src, int start_vertex, int num_vertices) = 0;
virtual void updateVertexData(const float *src, int start_vertex, int num_vertices) = 0;
virtual void updateFaceVaryingData(const int face_varying_channel,
const float *src,
int start_vertex,
int num_vertices) = 0;
virtual void refine() = 0;
// NOTE: P must point to a memory of at least float[3]*num_patch_coords.
virtual void evalPatches(const PatchCoord *patch_coord,
const int num_patch_coords,
float *P) = 0;
// NOTE: P, dPdu, dPdv must point to a memory of at least float[3]*num_patch_coords.
virtual void evalPatchesWithDerivatives(const PatchCoord *patch_coord,
const int num_patch_coords,
float *P,
float *dPdu,
float *dPdv) = 0;
// NOTE: varying must point to a memory of at least float[3]*num_patch_coords.
virtual void evalPatchesVarying(const PatchCoord *patch_coord,
const int num_patch_coords,
float *varying) = 0;
// NOTE: vertex_data must point to a memory of at least float*num_vertex_data.
virtual void evalPatchesVertexData(const PatchCoord *patch_coord,
const int num_patch_coords,
float *vertex_data) = 0;
virtual void evalPatchesFaceVarying(const int face_varying_channel,
const PatchCoord *patch_coord,
const int num_patch_coords,
float face_varying[2]) = 0;
// The following interfaces are dependant on the actual evaluator type (CPU, OpenGL, etc.) which
// have slightly different APIs to access patch arrays, as well as different types for their
// data structure. They need to be overridden in the specific instances of the EvalOutput derived
// classes if needed, while the interfaces above are overridden through VolatileEvalOutput.
virtual gpu::StorageBuf *create_patch_arrays_buf()
{
return nullptr;
}
virtual gpu::StorageBuf *get_patch_index_buf()
{
return nullptr;
}
virtual gpu::StorageBuf *get_patch_param_buf()
{
return nullptr;
}
virtual gpu::VertBuf *get_source_buf()
{
return nullptr;
}
virtual gpu::VertBuf *get_source_data_buf()
{
return nullptr;
}
virtual gpu::StorageBuf *create_face_varying_patch_array_buf(const int /*face_varying_channel*/)
{
return nullptr;
}
virtual gpu::StorageBuf *get_face_varying_patch_index_buf(const int /*face_varying_channel*/)
{
return nullptr;
}
virtual gpu::StorageBuf *get_face_varying_patch_param_buf(const int /*face_varying_channel*/)
{
return nullptr;
}
virtual gpu::VertBuf *get_face_varying_source_buf(const int /*face_varying_channel*/)
{
return nullptr;
}
virtual int get_face_varying_source_offset(const int /*face_varying_channel*/) const
{
return 0;
}
virtual bool hasVertexData() const
{
return false;
}
};
// Buffer which implements API required by OpenSubdiv and uses an existing memory as an underlying
// storage.
template<typename T> class RawDataWrapperBuffer {
public:
RawDataWrapperBuffer(T *data) : data_(data) {}
T *BindCpuBuffer()
{
return data_;
}
gpu::VertBuf *get_vertex_buffer()
{
return nullptr;
}
// TODO(sergey): Support UpdateData().
protected:
T *data_;
};
template<typename T> class RawDataWrapperVertexBuffer : public RawDataWrapperBuffer<T> {
public:
RawDataWrapperVertexBuffer(T *data, int num_vertices)
: RawDataWrapperBuffer<T>(data), num_vertices_(num_vertices)
{
}
int GetNumVertices()
{
return num_vertices_;
}
protected:
int num_vertices_;
};
class ConstPatchCoordWrapperBuffer : public RawDataWrapperVertexBuffer<const PatchCoord> {
public:
ConstPatchCoordWrapperBuffer(const PatchCoord *data, int num_vertices)
: RawDataWrapperVertexBuffer(data, num_vertices)
{
}
};
// Discriminators used in FaceVaryingVolatileEval in order to detect whether we are using adaptive
// patches as the CPU and OpenGL PatchTable have different APIs.
bool is_adaptive(const CpuPatchTable *patch_table);
#ifndef WITH_WEB
bool is_adaptive(const GPUPatchTable *patch_table);
#endif
template<typename EVAL_VERTEX_BUFFER,
typename STENCIL_TABLE,
typename PATCH_TABLE,
typename EVALUATOR,
typename DEVICE_CONTEXT = void>
class FaceVaryingVolatileEval {
public:
using EvaluatorCache = OpenSubdiv::Osd::EvaluatorCacheT<EVALUATOR>;
FaceVaryingVolatileEval(int face_varying_channel,
const StencilTable *face_varying_stencils,
int face_varying_width,
PATCH_TABLE *patch_table,
EvaluatorCache *evaluator_cache = NULL,
DEVICE_CONTEXT *device_context = NULL)
: face_varying_channel_(face_varying_channel),
src_face_varying_desc_(0, face_varying_width, face_varying_width),
patch_table_(patch_table),
evaluator_cache_(evaluator_cache),
device_context_(device_context)
{
using OpenSubdiv::Osd::convertToCompatibleStencilTable;
num_coarse_face_varying_vertices_ = face_varying_stencils->GetNumControlVertices();
const int num_total_face_varying_vertices = face_varying_stencils->GetNumControlVertices() +
face_varying_stencils->GetNumStencils();
src_face_varying_data_ = EVAL_VERTEX_BUFFER::Create(
2, num_total_face_varying_vertices, device_context);
face_varying_stencils_ = convertToCompatibleStencilTable<STENCIL_TABLE>(face_varying_stencils,
device_context_);
}
~FaceVaryingVolatileEval()
{
delete src_face_varying_data_;
delete face_varying_stencils_;
}
void updateData(const float *src, int start_vertex, int num_vertices)
{
src_face_varying_data_->UpdateData(src, start_vertex, num_vertices, device_context_);
}
void refine()
{
BufferDescriptor dst_face_varying_desc = src_face_varying_desc_;
dst_face_varying_desc.offset += num_coarse_face_varying_vertices_ *
src_face_varying_desc_.stride;
EVALUATOR *eval_instance = OpenSubdiv::Osd::GetEvaluator<EVALUATOR>(
evaluator_cache_, src_face_varying_desc_, dst_face_varying_desc, device_context_);
// in and out points to same buffer so output is put directly after coarse vertices, needed in
// adaptive mode
EVALUATOR::EvalStencils(src_face_varying_data_,
src_face_varying_desc_,
src_face_varying_data_,
dst_face_varying_desc,
face_varying_stencils_,
eval_instance,
device_context_);
}
// NOTE: face_varying must point to a memory of at least float[2]*num_patch_coords.
void evalPatches(const PatchCoord *patch_coord, const int num_patch_coords, float *face_varying)
{
RawDataWrapperBuffer<float> face_varying_data(face_varying);
BufferDescriptor face_varying_desc(0, 2, 2);
ConstPatchCoordWrapperBuffer patch_coord_buffer(patch_coord, num_patch_coords);
EVALUATOR *eval_instance = OpenSubdiv::Osd::GetEvaluator<EVALUATOR>(
evaluator_cache_, src_face_varying_desc_, face_varying_desc, device_context_);
BufferDescriptor src_desc = get_src_varying_desc();
EVALUATOR::EvalPatchesFaceVarying(src_face_varying_data_,
src_desc,
&face_varying_data,
face_varying_desc,
patch_coord_buffer.GetNumVertices(),
&patch_coord_buffer,
patch_table_,
face_varying_channel_,
eval_instance,
device_context_);
}
EVAL_VERTEX_BUFFER *getSrcBuffer() const
{
return src_face_varying_data_;
}
int get_face_varying_source_offset() const
{
BufferDescriptor src_desc = get_src_varying_desc();
return src_desc.offset;
}
PATCH_TABLE *getPatchTable() const
{
return patch_table_;
}
private:
BufferDescriptor get_src_varying_desc() const
{
// src_face_varying_data_ always contains coarse vertices at the beginning.
// In adaptive mode they are followed by number of blocks for intermediate
// subdivision levels, and this is what OSD expects in this mode.
// In non-adaptive mode (generateIntermediateLevels == false),
// they are followed by max subdivision level, but they break interpolation as OSD
// expects only one subd level in this buffer.
// So in non-adaptive mode we put offset into buffer descriptor to skip over coarse vertices.
BufferDescriptor src_desc = src_face_varying_desc_;
if (!is_adaptive(patch_table_)) {
src_desc.offset += num_coarse_face_varying_vertices_ * src_face_varying_desc_.stride;
}
return src_desc;
}
protected:
int face_varying_channel_;
BufferDescriptor src_face_varying_desc_;
int num_coarse_face_varying_vertices_;
EVAL_VERTEX_BUFFER *src_face_varying_data_;
const STENCIL_TABLE *face_varying_stencils_;
// NOTE: We reference this, do not own it.
PATCH_TABLE *patch_table_;
EvaluatorCache *evaluator_cache_;
DEVICE_CONTEXT *device_context_;
};
// Volatile evaluator which can be used from threads.
//
// TODO(sergey): Make it possible to evaluate coordinates in chunks.
// TODO(sergey): Make it possible to evaluate multiple face varying layers.
// (or maybe, it's cheap to create new evaluator for existing
// topology to evaluate all needed face varying layers?)
template<typename SRC_VERTEX_BUFFER,
typename EVAL_VERTEX_BUFFER,
typename STENCIL_TABLE,
typename PATCH_TABLE,
typename EVALUATOR,
typename DEVICE_CONTEXT = void>
class VolatileEvalOutput : public EvalOutputAPI::EvalOutput {
public:
using EvaluatorCache = OpenSubdiv::Osd::EvaluatorCacheT<EVALUATOR>;
using FaceVaryingEval = FaceVaryingVolatileEval<EVAL_VERTEX_BUFFER,
STENCIL_TABLE,
PATCH_TABLE,
EVALUATOR,
DEVICE_CONTEXT>;
VolatileEvalOutput(const StencilTable *vertex_stencils,
const StencilTable *varying_stencils,
const std::vector<const StencilTable *> &all_face_varying_stencils,
const int face_varying_width,
const PatchTable *patch_table,
EvaluatorCache *evaluator_cache = NULL,
DEVICE_CONTEXT *device_context = NULL)
: src_vertex_data_(NULL),
src_desc_(0, 3, 3),
src_varying_desc_(0, 3, 3),
src_vertex_data_desc_(0, 0, 0),
face_varying_width_(face_varying_width),
evaluator_cache_(evaluator_cache),
device_context_(device_context)
{
// Total number of vertices = coarse points + refined points + local points.
int num_total_vertices = vertex_stencils->GetNumControlVertices() +
vertex_stencils->GetNumStencils();
num_coarse_vertices_ = vertex_stencils->GetNumControlVertices();
using OpenSubdiv::Osd::convertToCompatibleStencilTable;
src_data_ = SRC_VERTEX_BUFFER::Create(3, num_total_vertices, device_context_);
src_varying_data_ = SRC_VERTEX_BUFFER::Create(3, num_total_vertices, device_context_);
patch_table_ = PATCH_TABLE::Create(patch_table, device_context_);
vertex_stencils_ = convertToCompatibleStencilTable<STENCIL_TABLE>(vertex_stencils,
device_context_);
varying_stencils_ = convertToCompatibleStencilTable<STENCIL_TABLE>(varying_stencils,
device_context_);
// Create evaluators for every face varying channel.
face_varying_evaluators_.reserve(all_face_varying_stencils.size());
int face_varying_channel = 0;
for (const StencilTable *face_varying_stencils : all_face_varying_stencils) {
face_varying_evaluators_.push_back(new FaceVaryingEval(face_varying_channel,
face_varying_stencils,
face_varying_width,
patch_table_,
evaluator_cache_,
device_context_));
++face_varying_channel;
}
}
~VolatileEvalOutput() override
{
delete src_data_;
delete src_varying_data_;
delete src_vertex_data_;
delete patch_table_;
delete vertex_stencils_;
delete varying_stencils_;
for (FaceVaryingEval *face_varying_evaluator : face_varying_evaluators_) {
delete face_varying_evaluator;
}
}
void updateSettings(const OpenSubdiv_EvaluatorSettings *settings) override
{
// Optionally allocate additional data to be subdivided like vertex coordinates.
if (settings->num_vertex_data != src_vertex_data_desc_.length) {
delete src_vertex_data_;
if (settings->num_vertex_data > 0) {
src_vertex_data_ = SRC_VERTEX_BUFFER::Create(
settings->num_vertex_data, src_data_->GetNumVertices(), device_context_);
}
else {
src_vertex_data_ = NULL;
}
src_vertex_data_desc_ = BufferDescriptor(
0, settings->num_vertex_data, settings->num_vertex_data);
}
}
// TODO(sergey): Implement binding API.
void updateData(const float *src, int start_vertex, int num_vertices) override
{
src_data_->UpdateData(src, start_vertex, num_vertices, device_context_);
}
void updateVaryingData(const float *src, int start_vertex, int num_vertices) override
{
src_varying_data_->UpdateData(src, start_vertex, num_vertices, device_context_);
}
void updateVertexData(const float *src, int start_vertex, int num_vertices) override
{
src_vertex_data_->UpdateData(src, start_vertex, num_vertices, device_context_);
}
void updateFaceVaryingData(const int face_varying_channel,
const float *src,
int start_vertex,
int num_vertices) override
{
assert(face_varying_channel >= 0);
assert(face_varying_channel < face_varying_evaluators_.size());
face_varying_evaluators_[face_varying_channel]->updateData(src, start_vertex, num_vertices);
}
bool hasVaryingData() const
{
// return varying_stencils_ != NULL;
// TODO(sergey): Check this based on actual topology.
return false;
}
bool hasFaceVaryingData() const
{
return face_varying_evaluators_.size() != 0;
}
bool hasVertexData() const override
{
return src_vertex_data_ != nullptr;
}
void refine() override
{
// Evaluate vertex positions.
BufferDescriptor dst_desc = src_desc_;
dst_desc.offset += num_coarse_vertices_ * src_desc_.stride;
EVALUATOR *eval_instance = OpenSubdiv::Osd::GetEvaluator<EVALUATOR>(
evaluator_cache_, src_desc_, dst_desc, device_context_);
EVALUATOR::EvalStencils(src_data_,
src_desc_,
src_data_,
dst_desc,
vertex_stencils_,
eval_instance,
device_context_);
// Evaluate smoothly interpolated vertex data.
if (src_vertex_data_) {
BufferDescriptor dst_vertex_data_desc = src_vertex_data_desc_;
dst_vertex_data_desc.offset += num_coarse_vertices_ * src_vertex_data_desc_.stride;
EVALUATOR *eval_instance = OpenSubdiv::Osd::GetEvaluator<EVALUATOR>(
evaluator_cache_, src_vertex_data_desc_, dst_vertex_data_desc, device_context_);
EVALUATOR::EvalStencils(src_vertex_data_,
src_vertex_data_desc_,
src_vertex_data_,
dst_vertex_data_desc,
vertex_stencils_,
eval_instance,
device_context_);
}
// Evaluate varying data.
if (hasVaryingData()) {
BufferDescriptor dst_varying_desc = src_varying_desc_;
dst_varying_desc.offset += num_coarse_vertices_ * src_varying_desc_.stride;
eval_instance = OpenSubdiv::Osd::GetEvaluator<EVALUATOR>(
evaluator_cache_, src_varying_desc_, dst_varying_desc, device_context_);
EVALUATOR::EvalStencils(src_varying_data_,
src_varying_desc_,
src_varying_data_,
dst_varying_desc,
varying_stencils_,
eval_instance,
device_context_);
}
// Evaluate face-varying data.
if (hasFaceVaryingData()) {
for (FaceVaryingEval *face_varying_evaluator : face_varying_evaluators_) {
face_varying_evaluator->refine();
}
}
}
// NOTE: P must point to a memory of at least float[3]*num_patch_coords.
void evalPatches(const PatchCoord *patch_coord, const int num_patch_coords, float *P) override
{
RawDataWrapperBuffer<float> P_data(P);
// TODO(sergey): Support interleaved vertex-varying data.
BufferDescriptor P_desc(0, 3, 3);
ConstPatchCoordWrapperBuffer patch_coord_buffer(patch_coord, num_patch_coords);
EVALUATOR *eval_instance = OpenSubdiv::Osd::GetEvaluator<EVALUATOR>(
evaluator_cache_, src_desc_, P_desc, device_context_);
EVALUATOR::EvalPatches(src_data_,
src_desc_,
&P_data,
P_desc,
patch_coord_buffer.GetNumVertices(),
&patch_coord_buffer,
patch_table_,
eval_instance,
device_context_);
}
// NOTE: P, dPdu, dPdv must point to a memory of at least float[3]*num_patch_coords.
void evalPatchesWithDerivatives(const PatchCoord *patch_coord,
const int num_patch_coords,
float *P,
float *dPdu,
float *dPdv) override
{
assert(dPdu);
assert(dPdv);
RawDataWrapperBuffer<float> P_data(P);
RawDataWrapperBuffer<float> dPdu_data(dPdu), dPdv_data(dPdv);
// TODO(sergey): Support interleaved vertex-varying data.
BufferDescriptor P_desc(0, 3, 3);
BufferDescriptor dpDu_desc(0, 3, 3), pPdv_desc(0, 3, 3);
ConstPatchCoordWrapperBuffer patch_coord_buffer(patch_coord, num_patch_coords);
EVALUATOR *eval_instance = OpenSubdiv::Osd::GetEvaluator<EVALUATOR>(
evaluator_cache_, src_desc_, P_desc, dpDu_desc, pPdv_desc, device_context_);
EVALUATOR::EvalPatches(src_data_,
src_desc_,
&P_data,
P_desc,
&dPdu_data,
dpDu_desc,
&dPdv_data,
pPdv_desc,
patch_coord_buffer.GetNumVertices(),
&patch_coord_buffer,
patch_table_,
eval_instance,
device_context_);
}
// NOTE: varying must point to a memory of at least float[3]*num_patch_coords.
void evalPatchesVarying(const PatchCoord *patch_coord,
const int num_patch_coords,
float *varying) override
{
RawDataWrapperBuffer<float> varying_data(varying);
BufferDescriptor varying_desc(3, 3, 6);
ConstPatchCoordWrapperBuffer patch_coord_buffer(patch_coord, num_patch_coords);
EVALUATOR *eval_instance = OpenSubdiv::Osd::GetEvaluator<EVALUATOR>(
evaluator_cache_, src_varying_desc_, varying_desc, device_context_);
EVALUATOR::EvalPatchesVarying(src_varying_data_,
src_varying_desc_,
&varying_data,
varying_desc,
patch_coord_buffer.GetNumVertices(),
&patch_coord_buffer,
patch_table_,
eval_instance,
device_context_);
}
// NOTE: data must point to a memory of at least float*num_vertex_data.
void evalPatchesVertexData(const PatchCoord *patch_coord,
const int num_patch_coords,
float *data) override
{
RawDataWrapperBuffer<float> vertex_data(data);
BufferDescriptor vertex_desc(0, src_vertex_data_desc_.length, src_vertex_data_desc_.length);
ConstPatchCoordWrapperBuffer patch_coord_buffer(patch_coord, num_patch_coords);
EVALUATOR *eval_instance = OpenSubdiv::Osd::GetEvaluator<EVALUATOR>(
evaluator_cache_, src_vertex_data_desc_, vertex_desc, device_context_);
EVALUATOR::EvalPatches(src_vertex_data_,
src_vertex_data_desc_,
&vertex_data,
vertex_desc,
patch_coord_buffer.GetNumVertices(),
&patch_coord_buffer,
patch_table_,
eval_instance,
device_context_);
}
void evalPatchesFaceVarying(const int face_varying_channel,
const PatchCoord *patch_coord,
const int num_patch_coords,
float face_varying[2]) override
{
assert(face_varying_channel >= 0);
assert(face_varying_channel < face_varying_evaluators_.size());
face_varying_evaluators_[face_varying_channel]->evalPatches(
patch_coord, num_patch_coords, face_varying);
}
SRC_VERTEX_BUFFER *getSrcBuffer() const
{
return src_data_;
}
SRC_VERTEX_BUFFER *getSrcVertexDataBuffer() const
{
return src_vertex_data_;
}
PATCH_TABLE *getPatchTable() const
{
return patch_table_;
}
SRC_VERTEX_BUFFER *getFVarSrcBuffer(const int face_varying_channel) const
{
return face_varying_evaluators_[face_varying_channel]->getSrcBuffer();
}
int get_face_varying_source_offset(const int face_varying_channel) const override
{
return face_varying_evaluators_[face_varying_channel]->get_face_varying_source_offset();
}
PATCH_TABLE *getFVarPatchTable(const int face_varying_channel) const
{
return face_varying_evaluators_[face_varying_channel]->getPatchTable();
}
private:
SRC_VERTEX_BUFFER *src_data_;
SRC_VERTEX_BUFFER *src_varying_data_;
SRC_VERTEX_BUFFER *src_vertex_data_;
PATCH_TABLE *patch_table_;
BufferDescriptor src_desc_;
BufferDescriptor src_varying_desc_;
BufferDescriptor src_vertex_data_desc_;
int num_coarse_vertices_;
const STENCIL_TABLE *vertex_stencils_;
const STENCIL_TABLE *varying_stencils_;
int face_varying_width_;
std::vector<FaceVaryingEval *> face_varying_evaluators_;
EvaluatorCache *evaluator_cache_;
DEVICE_CONTEXT *device_context_;
};
} // namespace blender::opensubdiv
#endif // OPENSUBDIV_EVAL_OUTPUT_H_

View File

@@ -0,0 +1,9 @@
/* SPDX-FileCopyrightText: 2021 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
namespace blender::opensubdiv {
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,52 @@
/* SPDX-FileCopyrightText: 2021 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#ifndef OPENSUBDIV_EVAL_OUTPUT_CPU_H_
#define OPENSUBDIV_EVAL_OUTPUT_CPU_H_
#include "internal/evaluator/eval_output.h"
#include <opensubdiv/osd/cpuEvaluator.h>
#include <opensubdiv/osd/cpuPatchTable.h>
#include <opensubdiv/osd/cpuVertexBuffer.h>
using OpenSubdiv::Far::StencilTable;
using OpenSubdiv::Osd::CpuEvaluator;
using OpenSubdiv::Osd::CpuVertexBuffer;
namespace blender::opensubdiv {
// NOTE: Define as a class instead of typedef to make it possible
// to have anonymous class in opensubdiv_evaluator_internal.h
class CpuEvalOutput : public VolatileEvalOutput<CpuVertexBuffer,
CpuVertexBuffer,
StencilTable,
CpuPatchTable,
CpuEvaluator> {
public:
CpuEvalOutput(const StencilTable *vertex_stencils,
const StencilTable *varying_stencils,
const std::vector<const StencilTable *> &all_face_varying_stencils,
const int face_varying_width,
const PatchTable *patch_table,
EvaluatorCache *evaluator_cache = nullptr)
: VolatileEvalOutput<CpuVertexBuffer,
CpuVertexBuffer,
StencilTable,
CpuPatchTable,
CpuEvaluator>(vertex_stencils,
varying_stencils,
all_face_varying_stencils,
face_varying_width,
patch_table,
evaluator_cache)
{
}
};
} // namespace blender::opensubdiv
#endif // OPENSUBDIV_EVAL_OUTPUT_CPU_H_

View File

@@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2021 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#include "internal/evaluator/eval_output_gpu.h"
#include "opensubdiv_evaluator.hh"
#include "gpu_patch_table.hh"
using OpenSubdiv::Osd::PatchArray;
using OpenSubdiv::Osd::PatchArrayVector;
namespace blender::opensubdiv {
static gpu::StorageBuf *create_patch_array_buffer(const PatchArrayVector &patch_arrays)
{
const size_t patch_array_size = sizeof(PatchArray);
const size_t patch_array_byte_size = patch_array_size * patch_arrays.size();
gpu::StorageBuf *storage_buf = GPU_storagebuf_create_ex(
patch_array_byte_size, patch_arrays.data(), GPU_USAGE_STATIC, "osd_patch_array");
return storage_buf;
}
GpuEvalOutput::GpuEvalOutput(const StencilTable *vertex_stencils,
const StencilTable *varying_stencils,
const std::vector<const StencilTable *> &all_face_varying_stencils,
const int face_varying_width,
const PatchTable *patch_table,
VolatileEvalOutput::EvaluatorCache *evaluator_cache)
: VolatileEvalOutput<GPUVertexBuffer,
GPUVertexBuffer,
GPUStencilTableSSBO,
GPUPatchTable,
GPUComputeEvaluator>(vertex_stencils,
varying_stencils,
all_face_varying_stencils,
face_varying_width,
patch_table,
evaluator_cache)
{
}
gpu::StorageBuf *GpuEvalOutput::create_patch_arrays_buf()
{
GPUPatchTable *patch_table = getPatchTable();
return create_patch_array_buffer(patch_table->GetPatchArrays());
}
gpu::StorageBuf *GpuEvalOutput::create_face_varying_patch_array_buf(const int face_varying_channel)
{
GPUPatchTable *patch_table = getFVarPatchTable(face_varying_channel);
return create_patch_array_buffer(patch_table->GetFVarPatchArrays(face_varying_channel));
}
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,79 @@
/* SPDX-FileCopyrightText: 2021 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#ifndef OPENSUBDIV_EVAL_OUTPUT_GPU_H_
#define OPENSUBDIV_EVAL_OUTPUT_GPU_H_
#include "internal/evaluator/eval_output.h"
#include "internal/evaluator/gpu_compute_evaluator.h"
#include "internal/evaluator/gpu_patch_table.hh"
#include <opensubdiv/osd/glPatchTable.h>
#include <opensubdiv/osd/glVertexBuffer.h>
#include "gpu_vertex_buffer_wrapper.hh"
namespace blender::opensubdiv {
class GpuEvalOutput : public VolatileEvalOutput<GPUVertexBuffer,
GPUVertexBuffer,
GPUStencilTableSSBO,
GPUPatchTable,
GPUComputeEvaluator> {
public:
GpuEvalOutput(const StencilTable *vertex_stencils,
const StencilTable *varying_stencils,
const std::vector<const StencilTable *> &all_face_varying_stencils,
const int face_varying_width,
const PatchTable *patch_table,
EvaluatorCache *evaluator_cache = nullptr);
gpu::StorageBuf *create_patch_arrays_buf() override;
gpu::StorageBuf *get_patch_index_buf() override
{
return getPatchTable()->GetPatchIndexBuffer();
}
gpu::StorageBuf *get_patch_param_buf() override
{
return getPatchTable()->GetPatchParamBuffer();
}
gpu::VertBuf *get_source_buf() override
{
return getSrcBuffer()->get_vertex_buffer();
}
gpu::VertBuf *get_source_data_buf() override
{
return getSrcVertexDataBuffer()->get_vertex_buffer();
}
gpu::StorageBuf *create_face_varying_patch_array_buf(const int face_varying_channel) override;
gpu::StorageBuf *get_face_varying_patch_index_buf(const int face_varying_channel) override
{
GPUPatchTable *patch_table = getFVarPatchTable(face_varying_channel);
return patch_table->GetFVarPatchIndexBuffer(face_varying_channel);
}
gpu::StorageBuf *get_face_varying_patch_param_buf(const int face_varying_channel) override
{
GPUPatchTable *patch_table = getFVarPatchTable(face_varying_channel);
return patch_table->GetFVarPatchParamBuffer(face_varying_channel);
}
gpu::VertBuf *get_face_varying_source_buf(const int face_varying_channel) override
{
GPUVertexBuffer *vertex_buffer = getFVarSrcBuffer(face_varying_channel);
return vertex_buffer->get_vertex_buffer();
}
};
} // namespace blender::opensubdiv
#endif // OPENSUBDIV_EVAL_OUTPUT_GPU_H_

View File

@@ -0,0 +1,42 @@
/* SPDX-FileCopyrightText: 2021 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "internal/evaluator/evaluator_cache_impl.h"
#ifndef WITH_WEB
# include "internal/evaluator/eval_output_gpu.h"
#endif
OpenSubdiv_EvaluatorCacheImpl::OpenSubdiv_EvaluatorCacheImpl() = default;
OpenSubdiv_EvaluatorCacheImpl::~OpenSubdiv_EvaluatorCacheImpl()
{
#ifndef WITH_WEB
delete static_cast<blender::opensubdiv::GpuEvalOutput::EvaluatorCache *>(eval_cache);
#endif
}
OpenSubdiv_EvaluatorCacheImpl *openSubdiv_createEvaluatorCacheInternal(
eOpenSubdivEvaluator evaluator_type)
{
#ifdef WITH_WEB
(void)evaluator_type;
return nullptr;
#else
if (evaluator_type != eOpenSubdivEvaluator::OPENSUBDIV_EVALUATOR_GPU) {
return nullptr;
}
OpenSubdiv_EvaluatorCacheImpl *evaluator_cache;
evaluator_cache = new OpenSubdiv_EvaluatorCacheImpl;
blender::opensubdiv::GpuEvalOutput::EvaluatorCache *eval_cache;
eval_cache = new blender::opensubdiv::GpuEvalOutput::EvaluatorCache();
evaluator_cache->eval_cache = eval_cache;
return evaluator_cache;
#endif
}
void openSubdiv_deleteEvaluatorCacheInternal(OpenSubdiv_EvaluatorCacheImpl *evaluator_cache)
{
delete evaluator_cache;
}

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2021 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef OPENSUBDIV_EVALUATOR_CACHE_IMPL_H_
#define OPENSUBDIV_EVALUATOR_CACHE_IMPL_H_
#include "internal/base/memory.h"
#include "opensubdiv_capi_type.hh"
struct OpenSubdiv_EvaluatorCacheImpl {
public:
OpenSubdiv_EvaluatorCacheImpl();
~OpenSubdiv_EvaluatorCacheImpl();
void *eval_cache;
MEM_CXX_CLASS_ALLOC_FUNCS("OpenSubdiv_EvaluatorCacheImpl");
};
OpenSubdiv_EvaluatorCacheImpl *openSubdiv_createEvaluatorCacheInternal(
eOpenSubdivEvaluator evaluator_type);
void openSubdiv_deleteEvaluatorCacheInternal(OpenSubdiv_EvaluatorCacheImpl *evaluator_cache);
#endif // OPENSUBDIV_EVALUATOR_CACHE_IMPL_H_

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2015 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#include "opensubdiv_evaluator_capi.hh"
#ifdef WITH_METAL_BACKEND
# include <opensubdiv/osd/mtlPatchShaderSource.h>
#endif
#if defined(WITH_VULKAN_BACKEND) || defined(WITH_OPENGL_BACKEND)
# include <opensubdiv/osd/glslPatchShaderSource.h>
#endif
#include "MEM_guardedalloc.h"
#include "GPU_context.hh"
#include "internal/evaluator/evaluator_cache_impl.h"
OpenSubdiv_EvaluatorCache *openSubdiv_createEvaluatorCache(eOpenSubdivEvaluator evaluator_type)
{
OpenSubdiv_EvaluatorCache *evaluator_cache = MEM_new<OpenSubdiv_EvaluatorCache>(__func__);
evaluator_cache->impl = openSubdiv_createEvaluatorCacheInternal(evaluator_type);
return evaluator_cache;
}
void openSubdiv_deleteEvaluatorCache(OpenSubdiv_EvaluatorCache *evaluator_cache)
{
if (!evaluator_cache) {
return;
}
openSubdiv_deleteEvaluatorCacheInternal(evaluator_cache->impl);
MEM_delete(evaluator_cache);
}
const char *openSubdiv_getGLSLPatchBasisSource()
{
/* Using a global string to avoid dealing with memory allocation/ownership. */
static std::string patch_basis_source;
if (patch_basis_source.empty()) {
patch_basis_source =
"#define OsdPatchParam_host_shared_ OsdPatchParam\n"
"#define OsdPatchArray_host_shared_ OsdPatchArray\n"
"#define OsdPatchCoord_host_shared_ OsdPatchCoord\n";
#ifdef WITH_METAL_BACKEND
patch_basis_source += OpenSubdiv::Osd::MTLPatchShaderSource::GetPatchBasisShaderSource();
#endif
#if defined(WITH_OPENGL_BACKEND) || defined(WITH_VULKAN_BACKEND)
patch_basis_source += OpenSubdiv::Osd::GLSLPatchShaderSource::GetPatchBasisShaderSource();
#endif
}
return patch_basis_source.c_str();
}

View File

@@ -0,0 +1,589 @@
/* SPDX-FileCopyrightText: 2018 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#include <cassert>
#ifdef _MSC_VER
# include <iso646.h>
#endif
#include <opensubdiv/far/patchMap.h>
#include <opensubdiv/far/patchTable.h>
#include <opensubdiv/far/patchTableFactory.h>
#include <opensubdiv/osd/mesh.h>
#include <opensubdiv/osd/types.h>
#include <opensubdiv/version.h>
#include "internal/evaluator/eval_output_cpu.h"
#ifndef WITH_WEB
# include "internal/evaluator/eval_output_gpu.h"
#endif
#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<typename T, int kNumMaxElementsOnStack> 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<PatchCoord, 32 * 32>;
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<const float *>(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<const float *>(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<const float *>(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<PatchTable::PatchHandle> &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<PatchTable::PatchHandle> buffer_handles =
patch_map_handles->data<PatchTable::PatchHandle>();
memcpy(buffer_handles.data(), handles.data(), sizeof(PatchTable::PatchHandle) * handles.size());
const std::vector<PatchMap::QuadNode> &quadtree = patch_map_->nodes();
GPU_vertbuf_data_alloc(*patch_map_quadtree, quadtree.size());
MutableSpan<PatchMap::QuadNode> buffer_nodes = patch_map_quadtree->data<PatchMap::QuadNode>();
memcpy(buffer_nodes.data(), quadtree.data(), sizeof(PatchMap::QuadNode) * quadtree.size());
}
gpu::StorageBuf *EvalOutputAPI::create_patch_arrays_buf()
{
return implementation_->create_patch_arrays_buf();
}
gpu::StorageBuf *EvalOutputAPI::get_patch_index_buf()
{
return implementation_->get_patch_index_buf();
}
gpu::StorageBuf *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();
}
gpu::StorageBuf *EvalOutputAPI::create_face_varying_patch_array_buf(const int face_varying_channel)
{
return implementation_->create_face_varying_patch_array_buf(face_varying_channel);
}
gpu::StorageBuf *EvalOutputAPI::get_face_varying_patch_index_buf(const int face_varying_channel)
{
return implementation_->get_face_varying_patch_index_buf(face_varying_channel);
}
gpu::StorageBuf *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<StencilTableReal<float>, StencilTable>);
delete reinterpret_cast<const StencilTableReal<float> *>(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<const StencilTable *> 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);
if (table == nullptr) {
return nullptr;
}
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;
#ifdef WITH_WEB
(void)use_gpu_evaluator;
eval_output = new blender::opensubdiv::CpuEvalOutput(
vertex_stencils, varying_stencils, all_face_varying_stencils, 2, patch_table);
#else
if (use_gpu_evaluator) {
blender::opensubdiv::GpuEvalOutput::EvaluatorCache *evaluator_cache = nullptr;
if (evaluator_cache_descr) {
evaluator_cache = static_cast<blender::opensubdiv::GpuEvalOutput::EvaluatorCache *>(
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);
}
#endif
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;
}

View File

@@ -0,0 +1,515 @@
/* SPDX-FileCopyrightText: 2025 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <epoxy/gl.h>
#include "gpu_compute_evaluator.h"
#include <opensubdiv/far/error.h>
#include <opensubdiv/far/patchDescriptor.h>
#include <opensubdiv/far/stencilTable.h>
#include <cassert>
#include <cmath>
#include <sstream>
#include <string>
#include <vector>
#include "GPU_capabilities.hh"
#include "GPU_compute.hh"
#include "GPU_context.hh"
#include "GPU_debug.hh"
#include "GPU_state.hh"
#include "GPU_vertex_buffer.hh"
#include "gpu_shader_create_info.hh"
using OpenSubdiv::Far::LimitStencilTable;
using OpenSubdiv::Far::StencilTable;
using OpenSubdiv::Osd::BufferDescriptor;
using OpenSubdiv::Osd::PatchArray;
using OpenSubdiv::Osd::PatchArrayVector;
extern "C" char datatoc_osd_eval_patches_comp_glsl[];
extern "C" char datatoc_osd_eval_stencils_comp_glsl[];
#define SHADER_SRC_VERTEX_BUFFER_BUF_SLOT 0
#define SHADER_DST_VERTEX_BUFFER_BUF_SLOT 1
#define SHADER_DU_BUFFER_BUF_SLOT 2
#define SHADER_DV_BUFFER_BUF_SLOT 3
#define SHADER_SIZES_BUF_SLOT 4
#define SHADER_OFFSETS_BUF_SLOT 5
#define SHADER_INDICES_BUF_SLOT 6
#define SHADER_WEIGHTS_BUF_SLOT 7
#define SHADER_DU_WEIGHTS_BUF_SLOT 8
#define SHADER_DV_WEIGHTS_BUF_SLOT 9
#define SHADER_PATCH_ARRAY_BUFFER_BUF_SLOT 4
#define SHADER_PATCH_COORDS_BUF_SLOT 5
#define SHADER_PATCH_INDEX_BUFFER_BUF_SLOT 6
#define SHADER_PATCH_PARAM_BUFFER_BUF_SLOT 7
namespace blender::opensubdiv {
template<class T> gpu::StorageBuf *create_buffer(std::vector<T> const &src, const char *name)
{
if (src.empty()) {
return nullptr;
}
const size_t buffer_size = src.size() * sizeof(T);
gpu::StorageBuf *storage_buffer = GPU_storagebuf_create_ex(
buffer_size, &src.at(0), GPU_USAGE_STATIC, name);
return storage_buffer;
}
GPUStencilTableSSBO::GPUStencilTableSSBO(StencilTable const *stencilTable)
{
_numStencils = stencilTable->GetNumStencils();
if (_numStencils > 0) {
sizes_buf = create_buffer(stencilTable->GetSizes(), "osd_sized");
offsets_buf = create_buffer(stencilTable->GetOffsets(), "osd_offsets");
indices_buf = create_buffer(stencilTable->GetControlIndices(), "osd_control_indices");
weights_buf = create_buffer(stencilTable->GetWeights(), "osd_weights");
}
}
GPUStencilTableSSBO::GPUStencilTableSSBO(LimitStencilTable const *limitStencilTable)
{
_numStencils = limitStencilTable->GetNumStencils();
if (_numStencils > 0) {
sizes_buf = create_buffer(limitStencilTable->GetSizes(), "osd_sized");
offsets_buf = create_buffer(limitStencilTable->GetOffsets(), "osd_offsets");
indices_buf = create_buffer(limitStencilTable->GetControlIndices(), "osd_control_indices");
weights_buf = create_buffer(limitStencilTable->GetWeights(), "osd_weights");
du_weights_buf = create_buffer(limitStencilTable->GetDuWeights(), "osd_du_weights");
dv_weights_buf = create_buffer(limitStencilTable->GetDvWeights(), "osd_dv_weights");
duu_weights_buf = create_buffer(limitStencilTable->GetDuuWeights(), "osd_duu_weights");
duv_weights_buf = create_buffer(limitStencilTable->GetDuvWeights(), "osd_duv_weights");
dvv_weights_buf = create_buffer(limitStencilTable->GetDvvWeights(), "osd_dvv_weights");
}
}
static void storage_buffer_free(gpu::StorageBuf **buffer)
{
if (*buffer) {
GPU_storagebuf_free(*buffer);
*buffer = nullptr;
}
}
GPUStencilTableSSBO::~GPUStencilTableSSBO()
{
storage_buffer_free(&sizes_buf);
storage_buffer_free(&offsets_buf);
storage_buffer_free(&indices_buf);
storage_buffer_free(&weights_buf);
storage_buffer_free(&du_weights_buf);
storage_buffer_free(&dv_weights_buf);
storage_buffer_free(&duu_weights_buf);
storage_buffer_free(&duv_weights_buf);
storage_buffer_free(&dvv_weights_buf);
}
// ---------------------------------------------------------------------------
GPUComputeEvaluator::GPUComputeEvaluator() : _workGroupSize(64), _patchArraysSSBO(nullptr)
{
memset((void *)&_stencilKernel, 0, sizeof(_stencilKernel));
memset((void *)&_patchKernel, 0, sizeof(_patchKernel));
}
GPUComputeEvaluator::~GPUComputeEvaluator()
{
if (_patchArraysSSBO) {
GPU_storagebuf_free(_patchArraysSSBO);
_patchArraysSSBO = nullptr;
}
}
bool GPUComputeEvaluator::Compile(BufferDescriptor const &srcDesc,
BufferDescriptor const &dstDesc,
BufferDescriptor const &duDesc,
BufferDescriptor const &dvDesc)
{
if (!_stencilKernel.Compile(srcDesc, dstDesc, duDesc, dvDesc, _workGroupSize)) {
return false;
}
if (!_patchKernel.Compile(srcDesc, dstDesc, duDesc, dvDesc, _workGroupSize)) {
return false;
}
return true;
}
/* static */
void GPUComputeEvaluator::Synchronize(void * /*kernel*/)
{
// XXX: this is currently just for the performance measuring purpose.
// need to be reimplemented by fence and sync.
GPU_finish();
}
int GPUComputeEvaluator::GetDispatchSize(int count) const
{
return (count + _workGroupSize - 1) / _workGroupSize;
}
void GPUComputeEvaluator::DispatchCompute(blender::gpu::Shader *shader,
int totalDispatchSize) const
{
const int dispatchSize = GetDispatchSize(totalDispatchSize);
int dispatchRX = dispatchSize;
int dispatchRY = 1u;
if (dispatchRX > GPU_max_work_group_count(0)) {
/* Since there are some limitations with regards to the maximum work group size (could be as
* low as 64k elements per call), we split the number elements into a "2d" number, with the
* final index being computed as `res_x + res_y * max_work_group_size`. Even with a maximum
* work group size of 64k, that still leaves us with roughly `64k * 64k = 4` billion elements
* total, which should be enough. If not, we could also use the 3rd dimension. */
/* TODO(fclem): We could dispatch fewer groups if we compute the prime factorization and
* get the smallest rect fitting the requirements. */
dispatchRX = dispatchRY = std::ceil(std::sqrt(dispatchSize));
/* Avoid a completely empty dispatch line caused by rounding. */
if ((dispatchRX * (dispatchRY - 1)) >= dispatchSize) {
dispatchRY -= 1;
}
}
/* X and Y dimensions may have different limits so the above computation may not be right, but
* even with the standard 64k minimum on all dimensions we still have a lot of room. Therefore,
* we presume it all fits. */
assert(dispatchRY < GPU_max_work_group_count(1));
GPU_compute_dispatch(shader, dispatchRX, dispatchRY, 1);
/* Next usage of the src/dst buffers will always be a shader storage. Vertices/normals/attributes
* are copied over to the final buffers using compute shaders. */
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
}
bool GPUComputeEvaluator::EvalStencils(gpu::VertBuf *srcBuffer,
BufferDescriptor const &srcDesc,
gpu::VertBuf *dstBuffer,
BufferDescriptor const &dstDesc,
gpu::VertBuf *duBuffer,
BufferDescriptor const &duDesc,
gpu::VertBuf *dvBuffer,
BufferDescriptor const &dvDesc,
gpu::StorageBuf *sizesBuffer,
gpu::StorageBuf *offsetsBuffer,
gpu::StorageBuf *indicesBuffer,
gpu::StorageBuf *weightsBuffer,
gpu::StorageBuf *duWeightsBuffer,
gpu::StorageBuf *dvWeightsBuffer,
int start,
int end) const
{
if (_stencilKernel.shader == nullptr) {
return false;
}
int count = end - start;
if (count <= 0) {
return true;
}
GPU_shader_bind(_stencilKernel.shader);
GPU_vertbuf_bind_as_ssbo(srcBuffer, SHADER_SRC_VERTEX_BUFFER_BUF_SLOT);
GPU_vertbuf_bind_as_ssbo(dstBuffer, SHADER_DST_VERTEX_BUFFER_BUF_SLOT);
if (duBuffer) {
GPU_vertbuf_bind_as_ssbo(duBuffer, SHADER_DU_BUFFER_BUF_SLOT);
}
if (dvBuffer) {
GPU_vertbuf_bind_as_ssbo(dvBuffer, SHADER_DV_BUFFER_BUF_SLOT);
}
GPU_storagebuf_bind(sizesBuffer, SHADER_SIZES_BUF_SLOT);
GPU_storagebuf_bind(offsetsBuffer, SHADER_OFFSETS_BUF_SLOT);
GPU_storagebuf_bind(indicesBuffer, SHADER_INDICES_BUF_SLOT);
GPU_storagebuf_bind(weightsBuffer, SHADER_WEIGHTS_BUF_SLOT);
if (duWeightsBuffer) {
GPU_storagebuf_bind(duWeightsBuffer, SHADER_DU_WEIGHTS_BUF_SLOT);
}
if (dvWeightsBuffer) {
GPU_storagebuf_bind(dvWeightsBuffer, SHADER_DV_WEIGHTS_BUF_SLOT);
}
GPU_shader_uniform_int_ex(_stencilKernel.shader, _stencilKernel.uniformStart, 1, 1, &start);
GPU_shader_uniform_int_ex(_stencilKernel.shader, _stencilKernel.uniformEnd, 1, 1, &end);
GPU_shader_uniform_int_ex(
_stencilKernel.shader, _stencilKernel.uniformSrcOffset, 1, 1, &srcDesc.offset);
GPU_shader_uniform_int_ex(
_stencilKernel.shader, _stencilKernel.uniformDstOffset, 1, 1, &dstDesc.offset);
// TODO init to -1 and check >= 0 to align with GPU module. Currently we assume that the uniform
// location is not zero as there are other uniforms defined as well.
#define BIND_BUF_DESC(uniform, desc) \
if (_stencilKernel.uniform > 0) { \
int value[] = {desc.offset, desc.length, desc.stride}; \
GPU_shader_uniform_int_ex(_stencilKernel.shader, _stencilKernel.uniform, 3, 1, value); \
}
BIND_BUF_DESC(uniformDuDesc, duDesc)
BIND_BUF_DESC(uniformDvDesc, dvDesc)
#undef BIND_BUF_DESC
DispatchCompute(_stencilKernel.shader, count);
// GPU_storagebuf_unbind_all();
GPU_shader_unbind();
return true;
}
bool GPUComputeEvaluator::EvalPatches(gpu::VertBuf *srcBuffer,
BufferDescriptor const &srcDesc,
gpu::VertBuf *dstBuffer,
BufferDescriptor const &dstDesc,
gpu::VertBuf *duBuffer,
BufferDescriptor const &duDesc,
gpu::VertBuf *dvBuffer,
BufferDescriptor const &dvDesc,
int numPatchCoords,
gpu::VertBuf *patchCoordsBuffer,
const PatchArrayVector &patchArrays,
gpu::StorageBuf *patchIndexBuffer,
gpu::StorageBuf *patchParamsBuffer)
{
if (_patchKernel.shader == nullptr) {
return false;
}
GPU_shader_bind(_patchKernel.shader);
GPU_vertbuf_bind_as_ssbo(srcBuffer, SHADER_SRC_VERTEX_BUFFER_BUF_SLOT);
GPU_vertbuf_bind_as_ssbo(dstBuffer, SHADER_DST_VERTEX_BUFFER_BUF_SLOT);
if (duBuffer) {
GPU_vertbuf_bind_as_ssbo(duBuffer, SHADER_DU_BUFFER_BUF_SLOT);
}
if (dvBuffer) {
GPU_vertbuf_bind_as_ssbo(dvBuffer, SHADER_DV_BUFFER_BUF_SLOT);
}
GPU_vertbuf_bind_as_ssbo(patchCoordsBuffer, SHADER_PATCH_COORDS_BUF_SLOT);
GPU_storagebuf_bind(patchIndexBuffer, SHADER_PATCH_INDEX_BUFFER_BUF_SLOT);
GPU_storagebuf_bind(patchParamsBuffer, SHADER_PATCH_PARAM_BUFFER_BUF_SLOT);
int patchArraySize = sizeof(PatchArray);
if (_patchArraysSSBO) {
GPU_storagebuf_free(_patchArraysSSBO);
_patchArraysSSBO = nullptr;
}
_patchArraysSSBO = GPU_storagebuf_create_ex(patchArrays.size() * patchArraySize,
static_cast<const void *>(&patchArrays[0]),
GPU_USAGE_STATIC,
"osd_patch_array");
GPU_storagebuf_bind(_patchArraysSSBO, SHADER_PATCH_ARRAY_BUFFER_BUF_SLOT);
GPU_shader_uniform_int_ex(
_patchKernel.shader, _patchKernel.uniformSrcOffset, 1, 1, &srcDesc.offset);
GPU_shader_uniform_int_ex(
_patchKernel.shader, _patchKernel.uniformDstOffset, 1, 1, &dstDesc.offset);
// TODO init to -1 and check >= 0 to align with GPU module.
#define BIND_BUF_DESC(uniform, desc) \
if (_stencilKernel.uniform > 0) { \
int value[] = {desc.offset, desc.length, desc.stride}; \
GPU_shader_uniform_int_ex(_patchKernel.shader, _patchKernel.uniform, 3, 1, value); \
}
BIND_BUF_DESC(uniformDuDesc, duDesc)
BIND_BUF_DESC(uniformDvDesc, dvDesc)
#undef BIND_BUF_DESC
DispatchCompute(_patchKernel.shader, numPatchCoords);
GPU_shader_unbind();
return true;
}
// ---------------------------------------------------------------------------
GPUComputeEvaluator::_StencilKernel::_StencilKernel() {}
GPUComputeEvaluator::_StencilKernel::~_StencilKernel()
{
if (shader) {
GPU_shader_free(shader);
shader = nullptr;
}
}
static blender::gpu::Shader *compile_eval_stencil_shader(BufferDescriptor const &srcDesc,
BufferDescriptor const &dstDesc,
BufferDescriptor const &duDesc,
BufferDescriptor const &dvDesc,
int workGroupSize)
{
using namespace blender::gpu::shader;
ShaderCreateInfo info("osd_eval_stencils_comp");
info.local_group_size(workGroupSize, 1, 1);
info.builtins(BuiltinBits::GLOBAL_INVOCATION_ID);
info.builtins(BuiltinBits::NUM_WORK_GROUP);
// TODO: use specialization constants for src_stride, dst_stride. Not sure we can use
// work group size as that requires extensions. This allows us to compile less shaders and
// improve overall performance. Adding length as specialization constant will not work as it is
// used to define an array length. This is not supported by Metal.
std::string length = std::to_string(srcDesc.length);
std::string src_stride = std::to_string(srcDesc.stride);
std::string dst_stride = std::to_string(dstDesc.stride);
std::string work_group_size = std::to_string(workGroupSize);
info.define("LENGTH", length);
info.define("SRC_STRIDE", src_stride);
info.define("DST_STRIDE", dst_stride);
info.define("WORK_GROUP_SIZE", work_group_size);
info.typedef_source("osd_patch_defines.glsl");
info.typedef_source("osd_patch_basis.glsl");
info.storage_buf(
SHADER_SRC_VERTEX_BUFFER_BUF_SLOT, Qualifier::read, "float", "srcVertexBuffer[]");
info.storage_buf(
SHADER_DST_VERTEX_BUFFER_BUF_SLOT, Qualifier::write, "float", "dstVertexBuffer[]");
info.push_constant(Type::int_t, "srcOffset");
info.push_constant(Type::int_t, "dstOffset");
bool deriv1 = (duDesc.length > 0 || dvDesc.length > 0);
if (deriv1) {
info.define("OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES");
info.storage_buf(SHADER_DU_BUFFER_BUF_SLOT, Qualifier::read_write, "float", "duBuffer[]");
info.storage_buf(SHADER_DV_BUFFER_BUF_SLOT, Qualifier::read_write, "float", "dvBuffer[]");
info.push_constant(Type::int3_t, "duDesc");
info.push_constant(Type::int3_t, "dvDesc");
}
info.storage_buf(SHADER_SIZES_BUF_SLOT, Qualifier::read, "int", "sizes_buf[]");
info.storage_buf(SHADER_OFFSETS_BUF_SLOT, Qualifier::read, "int", "offsets_buf[]");
info.storage_buf(SHADER_INDICES_BUF_SLOT, Qualifier::read, "int", "indices_buf[]");
info.storage_buf(SHADER_WEIGHTS_BUF_SLOT, Qualifier::read, "float", "weights_buf[]");
if (deriv1) {
info.storage_buf(
SHADER_DU_WEIGHTS_BUF_SLOT, Qualifier::read_write, "float", "du_weights_buf[]");
info.storage_buf(
SHADER_DV_WEIGHTS_BUF_SLOT, Qualifier::read_write, "float", "dv_weights_buf[]");
}
info.push_constant(Type::int_t, "batchStart");
info.push_constant(Type::int_t, "batchEnd");
info.compute_source("osd_eval_stencils_comp.glsl");
blender::gpu::Shader *shader = GPU_shader_create_from_info(
reinterpret_cast<const GPUShaderCreateInfo *>(&info));
return shader;
}
bool GPUComputeEvaluator::_StencilKernel::Compile(BufferDescriptor const &srcDesc,
BufferDescriptor const &dstDesc,
BufferDescriptor const &duDesc,
BufferDescriptor const &dvDesc,
int workGroupSize)
{
if (shader) {
GPU_shader_free(shader);
shader = nullptr;
}
shader = compile_eval_stencil_shader(srcDesc, dstDesc, duDesc, dvDesc, workGroupSize);
if (shader == nullptr) {
return false;
}
// cache uniform locations (TODO: use uniform block)
uniformStart = GPU_shader_get_uniform(shader, "batchStart");
uniformEnd = GPU_shader_get_uniform(shader, "batchEnd");
uniformSrcOffset = GPU_shader_get_uniform(shader, "srcOffset");
uniformDstOffset = GPU_shader_get_uniform(shader, "dstOffset");
uniformDuDesc = GPU_shader_get_uniform(shader, "duDesc");
uniformDvDesc = GPU_shader_get_uniform(shader, "dvDesc");
return true;
}
// ---------------------------------------------------------------------------
GPUComputeEvaluator::_PatchKernel::_PatchKernel() {}
GPUComputeEvaluator::_PatchKernel::~_PatchKernel()
{
if (shader) {
GPU_shader_free(shader);
shader = nullptr;
}
}
static blender::gpu::Shader *compile_eval_patches_shader(BufferDescriptor const &srcDesc,
BufferDescriptor const &dstDesc,
BufferDescriptor const &duDesc,
BufferDescriptor const &dvDesc,
int workGroupSize)
{
using namespace blender::gpu::shader;
ShaderCreateInfo info("osd_eval_patches_comp");
info.local_group_size(workGroupSize, 1, 1);
info.builtins(BuiltinBits::GLOBAL_INVOCATION_ID);
info.builtins(BuiltinBits::NUM_WORK_GROUP);
info.builtins(BuiltinBits::NO_BUFFER_TYPE_LINTING);
// TODO: use specialization constants for src_stride, dst_stride. Not sure we can use
// work group size as that requires extensions. This allows us to compile less shaders and
// improve overall performance. Adding length as specialization constant will not work as it is
// used to define an array length. This is not supported by Metal.
std::string length = std::to_string(srcDesc.length);
std::string src_stride = std::to_string(srcDesc.stride);
std::string dst_stride = std::to_string(dstDesc.stride);
std::string work_group_size = std::to_string(workGroupSize);
info.define("LENGTH", length);
info.define("SRC_STRIDE", src_stride);
info.define("DST_STRIDE", dst_stride);
info.define("WORK_GROUP_SIZE", work_group_size);
info.typedef_source("osd_patch_defines.glsl");
info.typedef_source("osd_patch_basis.glsl");
info.storage_buf(
SHADER_SRC_VERTEX_BUFFER_BUF_SLOT, Qualifier::read, "float", "srcVertexBuffer[]");
info.storage_buf(
SHADER_DST_VERTEX_BUFFER_BUF_SLOT, Qualifier::write, "float", "dstVertexBuffer[]");
info.push_constant(Type::int_t, "srcOffset");
info.push_constant(Type::int_t, "dstOffset");
bool deriv1 = (duDesc.length > 0 || dvDesc.length > 0);
if (deriv1) {
info.define("OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES");
info.storage_buf(SHADER_DU_BUFFER_BUF_SLOT, Qualifier::read_write, "float", "duBuffer[]");
info.storage_buf(SHADER_DV_BUFFER_BUF_SLOT, Qualifier::read_write, "float", "dvBuffer[]");
info.push_constant(Type::int3_t, "duDesc");
info.push_constant(Type::int3_t, "dvDesc");
}
info.storage_buf(
SHADER_PATCH_ARRAY_BUFFER_BUF_SLOT, Qualifier::read, "OsdPatchArray", "patchArrayBuffer[]");
info.storage_buf(
SHADER_PATCH_COORDS_BUF_SLOT, Qualifier::read, "OsdPatchCoord", "patchCoords[]");
info.storage_buf(
SHADER_PATCH_INDEX_BUFFER_BUF_SLOT, Qualifier::read, "int", "patchIndexBuffer[]");
info.storage_buf(
SHADER_PATCH_PARAM_BUFFER_BUF_SLOT, Qualifier::read, "OsdPatchParam", "patchParamBuffer[]");
info.compute_source("osd_eval_patches_comp.glsl");
blender::gpu::Shader *shader = GPU_shader_create_from_info(
reinterpret_cast<const GPUShaderCreateInfo *>(&info));
return shader;
}
bool GPUComputeEvaluator::_PatchKernel::Compile(BufferDescriptor const &srcDesc,
BufferDescriptor const &dstDesc,
BufferDescriptor const &duDesc,
BufferDescriptor const &dvDesc,
int workGroupSize)
{
if (shader) {
GPU_shader_free(shader);
shader = nullptr;
}
shader = compile_eval_patches_shader(srcDesc, dstDesc, duDesc, dvDesc, workGroupSize);
if (shader == nullptr) {
return false;
}
// cache uniform locations
uniformSrcOffset = GPU_shader_get_uniform(shader, "srcOffset");
uniformDstOffset = GPU_shader_get_uniform(shader, "dstOffset");
uniformDuDesc = GPU_shader_get_uniform(shader, "duDesc");
uniformDvDesc = GPU_shader_get_uniform(shader, "dvDesc");
return true;
}
} // namespace blender::opensubdiv

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,113 @@
/* SPDX-FileCopyrightText: 2025 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_patch_table.hh"
#include "opensubdiv/far/patchTable.h"
#include "opensubdiv/osd/cpuPatchTable.h"
using namespace OpenSubdiv::Osd;
namespace blender::opensubdiv {
GPUPatchTable *GPUPatchTable::Create(PatchTable const *far_patch_table, void * /*deviceContext*/)
{
GPUPatchTable *instance = new GPUPatchTable();
if (instance->allocate(far_patch_table)) {
return instance;
}
delete instance;
return nullptr;
}
static void discard_buffer(gpu::StorageBuf **buffer)
{
if (*buffer != nullptr) {
GPU_storagebuf_free(*buffer);
*buffer = nullptr;
}
}
static void discard_list(std::vector<gpu::StorageBuf *> &buffers)
{
while (!buffers.empty()) {
gpu::StorageBuf *buffer = buffers.back();
buffers.pop_back();
GPU_storagebuf_free(buffer);
}
}
GPUPatchTable::~GPUPatchTable()
{
discard_buffer(&_patchIndexBuffer);
discard_buffer(&_patchParamBuffer);
discard_buffer(&_varyingIndexBuffer);
discard_list(_fvarIndexBuffers);
discard_list(_fvarParamBuffers);
}
bool GPUPatchTable::allocate(PatchTable const *far_patch_table)
{
CpuPatchTable patch_table(far_patch_table);
/* Patch array */
size_t num_patch_arrays = patch_table.GetNumPatchArrays();
_patchArrays.assign(patch_table.GetPatchArrayBuffer(),
patch_table.GetPatchArrayBuffer() + num_patch_arrays);
/* Patch index buffer */
const size_t index_size = patch_table.GetPatchIndexSize();
_patchIndexBuffer = GPU_storagebuf_create_ex(
index_size * sizeof(int32_t),
static_cast<const void *>(patch_table.GetPatchIndexBuffer()),
GPU_USAGE_STATIC,
"osd_patch_index");
/* Patch param buffer */
const size_t patch_param_size = patch_table.GetPatchParamSize();
_patchParamBuffer = GPU_storagebuf_create_ex(patch_param_size * sizeof(PatchParam),
patch_table.GetPatchParamBuffer(),
GPU_USAGE_STATIC,
"osd_patch_param");
/* Varying patch array */
_varyingPatchArrays.assign(patch_table.GetVaryingPatchArrayBuffer(),
patch_table.GetVaryingPatchArrayBuffer() + num_patch_arrays);
/* Varying index buffer */
_varyingIndexBuffer = GPU_storagebuf_create_ex(patch_table.GetVaryingPatchIndexSize() *
sizeof(uint32_t),
patch_table.GetVaryingPatchIndexBuffer(),
GPU_USAGE_STATIC,
"osd_varying_index");
/* Face varying */
const int num_face_varying_channels = patch_table.GetNumFVarChannels();
_fvarPatchArrays.resize(num_face_varying_channels);
_fvarIndexBuffers.resize(num_face_varying_channels);
_fvarParamBuffers.resize(num_face_varying_channels);
for (int index = 0; index < num_face_varying_channels; index++) {
/* Face varying patch arrays */
_fvarPatchArrays[index].assign(patch_table.GetFVarPatchArrayBuffer(),
patch_table.GetFVarPatchArrayBuffer() + num_patch_arrays);
/* Face varying patch index buffer */
_fvarIndexBuffers[index] = GPU_storagebuf_create_ex(patch_table.GetFVarPatchIndexSize(index) *
sizeof(int32_t),
patch_table.GetFVarPatchIndexBuffer(index),
GPU_USAGE_STATIC,
"osd_face_varying_index");
/* Face varying patch param buffer */
_fvarParamBuffers[index] = GPU_storagebuf_create_ex(patch_table.GetFVarPatchParamSize(index) *
sizeof(PatchParam),
patch_table.GetFVarPatchParamBuffer(index),
GPU_USAGE_STATIC,
"osd_face_varying_params");
}
return true;
}
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,100 @@
/* SPDX-FileCopyrightText: 2025 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GPU_storage_buffer.hh"
#include <opensubdiv/version.h>
#include <opensubdiv/osd/nonCopyable.h>
#include <opensubdiv/osd/types.h>
using OpenSubdiv::Far::PatchTable;
using OpenSubdiv::Osd::NonCopyable;
using OpenSubdiv::Osd::PatchArrayVector;
namespace blender::opensubdiv {
// TODO: use Blenlib NonCopyable.
class GPUPatchTable : private OpenSubdiv::Osd::NonCopyable<GPUPatchTable> {
public:
~GPUPatchTable();
static GPUPatchTable *Create(PatchTable const *farPatchTable, void *deviceContext = nullptr);
/// Returns the patch arrays for vertex index buffer data
PatchArrayVector const &GetPatchArrays() const
{
return _patchArrays;
}
/// Returns the GL index buffer containing the patch control vertices
gpu::StorageBuf *GetPatchIndexBuffer() const
{
return _patchIndexBuffer;
}
/// Returns the GL index buffer containing the patch parameter
gpu::StorageBuf *GetPatchParamBuffer() const
{
return _patchParamBuffer;
}
/// Returns the patch arrays for varying index buffer data
PatchArrayVector const &GetVaryingPatchArrays() const
{
return _varyingPatchArrays;
}
/// Returns the GL index buffer containing the varying control vertices
gpu::StorageBuf *GetVaryingPatchIndexBuffer() const
{
return _varyingIndexBuffer;
}
/// Returns the number of face-varying channel buffers
int GetNumFVarChannels() const
{
return (int)_fvarPatchArrays.size();
}
/// Returns the patch arrays for face-varying index buffer data
PatchArrayVector const &GetFVarPatchArrays(int fvarChannel = 0) const
{
return _fvarPatchArrays[fvarChannel];
}
/// Returns the GL index buffer containing face-varying control vertices
gpu::StorageBuf *GetFVarPatchIndexBuffer(int fvarChannel = 0) const
{
return _fvarIndexBuffers[fvarChannel];
}
/// Returns the GL index buffer containing face-varying patch params
gpu::StorageBuf *GetFVarPatchParamBuffer(int fvarChannel = 0) const
{
return _fvarParamBuffers[fvarChannel];
}
protected:
GPUPatchTable() {}
// allocate buffers from patchTable
bool allocate(PatchTable const *farPatchTable);
PatchArrayVector _patchArrays;
gpu::StorageBuf *_patchIndexBuffer = nullptr;
gpu::StorageBuf *_patchParamBuffer = nullptr;
PatchArrayVector _varyingPatchArrays;
gpu::StorageBuf *_varyingIndexBuffer = nullptr;
std::vector<PatchArrayVector> _fvarPatchArrays;
std::vector<gpu::StorageBuf *> _fvarIndexBuffers;
std::vector<gpu::StorageBuf *> _fvarParamBuffers;
};
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2025 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GPU_context.hh"
#include "GPU_vertex_buffer.hh"
namespace blender::opensubdiv {
/**
* GLVertexBuffer compatible API wrapped around a blender::gpu::VertBuf
*
* The blender::gpu::VertBuf is owned by the wrapper.
* Vertex buffer is used as its API is able to wrap around SSBOs as well.
*/
class GPUVertexBuffer {
gpu::VertBuf &gpu_vertex_buffer_;
/** Number of float elements/components does a single vertex have. */
int element_count_;
/** Should we upload data directly to the GPU, or should we use a staging buffer. */
bool use_update_sub_;
public:
GPUVertexBuffer(gpu::VertBuf &gpu_vertex_buffer, int element_count, bool use_update_sub)
: gpu_vertex_buffer_(gpu_vertex_buffer),
element_count_(element_count),
use_update_sub_(use_update_sub)
{
}
/**
* Create a new gpu::VertBuf wrapped in a GPUVertexBuffer.
*
* @param element_count: Number of elements per vertex
* @param vertex_len: Number of vertices
* @param device_context: Unused.
*/
static GPUVertexBuffer *Create(int element_count, int vertex_len, void *device_context = nullptr)
{
using namespace blender::gpu;
(void)device_context;
GPUVertFormat format;
GPU_vertformat_clear(&format);
switch (element_count) {
case 4:
GPU_vertformat_attr_add(&format, "elements", VertAttrType::SFLOAT_32_32_32_32);
break;
case 3:
GPU_vertformat_attr_add(&format, "elements", VertAttrType::SFLOAT_32_32_32);
break;
case 2:
GPU_vertformat_attr_add(&format, "elements", VertAttrType::SFLOAT_32_32);
break;
case 1:
GPU_vertformat_attr_add(&format, "elements", VertAttrType::SFLOAT_32);
break;
default:
assert(0);
break;
}
const bool use_update_sub = GPU_backend_get_type() != GPU_BACKEND_VULKAN;
gpu::VertBuf *vertex_buffer = nullptr;
if (use_update_sub) {
vertex_buffer = GPU_vertbuf_calloc();
GPU_vertbuf_init_build_on_device(*vertex_buffer, format, vertex_len);
}
else {
vertex_buffer = GPU_vertbuf_create_with_format_ex(format, GPU_USAGE_DYNAMIC);
GPU_vertbuf_data_alloc(*vertex_buffer, vertex_len);
}
return new GPUVertexBuffer(*vertex_buffer, element_count, use_update_sub);
}
/// Destructor.
~GPUVertexBuffer()
{
GPU_vertbuf_discard(&gpu_vertex_buffer_);
}
/// This method is meant to be used in client code in order to provide coarse
/// vertices data to Osd.
void UpdateData(const float *src,
int start_vertex,
int num_vertices,
void *device_context = NULL)
{
(void)device_context;
if (use_update_sub_) {
GPU_vertbuf_use(&gpu_vertex_buffer_);
size_t offset = start_vertex * element_count_ * sizeof(float);
size_t data_len = num_vertices * element_count_ * sizeof(float);
GPU_vertbuf_update_sub(&gpu_vertex_buffer_, offset, data_len, src);
}
else {
MutableSpan<float> buffer_nodes = gpu_vertex_buffer_.data<float>();
buffer_nodes = buffer_nodes.drop_front(start_vertex * element_count_);
memcpy(buffer_nodes.data(), src, sizeof(float) * element_count_ * num_vertices);
GPU_vertbuf_tag_dirty(&gpu_vertex_buffer_);
}
}
/// Returns how many vertices allocated in this vertex buffer.
int GetNumVertices() const
{
return GPU_vertbuf_get_vertex_len(&gpu_vertex_buffer_);
}
gpu::VertBuf *get_vertex_buffer()
{
return &gpu_vertex_buffer_;
}
};
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,193 @@
/* SPDX-FileCopyrightText: 2013 Pixar
* SPDX-FileCopyrightText: 2021 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0
*
* Original code by Pixar with modifications by the Blender foundation. */
#include "internal/evaluator/patch_map.h"
#include <algorithm>
using OpenSubdiv::Far::ConstPatchParamArray;
using OpenSubdiv::Far::Index;
using OpenSubdiv::Far::PatchParam;
using OpenSubdiv::Far::PatchParamTable;
using OpenSubdiv::Far::PatchTable;
namespace blender::opensubdiv {
//
// Inline quadtree assembly methods used by the constructor:
//
// sets all the children to point to the patch of given index
inline void PatchMap::QuadNode::SetChildren(int index)
{
for (int i = 0; i < 4; ++i) {
children[i].isSet = true;
children[i].isLeaf = true;
children[i].index = index;
}
}
// sets the child in "quadrant" to point to the node or patch of the given index
inline void PatchMap::QuadNode::SetChild(int quadrant, int index, bool isLeaf)
{
assert(!children[quadrant].isSet);
children[quadrant].isSet = true;
children[quadrant].isLeaf = isLeaf;
children[quadrant].index = index;
}
inline void PatchMap::assignRootNode(QuadNode *node, int index)
{
// Assign the given index to all children of the node (all leaves)
node->SetChildren(index);
}
inline PatchMap::QuadNode *PatchMap::assignLeafOrChildNode(QuadNode *node,
bool isLeaf,
int quadrant,
int index)
{
// Assign the node given if it is a leaf node, otherwise traverse
// the node -- creating/assigning a new child node if needed
if (isLeaf) {
node->SetChild(quadrant, index, true);
return node;
}
if (node->children[quadrant].isSet) {
return &_quadtree[node->children[quadrant].index];
}
int newChildNodeIndex = (int)_quadtree.size();
_quadtree.emplace_back();
node->SetChild(quadrant, newChildNodeIndex, false);
return &_quadtree[newChildNodeIndex];
}
//
// Constructor and initialization methods for the handles and quadtree:
//
PatchMap::PatchMap(PatchTable const &patchTable)
: _minPatchFace(-1), _maxPatchFace(-1), _maxDepth(0)
{
_patchesAreTriangular = patchTable.GetVaryingPatchDescriptor().GetNumControlVertices() == 3;
if (patchTable.GetNumPatchesTotal() > 0) {
initializeHandles(patchTable);
initializeQuadtree(patchTable);
}
}
void PatchMap::initializeHandles(PatchTable const &patchTable)
{
//
// Populate the vector of patch Handles. Keep track of the min and max
// face indices to allocate resources accordingly and limit queries:
//
_minPatchFace = (int)patchTable.GetPatchParamTable()[0].GetFaceId();
_maxPatchFace = _minPatchFace;
int numArrays = patchTable.GetNumPatchArrays();
int numPatches = patchTable.GetNumPatchesTotal();
_handles.resize(numPatches);
for (int pArray = 0, handleIndex = 0; pArray < numArrays; ++pArray) {
ConstPatchParamArray params = patchTable.GetPatchParams(pArray);
int patchSize = patchTable.GetPatchArrayDescriptor(pArray).GetNumControlVertices();
for (Index j = 0; j < patchTable.GetNumPatches(pArray); ++j, ++handleIndex) {
Handle &h = _handles[handleIndex];
h.arrayIndex = pArray;
h.patchIndex = handleIndex;
h.vertIndex = j * patchSize;
int patchFaceId = params[j].GetFaceId();
_minPatchFace = std::min(_minPatchFace, patchFaceId);
_maxPatchFace = std::max(_maxPatchFace, patchFaceId);
}
}
}
void PatchMap::initializeQuadtree(PatchTable const &patchTable)
{
//
// Reserve quadtree nodes for the worst case and prune later. Set the
// initial size to accomodate the root node of each patch face:
//
int nPatchFaces = (_maxPatchFace - _minPatchFace) + 1;
int nHandles = int(_handles.size());
_quadtree.reserve(nPatchFaces + nHandles);
_quadtree.resize(nPatchFaces);
PatchParamTable const &params = patchTable.GetPatchParamTable();
for (int handle = 0; handle < nHandles; ++handle) {
PatchParam const &param = params[handle];
int depth = param.GetDepth();
int rootDepth = param.NonQuadRoot();
_maxDepth = std::max(_maxDepth, depth);
QuadNode *node = &_quadtree[param.GetFaceId() - _minPatchFace];
if (depth == rootDepth) {
assignRootNode(node, handle);
continue;
}
if (!_patchesAreTriangular) {
// Use the UV bits of the PatchParam directly for quad patches:
int u = param.GetU();
int v = param.GetV();
for (int j = rootDepth + 1; j <= depth; ++j) {
int uBit = (u >> (depth - j)) & 1;
int vBit = (v >> (depth - j)) & 1;
int quadrant = (vBit << 1) | uBit;
node = assignLeafOrChildNode(node, (j == depth), quadrant, handle);
}
}
else {
// Use an interior UV point of triangles to identify quadrants:
double u = 0.25;
double v = 0.25;
param.UnnormalizeTriangle(u, v);
double median = 0.5;
bool triRotated = false;
for (int j = rootDepth + 1; j <= depth; ++j, median *= 0.5) {
int quadrant = transformUVToTriQuadrant(median, u, v, triRotated);
node = assignLeafOrChildNode(node, (j == depth), quadrant, handle);
}
}
}
// Swap the Node vector with a copy to reduce worst case memory allocation:
QuadTree tmpTree = _quadtree;
_quadtree.swap(tmpTree);
}
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,246 @@
/* SPDX-FileCopyrightText: 2013 Pixar
* SPDX-FileCopyrightText: 2021 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0
*
* Original code by Pixar with modifications by the Blender foundation. */
#ifndef OPENSUBDIV_PATCH_MAP_H_
#define OPENSUBDIV_PATCH_MAP_H_
#include <opensubdiv/far/patchTable.h>
namespace blender::opensubdiv {
/// \brief An quadtree-based map connecting coarse faces to their sub-patches
///
/// PatchTable::PatchArrays contain lists of patches that represent the limit
/// surface of a mesh, sorted by their topological type. These arrays break the
/// connection between coarse faces and their sub-patches.
///
/// The PatchMap provides a quad-tree based lookup structure that, given a singular
/// parametric location, can efficiently return a handle to the sub-patch that
/// contains this location.
///
class PatchMap {
public:
// Quadtree node with 4 children, tree is just a vector of nodes
struct QuadNode {
QuadNode()
{
std::memset(this, 0, sizeof(QuadNode));
}
struct Child {
unsigned int isSet : 1; // true if the child has been set
unsigned int isLeaf : 1; // true if the child is a QuadNode
unsigned int index : 30; // child index (either QuadNode or Handle)
};
// sets all the children to point to the patch of given index
void SetChildren(int index);
// sets the child in "quadrant" to point to the node or patch of the given index
void SetChild(int quadrant, int index, bool isLeaf);
Child children[4];
};
using Handle = OpenSubdiv::Far::PatchTable::PatchHandle;
/// \brief Constructor
///
/// @param patchTable A valid PatchTable
///
PatchMap(OpenSubdiv::Far::PatchTable const &patchTable);
/// \brief Returns a handle to the sub-patch of the face at the given (u,v).
/// Note that the patch face ID corresponds to potentially quadrangulated
/// face indices and not the base face indices (see Far::PtexIndices for more
/// details).
///
/// @param patchFaceId The index of the patch (Ptex) face
///
/// @param u Local u parameter
///
/// @param v Local v parameter
///
/// @return A patch handle or 0 if the face is not supported (index
/// out of bounds) or is tagged as a hole
///
Handle const *FindPatch(int patchFaceId, double u, double v) const;
int getMinPatchFace() const
{
return _minPatchFace;
}
int getMaxPatchFace() const
{
return _maxPatchFace;
}
int getMaxDepth() const
{
return _maxDepth;
}
bool getPatchesAreTriangular() const
{
return _patchesAreTriangular;
}
const std::vector<Handle> &getHandles()
{
return _handles;
}
const std::vector<QuadNode> &nodes()
{
return _quadtree;
}
private:
void initializeHandles(OpenSubdiv::Far::PatchTable const &patchTable);
void initializeQuadtree(OpenSubdiv::Far::PatchTable const &patchTable);
using QuadTree = std::vector<QuadNode>;
// Internal methods supporting quadtree construction and queries
void assignRootNode(QuadNode *node, int index);
QuadNode *assignLeafOrChildNode(QuadNode *node, bool isLeaf, int quadrant, int index);
template<class T> static int transformUVToQuadQuadrant(T const &median, T &u, T &v);
template<class T>
static int transformUVToTriQuadrant(T const &median, T &u, T &v, bool &rotated);
bool _patchesAreTriangular; // tri and quad assembly and search requirements differ
int _minPatchFace; // minimum patch face index supported by the map
int _maxPatchFace; // maximum patch face index supported by the map
int _maxDepth; // maximum depth of a patch in the tree
std::vector<Handle> _handles; // all the patches in the PatchTable
std::vector<QuadNode> _quadtree; // quadtree nodes
};
//
// Given a median value for both U and V, these methods transform a (u,v) pair
// into the quadrant that contains them and returns the quadrant index.
//
// Quadrant indexing for tri and quad patches -- consistent with PatchParam's
// usage of UV bits:
//
// (0,1) o-----o-----o (1,1) (0,1) o (1,0) o-----o-----o (0,0)
// | | | |\ \ 1 |\ 0 |
// | 2 | 3 | | \ \ | \ |
// | | | | 2 \ \| 3 \|
// o-----o-----o o-----o o-----o
// | | | |\ 3 |\ \ 2 |
// | 0 | 1 | | \ | \ \ |
// | | | | 0 \| 1 \ \|
// (0,0) o-----o-----o (1,0) (0,0) o-----o-----o (1,0) o (0,1)
//
// The triangular case also takes and returns/affects the rotation of the
// quadrant being searched and identified (quadrant 3 imparts a rotation).
//
template<class T> inline int PatchMap::transformUVToQuadQuadrant(T const &median, T &u, T &v)
{
int uHalf = (u >= median);
if (uHalf) {
u -= median;
}
int vHalf = (v >= median);
if (vHalf) {
v -= median;
}
return (vHalf << 1) | uHalf;
}
template<class T>
int inline PatchMap::transformUVToTriQuadrant(T const &median, T &u, T &v, bool &rotated)
{
if (!rotated) {
if (u >= median) {
u -= median;
return 1;
}
if (v >= median) {
v -= median;
return 2;
}
if ((u + v) >= median) {
rotated = true;
return 3;
}
return 0;
}
if (u < median) {
v -= median;
return 1;
}
if (v < median) {
u -= median;
return 2;
}
u -= median;
v -= median;
if ((u + v) < median) {
rotated = false;
return 3;
}
return 0;
}
/// Returns a handle to the sub-patch of the face at the given (u,v).
inline PatchMap::Handle const *PatchMap::FindPatch(int faceid, double u, double v) const
{
//
// Reject patch faces not supported by this map, or those corresponding
// to holes or otherwise unassigned (the root node for a patch will
// have all or no quadrants set):
//
if ((faceid < _minPatchFace) || (faceid > _maxPatchFace)) {
return nullptr;
}
QuadNode const *node = &_quadtree[faceid - _minPatchFace];
if (!node->children[0].isSet) {
return nullptr;
}
//
// Search the tree for the sub-patch containing the given (u,v)
//
assert((u >= 0.0) && (u <= 1.0) && (v >= 0.0) && (v <= 1.0));
double median = 0.5;
bool triRotated = false;
for (int depth = 0; depth <= _maxDepth; ++depth, median *= 0.5) {
int quadrant = _patchesAreTriangular ? transformUVToTriQuadrant(median, u, v, triRotated) :
transformUVToQuadQuadrant(median, u, v);
// holes should have been rejected at the root node of the face
assert(node->children[quadrant].isSet);
if (node->children[quadrant].isLeaf) {
return &_handles[node->children[quadrant].index];
}
node = &_quadtree[node->children[quadrant].index];
}
assert(0);
return nullptr;
}
} // namespace blender::opensubdiv
#endif // OPENSUBDIV_PATCH_MAP_H_

View File

@@ -0,0 +1,150 @@
//
// Copyright 2013 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "gpu_shader_compat.hh"
/* `osd_patch_defines.glsl` must be included before `osd_patch_basis.glsl` */
#include "osd_patch_defines.glsl"
#include "osd_patch_basis.glsl"
/* Runtime create info. */
GPU_SHADER_CREATE_INFO(osd_eval_patches_comp)
GPU_SHADER_CREATE_END()
//------------------------------------------------------------------------------
OsdPatchCoord GetPatchCoord(int coordIndex)
{
return patchCoords[coordIndex];
}
OsdPatchArray GetPatchArray(int arrayIndex)
{
return patchArrayBuffer[arrayIndex];
}
OsdPatchParam GetPatchParam(int patchIndex)
{
return patchParamBuffer[patchIndex];
}
//------------------------------------------------------------------------------
struct Vertex {
float vertexData[LENGTH];
};
void clear(out Vertex v)
{
for (int i = 0; i < LENGTH; ++i) {
v.vertexData[i] = 0;
}
}
Vertex readVertex(int index)
{
Vertex v;
int vertexIndex = srcOffset + index * SRC_STRIDE;
for (int i = 0; i < LENGTH; ++i) {
v.vertexData[i] = srcVertexBuffer[vertexIndex + i];
}
return v;
}
void writeVertex(int index, Vertex v)
{
int vertexIndex = dstOffset + index * DST_STRIDE;
for (int i = 0; i < LENGTH; ++i) {
dstVertexBuffer[vertexIndex + i] = v.vertexData[i];
}
}
void addWithWeight(Vertex &v, const Vertex src, float weight)
{
for (int i = 0; i < LENGTH; ++i) {
v.vertexData[i] += weight * src.vertexData[i];
}
}
#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES)
void writeDu(int index, Vertex du)
{
int duIndex = duDesc.x + index * duDesc.z;
for (int i = 0; i < LENGTH; ++i) {
duBuffer[duIndex + i] = du.vertexData[i];
}
}
void writeDv(int index, Vertex dv)
{
int dvIndex = dvDesc.x + index * dvDesc.z;
for (int i = 0; i < LENGTH; ++i) {
dvBuffer[dvIndex + i] = dv.vertexData[i];
}
}
#endif
//------------------------------------------------------------------------------
// PERFORMANCE: stride could be constant, but not as significant as length
void main()
{
int current = int(gl_GlobalInvocationID.x);
OsdPatchCoord coord = GetPatchCoord(current);
OsdPatchArray array = GetPatchArray(coord.arrayIndex);
OsdPatchParam param = GetPatchParam(coord.patchIndex);
int patchType = OsdPatchParamIsRegular(param) ? array.regDesc : array.desc;
float wP[20], wDu[20], wDv[20], wDuu[20], wDuv[20], wDvv[20];
int nPoints = OsdEvaluatePatchBasis(
patchType, param, coord.s, coord.t, wP, wDu, wDv, wDuu, wDuv, wDvv);
Vertex dst, du, dv, duu, duv, dvv;
clear(dst);
clear(du);
clear(dv);
int indexBase = array.indexBase + array.stride * (coord.patchIndex - array.primitiveIdBase);
for (int cv = 0; cv < nPoints; ++cv) {
int index = patchIndexBuffer[indexBase + cv];
addWithWeight(dst, readVertex(index), wP[cv]);
addWithWeight(du, readVertex(index), wDu[cv]);
addWithWeight(dv, readVertex(index), wDv[cv]);
}
writeVertex(current, dst);
#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES)
if (duDesc.y > 0) { // length
writeDu(current, du);
}
if (dvDesc.y > 0) {
writeDv(current, dv);
}
#endif
}

View File

@@ -0,0 +1,140 @@
//
// Copyright 2013 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "gpu_shader_compat.hh"
/* `osd_patch_defines.glsl` must be included before `osd_patch_basis.glsl` */
#include "osd_patch_defines.glsl"
#include "osd_patch_basis.glsl"
/* Runtime create info. */
GPU_SHADER_CREATE_INFO(osd_eval_stencils_comp)
GPU_SHADER_CREATE_END()
//------------------------------------------------------------------------------
uint getGlobalInvocationIndex()
{
uint invocations_per_row = gl_WorkGroupSize.x * gl_NumWorkGroups.x;
return gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * invocations_per_row;
}
//------------------------------------------------------------------------------
struct Vertex {
float vertexData[LENGTH];
};
void clear(out Vertex v)
{
for (int i = 0; i < LENGTH; ++i) {
v.vertexData[i] = 0;
}
}
Vertex readVertex(int index)
{
Vertex v;
int vertexIndex = srcOffset + index * SRC_STRIDE;
for (int i = 0; i < LENGTH; ++i) {
v.vertexData[i] = srcVertexBuffer[vertexIndex + i];
}
return v;
}
void writeVertex(int index, Vertex v)
{
int vertexIndex = dstOffset + index * DST_STRIDE;
for (int i = 0; i < LENGTH; ++i) {
dstVertexBuffer[vertexIndex + i] = v.vertexData[i];
}
}
void addWithWeight(Vertex &v, const Vertex src, float weight)
{
for (int i = 0; i < LENGTH; ++i) {
v.vertexData[i] += weight * src.vertexData[i];
}
}
#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES)
void writeDu(int index, Vertex du)
{
int duIndex = duDesc.x + index * duDesc.z;
for (int i = 0; i < LENGTH; ++i) {
duBuffer[duIndex + i] = du.vertexData[i];
}
}
void writeDv(int index, Vertex dv)
{
int dvIndex = dvDesc.x + index * dvDesc.z;
for (int i = 0; i < LENGTH; ++i) {
dvBuffer[dvIndex + i] = dv.vertexData[i];
}
}
#endif
//------------------------------------------------------------------------------
void main()
{
int current = int(getGlobalInvocationIndex()) + batchStart;
if (current >= batchEnd) {
return;
}
Vertex dst;
clear(dst);
int offset = offsets_buf[current], size = sizes_buf[current];
for (int stencil = 0; stencil < size; ++stencil) {
int vindex = offset + stencil;
addWithWeight(dst, readVertex(indices_buf[vindex]), weights_buf[vindex]);
}
writeVertex(current, dst);
#if defined(OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES)
Vertex du, dv;
clear(du);
clear(dv);
for (int i = 0; i < size; ++i) {
// expects the compiler optimizes readVertex out here.
Vertex src = readVertex(indices_buf[offset + i]);
addWithWeight(du, src, du_weights_buf[offset + i]);
addWithWeight(dv, src, dv_weights_buf[offset + i]);
}
if (duDesc.y > 0) { // length
writeDu(current, du);
}
if (dvDesc.y > 0) {
writeDv(current, dv);
}
#endif
}

View File

@@ -0,0 +1,255 @@
/* SPDX-FileCopyrightText: 2020 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#include "internal/topology/mesh_topology.h"
#include <cassert>
namespace blender::opensubdiv {
MeshTopology::MeshTopology() : num_vertices_(0), num_edges_(0), num_faces_(0) {}
MeshTopology::~MeshTopology() = default;
////////////////////////////////////////////////////////////////////////////////
// Vertices.
void MeshTopology::setNumVertices(int num_vertices)
{
num_vertices_ = num_vertices;
}
int MeshTopology::getNumVertices() const
{
return num_vertices_;
}
void MeshTopology::setVertexSharpness(int vertex_index, float sharpness)
{
assert(vertex_index >= 0);
assert(vertex_index < getNumVertices());
ensureVertexTagsSize(vertex_index + 1);
vertex_tags_[vertex_index].sharpness = sharpness;
}
float MeshTopology::getVertexSharpness(int vertex_index) const
{
assert(vertex_index >= 0);
assert(vertex_index < getNumVertices());
if (vertex_index >= vertex_tags_.size()) {
// Sharpness for the vertex was never provided.
return 0.0f;
}
return vertex_tags_[vertex_index].sharpness;
}
void MeshTopology::ensureVertexTagsSize(int num_vertices)
{
if (vertex_tags_.size() < num_vertices) {
vertex_tags_.resize(num_vertices);
}
}
////////////////////////////////////////////////////////////////////////////////
// Edges.
void MeshTopology::setNumEdges(int num_edges)
{
num_edges_ = num_edges;
}
int MeshTopology::getNumEdges() const
{
return num_edges_;
}
void MeshTopology::setEdgeVertexIndices(int edge_index, int v1, int v2)
{
assert(edge_index >= 0);
assert(edge_index < getNumEdges());
assert(v1 >= 0);
assert(v1 < getNumVertices());
assert(v2 >= 0);
assert(v2 < getNumVertices());
ensureNumEdgesAtLeast(edge_index + 1);
Edge &edge = edges_[edge_index];
edge.v1 = v1;
edge.v2 = v2;
}
void MeshTopology::getEdgeVertexIndices(int edge_index, int *v1, int *v2) const
{
assert(edge_index >= 0);
assert(edge_index < getNumEdges());
if (edge_index >= edges_.size()) {
*v1 = -1;
*v2 = -1;
return;
}
const Edge &edge = edges_[edge_index];
*v1 = edge.v1;
*v2 = edge.v2;
}
bool MeshTopology::isEdgeEqual(int edge_index, int expected_v1, int expected_v2) const
{
assert(edge_index >= 0);
assert(edge_index < getNumEdges());
if (edge_index >= edges_.size()) {
return false;
}
const Edge &edge = edges_[edge_index];
return edge.v1 == expected_v1 && edge.v2 == expected_v2;
}
void MeshTopology::setEdgeSharpness(int edge_index, float sharpness)
{
assert(edge_index >= 0);
assert(edge_index < getNumEdges());
if (sharpness < 1e-6f) {
return;
}
ensureEdgeTagsSize(edge_index + 1);
edge_tags_[edge_index].sharpness = sharpness;
}
float MeshTopology::getEdgeSharpness(int edge_index) const
{
assert(edge_index >= 0);
if (edge_index >= edge_tags_.size()) {
// NOTE: It's possible that full topology is not known and that there was
// never sharpness assigned to any of the edges.
return 0.0f;
}
return edge_tags_[edge_index].sharpness;
}
void MeshTopology::ensureNumEdgesAtLeast(int num_edges)
{
if (edges_.size() < num_edges) {
edges_.resize(num_edges);
}
}
void MeshTopology::ensureEdgeTagsSize(int num_edges)
{
if (edge_tags_.size() < num_edges) {
edge_tags_.resize(num_edges);
}
}
////////////////////////////////////////////////////////////////////////////////
// Faces.
void MeshTopology::setNumFaces(int num_faces)
{
num_faces_ = num_faces;
// NOTE: Extra element to store fake face past the last real one to make it
// possible to calculate number of vertices in the last face.
faces_first_vertex_index_.resize(num_faces + 1, 0);
}
int MeshTopology::getNumFaces() const
{
return num_faces_;
}
void MeshTopology::setNumFaceVertices(int face_index, int num_face_vertices)
{
assert(face_index >= 0);
assert(face_index < getNumFaces());
faces_first_vertex_index_[face_index + 1] = faces_first_vertex_index_[face_index] +
num_face_vertices;
}
int MeshTopology::getNumFaceVertices(int face_index) const
{
assert(face_index >= 0);
assert(face_index < getNumFaces());
return faces_first_vertex_index_[face_index + 1] - faces_first_vertex_index_[face_index];
}
void MeshTopology::setFaceVertexIndices(int face_index,
int num_face_vertex_indices,
const int *face_vertex_indices)
{
assert(face_index >= 0);
assert(face_index < getNumFaces());
assert(num_face_vertex_indices == getNumFaceVertices(face_index));
int *face_vertex_indices_storage = getFaceVertexIndicesStorage(face_index);
memcpy(face_vertex_indices_storage, face_vertex_indices, sizeof(int) * num_face_vertex_indices);
}
bool MeshTopology::isFaceVertexIndicesEqual(int face_index,
int num_expected_face_vertex_indices,
const int *expected_face_vertex_indices) const
{
assert(face_index >= 0);
assert(face_index < getNumFaces());
if (getNumFaceVertices(face_index) != num_expected_face_vertex_indices) {
return false;
}
const int *face_vertex_indices_storage = getFaceVertexIndicesStorage(face_index);
return memcmp(face_vertex_indices_storage,
expected_face_vertex_indices,
sizeof(int) * num_expected_face_vertex_indices) == 0;
}
bool MeshTopology::isFaceVertexIndicesEqual(
int face_index, const std::vector<int> &expected_face_vertex_indices) const
{
return isFaceVertexIndicesEqual(
face_index, expected_face_vertex_indices.size(), expected_face_vertex_indices.data());
}
int *MeshTopology::getFaceVertexIndicesStorage(int face_index)
{
const MeshTopology *const_this = this;
return const_cast<int *>(const_this->getFaceVertexIndicesStorage(face_index));
}
const int *MeshTopology::getFaceVertexIndicesStorage(int face_index) const
{
assert(face_index >= 0);
assert(face_index < getNumFaces());
const int offset = faces_first_vertex_index_[face_index];
return face_vertex_indices_.data() + offset;
}
////////////////////////////////////////////////////////////////////////////////
// Pipeline related.
void MeshTopology::finishResizeTopology()
{
if (!faces_first_vertex_index_.empty()) {
face_vertex_indices_.resize(faces_first_vertex_index_.back());
}
}
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,168 @@
/* SPDX-FileCopyrightText: 2020 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#ifndef OPENSUBDIV_MESH_TOPOLOGY_H_
#define OPENSUBDIV_MESH_TOPOLOGY_H_
#include <vector>
#include "internal/base/memory.h"
struct OpenSubdiv_Converter;
namespace blender::opensubdiv {
// Simplified representation of mesh topology.
// Only includes parts of actual mesh topology which is needed to perform
// comparison between Application side and OpenSubddiv side.
//
// NOTE: It is an optimized storage which requires special order of topology
// specification. Basically, counters is to be set prior to anything else, in
// the following manner:
//
// MeshTopology mesh_topology;
//
// mesh_topology.setNumVertices(...);
// mesh_topology.setNumEdges(...);
// mesh_topology.setNumFaces(...);
//
// for (...) {
// mesh_topology.setNumFaceVertices(...);
// }
//
// mesh_topology.finishResizeTopology();
//
// /* it is now possible to set vertices of edge, vertices of face, and
// * sharpness. */
class MeshTopology {
public:
MeshTopology();
MeshTopology(const MeshTopology &other) = default;
MeshTopology(MeshTopology &&other) noexcept = default;
~MeshTopology();
MeshTopology &operator=(const MeshTopology &other) = default;
MeshTopology &operator=(MeshTopology &&other) = default;
//////////////////////////////////////////////////////////////////////////////
// Vertices.
void setNumVertices(int num_vertices);
int getNumVertices() const;
void setVertexSharpness(int vertex_index, float sharpness);
float getVertexSharpness(int vertex_index) const;
//////////////////////////////////////////////////////////////////////////////
// Edges.
void setNumEdges(int num_edges);
// NOTE: Unless full topology was specified will return number of edges based
// on last edge index for which topology tag was specified.
int getNumEdges() const;
void setEdgeVertexIndices(int edge_index, int v1, int v2);
void getEdgeVertexIndices(int edge_index, int *v1, int *v2) const;
bool isEdgeEqual(int edge_index, int expected_v1, int expected_v2) const;
void setEdgeSharpness(int edge_index, float sharpness);
float getEdgeSharpness(int edge_index) const;
//////////////////////////////////////////////////////////////////////////////
// Faces.
void setNumFaces(int num_faces);
int getNumFaces() const;
void setNumFaceVertices(int face_index, int num_face_vertices);
int getNumFaceVertices(int face_index) const;
void setFaceVertexIndices(int face_index,
int num_face_vertex_indices,
const int *face_vertex_indices);
bool isFaceVertexIndicesEqual(int face_index,
int num_expected_face_vertex_indices,
const int *expected_face_vertex_indices) const;
bool isFaceVertexIndicesEqual(int face_index,
const std::vector<int> &expected_face_vertex_indices) const;
//////////////////////////////////////////////////////////////////////////////
// Pipeline related.
// This function is to be called when number of vertices, edges, faces, and
// face-vertices are known.
//
// Usually is called from the end of topology refiner factory's
// resizeComponentTopology().
void finishResizeTopology();
//////////////////////////////////////////////////////////////////////////////
// Comparison.
// Compare given topology with converter. Returns truth if topology
// matches given converter, false otherwise.
//
// This allows users to construct converter (which is supposed to be cheap)
// and compare with existing topology before going into more computationally
// complicated parts of subdivision process.
bool isEqualToConverter(const OpenSubdiv_Converter *converter) const;
protected:
// Edges are allowed to be stored sparsly, to save memory used by
// non-semi-sharp edges.
void ensureNumEdgesAtLeast(int num_edges);
// Geometry tags are stored sparsly.
//
// These functions ensures that the storage can be addressed by an index which
// corresponds to the given size.
void ensureVertexTagsSize(int num_vertices);
void ensureEdgeTagsSize(int num_edges);
// Get pointer to the memory where face vertex indices are stored.
int *getFaceVertexIndicesStorage(int face_index);
const int *getFaceVertexIndicesStorage(int face_index) const;
struct VertexTag {
float sharpness = 0.0f;
};
struct Edge {
int v1 = -1;
int v2 = -1;
};
struct EdgeTag {
float sharpness = 0.0f;
};
int num_vertices_;
std::vector<VertexTag> vertex_tags_;
int num_edges_;
std::vector<Edge> edges_;
std::vector<EdgeTag> edge_tags_;
int num_faces_;
// Continuous array of all vertices of all faces:
// [vertex indices of face 0][vertex indices of face 1] .. [vertex indices of face n].
std::vector<int> face_vertex_indices_;
// Indexed by face contains index within face_vertex_indices_ which corresponds
// to the element which contains first vertex of the face.
std::vector<int> faces_first_vertex_index_;
MEM_CXX_CLASS_ALLOC_FUNCS("MeshTopology");
};
} // namespace blender::opensubdiv
#endif // OPENSUBDIV_MESH_TOPOLOGY_H_

View File

@@ -0,0 +1,229 @@
/* SPDX-FileCopyrightText: 2020 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#include "internal/topology/mesh_topology.h"
#include <cassert>
#include <cstring>
#include <opensubdiv/sdc/crease.h>
#include <vector>
#include "opensubdiv_converter_capi.hh"
namespace blender::opensubdiv {
////////////////////////////////////////////////////////////////////////////////
// Quick preliminary checks.
static int getEffectiveNumEdges(const OpenSubdiv_Converter *converter)
{
if (converter->getNumEdges == nullptr) {
return 0;
}
return converter->getNumEdges(converter);
}
static bool isEqualGeometryCounters(const MeshTopology &mesh_topology,
const OpenSubdiv_Converter *converter)
{
if (converter->getNumVertices(converter) != mesh_topology.getNumVertices()) {
return false;
}
if (converter->faces.size() != mesh_topology.getNumFaces()) {
return false;
}
if (getEffectiveNumEdges(converter) != mesh_topology.getNumEdges()) {
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
// Geometry.
// Edges.
static bool isEqualGeometryEdge(const MeshTopology &mesh_topology,
const OpenSubdiv_Converter *converter)
{
const int num_requested_edges = getEffectiveNumEdges(converter);
if (num_requested_edges != mesh_topology.getNumEdges()) {
return false;
}
// NOTE: Ignoring the sharpness we don't really care of the content of the
// edges, they should be in the consistent state with faces and face-vertices.
// If that's not the case the mesh is invalid and comparison can not happen
// reliably.
//
// For sharpness it is important to know that edges are connecting same pair
// of vertices. But since sharpness is stored sparesly the connectivity will
// be checked when comparing edge sharpness.
return true;
}
// Faces.
static bool isEqualGeometryFace(const MeshTopology &mesh_topology,
const OpenSubdiv_Converter *converter)
{
const int num_requested_faces = converter->faces.size();
if (num_requested_faces != mesh_topology.getNumFaces()) {
return false;
}
std::vector<int> vertices_of_face;
for (int face_index = 0; face_index < num_requested_faces; ++face_index) {
int num_face_vertices = converter->faces[face_index].size();
if (mesh_topology.getNumFaceVertices(face_index) != num_face_vertices) {
return false;
}
vertices_of_face.resize(num_face_vertices);
converter->getFaceVertices(converter, face_index, vertices_of_face.data());
if (!mesh_topology.isFaceVertexIndicesEqual(face_index, vertices_of_face)) {
return false;
}
}
return true;
}
// Geometry comparison entry point.
static bool isEqualGeometry(const MeshTopology &mesh_topology,
const OpenSubdiv_Converter *converter)
{
if (!isEqualGeometryEdge(mesh_topology, converter)) {
return false;
}
if (!isEqualGeometryFace(mesh_topology, converter)) {
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
// Geometry tags.
// Vertices.
// TODO(sergey): Make this function usable by factory as well.
static float getEffectiveVertexSharpness(const OpenSubdiv_Converter *converter,
const int vertex_index)
{
if (converter->isInfiniteSharpVertex != nullptr &&
converter->isInfiniteSharpVertex(converter, vertex_index))
{
return OpenSubdiv::Sdc::Crease::SHARPNESS_INFINITE;
}
if (converter->getVertexSharpness != nullptr) {
return converter->getVertexSharpness(converter, vertex_index);
}
return 0.0f;
}
static bool isEqualVertexTags(const MeshTopology &mesh_topology,
const OpenSubdiv_Converter *converter)
{
const int num_vertices = mesh_topology.getNumVertices();
for (int vertex_index = 0; vertex_index < num_vertices; ++vertex_index) {
const float current_sharpness = mesh_topology.getVertexSharpness(vertex_index);
const float requested_sharpness = getEffectiveVertexSharpness(converter, vertex_index);
if (current_sharpness != requested_sharpness) {
return false;
}
}
return true;
}
// Edges.
// TODO(sergey): Make this function usable by factory as well.
static float getEffectiveEdgeSharpness(const OpenSubdiv_Converter *converter, const int edge_index)
{
if (converter->getEdgeSharpness != nullptr) {
return converter->getEdgeSharpness(converter, edge_index);
}
return 0.0f;
}
static bool isEqualEdgeTags(const MeshTopology &mesh_topology,
const OpenSubdiv_Converter *converter)
{
const int num_edges = mesh_topology.getNumEdges();
for (int edge_index = 0; edge_index < num_edges; ++edge_index) {
const float current_sharpness = mesh_topology.getEdgeSharpness(edge_index);
const float requested_sharpness = getEffectiveEdgeSharpness(converter, edge_index);
if (current_sharpness != requested_sharpness) {
return false;
}
if (current_sharpness < 1e-6f) {
continue;
}
int requested_edge_vertices[2];
converter->getEdgeVertices(converter, edge_index, requested_edge_vertices);
if (!mesh_topology.isEdgeEqual(
edge_index, requested_edge_vertices[0], requested_edge_vertices[1]))
{
return false;
}
}
return true;
}
// Tags comparison entry point.
static bool isEqualTags(const MeshTopology &mesh_topology, const OpenSubdiv_Converter *converter)
{
if (!isEqualVertexTags(mesh_topology, converter)) {
return false;
}
if (!isEqualEdgeTags(mesh_topology, converter)) {
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
// Entry point.
bool MeshTopology::isEqualToConverter(const OpenSubdiv_Converter *converter) const
{
// Preliminary checks.
if (!isEqualGeometryCounters(*this, converter)) {
return false;
}
// Geometry.
if (!isEqualGeometry(*this, converter)) {
return false;
}
// Tags.
if (!isEqualTags(*this, converter)) {
return false;
}
return true;
}
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,84 @@
/* SPDX-FileCopyrightText: 2020 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#include "internal/topology/mesh_topology.h"
#include "testing/testing.h"
namespace blender::opensubdiv {
TEST(MeshTopology, TrivialVertexSharpness)
{
MeshTopology mesh_topology;
mesh_topology.setNumVertices(3);
mesh_topology.finishResizeTopology();
mesh_topology.setVertexSharpness(0, 0.1f);
mesh_topology.setVertexSharpness(1, 0.2f);
EXPECT_EQ(mesh_topology.getVertexSharpness(0), 0.1f);
EXPECT_EQ(mesh_topology.getVertexSharpness(1), 0.2f);
EXPECT_EQ(mesh_topology.getVertexSharpness(2), 0.0f);
}
TEST(MeshTopology, TrivialEdgeSharpness)
{
MeshTopology mesh_topology;
mesh_topology.setNumVertices(8);
mesh_topology.setNumEdges(3);
mesh_topology.finishResizeTopology();
mesh_topology.setEdgeVertexIndices(0, 0, 1);
mesh_topology.setEdgeVertexIndices(1, 1, 2);
mesh_topology.setEdgeVertexIndices(2, 2, 3);
mesh_topology.setEdgeSharpness(0, 0.1f);
mesh_topology.setEdgeSharpness(2, 0.2f);
EXPECT_EQ(mesh_topology.getEdgeSharpness(0), 0.1f);
EXPECT_EQ(mesh_topology.getEdgeSharpness(1), 0.0f);
EXPECT_EQ(mesh_topology.getEdgeSharpness(2), 0.2f);
}
TEST(MeshTopology, TrivialFaceTopology)
{
MeshTopology mesh_topology;
mesh_topology.setNumFaces(3);
mesh_topology.setNumFaceVertices(0, 4);
mesh_topology.setNumFaceVertices(1, 3);
mesh_topology.setNumFaceVertices(2, 5);
mesh_topology.finishResizeTopology();
EXPECT_EQ(mesh_topology.getNumFaceVertices(0), 4);
EXPECT_EQ(mesh_topology.getNumFaceVertices(1), 3);
EXPECT_EQ(mesh_topology.getNumFaceVertices(2), 5);
{
const int vertex_indices[] = {0, 1, 2, 3};
mesh_topology.setFaceVertexIndices(0, 4, vertex_indices);
}
{
const int vertex_indices[] = {4, 5, 6};
mesh_topology.setFaceVertexIndices(1, 3, vertex_indices);
}
{
const int vertex_indices[] = {7, 8, 9, 10, 11};
mesh_topology.setFaceVertexIndices(2, 5, vertex_indices);
}
EXPECT_TRUE(mesh_topology.isFaceVertexIndicesEqual(0, {{0, 1, 2, 3}}));
EXPECT_FALSE(mesh_topology.isFaceVertexIndicesEqual(0, {{10, 1, 2, 3}}));
EXPECT_FALSE(mesh_topology.isFaceVertexIndicesEqual(0, {{0, 1, 2}}));
EXPECT_TRUE(mesh_topology.isFaceVertexIndicesEqual(1, {{4, 5, 6}}));
EXPECT_TRUE(mesh_topology.isFaceVertexIndicesEqual(2, {{7, 8, 9, 10, 11}}));
}
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,363 @@
/* SPDX-FileCopyrightText: 2015 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#ifdef _MSC_VER
# include <iso646.h>
#endif
#include <cassert>
#include <cstdio>
#include <opensubdiv/far/topologyRefinerFactory.h>
#include "internal/base/type_convert.h"
#include "internal/topology/mesh_topology.h"
#include "opensubdiv_converter_capi.hh"
#include "opensubdiv_topology_refiner.hh"
struct TopologyRefinerData {
const OpenSubdiv_Converter *converter;
blender::opensubdiv::MeshTopology *base_mesh_topology;
};
using TopologyRefinerFactoryType = OpenSubdiv::Far::TopologyRefinerFactory<TopologyRefinerData>;
namespace OpenSubdiv::OPENSUBDIV_VERSION::Far {
template<>
inline bool TopologyRefinerFactory<TopologyRefinerData>::resizeComponentTopology(
TopologyRefiner &refiner, const TopologyRefinerData &cb_data)
{
using blender::opensubdiv::MeshTopology;
const OpenSubdiv_Converter *converter = cb_data.converter;
MeshTopology *base_mesh_topology = cb_data.base_mesh_topology;
// Vertices.
const int num_vertices = converter->getNumVertices(converter);
base_mesh_topology->setNumVertices(num_vertices);
setNumBaseVertices(refiner, num_vertices);
// Edges.
//
// NOTE: Always store edges in the base mesh topology so then comparison can
// happen, but only provide edges to TopologyRefiner if full topology is
// specified (if full topology is not specified then topology refiner must
// not see any edges, which will indicate to it that winding and edges are to
// be reconstructed).
//
// NOTE: it is a possible use case when user code does not need crease at all
// (which is the only real reason why converter would want to provide edges in
// the case of partial topology specification). So it might be so getNumEdges
// callback is nullptr.
if (converter->getNumEdges != nullptr) {
const int num_edges = converter->getNumEdges(converter);
base_mesh_topology->setNumEdges(num_edges);
}
// Faces and face-vertices.
const blender::OffsetIndices<int> src_faces = converter->faces;
base_mesh_topology->setNumFaces(src_faces.size());
setNumBaseFaces(refiner, src_faces.size());
for (const int face_index : src_faces.index_range()) {
const int num_face_vertices = src_faces[face_index].size();
base_mesh_topology->setNumFaceVertices(face_index, num_face_vertices);
setNumBaseFaceVertices(refiner, face_index, num_face_vertices);
}
// If converter does not provide full topology, we are done.
//
// The rest is needed to define relations between faces-of-edge and
// edges-of-vertex, which is not available for partially specified mesh.
if (!converter->specifiesFullTopology(converter)) {
base_mesh_topology->finishResizeTopology();
return true;
}
// Edges and edge-faces.
const int num_edges = converter->getNumEdges(converter);
setNumBaseEdges(refiner, num_edges);
for (int edge_index = 0; edge_index < num_edges; ++edge_index) {
const int num_edge_faces = converter->getNumEdgeFaces(converter, edge_index);
setNumBaseEdgeFaces(refiner, edge_index, num_edge_faces);
}
// Vertex-faces and vertex-edges.
for (int vertex_index = 0; vertex_index < num_vertices; ++vertex_index) {
const int num_vert_edges = converter->getNumVertexEdges(converter, vertex_index);
const int num_vert_faces = converter->getNumVertexFaces(converter, vertex_index);
setNumBaseVertexEdges(refiner, vertex_index, num_vert_edges);
setNumBaseVertexFaces(refiner, vertex_index, num_vert_faces);
}
base_mesh_topology->finishResizeTopology();
return true;
}
template<>
inline bool TopologyRefinerFactory<TopologyRefinerData>::assignComponentTopology(
TopologyRefiner &refiner, const TopologyRefinerData &cb_data)
{
using blender::opensubdiv::MeshTopology;
using Far::IndexArray;
const OpenSubdiv_Converter *converter = cb_data.converter;
MeshTopology *base_mesh_topology = cb_data.base_mesh_topology;
const bool full_topology_specified = converter->specifiesFullTopology(converter);
const blender::OffsetIndices<int> src_faces = converter->faces;
// Vertices of face.
for (const int face_index : src_faces.index_range()) {
IndexArray dst_face_verts = getBaseFaceVertices(refiner, face_index);
converter->getFaceVertices(converter, face_index, &dst_face_verts[0]);
base_mesh_topology->setFaceVertexIndices(
face_index, dst_face_verts.size(), &dst_face_verts[0]);
}
// If converter does not provide full topology, we are done.
//
// The rest is needed to define relations between faces-of-edge and
// edges-of-vertex, which is not available for partially specified mesh.
if (!full_topology_specified) {
return true;
}
// Vertex relations.
const int num_vertices = converter->getNumVertices(converter);
std::vector<int> vertex_faces, vertex_edges;
for (int vertex_index = 0; vertex_index < num_vertices; ++vertex_index) {
// Vertex-faces.
IndexArray dst_vertex_faces = getBaseVertexFaces(refiner, vertex_index);
const int num_vertex_faces = converter->getNumVertexFaces(converter, vertex_index);
vertex_faces.resize(num_vertex_faces);
converter->getVertexFaces(converter, vertex_index, vertex_faces.data());
// Vertex-edges.
IndexArray dst_vertex_edges = getBaseVertexEdges(refiner, vertex_index);
const int num_vertex_edges = converter->getNumVertexEdges(converter, vertex_index);
vertex_edges.resize(num_vertex_edges);
converter->getVertexEdges(converter, vertex_index, vertex_edges.data());
memcpy(&dst_vertex_edges[0], vertex_edges.data(), sizeof(int) * num_vertex_edges);
memcpy(&dst_vertex_faces[0], vertex_faces.data(), sizeof(int) * num_vertex_faces);
}
// Edge relations.
const int num_edges = converter->getNumEdges(converter);
for (int edge_index = 0; edge_index < num_edges; ++edge_index) {
// Vertices this edge connects.
IndexArray dst_edge_vertices = getBaseEdgeVertices(refiner, edge_index);
converter->getEdgeVertices(converter, edge_index, &dst_edge_vertices[0]);
// Faces adjacent to this edge.
IndexArray dst_edge_faces = getBaseEdgeFaces(refiner, edge_index);
converter->getEdgeFaces(converter, edge_index, &dst_edge_faces[0]);
}
// Face relations.
for (const int face_index : src_faces.index_range()) {
IndexArray dst_face_edges = getBaseFaceEdges(refiner, face_index);
converter->getFaceEdges(converter, face_index, &dst_face_edges[0]);
}
populateBaseLocalIndices(refiner);
return true;
}
template<>
inline bool TopologyRefinerFactory<TopologyRefinerData>::assignComponentTags(
TopologyRefiner &refiner, const TopologyRefinerData &cb_data)
{
using blender::opensubdiv::MeshTopology;
using OpenSubdiv::Sdc::Crease;
const OpenSubdiv_Converter *converter = cb_data.converter;
MeshTopology *base_mesh_topology = cb_data.base_mesh_topology;
const bool full_topology_specified = converter->specifiesFullTopology(converter);
if (full_topology_specified || converter->getEdgeVertices != nullptr) {
const int num_edges = converter->getNumEdges(converter);
for (int edge_index = 0; edge_index < num_edges; ++edge_index) {
const float sharpness = converter->getEdgeSharpness(converter, edge_index);
if (sharpness < 1e-6f) {
continue;
}
int edge_vertices[2];
converter->getEdgeVertices(converter, edge_index, edge_vertices);
base_mesh_topology->setEdgeVertexIndices(edge_index, edge_vertices[0], edge_vertices[1]);
base_mesh_topology->setEdgeSharpness(edge_index, sharpness);
if (full_topology_specified) {
setBaseEdgeSharpness(refiner, edge_index, sharpness);
}
else {
// TODO(sergey): Should be a faster way to find reconstructed edge to
// specify sharpness for (assuming, findBaseEdge has linear complexity).
const int base_edge_index = findBaseEdge(refiner, edge_vertices[0], edge_vertices[1]);
if (base_edge_index == OpenSubdiv::Far::INDEX_INVALID) {
printf("OpenSubdiv Error: failed to find reconstructed edge\n");
return false;
}
setBaseEdgeSharpness(refiner, base_edge_index, sharpness);
}
}
}
// OpenSubdiv expects non-manifold vertices to be sharp but at the time it
// handles correct cases when vertex is a corner of plane. Currently mark
// vertices which are adjacent to a loose edge as sharp, but this decision
// needs some more investigation.
const int num_vertices = converter->getNumVertices(converter);
for (int vertex_index = 0; vertex_index < num_vertices; ++vertex_index) {
ConstIndexArray vertex_edges = getBaseVertexEdges(refiner, vertex_index);
if (converter->isInfiniteSharpVertex(converter, vertex_index)) {
base_mesh_topology->setVertexSharpness(vertex_index, Crease::SHARPNESS_INFINITE);
setBaseVertexSharpness(refiner, vertex_index, Crease::SHARPNESS_INFINITE);
continue;
}
// Get sharpness provided by the converter.
float sharpness = 0.0f;
if (converter->getVertexSharpness != nullptr) {
sharpness = converter->getVertexSharpness(converter, vertex_index);
base_mesh_topology->setVertexSharpness(vertex_index, sharpness);
}
// If its vertex where 2 non-manifold edges meet adjust vertex sharpness to
// the edges.
// This way having a plane with all 4 edges set to be sharp produces sharp
// corners in the subdivided result.
if (vertex_edges.size() == 2) {
const int edge0 = vertex_edges[0], edge1 = vertex_edges[1];
const float sharpness0 = refiner._levels[0]->getEdgeSharpness(edge0);
const float sharpness1 = refiner._levels[0]->getEdgeSharpness(edge1);
// TODO(sergey): Find a better mixing between edge and vertex sharpness.
sharpness += std::min(sharpness0, sharpness1);
sharpness = std::min(sharpness, 10.0f);
}
setBaseVertexSharpness(refiner, vertex_index, sharpness);
}
return true;
}
template<>
inline bool TopologyRefinerFactory<TopologyRefinerData>::assignFaceVaryingTopology(
TopologyRefiner &refiner, const TopologyRefinerData &cb_data)
{
const OpenSubdiv_Converter *converter = cb_data.converter;
if (converter->getNumUVLayers == nullptr) {
assert(converter->precalcUVLayer == nullptr);
assert(converter->getNumUVCoordinates == nullptr);
assert(converter->getFaceCornerUVIndex == nullptr);
assert(converter->finishUVLayer == nullptr);
return true;
}
const int num_layers = converter->getNumUVLayers(converter);
if (num_layers <= 0) {
// No UV maps, we can skip any face-varying data.
return true;
}
const int num_faces = getNumBaseFaces(refiner);
for (int layer_index = 0; layer_index < num_layers; ++layer_index) {
converter->precalcUVLayer(converter, layer_index);
const int num_uvs = converter->getNumUVCoordinates(converter);
// Fill in per-corner index of the UV.
const int channel = createBaseFVarChannel(refiner, num_uvs);
// TODO(sergey): Need to check whether converter changed the winding of
// face to match OpenSubdiv's expectations.
for (int face_index = 0; face_index < num_faces; ++face_index) {
Far::IndexArray dst_face_uvs = getBaseFaceFVarValues(refiner, face_index, channel);
for (int corner = 0; corner < dst_face_uvs.size(); ++corner) {
const int uv_index = converter->getFaceCornerUVIndex(converter, face_index, corner);
dst_face_uvs[corner] = uv_index;
}
}
converter->finishUVLayer(converter);
}
return true;
}
template<>
inline void TopologyRefinerFactory<TopologyRefinerData>::reportInvalidTopology(
TopologyError /*errCode*/, const char *msg, const TopologyRefinerData & /*mesh*/)
{
printf("OpenSubdiv Error: %s\n", msg);
}
} // namespace OpenSubdiv::OPENSUBDIV_VERSION::Far
namespace blender::opensubdiv {
static OpenSubdiv::Sdc::Options getSDCOptions(OpenSubdiv_Converter *converter)
{
using OpenSubdiv::Sdc::Options;
const Options::FVarLinearInterpolation linear_interpolation = getFVarLinearInterpolationFromCAPI(
converter->getFVarLinearInterpolation(converter));
Options options;
options.SetVtxBoundaryInterpolation(
getVtxBoundaryInterpolationFromCAPI(converter->getVtxBoundaryInterpolation(converter)));
options.SetCreasingMethod(Options::CREASE_UNIFORM);
options.SetFVarLinearInterpolation(linear_interpolation);
return options;
}
static TopologyRefinerFactoryType::Options getTopologyRefinerOptions(
OpenSubdiv_Converter *converter)
{
using OpenSubdiv::Sdc::SchemeType;
OpenSubdiv::Sdc::Options sdc_options = getSDCOptions(converter);
const SchemeType scheme_type = getSchemeTypeFromCAPI(converter->getSchemeType(converter));
TopologyRefinerFactoryType::Options topology_options(scheme_type, sdc_options);
// NOTE: When debugging topology conversion related functionality it is helpful to set this
// to truth. In all other cases leave it at false. so debugging of other areas is not affected
// by performance penalty happening in this module.
topology_options.validateFullTopology = false;
return topology_options;
}
TopologyRefinerImpl *TopologyRefinerImpl::createFromConverter(
OpenSubdiv_Converter *converter, const OpenSubdiv_TopologyRefinerSettings &settings)
{
using OpenSubdiv::Far::TopologyRefiner;
blender::opensubdiv::MeshTopology base_mesh_topology;
TopologyRefinerData cb_data;
cb_data.converter = converter;
cb_data.base_mesh_topology = &base_mesh_topology;
// Create OpenSubdiv descriptor for the topology refiner.
TopologyRefinerFactoryType::Options topology_refiner_options = getTopologyRefinerOptions(
converter);
TopologyRefiner *topology_refiner = TopologyRefinerFactoryType::Create(cb_data,
topology_refiner_options);
if (topology_refiner == nullptr) {
return nullptr;
}
// Create Blender-side object holding all necessary data for the topology refiner.
TopologyRefinerImpl *topology_refiner_impl = new TopologyRefinerImpl();
topology_refiner_impl->topology_refiner = topology_refiner;
topology_refiner_impl->settings = settings;
topology_refiner_impl->base_mesh_topology = std::move(base_mesh_topology);
return topology_refiner_impl;
}
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2016 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#include "opensubdiv_topology_refiner.hh"
namespace blender::opensubdiv {
TopologyRefinerImpl::TopologyRefinerImpl() : topology_refiner(nullptr) {}
TopologyRefinerImpl::~TopologyRefinerImpl()
{
delete topology_refiner;
}
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,133 @@
/* SPDX-FileCopyrightText: 2018 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#include "internal/base/type_convert.h"
#include "internal/topology/mesh_topology.h"
#include "opensubdiv_converter_capi.hh"
#include "opensubdiv_topology_refiner.hh"
namespace blender::opensubdiv {
static const OpenSubdiv::Far::TopologyRefiner *getOSDTopologyRefiner(
const TopologyRefinerImpl *topology_refiner_impl)
{
return topology_refiner_impl->topology_refiner;
}
static const OpenSubdiv::Far::TopologyLevel &getOSDTopologyBaseLevel(
const TopologyRefinerImpl *topology_refiner_impl)
{
return getOSDTopologyRefiner(topology_refiner_impl)->GetLevel(0);
}
////////////////////////////////////////////////////////////////////////////////
// Quick preliminary checks.
static bool checkSchemeTypeMatches(const TopologyRefinerImpl *topology_refiner_impl,
const OpenSubdiv_Converter *converter)
{
const OpenSubdiv::Sdc::SchemeType converter_scheme_type =
blender::opensubdiv::getSchemeTypeFromCAPI(converter->getSchemeType(converter));
return (converter_scheme_type == getOSDTopologyRefiner(topology_refiner_impl)->GetSchemeType());
}
static bool checkOptionsMatches(const TopologyRefinerImpl *topology_refiner_impl,
const OpenSubdiv_Converter *converter)
{
using Options = OpenSubdiv::Sdc::Options;
const Options options = getOSDTopologyRefiner(topology_refiner_impl)->GetSchemeOptions();
const Options::FVarLinearInterpolation fvar_interpolation = options.GetFVarLinearInterpolation();
const Options::FVarLinearInterpolation converter_fvar_interpolation =
blender::opensubdiv::getFVarLinearInterpolationFromCAPI(
converter->getFVarLinearInterpolation(converter));
if (fvar_interpolation != converter_fvar_interpolation) {
return false;
}
return true;
}
static bool checkPreliminaryMatches(const TopologyRefinerImpl *topology_refiner_impl,
const OpenSubdiv_Converter *converter)
{
return checkSchemeTypeMatches(topology_refiner_impl, converter) &&
checkOptionsMatches(topology_refiner_impl, converter);
}
////////////////////////////////////////////////////////////////////////////////
// Compare attributes which affects on topology.
//
// TODO(sergey): Need to look into how auto-winding affects on face-varying
// indexing and, possibly, move to mesh topology as well if winding affects
// face-varyign as well.
static bool checkSingleUVLayerMatch(const OpenSubdiv::Far::TopologyLevel &base_level,
const OpenSubdiv_Converter *converter,
const int layer_index)
{
converter->precalcUVLayer(converter, layer_index);
const int num_faces = base_level.GetNumFaces();
// TODO(sergey): Need to check whether converter changed the winding of
// face to match OpenSubdiv's expectations.
for (int face_index = 0; face_index < num_faces; ++face_index) {
OpenSubdiv::Far::ConstIndexArray base_level_face_uvs = base_level.GetFaceFVarValues(
face_index, layer_index);
for (int corner = 0; corner < base_level_face_uvs.size(); ++corner) {
const int uv_index = converter->getFaceCornerUVIndex(converter, face_index, corner);
if (base_level_face_uvs[corner] != uv_index) {
converter->finishUVLayer(converter);
return false;
}
}
}
converter->finishUVLayer(converter);
return true;
}
static bool checkUVLayersMatch(const TopologyRefinerImpl *topology_refiner_impl,
const OpenSubdiv_Converter *converter)
{
using OpenSubdiv::Far::TopologyLevel;
const int num_layers = converter->getNumUVLayers(converter);
const TopologyLevel &base_level = getOSDTopologyBaseLevel(topology_refiner_impl);
// Number of UV layers should match.
if (base_level.GetNumFVarChannels() != num_layers) {
return false;
}
for (int layer_index = 0; layer_index < num_layers; ++layer_index) {
if (!checkSingleUVLayerMatch(base_level, converter, layer_index)) {
return false;
}
}
return true;
}
static bool checkTopologyAttributesMatch(const TopologyRefinerImpl *topology_refiner_impl,
const OpenSubdiv_Converter *converter)
{
return checkUVLayersMatch(topology_refiner_impl, converter);
}
bool TopologyRefinerImpl::isEqualToConverter(const OpenSubdiv_Converter *converter) const
{
if (!blender::opensubdiv::checkPreliminaryMatches(this, converter)) {
return false;
}
if (!base_mesh_topology.isEqualToConverter(converter)) {
return false;
}
// NOTE: Do after geometry check, to be sure topology does match and all
// indexing will go fine.
if (!blender::opensubdiv::checkTopologyAttributesMatch(this, converter)) {
return false;
}
return true;
}
} // namespace blender::opensubdiv

View File

@@ -0,0 +1,13 @@
/* SPDX-FileCopyrightText: 2013 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
// Global initialization/deinitialization.
//
// Supposed to be called from main thread.
void openSubdiv_init();
void openSubdiv_cleanup();
int openSubdiv_getVersionHex();

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2013 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
enum eOpenSubdivEvaluator {
OPENSUBDIV_EVALUATOR_CPU = 0,
OPENSUBDIV_EVALUATOR_GPU = 1,
};
enum OpenSubdiv_SchemeType {
OSD_SCHEME_BILINEAR,
OSD_SCHEME_CATMARK,
OSD_SCHEME_LOOP,
};
enum OpenSubdiv_VtxBoundaryInterpolation {
// Do not interpolate boundaries
OSD_VTX_BOUNDARY_NONE,
// Sharpen edges.
OSD_VTX_BOUNDARY_EDGE_ONLY,
// sharpen edges and corners,
OSD_VTX_BOUNDARY_EDGE_AND_CORNER,
};
enum OpenSubdiv_FVarLinearInterpolation {
OSD_FVAR_LINEAR_INTERPOLATION_NONE,
OSD_FVAR_LINEAR_INTERPOLATION_CORNERS_ONLY,
OSD_FVAR_LINEAR_INTERPOLATION_CORNERS_PLUS1,
OSD_FVAR_LINEAR_INTERPOLATION_CORNERS_PLUS2,
OSD_FVAR_LINEAR_INTERPOLATION_BOUNDARIES,
OSD_FVAR_LINEAR_INTERPOLATION_ALL,
};
struct OpenSubdiv_PatchCoord {
int ptex_face;
// Parametric location on patch.
float u, v;
};

View File

@@ -0,0 +1,130 @@
/* SPDX-FileCopyrightText: 2015 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_offset_indices.hh"
#include "opensubdiv_capi_type.hh"
struct OpenSubdiv_Converter {
/**
* The face topology of the base mesh to be subdivided. See #Mesh::faces() documentation for the
* details.
*
* Other topology information is currently encoded with callbacks rather than arrays directly.
*/
blender::OffsetIndices<int> faces;
OpenSubdiv_SchemeType (*getSchemeType)(const OpenSubdiv_Converter *converter);
OpenSubdiv_VtxBoundaryInterpolation (*getVtxBoundaryInterpolation)(
const OpenSubdiv_Converter *converter);
OpenSubdiv_FVarLinearInterpolation (*getFVarLinearInterpolation)(
const OpenSubdiv_Converter *converter);
// Denotes whether this converter specifies full topology, which includes
// vertices, edges, faces, vertices+edges of a face and edges/faces of a
// vertex.
// Otherwise this converter will only provide number of vertices and faces,
// and vertices of faces. The rest of topology will be created by OpenSubdiv.
//
// NOTE: Even if converter does not provide full topology, it still needs
// to provide number of edges and vertices-of-edge. Those are used to assign
// topology tags.
bool (*specifiesFullTopology)(const OpenSubdiv_Converter *converter);
//////////////////////////////////////////////////////////////////////////////
// Global geometry counters.
// Number of faces/edges/vertices in the base mesh.
int (*getNumEdges)(const OpenSubdiv_Converter *converter);
int (*getNumVertices)(const OpenSubdiv_Converter *converter);
//////////////////////////////////////////////////////////////////////////////
// Face relationships.
// Array of vertex indices the face consists of.
void (*getFaceVertices)(const OpenSubdiv_Converter *converter,
const int face_index,
int *face_vertices);
// Array of edge indices the face consists of.
// Aligned with the vertex indices array, edge i connects face vertex i
// with face index i+1.
void (*getFaceEdges)(const OpenSubdiv_Converter *converter,
const int face_index,
int *face_edges);
//////////////////////////////////////////////////////////////////////////////
// Edge relationships.
// Vertices the edge consists of.
void (*getEdgeVertices)(const OpenSubdiv_Converter *converter,
const int edge_index,
int edge_vertices[2]);
// Number of faces which are sharing the given edge.
int (*getNumEdgeFaces)(const OpenSubdiv_Converter *converter, const int edge_index);
// Array of face indices which are sharing the given edge.
void (*getEdgeFaces)(const OpenSubdiv_Converter *converter, const int edge, int *edge_faces);
// Edge sharpness (aka crease).
float (*getEdgeSharpness)(const OpenSubdiv_Converter *converter, const int edge_index);
//////////////////////////////////////////////////////////////////////////////
// Vertex relationships.
// Number of edges which are adjacent to the given vertex.
int (*getNumVertexEdges)(const OpenSubdiv_Converter *converter, const int vertex_index);
// Array for edge indices which are adjacent to the given vertex.
void (*getVertexEdges)(const OpenSubdiv_Converter *converter,
const int vertex_index,
int *vertex_edges);
// Number of faces which are adjacent to the given vertex.
int (*getNumVertexFaces)(const OpenSubdiv_Converter *converter, const int vertex_index);
// Array for face indices which are adjacent to the given vertex.
void (*getVertexFaces)(const OpenSubdiv_Converter *converter,
const int vertex_index,
int *vertex_faces);
// Check whether vertex is to be marked as an infinite sharp.
// This is a way to make sharp vertices which are adjacent to a loose edges.
bool (*isInfiniteSharpVertex)(const OpenSubdiv_Converter *converter, const int vertex_index);
// If vertex is not infinitely sharp, this is its actual sharpness.
float (*getVertexSharpness)(const OpenSubdiv_Converter *converter, const int vertex_index);
//////////////////////////////////////////////////////////////////////////////
// Face-varying data.
/////////////////////////////////////
// UV coordinates.
// Number of UV layers.
int (*getNumUVLayers)(const OpenSubdiv_Converter *converter);
// We need some corner connectivity information, which might not be trivial
// to be gathered (might require multiple matching calculations per corver
// query).
// precalc() is called before any corner connectivity or UV coordinate is
// queried from the given layer, allowing converter to calculate and cache
// complex complex-to-calculate information.
// finish() is called after converter is done porting UV layer to OpenSubdiv,
// allowing to free cached data.
void (*precalcUVLayer)(const OpenSubdiv_Converter *converter, const int layer_index);
void (*finishUVLayer)(const OpenSubdiv_Converter *converter);
// Get number of UV coordinates in the current layer (layer which was
// specified in precalcUVLayer().
int (*getNumUVCoordinates)(const OpenSubdiv_Converter *converter);
// For the given face index and its corner (known as loop in Blender)
// get corresponding UV coordinate index.
int (*getFaceCornerUVIndex)(const OpenSubdiv_Converter *converter,
const int face_index,
const int corner_index);
//////////////////////////////////////////////////////////////////////////////
// User data associated with this converter.
void (*freeUserData)(const OpenSubdiv_Converter *converter);
void *user_data;
};

View File

@@ -0,0 +1,199 @@
/* SPDX-FileCopyrightText: 2018 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#ifndef OPENSUBDIV_EVALUATOR_IMPL_H_
#define OPENSUBDIV_EVALUATOR_IMPL_H_
#ifdef _MSC_VER
# include <iso646.h>
#endif
#include <opensubdiv/far/patchMap.h>
#include <opensubdiv/far/patchTable.h>
#include "opensubdiv_capi_type.hh"
#include "GPU_storage_buffer.hh"
struct OpenSubdiv_EvaluatorCache;
struct OpenSubdiv_EvaluatorSettings;
struct OpenSubdiv_PatchCoord;
namespace blender::gpu {
class VertBuf;
}
namespace blender::opensubdiv {
class TopologyRefinerImpl;
class PatchMap;
// Wrapper around implementation, which defines API which we are capable to
// provide over the implementation.
class EvalOutputAPI {
public:
// Anonymous forward declaration of actual evaluator implementation.
class EvalOutput;
// NOTE: PatchMap is not owned, only referenced.
EvalOutputAPI(EvalOutput *implementation, PatchMap *patch_map);
~EvalOutputAPI();
// Set settings for data buffers.
void setSettings(const OpenSubdiv_EvaluatorSettings *settings);
// Set coarse positions from a continuous array of coordinates.
void setCoarsePositions(const float *positions,
const int start_vertex_index,
const int num_vertices);
// Set vertex data from a continuous array of data.
void setVertexData(const float *data, const int start_vertex_index, const int num_vertices);
// Set varying data from a continuous array of data.
void setVaryingData(const float *varying_data,
const int start_vertex_index,
const int num_vertices);
// Set face varying data from a continuous array of data.
//
// TODO(sergey): Find a better name for vertex here. It is not the vertex of
// geometry, but a vertex of UV map.
void setFaceVaryingData(const int face_varying_channel,
const float *varying_data,
const int start_vertex_index,
const int num_vertices);
// Set coarse vertex position from a continuous memory buffer where
// first coordinate starts at offset of `start_offset` and there is `stride`
// bytes between adjacent vertex coordinates.
void setCoarsePositionsFromBuffer(const void *buffer,
const int start_offset,
const int stride,
const int start_vertex_index,
const int num_vertices);
// Set varying data from a continuous memory buffer where
// first coordinate starts at offset of `start_offset` and there is `stride`
// bytes between adjacent vertex coordinates.
void setVaryingDataFromBuffer(const void *buffer,
const int start_offset,
const int stride,
const int start_vertex_index,
const int num_vertices);
// Set face varying data from a continuous memory buffer where
// first coordinate starts at offset of `start_offset` and there is `stride`
// bytes between adjacent vertex coordinates.
//
// TODO(sergey): Find a better name for vertex here. It is not the vertex of
// geometry, but a vertex of UV map.
void 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);
// Refine after coarse positions update.
void refine();
// Evaluate given ptex face at given bilinear coordinate.
// If derivatives are NULL, they will not be evaluated.
void evaluateLimit(const int ptex_face_index,
float face_u,
float face_v,
float P[3],
float dPdu[3],
float dPdv[3]);
// Evaluate varying data at a given bilinear coordinate of given ptex face.
void evaluateVertexData(const int ptex_face_index, float face_u, float face_v, float data[]);
// Evaluate varying data at a given bilinear coordinate of given ptex face.
void evaluateVarying(const int ptex_face_index, float face_u, float face_v, float varying[3]);
// Evaluate facee-varying data at a given bilinear coordinate of given
// ptex face.
void evaluateFaceVarying(const int face_varying_channel,
const int ptex_face_index,
float face_u,
float face_v,
float face_varying[2]);
// Batched evaluation of multiple input coordinates.
// Evaluate given ptex face at given bilinear coordinate.
// If derivatives are NULL, they will not be evaluated.
//
// NOTE: Output arrays must point to a memory of size float[3]*num_patch_coords.
void evaluatePatchesLimit(const OpenSubdiv_PatchCoord *patch_coords,
const int num_patch_coords,
float *P,
float *dPdu,
float *dPdv);
// Fill the output buffers and variables with data from the PatchMap.
void 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);
// Copy the patch arrays buffer used by OpenSubDiv for the source data to the given buffer.
gpu::StorageBuf *create_patch_arrays_buf();
// Wrap the patch index buffer used by OpenSubDiv for the source data with the given buffer.
gpu::StorageBuf *get_patch_index_buf();
// Wrap the patch param buffer used by OpenSubDiv for the source data with the given buffer.
gpu::StorageBuf *get_patch_param_buf();
// Wrap the buffer used by OpenSubDiv for the source data with the given buffer.
gpu::VertBuf *get_source_buf();
// Wrap the buffer used by OpenSubDiv for the extra source data with the given buffer.
gpu::VertBuf *get_source_data_buf();
// Copy the patch arrays buffer used by OpenSubDiv for the face varying channel with the given
// buffer.
gpu::StorageBuf *create_face_varying_patch_array_buf(const int face_varying_channel);
// Wrap the patch index buffer used by OpenSubDiv for the face varying channel with the given
// buffer.
gpu::StorageBuf *get_face_varying_patch_index_buf(const int face_varying_channel);
// Wrap the patch param buffer used by OpenSubDiv for the face varying channel with the given
// buffer.
gpu::StorageBuf *get_face_varying_patch_param_buf(const int face_varying_channel);
// Wrap thebuffer used by OpenSubDiv for the face varying channel with the given buffer.
gpu::VertBuf *get_face_varying_source_buf(const int face_varying_channel);
/** Get the source buffer offset for the given channel. */
int get_face_varying_source_offset(const int face_varying_channel) const;
// Return true if source vertex data has been set.
bool hasVertexData() const;
protected:
PatchMap *patch_map_;
EvalOutput *implementation_;
};
} // namespace blender::opensubdiv
struct OpenSubdiv_Evaluator {
blender::opensubdiv::EvalOutputAPI *eval_output;
const blender::opensubdiv::PatchMap *patch_map;
const OpenSubdiv::Far::PatchTable *patch_table;
eOpenSubdivEvaluator type;
OpenSubdiv_Evaluator();
~OpenSubdiv_Evaluator();
};
OpenSubdiv_Evaluator *openSubdiv_createEvaluatorFromTopologyRefiner(
blender::opensubdiv::TopologyRefinerImpl *topology_refiner,
eOpenSubdivEvaluator evaluator_type,
OpenSubdiv_EvaluatorCache *evaluator_cache_descr);
#endif // OPENSUBDIV_EVALUATOR_IMPL_H_

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2013 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstdint> // for uint64_t
#include "opensubdiv_capi_type.hh"
struct OpenSubdiv_EvaluatorCacheImpl;
struct OpenSubdiv_PatchCoord;
namespace blender::opensubdiv {
class TopologyRefinerImpl;
}
struct OpenSubdiv_EvaluatorSettings {
// Number of smoothly interpolated vertex data channels.
int num_vertex_data;
};
struct OpenSubdiv_EvaluatorCache {
// Implementation of the evaluator cache.
OpenSubdiv_EvaluatorCacheImpl *impl;
};
OpenSubdiv_EvaluatorCache *openSubdiv_createEvaluatorCache(eOpenSubdivEvaluator evaluator_type);
void openSubdiv_deleteEvaluatorCache(OpenSubdiv_EvaluatorCache *evaluator_cache);
// Return the GLSL source code from the OpenSubDiv library used for patch evaluation.
// This function is not thread-safe.
const char *openSubdiv_getGLSLPatchBasisSource();

View File

@@ -0,0 +1,78 @@
/* SPDX-FileCopyrightText: 2016 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#ifndef OPENSUBDIV_TOPOLOGY_REFINER_IMPL_H_
#define OPENSUBDIV_TOPOLOGY_REFINER_IMPL_H_
#ifdef _MSC_VER
# include <iso646.h>
#endif
#include <opensubdiv/far/topologyRefiner.h>
#include "internal/topology/mesh_topology.h"
// Those settings don't really belong to OpenSubdiv's topology refiner, but
// we are keeping track of them on our side of topology refiner. This is to
// make it possible to ensure we are not trying to abuse same OpenSubdiv's
// topology refiner with different subdivision levels or with different
// adaptive settings.
struct OpenSubdiv_TopologyRefinerSettings {
bool is_adaptive;
int level;
};
struct OpenSubdiv_Converter;
namespace blender::opensubdiv {
class TopologyRefinerImpl {
public:
// NOTE: Will return nullptr if topology refiner can not be created (for
// example, when topology is detected to be corrupted or invalid).
static TopologyRefinerImpl *createFromConverter(
OpenSubdiv_Converter *converter, const OpenSubdiv_TopologyRefinerSettings &settings);
TopologyRefinerImpl();
~TopologyRefinerImpl();
const OpenSubdiv::Far::TopologyLevel &base_level() const
{
return topology_refiner->GetLevel(0);
}
// Check whether this topology refiner defines same topology as the given
// converter.
// Covers options, geometry, and geometry tags.
bool isEqualToConverter(const OpenSubdiv_Converter *converter) const;
OpenSubdiv::Far::TopologyRefiner *topology_refiner;
// Subdivision settingsa this refiner is created for.
OpenSubdiv_TopologyRefinerSettings settings;
// Topology of the mesh which corresponds to the base level.
//
// All the indices and values are kept exactly the same as user-defined
// converter provided them. This allows to easily compare values which might
// be touched by the refinement process.
//
// On a more technical note this allows to easier/faster to compare following
// things:
//
// - Face vertices, where OpenSubdiv could re-arrange them to keep winding
// uniform.
//
// - Vertex crease where OpenSubdiv will force crease for non-manifold or
// corner vertices.
MeshTopology base_mesh_topology;
MEM_CXX_CLASS_ALLOC_FUNCS("TopologyRefinerImpl");
};
} // namespace blender::opensubdiv
#endif // OPENSUBDIV_TOPOLOGY_REFINER_IMPL_H_

View File

@@ -0,0 +1,21 @@
/* SPDX-FileCopyrightText: 2018 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#include "opensubdiv_evaluator_capi.hh"
#include <cstddef>
OpenSubdiv_EvaluatorCache *openSubdiv_createEvaluatorCache(eOpenSubdivEvaluator /*evaluator_type*/)
{
return nullptr;
}
void openSubdiv_deleteEvaluatorCache(OpenSubdiv_EvaluatorCache * /*evaluator_cache*/) {}
const char *openSubdiv_getGLSLPatchBasisSource()
{
return nullptr;
}

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2018 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Author: Sergey Sharybin. */
#include "opensubdiv_capi.hh"
#include <cstddef>
void openSubdiv_init() {}
void openSubdiv_cleanup() {}
int openSubdiv_getVersionHex()
{
return 0;
}