diff --git a/core/include/tangram/map.h b/core/include/tangram/map.h index 6ed6d4c844..6c8368b755 100644 --- a/core/include/tangram/map.h +++ b/core/include/tangram/map.h @@ -348,6 +348,8 @@ class Map { std::shared_ptr& getPlatform(); + void enablePostProcess(bool _enable); + private: class Impl; diff --git a/core/shaders/fxaa.fs b/core/shaders/fxaa.fs new file mode 100644 index 0000000000..5b6b282913 --- /dev/null +++ b/core/shaders/fxaa.fs @@ -0,0 +1,87 @@ +#ifdef GL_ES +precision mediump float; +#else +#define mediump +#endif + +//texcoords computed in vertex step +//to avoid dependent texture reads +varying vec2 v_rgbNW; +varying vec2 v_rgbNE; +varying vec2 v_rgbSW; +varying vec2 v_rgbSE; +varying vec2 v_rgbM; + +varying vec2 vUv; +uniform vec2 u_resolution; +uniform sampler2D iChannel0; + +#ifndef FXAA_REDUCE_MIN + #define FXAA_REDUCE_MIN (1.0/ 128.0) +#endif +#ifndef FXAA_REDUCE_MUL + #define FXAA_REDUCE_MUL (1.0 / 8.0) +#endif +#ifndef FXAA_SPAN_MAX + #define FXAA_SPAN_MAX 8.0 +#endif + +//optimized version for mobile, where dependent +//texture reads can be a bottleneck +vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 resolution, + vec2 v_rgbNW, vec2 v_rgbNE, + vec2 v_rgbSW, vec2 v_rgbSE, + vec2 v_rgbM) { + vec4 color; + mediump vec2 inverseVP = vec2(1.0 / resolution.x, 1.0 / resolution.y); + vec3 rgbNW = texture2D(tex, v_rgbNW).xyz; + vec3 rgbNE = texture2D(tex, v_rgbNE).xyz; + vec3 rgbSW = texture2D(tex, v_rgbSW).xyz; + vec3 rgbSE = texture2D(tex, v_rgbSE).xyz; + vec4 texColor = texture2D(tex, v_rgbM); + vec3 rgbM = texColor.xyz; + vec3 luma = vec3(0.299, 0.587, 0.114); + float lumaNW = dot(rgbNW, luma); + float lumaNE = dot(rgbNE, luma); + float lumaSW = dot(rgbSW, luma); + float lumaSE = dot(rgbSE, luma); + float lumaM = dot(rgbM, luma); + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + + mediump vec2 dir; + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * + (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); + + float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); + dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), + max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), + dir * rcpDirMin)) * inverseVP; + + vec3 rgbA = 0.5 * ( + texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + + texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz); + vec3 rgbB = rgbA * 0.5 + 0.25 * ( + texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + + texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz); + + float lumaB = dot(rgbB, luma); + if ((lumaB < lumaMin) || (lumaB > lumaMax)) + color = vec4(rgbA, 1.0); // texColor.a); + else + color = vec4(rgbB, 1.0); // texColor.a); + return color; +} + + +void main() { + //can also use gl_FragCoord.xy + mediump vec2 fragCoord = vUv * u_resolution; + //mediump vec2 fragCoord = gl_FragCoord.xy; + + gl_FragColor = fxaa(iChannel0, fragCoord, u_resolution, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); + +} diff --git a/core/shaders/fxaa.vs b/core/shaders/fxaa.vs new file mode 100644 index 0000000000..b7add4861f --- /dev/null +++ b/core/shaders/fxaa.vs @@ -0,0 +1,46 @@ +#ifdef GL_ES +precision mediump float; +#endif + +uniform mat4 u_proj; + +//texcoords computed in vertex step +//to avoid dependent texture reads +varying vec2 v_rgbNW; +varying vec2 v_rgbNE; +varying vec2 v_rgbSW; +varying vec2 v_rgbSE; +varying vec2 v_rgbM; + +//a resolution for our optimized shader +uniform vec2 u_resolution; +attribute vec2 a_position; +attribute vec2 a_uv; +varying vec2 vUv; + +void texcoords(vec2 fragCoord, vec2 resolution, + out vec2 v_rgbNW, out vec2 v_rgbNE, + out vec2 v_rgbSW, out vec2 v_rgbSE, + out vec2 v_rgbM) { + vec2 inverseVP = 1.0 / resolution.xy; + v_rgbNW = (fragCoord + vec2(-1.0, -1.0)) * inverseVP; + v_rgbNE = (fragCoord + vec2(1.0, -1.0)) * inverseVP; + v_rgbSW = (fragCoord + vec2(-1.0, 1.0)) * inverseVP; + v_rgbSE = (fragCoord + vec2(1.0, 1.0)) * inverseVP; + v_rgbM = vec2(fragCoord * inverseVP); +} + +void main(void) { + //gl_Position = vec4(a_uv, 1.0, 1.0); + //gl_Position = vec4(a_position, 0.0, 1.0); + gl_Position = u_proj * vec4(a_position, 1.0, 1.0); + + //compute the texture coords and send them to varyings + vec2 pos = a_position / u_resolution; + // vUv = (pos - 0.5) * 2.0; + // vUv.y = 1.0 - vUv.y; + vUv = a_uv; + //vUv = pos; + vec2 fragCoord = vUv * u_resolution; + texcoords(fragCoord, u_resolution, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); +} diff --git a/core/src/gl/framebuffer.h b/core/src/gl/framebuffer.h index 16864a6f21..bc285a6701 100644 --- a/core/src/gl/framebuffer.h +++ b/core/src/gl/framebuffer.h @@ -43,6 +43,8 @@ class FrameBuffer { void drawDebug(RenderState& _rs, glm::vec2 _dim); + auto& colorTexture() { return m_texture; } + private: void init(RenderState& _rs); diff --git a/core/src/gl/fxaa.cpp b/core/src/gl/fxaa.cpp new file mode 100644 index 0000000000..0bfb968a19 --- /dev/null +++ b/core/src/gl/fxaa.cpp @@ -0,0 +1,65 @@ +#include "fxaa.h" + +#include "fxaa_vs.h" +#include "fxaa_fs.h" + +#include "glm/mat4x4.hpp" +#include "glm/gtc/matrix_transform.hpp" + +namespace Tangram { + +Fxaa::Fxaa() { + + m_shader = std::make_unique(); + + m_shader->setShaderSource(SHADER_SOURCE(fxaa_vs), + SHADER_SOURCE(fxaa_fs)); + + m_vertexLayout = std::unique_ptr(new VertexLayout({ + {"a_position", 2, GL_FLOAT, false, 0}, + {"a_uv", 2, GL_FLOAT, false, 0},})); + +} + +Fxaa::~Fxaa() {} + +void Fxaa::draw(RenderState& _rs, Texture& _tex, glm::vec2 _dim) { + + if (!m_shader->use(_rs)) { return; } + + _rs.vertexBuffer(0); + _rs.depthTest(GL_FALSE); + + float w = _tex.getWidth(); + float h = _tex.getHeight(); + + if (_dim != glm::vec2(0)) { + w = _dim.x; + h = _dim.y; + } + + glm::mat4 proj = glm::ortho(0.f, w, h, 0.f, -1.f, 1.f); + m_shader->setUniformf(_rs, m_uResolution, w, h); + m_shader->setUniformMatrix4f(_rs, m_uProj, proj); + + glm::vec2 _pos(0); + + glm::vec4 vertices[6] = { + {_pos.x, _pos.y, 0, 1}, + {_pos.x, _pos.y + h, 0, 0}, + {_pos.x + w, _pos.y, 1, 1}, + + {_pos.x + w, _pos.y, 1, 1}, + {_pos.x, _pos.y + h, 0, 0}, + {_pos.x + w, _pos.y + h, 1, 0}, + }; + + _tex.bind(_rs, 0); + + // enable the layout for the _polygon vertices + m_vertexLayout->enable(_rs, *m_shader, 0, (void*)vertices); + + GL::drawArrays(GL_TRIANGLES, 0, 6); +} + +} diff --git a/core/src/gl/fxaa.h b/core/src/gl/fxaa.h new file mode 100644 index 0000000000..75a02c15e3 --- /dev/null +++ b/core/src/gl/fxaa.h @@ -0,0 +1,29 @@ +#pragma once + +#include "gl/renderState.h" +#include "gl/shaderProgram.h" +#include "gl/texture.h" +#include "gl/vertexLayout.h" + +namespace Tangram { + +struct PostProcessEffect { + virtual void draw(RenderState& _rs, Texture& _tex, glm::vec2 _dim) = 0; +}; + +class Fxaa : PostProcessEffect { +public: + + Fxaa(); + ~Fxaa(); + + void draw(RenderState& _rs, Texture& _tex, glm::vec2 _dim) override; + + std::unique_ptr m_shader; + std::unique_ptr m_vertexLayout; + + UniformLocation m_uProj{"u_proj"}; + UniformLocation m_uResolution{"u_resolution"}; +}; + +} diff --git a/core/src/map.cpp b/core/src/map.cpp index f7619d5b44..db8f509902 100644 --- a/core/src/map.cpp +++ b/core/src/map.cpp @@ -6,6 +6,7 @@ #include "gl.h" #include "gl/glError.h" #include "gl/framebuffer.h" +#include "gl/fxaa.h" #include "gl/hardware.h" #include "gl/primitives.h" #include "gl/renderState.h" @@ -85,6 +86,9 @@ class Map::Impl { TileManager tileManager; MarkerManager markerManager; std::unique_ptr selectionBuffer = std::make_unique(0, 0); + std::unique_ptr renderBuffer; + bool postProcess = true; + std::unique_ptr postProcessFxaa = std::make_unique(); bool cacheGlState = false; float pickRadius = .5f; @@ -94,6 +98,10 @@ class Map::Impl { SceneReadyCallback onSceneReady = nullptr; }; +void Map::enablePostProcess(bool _enable) { + impl->postProcess = _enable; +} + void Map::Impl::setEase(EaseField _f, Ease _e) { eases[static_cast(_f)] = _e; platform->requestRender(); @@ -176,6 +184,10 @@ void Map::Impl::setScene(std::shared_ptr& _scene) { if (animated != platform->isContinuousRendering()) { platform->setContinuousRendering(animated); } + + if (postProcess) { + renderBuffer = std::make_unique(view.getWidth(), view.getHeight(), false); + } } // NB: Not thread-safe. Must be called on the main/render thread! @@ -369,6 +381,9 @@ void Map::resize(int _newWidth, int _newHeight) { impl->view.setSize(_newWidth, _newHeight); impl->selectionBuffer = std::make_unique(_newWidth/2, _newHeight/2); + if (impl->postProcess) { + impl->renderBuffer = std::make_unique(_newWidth, _newHeight); + } Primitives::setResolution(impl->renderState, _newWidth, _newHeight); } @@ -522,8 +537,15 @@ void Map::render() { // Setup default framebuffer for a new frame glm::vec2 viewport(impl->view.getWidth(), impl->view.getHeight()); - FrameBuffer::apply(impl->renderState, impl->renderState.defaultFrameBuffer(), - viewport, impl->scene->background().asIVec4()); + + if (impl->postProcess && !drawSelectionBuffer) { + impl->renderBuffer->applyAsRenderTarget(impl->renderState, + impl->scene->background().asIVec4()); + + } else { + FrameBuffer::apply(impl->renderState, impl->renderState.defaultFrameBuffer(), + viewport, impl->scene->background().asIVec4()); + } if (drawSelectionBuffer) { impl->selectionBuffer->drawDebug(impl->renderState, viewport); @@ -556,6 +578,15 @@ void Map::render() { } } + if (impl->postProcess) { + FrameBuffer::apply(impl->renderState, impl->renderState.defaultFrameBuffer(), + viewport, impl->scene->background().asIVec4()); + + if (impl->renderBuffer->colorTexture()) { + impl->postProcessFxaa->draw(impl->renderState, *impl->renderBuffer->colorTexture(), viewport); + } + } + impl->labels.drawDebug(impl->renderState, impl->view); FrameInfo::draw(impl->renderState, impl->view, impl->tileManager); diff --git a/platforms/common/glfwApp.cpp b/platforms/common/glfwApp.cpp index a5189f5f8c..3307dac84d 100644 --- a/platforms/common/glfwApp.cpp +++ b/platforms/common/glfwApp.cpp @@ -54,6 +54,7 @@ Tangram::MarkerID poiMarker = 0; Tangram::MarkerID polyline = 0; bool keepRunning = true; +bool postProcessOn = true; void loadSceneFile(bool setPosition = false) { std::vector updates; @@ -127,7 +128,7 @@ void create(std::shared_ptr p, int w, int h) { } // Create a windowed mode window and its OpenGL context - glfwWindowHint(GLFW_SAMPLES, 2); + //glfwWindowHint(GLFW_SAMPLES, 2); if (!main_window) { main_window = glfwCreateWindow(width, height, "Tangram ES", NULL, NULL); } @@ -364,6 +365,10 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods if (action == GLFW_PRESS) { switch (key) { + case GLFW_KEY_0: + postProcessOn = !postProcessOn; + map->enablePostProcess(postProcessOn); + break; case GLFW_KEY_1: Tangram::toggleDebugFlag(Tangram::DebugFlags::freeze_tiles); break;