You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
731 lines
26 KiB
731 lines
26 KiB
/****************************************************************************
|
|
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
|
|
|
http://www.cocos.com
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights to
|
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
of the Software, and to permit persons to whom the Software is furnished to do so,
|
|
subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
#include "3d/assets/MorphRendering.h"
|
|
|
|
#include <memory>
|
|
#include "3d/assets/Mesh.h"
|
|
#include "3d/assets/Morph.h"
|
|
#include "base/RefCounted.h"
|
|
#include "core/DataView.h"
|
|
#include "core/TypedArray.h"
|
|
#include "core/assets/ImageAsset.h"
|
|
#include "core/assets/RenderingSubMesh.h"
|
|
#include "core/assets/Texture2D.h"
|
|
#include "platform/Image.h"
|
|
#include "renderer/pipeline/Define.h"
|
|
#include "scene/Pass.h"
|
|
|
|
namespace cc {
|
|
|
|
MorphRendering *createMorphRendering(Mesh *mesh, gfx::Device *gfxDevice) {
|
|
return ccnew StdMorphRendering(mesh, gfxDevice);
|
|
}
|
|
|
|
/**
|
|
* The instance of once sub-mesh morph rendering.
|
|
*/
|
|
class SubMeshMorphRenderingInstance : public RefCounted {
|
|
public:
|
|
~SubMeshMorphRenderingInstance() override = default;
|
|
/**
|
|
* Set weights of each morph target.
|
|
* @param weights The weights.
|
|
*/
|
|
virtual void setWeights(const ccstd::vector<float> &weights) = 0;
|
|
|
|
/**
|
|
* Asks the define overrides needed to do the rendering.
|
|
*/
|
|
virtual ccstd::vector<scene::IMacroPatch> requiredPatches() = 0;
|
|
|
|
/**
|
|
* Adapts the pipelineState to apply the rendering.
|
|
* @param pipelineState
|
|
*/
|
|
virtual void adaptPipelineState(gfx::DescriptorSet *descriptorSet) = 0;
|
|
|
|
/**
|
|
* Destroy this instance.
|
|
*/
|
|
virtual void destroy() = 0;
|
|
};
|
|
|
|
/**
|
|
* Describes how to render a sub-mesh morph.
|
|
*/
|
|
class SubMeshMorphRendering : public RefCounted {
|
|
public:
|
|
~SubMeshMorphRendering() override = default;
|
|
/**
|
|
* Creates a rendering instance.
|
|
*/
|
|
virtual SubMeshMorphRenderingInstance *createInstance() = 0;
|
|
};
|
|
|
|
namespace {
|
|
/**
|
|
* True if force to use cpu computing based sub-mesh rendering.
|
|
*/
|
|
const bool PREFER_CPU_COMPUTING = false;
|
|
|
|
class MorphTexture final : public RefCounted {
|
|
public:
|
|
MorphTexture() = default;
|
|
|
|
~MorphTexture() override = default;
|
|
/**
|
|
* Gets the GFX texture.
|
|
*/
|
|
gfx::Texture *getTexture() {
|
|
return _textureAsset->getGFXTexture();
|
|
}
|
|
|
|
/**
|
|
* Gets the GFX sampler.
|
|
*/
|
|
gfx::Sampler *getSampler() {
|
|
return _sampler;
|
|
}
|
|
|
|
/**
|
|
* Value view.
|
|
*/
|
|
Float32Array &getValueView() {
|
|
return _valueView;
|
|
}
|
|
|
|
/**
|
|
* Destroy the texture. Release its GPU resources.
|
|
*/
|
|
void destroy() {
|
|
_textureAsset->destroy();
|
|
// Samplers allocated from `samplerLib` are not required and
|
|
// should not be destroyed.
|
|
// _sampler.destroy();
|
|
}
|
|
|
|
/**
|
|
* Update the pixels content to `valueView`.
|
|
*/
|
|
void updatePixels() {
|
|
_textureAsset->uploadData(_arrayBuffer->getData());
|
|
}
|
|
|
|
void initialize(gfx::Device *gfxDevice, uint32_t width, uint32_t height, uint32_t pixelBytes, bool /*useFloat32Array*/, PixelFormat pixelFormat) {
|
|
_arrayBuffer = ccnew ArrayBuffer(width * height * pixelBytes);
|
|
_valueView = Float32Array(_arrayBuffer);
|
|
|
|
auto *imageAsset = ccnew ImageAsset();
|
|
IMemoryImageSource source{_arrayBuffer, false, width, height, pixelFormat};
|
|
imageAsset->setNativeAsset(source);
|
|
|
|
_textureAsset = ccnew Texture2D();
|
|
_textureAsset->setFilters(Texture2D::Filter::NEAREST, Texture2D::Filter::NEAREST);
|
|
_textureAsset->setMipFilter(Texture2D::Filter::NONE);
|
|
_textureAsset->setWrapMode(Texture2D::WrapMode::CLAMP_TO_EDGE, Texture2D::WrapMode::CLAMP_TO_EDGE, Texture2D::WrapMode::CLAMP_TO_EDGE);
|
|
_textureAsset->setImage(imageAsset);
|
|
|
|
if (nullptr == _textureAsset->getGFXTexture()) {
|
|
CC_LOG_WARNING("Unexpected: failed to create morph texture?");
|
|
}
|
|
_sampler = gfxDevice->getSampler(_textureAsset->getSamplerInfo());
|
|
}
|
|
|
|
private:
|
|
IntrusivePtr<Texture2D> _textureAsset;
|
|
gfx::Sampler *_sampler{nullptr};
|
|
ArrayBuffer::Ptr _arrayBuffer;
|
|
Float32Array _valueView;
|
|
|
|
CC_DISALLOW_COPY_MOVE_ASSIGN(MorphTexture);
|
|
};
|
|
|
|
struct GpuMorphAttribute {
|
|
ccstd::string attributeName;
|
|
IntrusivePtr<MorphTexture> morphTexture;
|
|
};
|
|
|
|
struct CpuMorphAttributeTarget {
|
|
Float32Array displacements;
|
|
};
|
|
|
|
using CpuMorphAttributeTargetList = ccstd::vector<CpuMorphAttributeTarget>;
|
|
|
|
struct CpuMorphAttribute {
|
|
ccstd::string name;
|
|
CpuMorphAttributeTargetList targets;
|
|
};
|
|
|
|
struct Vec4TextureFactory {
|
|
uint32_t width{0};
|
|
uint32_t height{0};
|
|
std::function<MorphTexture *()> create{nullptr};
|
|
};
|
|
|
|
/**
|
|
* Decides a best texture size to have the specified pixel capacity at least.
|
|
* The decided width and height has the following characteristics:
|
|
* - the width and height are both power of 2;
|
|
* - if the width and height are different, the width would be set to the larger once;
|
|
* - the width is ensured to be multiple of 4.
|
|
* @param nPixels Least pixel capacity.
|
|
*/
|
|
bool bestSizeToHavePixels(uint32_t nPixels, uint32_t *pWidth, uint32_t *pHeight) {
|
|
if (pWidth == nullptr || pHeight == nullptr) {
|
|
if (pWidth != nullptr) {
|
|
*pWidth = 0;
|
|
}
|
|
|
|
if (pHeight != nullptr) {
|
|
*pHeight = 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (nPixels < 5) {
|
|
nPixels = 5;
|
|
}
|
|
const uint32_t aligned = pipeline::nextPow2(nPixels);
|
|
const auto epxSum = static_cast<uint32_t>(std::log2(aligned));
|
|
const uint32_t h = epxSum >> 1;
|
|
const uint32_t w = (epxSum & 1) ? (h + 1) : h;
|
|
|
|
*pWidth = 1 << w;
|
|
*pHeight = 1 << h;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* When use vertex-texture-fetch technique, we do need
|
|
* `gl_vertexId` when we sample per-vertex data.
|
|
* WebGL 1.0 does not have `gl_vertexId`; WebGL 2.0, however, does.
|
|
* @param mesh
|
|
* @param subMeshIndex
|
|
* @param gfxDevice
|
|
*/
|
|
void enableVertexId(Mesh *mesh, uint32_t subMeshIndex, gfx::Device *gfxDevice) {
|
|
mesh->getRenderingSubMeshes()[subMeshIndex]->enableVertexIdChannel(gfxDevice);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param gfxDevice
|
|
* @param vec4Capacity Capacity of vec4.
|
|
*/
|
|
Vec4TextureFactory createVec4TextureFactory(gfx::Device *gfxDevice, uint32_t vec4Capacity) {
|
|
bool hasFeatureFloatTexture = static_cast<uint32_t>(gfxDevice->getFormatFeatures(gfx::Format::RGBA32F) & gfx::FormatFeature::SAMPLED_TEXTURE) != 0;
|
|
|
|
uint32_t pixelRequired = 0;
|
|
PixelFormat pixelFormat = PixelFormat::RGBA8888;
|
|
uint32_t pixelBytes = 4;
|
|
bool useFloat32Array = false;
|
|
if (hasFeatureFloatTexture) {
|
|
pixelRequired = vec4Capacity;
|
|
pixelBytes = 16;
|
|
pixelFormat = Texture2D::PixelFormat::RGBA32F;
|
|
useFloat32Array = true;
|
|
} else {
|
|
pixelRequired = 4 * vec4Capacity;
|
|
pixelBytes = 4;
|
|
pixelFormat = Texture2D::PixelFormat::RGBA8888;
|
|
useFloat32Array = false;
|
|
}
|
|
|
|
uint32_t width = 0;
|
|
uint32_t height = 0;
|
|
bestSizeToHavePixels(pixelRequired, &width, &height);
|
|
CC_ASSERT_GE(width * height, pixelRequired);
|
|
|
|
Vec4TextureFactory ret;
|
|
ret.width = width;
|
|
ret.height = height;
|
|
ret.create = [=]() -> MorphTexture * {
|
|
auto *texture = ccnew MorphTexture(); // texture will be held by IntrusivePtr in GpuMorphAttribute
|
|
texture->initialize(gfxDevice, width, height, pixelBytes, useFloat32Array, pixelFormat);
|
|
return texture;
|
|
};
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Provides the access to morph related uniforms.
|
|
*/
|
|
class MorphUniforms final : public RefCounted {
|
|
public:
|
|
MorphUniforms(gfx::Device *gfxDevice, uint32_t targetCount) {
|
|
_targetCount = targetCount;
|
|
_localBuffer = ccnew DataView(ccnew ArrayBuffer(pipeline::UBOMorph::SIZE));
|
|
|
|
_remoteBuffer = gfxDevice->createBuffer(gfx::BufferInfo{
|
|
gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST,
|
|
gfx::MemoryUsageBit::HOST | gfx::MemoryUsageBit::DEVICE,
|
|
pipeline::UBOMorph::SIZE,
|
|
pipeline::UBOMorph::SIZE,
|
|
});
|
|
}
|
|
|
|
~MorphUniforms() override {
|
|
delete _localBuffer;
|
|
}
|
|
|
|
void destroy() {
|
|
_remoteBuffer->destroy();
|
|
}
|
|
|
|
gfx::Buffer *getBuffer() const {
|
|
return _remoteBuffer;
|
|
}
|
|
|
|
void setWeights(const ccstd::vector<float> &weights) {
|
|
CC_ASSERT_EQ(weights.size(), _targetCount);
|
|
for (size_t iWeight = 0; iWeight < weights.size(); ++iWeight) {
|
|
_localBuffer->setFloat32(static_cast<uint32_t>(pipeline::UBOMorph::OFFSET_OF_WEIGHTS + 4 * iWeight), weights[iWeight]);
|
|
}
|
|
}
|
|
|
|
void setMorphTextureInfo(float width, float height) {
|
|
_localBuffer->setFloat32(pipeline::UBOMorph::OFFSET_OF_DISPLACEMENT_TEXTURE_WIDTH, width);
|
|
_localBuffer->setFloat32(pipeline::UBOMorph::OFFSET_OF_DISPLACEMENT_TEXTURE_HEIGHT, height);
|
|
}
|
|
|
|
void setVerticesCount(uint32_t count) {
|
|
_localBuffer->setFloat32(pipeline::UBOMorph::OFFSET_OF_VERTICES_COUNT, static_cast<float>(count));
|
|
}
|
|
|
|
void commit() {
|
|
ArrayBuffer *buffer = _localBuffer->buffer();
|
|
_remoteBuffer->update(buffer->getData(), buffer->byteLength());
|
|
}
|
|
|
|
private:
|
|
uint32_t _targetCount{0};
|
|
DataView *_localBuffer{nullptr};
|
|
IntrusivePtr<gfx::Buffer> _remoteBuffer;
|
|
};
|
|
|
|
class CpuComputing final : public SubMeshMorphRendering {
|
|
public:
|
|
explicit CpuComputing(Mesh *mesh, uint32_t subMeshIndex, const Morph *morph, gfx::Device *gfxDevice);
|
|
|
|
SubMeshMorphRenderingInstance *createInstance() override;
|
|
const ccstd::vector<CpuMorphAttribute> &getData() const;
|
|
|
|
private:
|
|
ccstd::vector<CpuMorphAttribute> _attributes;
|
|
gfx::Device *_gfxDevice{nullptr};
|
|
};
|
|
|
|
class GpuComputing final : public SubMeshMorphRendering {
|
|
public:
|
|
explicit GpuComputing(Mesh *mesh, uint32_t subMeshIndex, const Morph *morph, gfx::Device *gfxDevice);
|
|
SubMeshMorphRenderingInstance *createInstance() override;
|
|
|
|
void destroy();
|
|
|
|
private:
|
|
gfx::Device *_gfxDevice{nullptr};
|
|
const SubMeshMorph *_subMeshMorph{nullptr};
|
|
uint32_t _textureWidth{0};
|
|
uint32_t _textureHeight{0};
|
|
ccstd::vector<GpuMorphAttribute> _attributes;
|
|
uint32_t _verticesCount{0};
|
|
|
|
friend class GpuComputingRenderingInstance;
|
|
};
|
|
|
|
class CpuComputingRenderingInstance final : public SubMeshMorphRenderingInstance {
|
|
public:
|
|
explicit CpuComputingRenderingInstance(CpuComputing *owner, uint32_t nVertices, gfx::Device *gfxDevice) {
|
|
_owner = owner; //NOTE: release by mesh`s destroy, it`ll call current instance`s destroy method
|
|
_morphUniforms = ccnew MorphUniforms(gfxDevice, 0 /* TODO? */);
|
|
|
|
auto vec4TextureFactory = createVec4TextureFactory(gfxDevice, nVertices);
|
|
_morphUniforms->setMorphTextureInfo(static_cast<float>(vec4TextureFactory.width), static_cast<float>(vec4TextureFactory.height));
|
|
_morphUniforms->commit();
|
|
for (const auto &attributeMorph : _owner->getData()) {
|
|
auto *morphTexture = vec4TextureFactory.create();
|
|
_attributes.emplace_back(GpuMorphAttribute{attributeMorph.name, morphTexture});
|
|
}
|
|
}
|
|
|
|
void setWeights(const ccstd::vector<float> &weights) override {
|
|
for (size_t iAttribute = 0; iAttribute < _attributes.size(); ++iAttribute) {
|
|
const auto &myAttribute = _attributes[iAttribute];
|
|
Float32Array &valueView = myAttribute.morphTexture->getValueView();
|
|
const auto &attributeMorph = _owner->getData()[iAttribute];
|
|
CC_ASSERT(weights.size() == attributeMorph.targets.size());
|
|
for (size_t iTarget = 0; iTarget < attributeMorph.targets.size(); ++iTarget) {
|
|
const auto &targetDisplacements = attributeMorph.targets[iTarget].displacements;
|
|
const float weight = weights[iTarget];
|
|
const uint32_t nVertices = targetDisplacements.length() / 3;
|
|
if (iTarget == 0) {
|
|
for (uint32_t iVertex = 0; iVertex < nVertices; ++iVertex) {
|
|
valueView[4 * iVertex + 0] = targetDisplacements[3 * iVertex + 0] * weight;
|
|
valueView[4 * iVertex + 1] = targetDisplacements[3 * iVertex + 1] * weight;
|
|
valueView[4 * iVertex + 2] = targetDisplacements[3 * iVertex + 2] * weight;
|
|
}
|
|
} else if (std::fabs(weight) >= std::numeric_limits<float>::epsilon()) {
|
|
for (uint32_t iVertex = 0; iVertex < nVertices; ++iVertex) {
|
|
valueView[4 * iVertex + 0] += targetDisplacements[3 * iVertex + 0] * weight;
|
|
valueView[4 * iVertex + 1] += targetDisplacements[3 * iVertex + 1] * weight;
|
|
valueView[4 * iVertex + 2] += targetDisplacements[3 * iVertex + 2] * weight;
|
|
}
|
|
}
|
|
}
|
|
|
|
myAttribute.morphTexture->updatePixels();
|
|
}
|
|
}
|
|
|
|
ccstd::vector<scene::IMacroPatch> requiredPatches() override {
|
|
return {
|
|
{"CC_MORPH_TARGET_USE_TEXTURE", true},
|
|
{"CC_MORPH_PRECOMPUTED", true},
|
|
};
|
|
}
|
|
|
|
void adaptPipelineState(gfx::DescriptorSet *descriptorSet) override {
|
|
for (const auto &attribute : _attributes) {
|
|
const auto &attributeName = attribute.attributeName;
|
|
ccstd::optional<uint32_t> binding;
|
|
if (attributeName == gfx::ATTR_NAME_POSITION) {
|
|
binding = uint32_t{pipeline::POSITIONMORPH::BINDING};
|
|
} else if (attributeName == gfx::ATTR_NAME_NORMAL) {
|
|
binding = uint32_t{pipeline::NORMALMORPH::BINDING};
|
|
} else if (attributeName == gfx::ATTR_NAME_TANGENT) {
|
|
binding = uint32_t{pipeline::TANGENTMORPH::BINDING};
|
|
} else {
|
|
CC_LOG_WARNING("Unexpected attribute!");
|
|
}
|
|
|
|
if (binding.has_value()) {
|
|
descriptorSet->bindSampler(binding.value(), attribute.morphTexture->getSampler());
|
|
descriptorSet->bindTexture(binding.value(), attribute.morphTexture->getTexture());
|
|
}
|
|
}
|
|
descriptorSet->bindBuffer(pipeline::UBOMorph::BINDING, _morphUniforms->getBuffer());
|
|
descriptorSet->update();
|
|
}
|
|
|
|
void destroy() override {
|
|
CC_SAFE_DESTROY(_morphUniforms);
|
|
for (auto &myAttribute : _attributes) {
|
|
CC_SAFE_DESTROY(myAttribute.morphTexture);
|
|
}
|
|
}
|
|
|
|
private:
|
|
ccstd::vector<GpuMorphAttribute> _attributes;
|
|
IntrusivePtr<CpuComputing> _owner;
|
|
IntrusivePtr<MorphUniforms> _morphUniforms;
|
|
};
|
|
|
|
class GpuComputingRenderingInstance final : public SubMeshMorphRenderingInstance {
|
|
public:
|
|
explicit GpuComputingRenderingInstance(GpuComputing *owner, gfx::Device *gfxDevice) {
|
|
_owner = owner;
|
|
_morphUniforms = ccnew MorphUniforms(gfxDevice, static_cast<uint32_t>(_owner->_subMeshMorph->targets.size()));
|
|
_morphUniforms->setMorphTextureInfo(static_cast<float>(_owner->_textureWidth), static_cast<float>(_owner->_textureHeight));
|
|
_morphUniforms->setVerticesCount(_owner->_verticesCount);
|
|
_morphUniforms->commit();
|
|
_attributes = &_owner->_attributes;
|
|
}
|
|
|
|
void setWeights(const ccstd::vector<float> &weights) override {
|
|
_morphUniforms->setWeights(weights);
|
|
_morphUniforms->commit();
|
|
}
|
|
|
|
ccstd::vector<scene::IMacroPatch> requiredPatches() override {
|
|
return {
|
|
{"CC_MORPH_TARGET_USE_TEXTURE", true},
|
|
};
|
|
}
|
|
|
|
void adaptPipelineState(gfx::DescriptorSet *descriptorSet) override {
|
|
for (const auto &attribute : *_attributes) {
|
|
const auto &attributeName = attribute.attributeName;
|
|
ccstd::optional<uint32_t> binding;
|
|
if (attributeName == gfx::ATTR_NAME_POSITION) {
|
|
binding = uint32_t{pipeline::POSITIONMORPH::BINDING};
|
|
} else if (attributeName == gfx::ATTR_NAME_NORMAL) {
|
|
binding = uint32_t{pipeline::NORMALMORPH::BINDING};
|
|
} else if (attributeName == gfx::ATTR_NAME_TANGENT) {
|
|
binding = uint32_t{pipeline::TANGENTMORPH::BINDING};
|
|
} else {
|
|
CC_LOG_WARNING("Unexpected attribute!");
|
|
}
|
|
|
|
if (binding.has_value()) {
|
|
descriptorSet->bindSampler(binding.value(), attribute.morphTexture->getSampler());
|
|
descriptorSet->bindTexture(binding.value(), attribute.morphTexture->getTexture());
|
|
}
|
|
}
|
|
descriptorSet->bindBuffer(pipeline::UBOMorph::BINDING, _morphUniforms->getBuffer());
|
|
descriptorSet->update();
|
|
}
|
|
|
|
void destroy() override {
|
|
}
|
|
|
|
private:
|
|
ccstd::vector<GpuMorphAttribute> *_attributes{nullptr};
|
|
IntrusivePtr<GpuComputing> _owner;
|
|
IntrusivePtr<MorphUniforms> _morphUniforms;
|
|
};
|
|
|
|
CpuComputing::CpuComputing(Mesh *mesh, uint32_t subMeshIndex, const Morph *morph, gfx::Device *gfxDevice) {
|
|
_gfxDevice = gfxDevice;
|
|
const auto &subMeshMorph = morph->subMeshMorphs[subMeshIndex].value();
|
|
enableVertexId(mesh, subMeshIndex, gfxDevice);
|
|
|
|
for (size_t attributeIndex = 0, len = subMeshMorph.attributes.size(); attributeIndex < len; ++attributeIndex) {
|
|
const auto &attributeName = subMeshMorph.attributes[attributeIndex];
|
|
|
|
CpuMorphAttribute attr;
|
|
attr.name = attributeName;
|
|
attr.targets.resize(subMeshMorph.targets.size());
|
|
|
|
uint32_t i = 0;
|
|
for (const auto &attributeDisplacement : subMeshMorph.targets) {
|
|
const Mesh::IBufferView &displacementsView = attributeDisplacement.displacements[attributeIndex];
|
|
attr.targets[i].displacements = Float32Array(mesh->getData().buffer(),
|
|
mesh->getData().byteOffset() + displacementsView.offset,
|
|
attributeDisplacement.displacements[attributeIndex].count);
|
|
|
|
++i;
|
|
}
|
|
|
|
_attributes.emplace_back(attr);
|
|
}
|
|
}
|
|
|
|
SubMeshMorphRenderingInstance *CpuComputing::createInstance() {
|
|
return ccnew CpuComputingRenderingInstance(
|
|
this,
|
|
_attributes[0].targets[0].displacements.length() / 3,
|
|
_gfxDevice);
|
|
}
|
|
|
|
const ccstd::vector<CpuMorphAttribute> &CpuComputing::getData() const {
|
|
return _attributes;
|
|
}
|
|
|
|
GpuComputing::GpuComputing(Mesh *mesh, uint32_t subMeshIndex, const Morph *morph, gfx::Device *gfxDevice) {
|
|
_gfxDevice = gfxDevice;
|
|
const auto &subMeshMorph = morph->subMeshMorphs[subMeshIndex].value();
|
|
|
|
_subMeshMorph = &subMeshMorph;
|
|
// assertIsNonNullable(subMeshMorph);
|
|
|
|
enableVertexId(mesh, subMeshIndex, gfxDevice);
|
|
|
|
uint32_t nVertices = mesh->getStruct().vertexBundles[mesh->getStruct().primitives[subMeshIndex].vertexBundelIndices[0]].view.count;
|
|
_verticesCount = nVertices;
|
|
auto nTargets = static_cast<uint32_t>(subMeshMorph.targets.size());
|
|
uint32_t vec4Required = nVertices * nTargets;
|
|
|
|
auto vec4TextureFactory = createVec4TextureFactory(gfxDevice, vec4Required);
|
|
_textureWidth = vec4TextureFactory.width;
|
|
_textureHeight = vec4TextureFactory.height;
|
|
|
|
// Creates texture for each attribute.
|
|
uint32_t attributeIndex = 0;
|
|
_attributes.reserve(subMeshMorph.attributes.size());
|
|
for (const auto &attributeName : subMeshMorph.attributes) {
|
|
auto *vec4Tex = vec4TextureFactory.create();
|
|
Float32Array &valueView = vec4Tex->getValueView();
|
|
// if (DEV) { // Make it easy to view texture in profilers...
|
|
// for (let i = 0; i < valueView.length / 4; ++i) {
|
|
// valueView[i * 4 + 3] = 1.0;
|
|
// }
|
|
// }
|
|
|
|
uint32_t morphTargetIndex = 0;
|
|
for (const auto &morphTarget : subMeshMorph.targets) {
|
|
const auto &displacementsView = morphTarget.displacements[attributeIndex];
|
|
Float32Array displacements(mesh->getData().buffer(),
|
|
mesh->getData().byteOffset() + displacementsView.offset,
|
|
displacementsView.count);
|
|
const uint32_t displacementsOffset = (nVertices * morphTargetIndex) * 4;
|
|
for (uint32_t iVertex = 0; iVertex < nVertices; ++iVertex) {
|
|
valueView[displacementsOffset + 4 * iVertex + 0] = displacements[3 * iVertex + 0];
|
|
valueView[displacementsOffset + 4 * iVertex + 1] = displacements[3 * iVertex + 1];
|
|
valueView[displacementsOffset + 4 * iVertex + 2] = displacements[3 * iVertex + 2];
|
|
}
|
|
|
|
++morphTargetIndex;
|
|
}
|
|
|
|
vec4Tex->updatePixels();
|
|
|
|
_attributes.emplace_back(GpuMorphAttribute{attributeName, vec4Tex});
|
|
|
|
++attributeIndex;
|
|
}
|
|
}
|
|
|
|
SubMeshMorphRenderingInstance *GpuComputing::createInstance() {
|
|
return ccnew GpuComputingRenderingInstance(this, _gfxDevice);
|
|
}
|
|
|
|
void GpuComputing::destroy() {
|
|
for (auto &attribute : _attributes) {
|
|
attribute.morphTexture->destroy();
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class StdMorphRenderingInstance : public MorphRenderingInstance {
|
|
public:
|
|
explicit StdMorphRenderingInstance(StdMorphRendering *owner) {
|
|
_owner = owner;
|
|
size_t nSubMeshes = _owner->_mesh->getStruct().primitives.size();
|
|
_subMeshInstances.resize(nSubMeshes, nullptr);
|
|
|
|
for (size_t iSubMesh = 0; iSubMesh < nSubMeshes; ++iSubMesh) {
|
|
if (_owner->_subMeshRenderings[iSubMesh] != nullptr) {
|
|
_subMeshInstances[iSubMesh] = _owner->_subMeshRenderings[iSubMesh]->createInstance();
|
|
}
|
|
}
|
|
}
|
|
|
|
~StdMorphRenderingInstance() override = default;
|
|
|
|
void setWeights(index_t subMeshIndex, const MeshWeightsType &weights) override {
|
|
if (_subMeshInstances[subMeshIndex]) {
|
|
_subMeshInstances[subMeshIndex]->setWeights(weights);
|
|
}
|
|
}
|
|
|
|
void adaptPipelineState(index_t subMeshIndex, gfx::DescriptorSet *descriptorSet) override {
|
|
if (_subMeshInstances[subMeshIndex]) {
|
|
_subMeshInstances[subMeshIndex]->adaptPipelineState(descriptorSet);
|
|
}
|
|
}
|
|
|
|
ccstd::vector<scene::IMacroPatch> requiredPatches(index_t subMeshIndex) override {
|
|
CC_ASSERT(_owner->_mesh->getStruct().morph.has_value());
|
|
const auto &subMeshMorphOpt = _owner->_mesh->getStruct().morph.value().subMeshMorphs[subMeshIndex];
|
|
auto *subMeshRenderingInstance = _subMeshInstances[subMeshIndex].get();
|
|
if (subMeshRenderingInstance == nullptr || !subMeshMorphOpt.has_value()) {
|
|
return {};
|
|
}
|
|
const auto &subMeshMorph = subMeshMorphOpt.value();
|
|
|
|
ccstd::vector<scene::IMacroPatch> patches{
|
|
{"CC_USE_MORPH", true},
|
|
{"CC_MORPH_TARGET_COUNT", static_cast<int32_t>(subMeshMorph.targets.size())}};
|
|
|
|
auto posIter = std::find(subMeshMorph.attributes.begin(), subMeshMorph.attributes.end(), gfx::ATTR_NAME_POSITION);
|
|
if (posIter != subMeshMorph.attributes.end()) {
|
|
patches.emplace_back(scene::IMacroPatch{
|
|
"CC_MORPH_TARGET_HAS_POSITION",
|
|
true,
|
|
});
|
|
}
|
|
|
|
auto normalIter = std::find(subMeshMorph.attributes.begin(), subMeshMorph.attributes.end(), gfx::ATTR_NAME_NORMAL);
|
|
if (normalIter != subMeshMorph.attributes.end()) {
|
|
patches.emplace_back(scene::IMacroPatch{
|
|
"CC_MORPH_TARGET_HAS_NORMAL",
|
|
true,
|
|
});
|
|
}
|
|
|
|
auto tangentIter = std::find(subMeshMorph.attributes.begin(), subMeshMorph.attributes.end(), gfx::ATTR_NAME_TANGENT);
|
|
if (tangentIter != subMeshMorph.attributes.end()) {
|
|
patches.emplace_back(scene::IMacroPatch{
|
|
"CC_MORPH_TARGET_HAS_TANGENT",
|
|
true,
|
|
});
|
|
}
|
|
|
|
auto renderingInstancePatches = subMeshRenderingInstance->requiredPatches();
|
|
for (auto &renderingInstancePatch : renderingInstancePatches) {
|
|
patches.emplace_back(renderingInstancePatch);
|
|
}
|
|
|
|
return patches;
|
|
}
|
|
|
|
void destroy() override {
|
|
for (auto &subMeshInstance : _subMeshInstances) {
|
|
if (subMeshInstance != nullptr) {
|
|
subMeshInstance->destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
IntrusivePtr<StdMorphRendering> _owner;
|
|
ccstd::vector<IntrusivePtr<SubMeshMorphRenderingInstance>> _subMeshInstances;
|
|
};
|
|
|
|
StdMorphRendering::StdMorphRendering(Mesh *mesh, gfx::Device *gfxDevice) {
|
|
_mesh = mesh;
|
|
const auto &structInfo = _mesh->getStruct();
|
|
if (!structInfo.morph.has_value()) {
|
|
return;
|
|
}
|
|
|
|
const size_t nSubMeshes = structInfo.primitives.size();
|
|
_subMeshRenderings.resize(nSubMeshes, nullptr);
|
|
const auto &morph = structInfo.morph.value();
|
|
for (size_t iSubMesh = 0; iSubMesh < nSubMeshes; ++iSubMesh) {
|
|
const auto &subMeshMorphHolder = morph.subMeshMorphs[iSubMesh];
|
|
if (!subMeshMorphHolder.has_value()) {
|
|
continue;
|
|
}
|
|
|
|
const auto &subMeshMorph = subMeshMorphHolder.value();
|
|
|
|
if (PREFER_CPU_COMPUTING || subMeshMorph.targets.size() > pipeline::UBOMorph::MAX_MORPH_TARGET_COUNT) {
|
|
_subMeshRenderings[iSubMesh] = ccnew CpuComputing(
|
|
_mesh,
|
|
static_cast<uint32_t>(iSubMesh),
|
|
&morph,
|
|
gfxDevice);
|
|
} else {
|
|
_subMeshRenderings[iSubMesh] = ccnew GpuComputing(
|
|
_mesh,
|
|
static_cast<uint32_t>(iSubMesh),
|
|
&morph,
|
|
gfxDevice);
|
|
}
|
|
}
|
|
}
|
|
|
|
StdMorphRendering::~StdMorphRendering() = default;
|
|
|
|
MorphRenderingInstance *StdMorphRendering::createInstance() {
|
|
auto *ret = ccnew StdMorphRenderingInstance(this);
|
|
return ret;
|
|
}
|
|
|
|
} // namespace cc
|
|
|