Add Chromium-only Blender WebEngine parity work
This commit is contained in:
160
blender-5.2.0/intern/opensubdiv/CMakeLists.txt
Normal file
160
blender-5.2.0/intern/opensubdiv/CMakeLists.txt
Normal 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()
|
||||
14
blender-5.2.0/intern/opensubdiv/internal/base/memory.h
Normal file
14
blender-5.2.0/intern/opensubdiv/internal/base/memory.h
Normal 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_
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
40
blender-5.2.0/intern/opensubdiv/internal/base/type_convert.h
Normal file
40
blender-5.2.0/intern/opensubdiv/internal/base/type_convert.h
Normal 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_
|
||||
41
blender-5.2.0/intern/opensubdiv/internal/base/util.cc
Normal file
41
blender-5.2.0/intern/opensubdiv/internal/base/util.cc
Normal 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
|
||||
20
blender-5.2.0/intern/opensubdiv/internal/base/util.h
Normal file
20
blender-5.2.0/intern/opensubdiv/internal/base/util.h
Normal 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_
|
||||
@@ -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
|
||||
668
blender-5.2.0/intern/opensubdiv/internal/evaluator/eval_output.h
Normal file
668
blender-5.2.0/intern/opensubdiv/internal/evaluator/eval_output.h
Normal 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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
193
blender-5.2.0/intern/opensubdiv/internal/evaluator/patch_map.cc
Normal file
193
blender-5.2.0/intern/opensubdiv/internal/evaluator/patch_map.cc
Normal 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 ¶ms = patchTable.GetPatchParamTable();
|
||||
|
||||
for (int handle = 0; handle < nHandles; ++handle) {
|
||||
|
||||
PatchParam const ¶m = 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
|
||||
246
blender-5.2.0/intern/opensubdiv/internal/evaluator/patch_map.h
Normal file
246
blender-5.2.0/intern/opensubdiv/internal/evaluator/patch_map.h
Normal 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_
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
13
blender-5.2.0/intern/opensubdiv/opensubdiv_capi.hh
Normal file
13
blender-5.2.0/intern/opensubdiv/opensubdiv_capi.hh
Normal 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();
|
||||
41
blender-5.2.0/intern/opensubdiv/opensubdiv_capi_type.hh
Normal file
41
blender-5.2.0/intern/opensubdiv/opensubdiv_capi_type.hh
Normal 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;
|
||||
};
|
||||
130
blender-5.2.0/intern/opensubdiv/opensubdiv_converter_capi.hh
Normal file
130
blender-5.2.0/intern/opensubdiv/opensubdiv_converter_capi.hh
Normal 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;
|
||||
};
|
||||
199
blender-5.2.0/intern/opensubdiv/opensubdiv_evaluator.hh
Normal file
199
blender-5.2.0/intern/opensubdiv/opensubdiv_evaluator.hh
Normal 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_
|
||||
33
blender-5.2.0/intern/opensubdiv/opensubdiv_evaluator_capi.hh
Normal file
33
blender-5.2.0/intern/opensubdiv/opensubdiv_evaluator_capi.hh
Normal 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();
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
18
blender-5.2.0/intern/opensubdiv/stub/opensubdiv_stub.cc
Normal file
18
blender-5.2.0/intern/opensubdiv/stub/opensubdiv_stub.cc
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user