// // RenderablePolyLineEntityItem.cpp // libraries/entities-renderer/src/ // // Created by Eric Levin on 8/10/15 // Copyright 2015 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "RenderablePolyLineEntityItem.h" #include #include #include #include #include #include //#define POLYLINE_ENTITY_USE_FADE_EFFECT #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT # include #endif #include "paintStroke_vert.h" #include "paintStroke_frag.h" #include "paintStroke_fade_vert.h" #include "paintStroke_fade_frag.h" using namespace render; using namespace render::entities; static uint8_t CUSTOM_PIPELINE_NUMBER { 0 }; static const int32_t PAINTSTROKE_TEXTURE_SLOT { 0 }; // FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 11 to avoid collisions static const int32_t PAINTSTROKE_UNIFORM_SLOT { 11 }; static gpu::Stream::FormatPointer polylineFormat; static gpu::PipelinePointer polylinePipeline; #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT static gpu::PipelinePointer polylineFadePipeline; #endif struct PolyLineUniforms { glm::vec3 color; }; static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key) { if (!polylinePipeline) { auto VS = gpu::Shader::createVertex(std::string(paintStroke_vert)); auto PS = gpu::Shader::createPixel(std::string(paintStroke_frag)); gpu::ShaderPointer program = gpu::Shader::createProgram(VS, PS); #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT auto fadeVS = gpu::Shader::createVertex(std::string(paintStroke_fade_vert)); auto fadePS = gpu::Shader::createPixel(std::string(paintStroke_fade_frag)); gpu::ShaderPointer fadeProgram = gpu::Shader::createProgram(fadeVS, fadePS); #endif gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), PAINTSTROKE_TEXTURE_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("polyLineBuffer"), PAINTSTROKE_UNIFORM_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), PAINTSTROKE_TEXTURE_SLOT + 1)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), PAINTSTROKE_UNIFORM_SLOT + 1)); gpu::Shader::makeProgram(*fadeProgram, slotBindings); #endif gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(true, true, gpu::LESS_EQUAL); PrepareStencil::testMask(*state); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); polylinePipeline = gpu::Pipeline::create(program, state); #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT _fadePipeline = gpu::Pipeline::create(fadeProgram, state); #endif } #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT if (key.isFaded()) { auto fadeEffect = DependencyManager::get(); return std::make_shared(_fadePipeline, nullptr, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); } else { #endif return std::make_shared(polylinePipeline, nullptr, nullptr, nullptr); #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT } #endif } PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { static std::once_flag once; std::call_once(once, [&] { CUSTOM_PIPELINE_NUMBER = render::ShapePipeline::registerCustomShapePipelineFactory(shapePipelineFactory); polylineFormat.reset(new gpu::Stream::Format()); polylineFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), offsetof(Vertex, position)); polylineFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), offsetof(Vertex, normal)); polylineFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), offsetof(Vertex, uv)); polylineFormat->setAttribute(gpu::Stream::COLOR, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB), offsetof(Vertex, color)); }); PolyLineUniforms uniforms; _uniformBuffer = std::make_shared(sizeof(PolyLineUniforms), (const gpu::Byte*) &uniforms); _verticesBuffer = std::make_shared(); } ItemKey PolyLineEntityRenderer::getKey() { return ItemKey::Builder::transparentShape().withTypeMeta(); } ShapeKey PolyLineEntityRenderer::getShapeKey() { return ShapeKey::Builder().withCustom(CUSTOM_PIPELINE_NUMBER).build(); } bool PolyLineEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { return ( entity->pointsChanged() || entity->strokeWidthsChanged() || entity->normalsChanged() || entity->texturesChanged() || entity->strokeColorsChanged() ); } void PolyLineEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { static const QUrl DEFAULT_POLYLINE_TEXTURE = QUrl(PathUtils::resourcesPath() + "images/paintStroke.png"); QUrl entityTextures = DEFAULT_POLYLINE_TEXTURE; if (entity->texturesChanged()) { entity->resetTexturesChanged(); auto textures = entity->getTextures(); if (!textures.isEmpty()) { entityTextures = QUrl(textures); } _texture = DependencyManager::get()->getTexture(entityTextures); } if (!_texture) { _texture = DependencyManager::get()->getTexture(entityTextures); } } void PolyLineEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { PolyLineUniforms uniforms; uniforms.color = toGlm(entity->getXColor()); memcpy(&_uniformBuffer.edit(), &uniforms, sizeof(PolyLineUniforms)); auto pointsChanged = entity->pointsChanged(); auto strokeWidthsChanged = entity->strokeWidthsChanged(); auto normalsChanged = entity->normalsChanged(); auto strokeColorsChanged = entity->strokeColorsChanged(); bool isUVModeStretch = entity->getIsUVModeStretch(); entity->resetPolyLineChanged(); _polylineTransform = Transform(); _polylineTransform.setTranslation(entity->getWorldPosition()); _polylineTransform.setRotation(entity->getWorldOrientation()); if (pointsChanged) { _lastPoints = entity->getLinePoints(); } if (strokeWidthsChanged) { _lastStrokeWidths = entity->getStrokeWidths(); } if (normalsChanged) { _lastNormals = entity->getNormals(); } if (strokeColorsChanged) { _lastStrokeColors = entity->getStrokeColors(); _lastStrokeColors = _lastNormals.size() == _lastStrokeColors.size() ? _lastStrokeColors : QVector({ toGlm(entity->getXColor()) }); } if (pointsChanged || strokeWidthsChanged || normalsChanged || strokeColorsChanged) { _empty = std::min(_lastPoints.size(), std::min(_lastNormals.size(), _lastStrokeWidths.size())) < 2; if (!_empty) { updateGeometry(updateVertices(_lastPoints, _lastNormals, _lastStrokeWidths, _lastStrokeColors, isUVModeStretch, _textureAspectRatio)); } } } void PolyLineEntityRenderer::updateGeometry(const std::vector& vertices) { _numVertices = (uint32_t)vertices.size(); auto bufferSize = _numVertices * sizeof(Vertex); if (bufferSize > _verticesBuffer->getSize()) { _verticesBuffer->resize(bufferSize); } _verticesBuffer->setSubData(0, vertices); } std::vector PolyLineEntityRenderer::updateVertices(const QVector& points, const QVector& normals, const QVector& strokeWidths, const QVector& strokeColors, const bool isUVModeStretch, const float textureAspectRatio) { // Calculate the minimum vector size out of normals, points, and stroke widths int size = std::min(points.size(), std::min(normals.size(), strokeWidths.size())); std::vector vertices; // Guard against an empty polyline if (size <= 0) { return vertices; } float uCoordInc = 1.0f / size; float uCoord = 0.0f; int finalIndex = size - 1; glm::vec3 binormal; float accumulatedDistance = 0.0f; float distanceToLastPoint = 0.0f; float accumulatedStrokeWidth = 0.0f; float strokeWidth = 0.0f; bool doesStrokeWidthVary = false; for (int i = 1; i < strokeWidths.size(); i++) { if (strokeWidths[i] != strokeWidths[i - 1]) { doesStrokeWidthVary = true; break; } } for (int i = 0; i <= finalIndex; i++) { const float& width = strokeWidths.at(i); const auto& point = points.at(i); const auto& normal = normals.at(i); const auto& color = strokeColors.size() == normals.size() ? strokeColors.at(i) : strokeColors.at(0); int vertexIndex = i * 2; if (!isUVModeStretch && i >= 1) { distanceToLastPoint = glm::distance(points.at(i), points.at(i - 1)); accumulatedDistance += distanceToLastPoint; strokeWidth = 2 * strokeWidths[i]; if (doesStrokeWidthVary) { //If the stroke varies along the line the texture will stretch more or less depending on the speed //because it looks better than using the same method as below accumulatedStrokeWidth += strokeWidth; float increaseValue = 1; if (accumulatedStrokeWidth != 0) { float newUcoord = glm::ceil(((1.0f / textureAspectRatio) * accumulatedDistance) / (accumulatedStrokeWidth / i)); increaseValue = newUcoord - uCoord; } increaseValue = increaseValue > 0 ? increaseValue : 1; uCoord += increaseValue; } else { //If the stroke width is constant then the textures should keep the aspect ratio along the line uCoord = ((1.0f / textureAspectRatio) * accumulatedDistance) / strokeWidth; } } else if (vertexIndex >= 2) { uCoord += uCoordInc; } // For last point we can assume binormals are the same since it represents the last two vertices of quad if (i < finalIndex) { const auto tangent = points.at(i + 1) - point; binormal = glm::normalize(glm::cross(tangent, normal)) * width; // Check to make sure binormal is not a NAN. If it is, don't add to vertices vector if (binormal.x != binormal.x) { continue; } } const auto v1 = points.at(i) + binormal; const auto v2 = points.at(i) - binormal; vertices.emplace_back(v1, normal, vec2(uCoord, 0.0f), color); vertices.emplace_back(v2, normal, vec2(uCoord, 1.0f), color); } return vertices; } void PolyLineEntityRenderer::doRender(RenderArgs* args) { if (_empty) { return; } PerformanceTimer perfTimer("RenderablePolyLineEntityItem::render"); Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; batch.setModelTransform(_polylineTransform); batch.setUniformBuffer(PAINTSTROKE_UNIFORM_SLOT, _uniformBuffer); if (_texture && _texture->isLoaded()) { batch.setResourceTexture(PAINTSTROKE_TEXTURE_SLOT, _texture->getGPUTexture()); } else { batch.setResourceTexture(PAINTSTROKE_TEXTURE_SLOT, DependencyManager::get()->getWhiteTexture()); } float textureWidth = (float)_texture->getOriginalWidth(); float textureHeight = (float)_texture->getOriginalHeight(); if (textureWidth != 0 && textureHeight != 0) { _textureAspectRatio = textureWidth / textureHeight; } batch.setInputFormat(polylineFormat); batch.setInputBuffer(0, _verticesBuffer, 0, sizeof(Vertex)); #ifndef POLYLINE_ENTITY_USE_FADE_EFFECT // glColor4f must be called after setInputFormat if it must be taken into account if (_isFading) { batch._glColor4f(1.0f, 1.0f, 1.0f, Interpolate::calculateFadeRatio(_fadeStartTime)); } else { batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f); } #endif batch.draw(gpu::TRIANGLE_STRIP, _numVertices, 0); }