// // RenderableParticleEffectEntityItem.cpp // interface/src // // Created by Jason Rickwald on 3/2/15. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include #include #include #include #include #include #include "EntitiesRendererLogging.h" #include "RenderableParticleEffectEntityItem.h" #include "untextured_particle_vert.h" #include "untextured_particle_frag.h" #include "textured_particle_vert.h" #include "textured_particle_frag.h" static const size_t VERTEX_PER_PARTICLE = 4; class ParticlePayloadData { public: template struct InterpolationData { T start; T middle; T finish; T spread; }; struct ParticleUniforms { InterpolationData radius; InterpolationData color; // rgba float lifespan; }; struct ParticlePrimitive { ParticlePrimitive(glm::vec3 xyzIn, glm::vec2 uvIn) : xyz(xyzIn), uv(uvIn) {} glm::vec3 xyz; // Position glm::vec2 uv; // Lifetime + seed }; using Payload = render::Payload; using Pointer = Payload::DataPointer; using PipelinePointer = gpu::PipelinePointer; using FormatPointer = gpu::Stream::FormatPointer; using BufferPointer = gpu::BufferPointer; using TexturePointer = gpu::TexturePointer; using Format = gpu::Stream::Format; using Buffer = gpu::Buffer; using BufferView = gpu::BufferView; using ParticlePrimitives = std::vector; ParticlePayloadData() { ParticleUniforms uniforms; _uniformBuffer = std::make_shared(sizeof(ParticleUniforms), (const gpu::Byte*) &uniforms); _vertexFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element::VEC3F_XYZ, offsetof(ParticlePrimitive, xyz), gpu::Stream::PER_INSTANCE); _vertexFormat->setAttribute(gpu::Stream::COLOR, 0, gpu::Element::VEC2F_UV, offsetof(ParticlePrimitive, uv), gpu::Stream::PER_INSTANCE); } void setPipeline(PipelinePointer pipeline) { _pipeline = pipeline; } const PipelinePointer& getPipeline() const { return _pipeline; } const Transform& getModelTransform() const { return _modelTransform; } void setModelTransform(const Transform& modelTransform) { _modelTransform = modelTransform; } const AABox& getBound() const { return _bound; } void setBound(const AABox& bound) { _bound = bound; } BufferPointer getParticleBuffer() { return _particleBuffer; } const BufferPointer& getParticleBuffer() const { return _particleBuffer; } const ParticleUniforms& getParticleUniforms() const { return _uniformBuffer.get(); } ParticleUniforms& editParticleUniforms() { return _uniformBuffer.edit(); } void setTexture(TexturePointer texture) { _texture = texture; } const TexturePointer& getTexture() const { return _texture; } bool getVisibleFlag() const { return _visibleFlag; } void setVisibleFlag(bool visibleFlag) { _visibleFlag = visibleFlag; } void render(RenderArgs* args) const { assert(_pipeline); gpu::Batch& batch = *args->_batch; batch.setPipeline(_pipeline); if (_texture) { batch.setResourceTexture(0, _texture); } batch.setModelTransform(_modelTransform); batch.setUniformBuffer(0, _uniformBuffer); batch.setInputFormat(_vertexFormat); batch.setInputBuffer(0, _particleBuffer, 0, sizeof(ParticlePrimitive)); auto numParticles = _particleBuffer->getSize() / sizeof(ParticlePrimitive); batch.drawInstanced(numParticles, gpu::TRIANGLE_STRIP, VERTEX_PER_PARTICLE); } protected: Transform _modelTransform; AABox _bound; PipelinePointer _pipeline; FormatPointer _vertexFormat { std::make_shared() }; BufferPointer _particleBuffer { std::make_shared() }; BufferView _uniformBuffer; TexturePointer _texture; bool _visibleFlag = true; }; namespace render { template <> const ItemKey payloadGetKey(const ParticlePayloadData::Pointer& payload) { if (payload->getVisibleFlag()) { return ItemKey::Builder::transparentShape(); } else { return ItemKey::Builder().withInvisible().build(); } } template <> const Item::Bound payloadGetBound(const ParticlePayloadData::Pointer& payload) { return payload->getBound(); } template <> void payloadRender(const ParticlePayloadData::Pointer& payload, RenderArgs* args) { payload->render(args); } } EntityItemPointer RenderableParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return std::make_shared(entityID, properties); } RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : ParticleEffectEntityItem(entityItemID, properties) { // lazy creation of particle system pipeline if (!_untexturedPipeline || !_texturedPipeline) { createPipelines(); } } bool RenderableParticleEffectEntityItem::addToScene(EntityItemPointer self, render::ScenePointer scene, render::PendingChanges& pendingChanges) { _scene = scene; _renderItemId = _scene->allocateID(); auto particlePayloadData = std::make_shared(); particlePayloadData->setPipeline(_untexturedPipeline); auto renderPayload = std::make_shared(particlePayloadData); render::Item::Status::Getters statusGetters; makeEntityItemStatusGetters(shared_from_this(), statusGetters); renderPayload->addStatusGetters(statusGetters); pendingChanges.resetItem(_renderItemId, renderPayload); return true; } void RenderableParticleEffectEntityItem::removeFromScene(EntityItemPointer self, render::ScenePointer scene, render::PendingChanges& pendingChanges) { pendingChanges.removeItem(_renderItemId); _scene = nullptr; }; void RenderableParticleEffectEntityItem::update(const quint64& now) { ParticleEffectEntityItem::update(now); if (_texturesChangedFlag) { if (_textures.isEmpty()) { _texture.clear(); } else { // for now use the textures string directly. // Eventually we'll want multiple textures in a map or array. _texture = DependencyManager::get()->getTexture(_textures); } _texturesChangedFlag = false; } updateRenderItem(); } static glm::vec4 toGlm(const xColor& color, float alpha) { return glm::vec4((float)color.red / 255.0f, (float)color.green / 255.0f, (float)color.blue / 255.0f, alpha); } void RenderableParticleEffectEntityItem::updateRenderItem() { if (!_scene) { return; } using ParticleUniforms = ParticlePayloadData::ParticleUniforms; using ParticlePrimitive = ParticlePayloadData::ParticlePrimitive; using ParticlePrimitives = ParticlePayloadData::ParticlePrimitives; // Fill in Uniforms structure ParticleUniforms particleUniforms; particleUniforms.radius.start = getRadiusStart(); particleUniforms.radius.middle = getParticleRadius(); particleUniforms.radius.finish = getRadiusFinish(); particleUniforms.radius.spread = getRadiusSpread(); particleUniforms.color.start = toGlm(getColorStart(), getAlphaStart()); particleUniforms.color.middle = toGlm(getXColor(), getAlpha()); particleUniforms.color.finish = toGlm(getColorFinish(), getAlphaFinish()); particleUniforms.color.spread = toGlm(getColorSpread(), getAlphaSpread()); particleUniforms.lifespan = getLifespan(); // Build particle primitives auto particlePrimitives = std::make_shared(); particlePrimitives->reserve(_particles.size()); // Reserve space for (auto& particle : _particles) { particlePrimitives->emplace_back(particle.position, glm::vec2(particle.lifetime, particle.seed)); } auto bounds = getAABox(); auto position = getPosition(); auto rotation = getRotation(); Transform transform; transform.setTranslation(position); transform.setRotation(rotation); render::PendingChanges pendingChanges; pendingChanges.updateItem(_renderItemId, [=](ParticlePayloadData& payload) { // Update particle uniforms memcpy(&payload.editParticleUniforms(), &particleUniforms, sizeof(ParticleUniforms)); // Update particle buffer auto particleBuffer = payload.getParticleBuffer(); size_t numBytes = sizeof(ParticlePrimitive) * particlePrimitives->size(); particleBuffer->resize(numBytes); if (numBytes == 0) { return; } memcpy(particleBuffer->editData(), particlePrimitives->data(), numBytes); // Update transform and bounds payload.setModelTransform(transform); payload.setBound(bounds); if (_texture && _texture->isLoaded()) { payload.setTexture(_texture->getGPUTexture()); payload.setPipeline(_texturedPipeline); } else { payload.setTexture(nullptr); payload.setPipeline(_untexturedPipeline); } }); _scene->enqueuePendingChanges(pendingChanges); } void RenderableParticleEffectEntityItem::createPipelines() { if (!_untexturedPipeline) { auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, false, gpu::LESS_EQUAL); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); auto vertShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(untextured_particle_vert))); auto fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(untextured_particle_frag))); auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vertShader, fragShader)); _untexturedPipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); } if (!_texturedPipeline) { auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, false, gpu::LESS_EQUAL); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); auto vertShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(textured_particle_vert))); auto fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(textured_particle_frag))); auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vertShader, fragShader)); _texturedPipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); } }