From de5e95f7dcdae2e11482c9d106d215b72af7c8fd Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 28 Sep 2015 09:59:18 -0700 Subject: [PATCH] Improved procedural surfaces, textures and more standard uniforms --- .../src/RenderableBoxEntityItem.cpp | 2 +- .../src/RenderableSphereEntityItem.cpp | 2 +- libraries/gpu/src/gpu/Batch.h | 54 ++-- .../procedural/src/procedural/Procedural.cpp | 246 +++++++++++++----- .../procedural/src/procedural/Procedural.h | 35 ++- .../src/procedural/ProceduralShaders.h | 38 ++- .../src/procedural/ProceduralSkybox.cpp | 36 +-- 7 files changed, 305 insertions(+), 108 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index 187b25e75a..077f28350b 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -56,7 +56,7 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { if (_procedural->ready()) { batch.setModelTransform(getTransformToCenter()); // we want to include the scale as well - _procedural->prepare(batch, this->getDimensions()); + _procedural->prepare(batch, getPosition(), getDimensions()); auto color = _procedural->getColor(cubeColor); batch._glColor4f(color.r, color.g, color.b, color.a); DependencyManager::get()->renderCube(batch); diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp index 3cfc18046a..246cd2fea7 100644 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp @@ -62,7 +62,7 @@ void RenderableSphereEntityItem::render(RenderArgs* args) { modelTransform.postScale(SPHERE_ENTITY_SCALE); if (_procedural->ready()) { batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation - _procedural->prepare(batch, getDimensions()); + _procedural->prepare(batch, getPosition(), getDimensions()); auto color = _procedural->getColor(sphereColor); batch._glColor4f(color.r, color.g, color.b, color.a); DependencyManager::get()->renderSphere(batch); diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 2a35d04aac..e1b76e2e81 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -156,24 +156,24 @@ public: // Indirect buffer is used by the multiDrawXXXIndirect calls // The indirect buffer contains the command descriptions to execute multiple drawcalls in a single call void setIndirectBuffer(const BufferPointer& buffer, Offset offset = 0, Offset stride = 0); - - // multi command desctription for multiDrawIndexedIndirect - class DrawIndirectCommand { - public: - uint _count{ 0 }; - uint _instanceCount{ 0 }; - uint _firstIndex{ 0 }; - uint _baseInstance{ 0 }; + + // multi command desctription for multiDrawIndexedIndirect + class DrawIndirectCommand { + public: + uint _count{ 0 }; + uint _instanceCount{ 0 }; + uint _firstIndex{ 0 }; + uint _baseInstance{ 0 }; }; - - // multi command desctription for multiDrawIndexedIndirect - class DrawIndexedIndirectCommand { - public: - uint _count{ 0 }; - uint _instanceCount{ 0 }; - uint _firstIndex{ 0 }; - uint _baseVertex{ 0 }; - uint _baseInstance{ 0 }; + + // multi command desctription for multiDrawIndexedIndirect + class DrawIndexedIndirectCommand { + public: + uint _count{ 0 }; + uint _instanceCount{ 0 }; + uint _firstIndex{ 0 }; + uint _baseVertex{ 0 }; + uint _baseInstance{ 0 }; }; // Transform Stage @@ -246,6 +246,26 @@ public: void _glUniform4iv(int location, int count, const int* value); void _glUniformMatrix4fv(int location, int count, unsigned char transpose, const float* value); + void _glUniform(int location, int v0) { + _glUniform1i(location, v0); + } + + void _glUniform(int location, float v0) { + _glUniform1f(location, v0); + } + + void _glUniform(int location, const glm::vec2& v) { + _glUniform2f(location, v.x, v.y); + } + + void _glUniform(int location, const glm::vec3& v) { + _glUniform3f(location, v.x, v.y, v.z); + } + + void _glUniform(int location, const glm::vec4& v) { + _glUniform4f(location, v.x, v.y, v.z, v.w); + } + void _glColor4f(float red, float green, float blue, float alpha); enum Command { diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index aa8946f62b..334aaca741 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -17,20 +17,30 @@ #include #include #include +#include #include "ProceduralShaders.h" -static const char* const UNIFORM_TIME_NAME= "iGlobalTime"; -static const char* const UNIFORM_SCALE_NAME = "iWorldScale"; +// Userdata parsing constants static const QString PROCEDURAL_USER_DATA_KEY = "ProceduralEntity"; - static const QString URL_KEY = "shaderUrl"; static const QString VERSION_KEY = "version"; static const QString UNIFORMS_KEY = "uniforms"; +static const QString CHANNELS_KEY = "channels"; + +// Shader replace strings static const std::string PROCEDURAL_BLOCK = "//PROCEDURAL_BLOCK"; static const std::string PROCEDURAL_COMMON_BLOCK = "//PROCEDURAL_COMMON_BLOCK"; static const std::string PROCEDURAL_VERSION = "//PROCEDURAL_VERSION"; +static const std::string STANDARD_UNIFORM_NAMES[Procedural::NUM_STANDARD_UNIFORMS] = { + "iDate", + "iGlobalTime", + "iFrameCount", + "iWorldScale", + "iWorldPosition", + "iChannelResolution" +}; // Example //{ @@ -100,7 +110,21 @@ void Procedural::parse(const QJsonObject& proceduralData) { { auto uniforms = proceduralData[UNIFORMS_KEY]; if (uniforms.isObject()) { - _uniforms = uniforms.toObject();; + _parsedUniforms = uniforms.toObject(); + } + } + + // Grab any textures + { + auto channels = proceduralData[CHANNELS_KEY]; + if (channels.isArray()) { + auto textureCache = DependencyManager::get(); + _parsedChannels = channels.toArray(); + size_t channelCount = std::min(MAX_PROCEDURAL_TEXTURE_CHANNELS, (size_t)_parsedChannels.size()); + for (size_t i = 0; i < channelCount; ++i) { + QString url = _parsedChannels.at(i).toString(); + _channels[i] = textureCache->getTexture(QUrl(url)); + } } } _enabled = true; @@ -111,20 +135,26 @@ bool Procedural::ready() { return false; } - if (!_shaderPath.isEmpty()) { - return true; + // Do we have a network or local shader + if (_shaderPath.isEmpty() && (!_networkShader || !_networkShader->isLoaded())) { + return false; } - if (_networkShader) { - return _networkShader->isLoaded(); + // Do we have textures, and if so, are they loaded? + for (size_t i = 0; i < MAX_PROCEDURAL_TEXTURE_CHANNELS; ++i) { + if (_channels[i] && !_channels[i]->isLoaded()) { + return false; + } } - return false; + return true; } -void Procedural::prepare(gpu::Batch& batch, const glm::vec3& size) { +void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size) { + _entityDimensions = size; + _entityPosition = position; if (_shaderUrl.isLocalFile()) { - auto lastModified = (quint64) QFileInfo(_shaderPath).lastModified().toMSecsSinceEpoch(); + auto lastModified = (quint64)QFileInfo(_shaderPath).lastModified().toMSecsSinceEpoch(); if (lastModified > _shaderModified) { QFile file(_shaderPath); file.open(QIODevice::ReadOnly); @@ -164,69 +194,169 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& size) { //qDebug() << "FragmentShader:\n" << fragmentShaderSource.c_str(); _fragmentShader = gpu::ShaderPointer(gpu::Shader::createPixel(fragmentShaderSource)); _shader = gpu::ShaderPointer(gpu::Shader::createProgram(_vertexShader, _fragmentShader)); - gpu::Shader::makeProgram(*_shader); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("iChannel0"), 0)); + slotBindings.insert(gpu::Shader::Binding(std::string("iChannel1"), 1)); + slotBindings.insert(gpu::Shader::Binding(std::string("iChannel2"), 2)); + slotBindings.insert(gpu::Shader::Binding(std::string("iChannel3"), 3)); + gpu::Shader::makeProgram(*_shader, slotBindings); + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(_shader, _state)); - _timeSlot = _shader->getUniforms().findLocation(UNIFORM_TIME_NAME); - _scaleSlot = _shader->getUniforms().findLocation(UNIFORM_SCALE_NAME); + for (size_t i = 0; i < NUM_STANDARD_UNIFORMS; ++i) { + const std::string& name = STANDARD_UNIFORM_NAMES[i]; + _standardUniformSlots[i] = _shader->getUniforms().findLocation(name); + } _start = usecTimestampNow(); + _frameCount = 0; } batch.setPipeline(_pipeline); if (_pipelineDirty) { _pipelineDirty = false; - // Set any userdata specified uniforms - foreach(QString key, _uniforms.keys()) { - std::string uniformName = key.toLocal8Bit().data(); - int32_t slot = _shader->getUniforms().findLocation(uniformName); - if (gpu::Shader::INVALID_LOCATION == slot) { - continue; - } - QJsonValue value = _uniforms[key]; - if (value.isDouble()) { - batch._glUniform1f(slot, value.toDouble()); - } else if (value.isArray()) { - auto valueArray = value.toArray(); - switch (valueArray.size()) { - case 0: - break; + setupUniforms(); + } - case 1: - batch._glUniform1f(slot, valueArray[0].toDouble()); - break; - case 2: - batch._glUniform2f(slot, - valueArray[0].toDouble(), - valueArray[1].toDouble()); - break; - case 3: - batch._glUniform3f(slot, - valueArray[0].toDouble(), - valueArray[1].toDouble(), - valueArray[2].toDouble()); - break; - case 4: - default: - batch._glUniform4f(slot, - valueArray[0].toDouble(), - valueArray[1].toDouble(), - valueArray[2].toDouble(), - valueArray[3].toDouble()); - break; + for (auto lambda : _uniforms) { + lambda(batch); + } + + for (size_t i = 0; i < MAX_PROCEDURAL_TEXTURE_CHANNELS; ++i) { + if (_channels[i] && _channels[i]->isLoaded()) { + batch.setResourceTexture(i, _channels[i]->getGPUTexture()); + } + } +} + +void Procedural::setupUniforms() { + _uniforms.clear(); + // Set any userdata specified uniforms + foreach(QString key, _parsedUniforms.keys()) { + std::string uniformName = key.toLocal8Bit().data(); + int32_t slot = _shader->getUniforms().findLocation(uniformName); + if (gpu::Shader::INVALID_LOCATION == slot) { + continue; + } + QJsonValue value = _parsedUniforms[key]; + if (value.isDouble()) { + float v = value.toDouble(); + _uniforms.push_back([=](gpu::Batch& batch) { + batch._glUniform1f(slot, v); + }); + } else if (value.isArray()) { + auto valueArray = value.toArray(); + switch (valueArray.size()) { + case 0: + break; + + case 1: { + float v = valueArray[0].toDouble(); + _uniforms.push_back([=](gpu::Batch& batch) { + batch._glUniform1f(slot, v); + }); + break; + } + case 2: { + glm::vec2 v{ valueArray[0].toDouble(), valueArray[1].toDouble() }; + _uniforms.push_back([=](gpu::Batch& batch) { + batch._glUniform2f(slot, v.x, v.y); + }); + break; + } + case 3: { + glm::vec3 v{ + valueArray[0].toDouble(), + valueArray[1].toDouble(), + valueArray[2].toDouble(), + }; + _uniforms.push_back([=](gpu::Batch& batch) { + batch._glUniform3f(slot, v.x, v.y, v.z); + }); + break; + } + + default: + case 4: { + glm::vec4 v{ + valueArray[0].toDouble(), + valueArray[1].toDouble(), + valueArray[2].toDouble(), + valueArray[3].toDouble(), + }; + _uniforms.push_back([=](gpu::Batch& batch) { + batch._glUniform4f(slot, v.x, v.y, v.z, v.w); + }); + break; } - valueArray.size(); } } } - // Minimize floating point error by doing an integer division to milliseconds, before the floating point division to seconds - float time = (float)((usecTimestampNow() - _start) / USECS_PER_MSEC) / MSECS_PER_SECOND; - batch._glUniform1f(_timeSlot, time); - // FIXME move into the 'set once' section, since this doesn't change over time - batch._glUniform3f(_scaleSlot, size.x, size.y, size.z); -} + if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[TIME]) { + _uniforms.push_back([=](gpu::Batch& batch) { + // Minimize floating point error by doing an integer division to milliseconds, before the floating point division to seconds + float time = (float)((usecTimestampNow() - _start) / USECS_PER_MSEC) / MSECS_PER_SECOND; + batch._glUniform(_standardUniformSlots[TIME], time); + }); + } + if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[DATE]) { + _uniforms.push_back([=](gpu::Batch& batch) { + QDateTime now = QDateTime::currentDateTimeUtc(); + QDate date = now.date(); + QTime time = now.time(); + vec4 v; + v.x = date.year(); + // Shadertoy month is 0 based + v.y = date.month() - 1; + // But not the day... go figure + v.z = date.day(); + v.w = (time.hour() * 3600) + (time.minute() * 60) + time.second(); + batch._glUniform(_standardUniformSlots[DATE], v); + }); + } + + if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[FRAME_COUNT]) { + _uniforms.push_back([=](gpu::Batch& batch) { + batch._glUniform(_standardUniformSlots[FRAME_COUNT], ++_frameCount); + }); + } + + if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[SCALE]) { + // FIXME move into the 'set once' section, since this doesn't change over time + _uniforms.push_back([=](gpu::Batch& batch) { + batch._glUniform(_standardUniformSlots[SCALE], _entityDimensions); + }); + } + + if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[SCALE]) { + // FIXME move into the 'set once' section, since this doesn't change over time + _uniforms.push_back([=](gpu::Batch& batch) { + batch._glUniform(_standardUniformSlots[SCALE], _entityDimensions); + }); + } + + if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[POSITION]) { + // FIXME move into the 'set once' section, since this doesn't change over time + _uniforms.push_back([=](gpu::Batch& batch) { + batch._glUniform(_standardUniformSlots[POSITION], _entityPosition); + }); + } + + if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[CHANNEL_RESOLUTION]) { + _uniforms.push_back([=](gpu::Batch& batch) { + vec3 channelSizes[MAX_PROCEDURAL_TEXTURE_CHANNELS]; + for (size_t i = 0; i < MAX_PROCEDURAL_TEXTURE_CHANNELS; ++i) { + if (_channels[i]) { + channelSizes[i] = vec3(_channels[i]->getWidth(), _channels[i]->getHeight(), 1.0); + } + } + batch._glUniform3fv(_standardUniformSlots[CHANNEL_RESOLUTION], MAX_PROCEDURAL_TEXTURE_CHANNELS, &channelSizes[0].x); + }); + } +} glm::vec4 Procedural::getColor(const glm::vec4& entityColor) { if (_version == 1) { diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h index 5b3f2b4742..1b02fbd435 100644 --- a/libraries/procedural/src/procedural/Procedural.h +++ b/libraries/procedural/src/procedural/Procedural.h @@ -14,11 +14,16 @@ #include #include #include +#include #include #include #include #include +#include + +using UniformLambdas = std::list>; +const size_t MAX_PROCEDURAL_TEXTURE_CHANNELS{ 4 }; // FIXME better encapsulation // FIXME better mechanism for extending to things rendered using shaders other than simple.slv @@ -29,7 +34,8 @@ struct Procedural { void parse(const QString& userDataJson); void parse(const QJsonObject&); bool ready(); - void prepare(gpu::Batch& batch, const glm::vec3& size); + void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size); + void setupUniforms(); glm::vec4 getColor(const glm::vec4& entityColor); bool _enabled{ false }; @@ -43,17 +49,34 @@ struct Procedural { QUrl _shaderUrl; quint64 _shaderModified{ 0 }; bool _pipelineDirty{ true }; - int32_t _timeSlot{ gpu::Shader::INVALID_LOCATION }; - int32_t _scaleSlot{ gpu::Shader::INVALID_LOCATION }; - uint64_t _start{ 0 }; - NetworkShaderPointer _networkShader; - QJsonObject _uniforms; + enum StandardUniforms { + DATE, + TIME, + FRAME_COUNT, + SCALE, + POSITION, + CHANNEL_RESOLUTION, + NUM_STANDARD_UNIFORMS + }; + + int32_t _standardUniformSlots[NUM_STANDARD_UNIFORMS]; + + uint64_t _start{ 0 }; + int32_t _frameCount{ 0 }; + NetworkShaderPointer _networkShader; + QJsonObject _parsedUniforms; + QJsonArray _parsedChannels; + + UniformLambdas _uniforms; + NetworkTexturePointer _channels[MAX_PROCEDURAL_TEXTURE_CHANNELS]; gpu::PipelinePointer _pipeline; gpu::ShaderPointer _vertexShader; gpu::ShaderPointer _fragmentShader; gpu::ShaderPointer _shader; gpu::StatePointer _state; + glm::vec3 _entityDimensions; + glm::vec3 _entityPosition; }; #endif diff --git a/libraries/procedural/src/procedural/ProceduralShaders.h b/libraries/procedural/src/procedural/ProceduralShaders.h index 9943a322cc..eddf53cb09 100644 --- a/libraries/procedural/src/procedural/ProceduralShaders.h +++ b/libraries/procedural/src/procedural/ProceduralShaders.h @@ -262,15 +262,39 @@ float snoise(vec2 v) { return 130.0 * dot(m, g); } -// TODO add more uniforms -uniform float iGlobalTime; // shader playback time (in seconds) -uniform vec3 iWorldScale; // the dimensions of the object being rendered - -// TODO add support for textures -// TODO document available inputs other than the uniforms -// TODO provide world scale in addition to the untransformed position +// shader playback time (in seconds) +uniform float iGlobalTime; +// the dimensions of the object being rendered +uniform vec3 iWorldScale; #define PROCEDURAL 1 //PROCEDURAL_VERSION + +#ifdef PROCEDURAL_V1 + +#else + +// Unimplemented uniforms +// Resolution doesn't make sense in the VR context +const vec3 iResolution = vec3(1.0); +// Mouse functions not enabled currently +const vec4 iMouse = vec4(0.0); +// No support for audio input +const float iSampleRate = 1.0; +// No support for video input +const vec4 iChannelTime = vec4(0.0); + + +uniform vec4 iDate; +uniform int iFrameCount; +uniform vec3 iWorldPosition; +uniform vec3 iChannelResolution[4]; +uniform sampler2D iChannel0; +uniform sampler2D iChannel1; +uniform sampler2D iChannel2; +uniform sampler2D iChannel3; + +#endif + )SHADER"; diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 1c7e7e457c..022bf7898a 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -1,20 +1,20 @@ -// -// ProceduralSkybox.cpp -// libraries/procedural/src/procedural -// -// Created by Sam Gateau on 9/21/2015. -// 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 "ProceduralSkybox.h" - - -#include -#include -#include - +// +// ProceduralSkybox.cpp +// libraries/procedural/src/procedural +// +// Created by Sam Gateau on 9/21/2015. +// 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 "ProceduralSkybox.h" + + +#include +#include +#include + #include "ProceduralSkybox_vert.h" #include "ProceduralSkybox_frag.h" @@ -74,7 +74,7 @@ void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, batch.setResourceTexture(0, skybox.getCubemap()); } - skybox._procedural->prepare(batch, glm::vec3(1)); + skybox._procedural->prepare(batch, glm::vec3(0), glm::vec3(1)); batch.draw(gpu::TRIANGLE_STRIP, 4); } }