Add Chromium-only Blender WebEngine parity work

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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