// // 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 #include #include "paintStroke_Shared.slh" using namespace render; using namespace render::entities; std::map, gpu::PipelinePointer> PolyLineEntityRenderer::_pipelines; static const QUrl DEFAULT_POLYLINE_TEXTURE = PathUtils::resourcesUrl("images/paintStroke.png"); PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { _texture = DependencyManager::get()->getTexture(DEFAULT_POLYLINE_TEXTURE); { // Initialize our buffers _polylineDataBuffer = std::make_shared(); _polylineDataBuffer->resize(sizeof(PolylineData)); PolylineData data { glm::vec2(_faceCamera, _glow), glm::vec2(0.0f) }; _polylineDataBuffer->setSubData(0, data); _polylineGeometryBuffer = std::make_shared(); } } void PolyLineEntityRenderer::buildPipelines() { // FIXME: opaque pipelines static const std::vector> keys = { { render::Args::DEFERRED, false }, { render::Args::DEFERRED, true }, { render::Args::FORWARD, false }, { render::Args::FORWARD, true }, }; for (auto& key : keys) { gpu::ShaderPointer program = gpu::Shader::createProgram(key.first == render::Args::DEFERRED ? shader::entities_renderer::program::paintStroke : shader::entities_renderer::program::paintStroke_forward); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setCullMode(gpu::State::CullMode::CULL_NONE); state->setDepthTest(true, !key.second, 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); _pipelines[key] = gpu::Pipeline::create(program, state); } } ItemKey PolyLineEntityRenderer::getKey() { return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()); } ShapeKey PolyLineEntityRenderer::getShapeKey() { auto builder = ShapeKey::Builder().withOwnPipeline().withTranslucent().withoutCullFace(); if (_primitiveMode == PrimitiveMode::LINES) { builder.withWireframe(); } return builder.build(); } bool PolyLineEntityRenderer::needsRenderUpdate() const { bool textureLoadedChanged = resultWithReadLock([&] { return (!_textureLoaded && _texture && _texture->isLoaded()); }); if (textureLoadedChanged) { return true; } return Parent::needsRenderUpdate(); } bool PolyLineEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { if (entity->pointsChanged() || entity->widthsChanged() || entity->normalsChanged() || entity->texturesChanged() || entity->colorsChanged()) { return true; } if (_isUVModeStretch != entity->getIsUVModeStretch() || _glow != entity->getGlow() || _faceCamera != entity->getFaceCamera()) { return true; } return Parent::needsRenderUpdateFromTypedEntity(entity); } void PolyLineEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { auto pointsChanged = entity->pointsChanged(); auto widthsChanged = entity->widthsChanged(); auto normalsChanged = entity->normalsChanged(); auto colorsChanged = entity->colorsChanged(); bool isUVModeStretch = entity->getIsUVModeStretch(); bool glow = entity->getGlow(); bool faceCamera = entity->getFaceCamera(); entity->resetPolyLineChanged(); // Textures if (entity->texturesChanged()) { entity->resetTexturesChanged(); QUrl entityTextures = DEFAULT_POLYLINE_TEXTURE; auto textures = entity->getTextures(); if (!textures.isEmpty()) { entityTextures = QUrl(textures); } withWriteLock([&] { _texture = DependencyManager::get()->getTexture(entityTextures); }); _textureAspectRatio = 1.0f; _textureLoaded = false; } bool textureChanged = false; if (!_textureLoaded && _texture && _texture->isLoaded()) { textureChanged = true; _textureAspectRatio = (float)_texture->getOriginalHeight() / (float)_texture->getOriginalWidth(); _textureLoaded = true; } // Data bool faceCameraChanged = faceCamera != _faceCamera; withWriteLock([&] { if (faceCameraChanged || glow != _glow) { _faceCamera = faceCamera; _glow = glow; updateData(); } }); // Geometry if (pointsChanged) { _points = entity->getLinePoints(); } if (widthsChanged) { _widths = entity->getStrokeWidths(); } if (normalsChanged) { _normals = entity->getNormals(); } if (colorsChanged) { _colors = entity->getStrokeColors(); _color = toGlm(entity->getColor()); } bool uvModeStretchChanged = _isUVModeStretch != isUVModeStretch; _isUVModeStretch = isUVModeStretch; bool geometryChanged = uvModeStretchChanged || pointsChanged || widthsChanged || normalsChanged || colorsChanged || textureChanged || faceCameraChanged; void* key = (void*)this; AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, geometryChanged] () { withWriteLock([&] { updateModelTransformAndBound(); _renderTransform = getModelTransform(); if (geometryChanged) { updateGeometry(); } }); }); } void PolyLineEntityRenderer::updateGeometry() { int maxNumVertices = std::min(_points.length(), _normals.length()); bool doesStrokeWidthVary = false; if (_widths.size() > 0) { float prevWidth = _widths[0]; for (int i = 1; i < maxNumVertices; i++) { float width = i < _widths.length() ? _widths[i] : PolyLineEntityItem::DEFAULT_LINE_WIDTH; if (width != prevWidth) { doesStrokeWidthVary = true; break; } if (i > _widths.length() + 1) { break; } prevWidth = width; } } float uCoordInc = 1.0f / maxNumVertices; float uCoord = 0.0f; float accumulatedDistance = 0.0f; float accumulatedStrokeWidth = 0.0f; glm::vec3 binormal; std::vector vertices; vertices.reserve(maxNumVertices); for (int i = 0; i < maxNumVertices; i++) { // Position glm::vec3 point = _points[i]; // uCoord float width = i < _widths.size() ? _widths[i] : PolyLineEntityItem::DEFAULT_LINE_WIDTH; if (i > 0) { // First uCoord is 0.0f if (!_isUVModeStretch) { accumulatedDistance += glm::distance(point, _points[i - 1]); 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 += width; float increaseValue = 1; if (accumulatedStrokeWidth != 0) { float newUcoord = glm::ceil((_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 = (_textureAspectRatio * accumulatedDistance) / width; } } else { uCoord += uCoordInc; } } // Color glm::vec3 color = i < _colors.length() ? _colors[i] : _color; // Normal glm::vec3 normal = _normals[i]; // Binormal // For last point we can assume binormals are the same since it represents the last two vertices of quad if (i < maxNumVertices - 1) { glm::vec3 tangent = _points[i + 1] - point; if (_faceCamera) { // In faceCamera mode, we actually pass the tangent, and recompute the binormal in the shader binormal = tangent; } else { binormal = glm::normalize(glm::cross(tangent, normal)); // Check to make sure binormal is not a NAN. If it is, don't add to vertices vector if (glm::any(glm::isnan(binormal))) { continue; } } } PolylineVertex vertex = { glm::vec4(point, uCoord), glm::vec4(color, 1.0f), glm::vec4(normal, 0.0f), glm::vec4(binormal, 0.5f * width) }; vertices.push_back(vertex); } _numVertices = vertices.size(); _polylineGeometryBuffer->setData(vertices.size() * sizeof(PolylineVertex), (const gpu::Byte*) vertices.data()); } void PolyLineEntityRenderer::updateData() { PolylineData data { glm::vec2(_faceCamera, _glow), glm::vec2(0.0f) }; _polylineDataBuffer->setSubData(0, data); } void PolyLineEntityRenderer::doRender(RenderArgs* args) { PerformanceTimer perfTimer("RenderablePolyLineEntityItem::render"); Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; size_t numVertices; Transform transform; gpu::TexturePointer texture; withReadLock([&] { numVertices = _numVertices; transform = _renderTransform; texture = _textureLoaded ? _texture->getGPUTexture() : DependencyManager::get()->getWhiteTexture(); batch.setResourceBuffer(0, _polylineGeometryBuffer); batch.setUniformBuffer(0, _polylineDataBuffer); }); if (numVertices < 2) { return; } if (_pipelines.empty()) { buildPipelines(); } batch.setPipeline(_pipelines[{args->_renderMethod, _glow}]); batch.setModelTransform(transform); batch.setResourceTexture(0, texture); batch.draw(gpu::TRIANGLE_STRIP, (gpu::uint32)(2 * numVertices), 0); }