From c21d5a7854ee6f4c270cb6242cba581cef07479f Mon Sep 17 00:00:00 2001 From: "r.kuznetsov" Date: Fri, 12 May 2017 10:20:09 +0300 Subject: [PATCH] Added Subpixel Morphological Antialiasing (SMAA) --- data/resources-default/smaa-area.png | Bin 0 -> 63887 bytes data/resources-default/smaa-search.png | Bin 0 -> 598 bytes drape/drape_global.hpp | 2 + drape/drape_tests/glfunctions.cpp | 4 + drape/drape_tests/static_texture_tests.cpp | 20 +- drape/framebuffer.cpp | 143 ++++-- drape/framebuffer.hpp | 40 +- drape/glconstants.cpp | 17 + drape/glconstants.hpp | 19 + drape/glfunctions.cpp | 39 +- drape/glfunctions.hpp | 6 +- drape/hw_texture.cpp | 11 +- drape/static_texture.cpp | 86 ++-- drape/static_texture.hpp | 22 +- drape/support_manager.cpp | 61 ++- drape/support_manager.hpp | 23 +- drape/texture_manager.cpp | 111 +++-- drape/texture_manager.hpp | 8 +- drape_frontend/CMakeLists.txt | 8 + drape_frontend/backend_renderer.cpp | 12 +- drape_frontend/drape_engine.cpp | 111 +++-- drape_frontend/drape_engine.hpp | 37 +- drape_frontend/drape_frontend.pro | 8 + drape_frontend/frontend_renderer.cpp | 300 +++++++------ drape_frontend/frontend_renderer.hpp | 114 +++-- drape_frontend/message.hpp | 4 +- drape_frontend/message_subclasses.hpp | 40 +- drape_frontend/postprocess_renderer.cpp | 416 ++++++++++++++++++ drape_frontend/postprocess_renderer.hpp | 83 ++++ drape_frontend/screen_quad_renderer.cpp | 118 +++-- drape_frontend/screen_quad_renderer.hpp | 28 +- drape_frontend/shaders/shader_index.txt | 3 + .../shaders/smaa_blending_weight.fsh.glsl | 189 ++++++++ .../shaders/smaa_blending_weight.vsh.glsl | 28 ++ drape_frontend/shaders/smaa_edges.fsh.glsl | 65 +++ drape_frontend/shaders/smaa_edges.vsh.glsl | 21 + drape_frontend/shaders/smaa_final.fsh.glsl | 51 +++ drape_frontend/shaders/smaa_final.vsh.glsl | 16 + geometry/rect2d.hpp | 11 + indexer/map_style_reader.cpp | 39 +- indexer/map_style_reader.hpp | 5 +- map/framework.cpp | 25 +- qt/qt_common/map_widget.cpp | 21 + qt/qt_common/map_widget.hpp | 3 + .../drape_frontend.xcodeproj/project.pbxproj | 8 + 45 files changed, 1898 insertions(+), 478 deletions(-) create mode 100644 data/resources-default/smaa-area.png create mode 100644 data/resources-default/smaa-search.png create mode 100644 drape_frontend/postprocess_renderer.cpp create mode 100644 drape_frontend/postprocess_renderer.hpp create mode 100644 drape_frontend/shaders/smaa_blending_weight.fsh.glsl create mode 100644 drape_frontend/shaders/smaa_blending_weight.vsh.glsl create mode 100644 drape_frontend/shaders/smaa_edges.fsh.glsl create mode 100644 drape_frontend/shaders/smaa_edges.vsh.glsl create mode 100644 drape_frontend/shaders/smaa_final.fsh.glsl create mode 100644 drape_frontend/shaders/smaa_final.vsh.glsl diff --git a/data/resources-default/smaa-area.png b/data/resources-default/smaa-area.png new file mode 100644 index 0000000000000000000000000000000000000000..a11af5907aced07582c4b65a19ef440ec27551b3 GIT binary patch literal 63887 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 X#_A)kJJXhL0i^&>S3j3^P6 @@ -8,9 +11,20 @@ UNIT_TEST(CheckTrafficArrowTextures) { static std::vector skinPaths = {"6plus", "mdpi", "hdpi", "xhdpi", "xxhdpi"}; - for (size_t i = 0; i < skinPaths.size(); ++i) + static std::vector styles = {MapStyle::MapStyleClear, MapStyle::MapStyleDark, + MapStyle::MapStyleVehicleClear, + MapStyle::MapStyleVehicleDark}; + + for (auto const & style : styles) { - dp::StaticTexture texture("traffic-arrow", skinPaths[i], nullptr); - TEST(texture.IsLoadingCorrect(), ()); + GetStyleReader().SetCurrentStyle(style); + for (size_t i = 0; i < skinPaths.size(); ++i) + { + dp::StaticTexture texture("traffic-arrow", skinPaths[i], dp::RGBA8, nullptr); + TEST(texture.IsLoadingCorrect(), ()); + + dp::StaticTexture texture2("area-hatching", skinPaths[i], dp::RGBA8, nullptr); + TEST(texture2.IsLoadingCorrect(), ()); + } } } diff --git a/drape/framebuffer.cpp b/drape/framebuffer.cpp index 73d79173f5..39759deee7 100644 --- a/drape/framebuffer.cpp +++ b/drape/framebuffer.cpp @@ -1,7 +1,5 @@ #include "drape/framebuffer.hpp" - #include "drape/glfunctions.hpp" -#include "drape/oglcontext.hpp" #include "base/assert.hpp" #include "base/logging.hpp" @@ -9,7 +7,82 @@ namespace dp { +Framebuffer::DepthStencil::DepthStencil(bool stencilEnabled) + : m_stencilEnabled(stencilEnabled) +{ + if (m_stencilEnabled) + { + m_layout = gl_const::GLDepthStencil; + m_pixelType = gl_const::GLUnsignedInt24_8Type; + } + else + { + m_layout = gl_const::GLDepthComponent; + m_pixelType = gl_const::GLUnsignedIntType; + } +} + +Framebuffer::DepthStencil::~DepthStencil() +{ + Destroy(); +} + +void Framebuffer::DepthStencil::SetSize(uint32_t width, uint32_t height) +{ + Destroy(); + + m_textureId = GLFunctions::glGenTexture(); + GLFunctions::glBindTexture(m_textureId); + GLFunctions::glTexImage2D(width, height, m_layout, m_pixelType, nullptr); + if (GLFunctions::CurrentApiVersion == dp::ApiVersion::OpenGLES3) + { + GLFunctions::glTexParameter(gl_const::GLMagFilter, gl_const::GLNearest); + GLFunctions::glTexParameter(gl_const::GLMinFilter, gl_const::GLNearest); + GLFunctions::glTexParameter(gl_const::GLWrapT, gl_const::GLClampToEdge); + GLFunctions::glTexParameter(gl_const::GLWrapS, gl_const::GLClampToEdge); + } +} + +void Framebuffer::DepthStencil::Destroy() +{ + if (m_textureId != 0) + { + GLFunctions::glDeleteTexture(m_textureId); + m_textureId = 0; + } +} + +uint32_t Framebuffer::DepthStencil::GetDepthAttachmentId() const +{ + return m_textureId; +} + +uint32_t Framebuffer::DepthStencil::GetStencilAttachmentId() const +{ + return m_stencilEnabled ? m_textureId : 0; +} + +Framebuffer::Framebuffer() + : m_colorFormat(gl_const::GLRGBA) +{ + ApplyOwnDepthStencil(); +} + +Framebuffer::Framebuffer(uint32_t colorFormat) + : m_colorFormat(colorFormat) +{ + ApplyOwnDepthStencil(); +} + +Framebuffer::Framebuffer(uint32_t colorFormat, bool stencilEnabled) + : m_colorFormat(colorFormat) + , m_depthStencil(make_unique_dp(stencilEnabled)) +{ + ApplyOwnDepthStencil(); +} + Framebuffer::~Framebuffer() { Destroy(); } + void Framebuffer::Destroy() { if (m_colorTextureId != 0) @@ -18,11 +91,8 @@ void Framebuffer::Destroy() m_colorTextureId = 0; } - if (m_depthTextureId != 0) - { - GLFunctions::glDeleteTexture(m_depthTextureId); - m_depthTextureId = 0; - } + if (m_depthStencil != nullptr) + m_depthStencil->Destroy(); if (m_framebufferId != 0) { @@ -31,19 +101,21 @@ void Framebuffer::Destroy() } } -void Framebuffer::SetDefaultContext(dp::OGLContext * context) { m_defaultContext = context; } +void Framebuffer::SetFramebufferFallback(FramebufferFallback && fallback) +{ + m_framebufferFallback = std::move(fallback); +} + void Framebuffer::SetSize(uint32_t width, uint32_t height) { - ASSERT(m_defaultContext, ()); - if (!m_isSupported) return; if (m_width == width && m_height == height) return; - m_height = height; m_width = width; + m_height = height; Destroy(); @@ -56,16 +128,14 @@ void Framebuffer::SetSize(uint32_t width, uint32_t height) GLFunctions::glTexParameter(gl_const::GLWrapT, gl_const::GLClampToEdge); GLFunctions::glTexParameter(gl_const::GLWrapS, gl_const::GLClampToEdge); - m_depthTextureId = GLFunctions::glGenTexture(); - GLFunctions::glBindTexture(m_depthTextureId); - GLFunctions::glTexImage2D(m_width, m_height, gl_const::GLDepthComponent, - gl_const::GLUnsignedIntType, nullptr); - if (GLFunctions::CurrentApiVersion == dp::ApiVersion::OpenGLES3) + glConst depthAttachmentId = 0; + glConst stencilAttachmentId = 0; + if (m_depthStencilRef != nullptr) { - GLFunctions::glTexParameter(gl_const::GLMagFilter, gl_const::GLNearest); - GLFunctions::glTexParameter(gl_const::GLMinFilter, gl_const::GLNearest); - GLFunctions::glTexParameter(gl_const::GLWrapT, gl_const::GLClampToEdge); - GLFunctions::glTexParameter(gl_const::GLWrapS, gl_const::GLClampToEdge); + if (m_depthStencilRef == m_depthStencil.get()) + m_depthStencilRef->SetSize(m_width, m_height); + depthAttachmentId = m_depthStencilRef->GetDepthAttachmentId(); + stencilAttachmentId = m_depthStencilRef->GetStencilAttachmentId(); } GLFunctions::glBindTexture(0); @@ -74,8 +144,15 @@ void Framebuffer::SetSize(uint32_t width, uint32_t height) GLFunctions::glBindFramebuffer(m_framebufferId); GLFunctions::glFramebufferTexture2D(gl_const::GLColorAttachment, m_colorTextureId); - GLFunctions::glFramebufferTexture2D(gl_const::GLDepthAttachment, m_depthTextureId); - GLFunctions::glFramebufferTexture2D(gl_const::GLStencilAttachment, 0); + if (depthAttachmentId != stencilAttachmentId) + { + GLFunctions::glFramebufferTexture2D(gl_const::GLDepthAttachment, depthAttachmentId); + GLFunctions::glFramebufferTexture2D(gl_const::GLStencilAttachment, stencilAttachmentId); + } + else + { + GLFunctions::glFramebufferTexture2D(gl_const::GLDepthStencilAttachment, depthAttachmentId); + } uint32_t const status = GLFunctions::glCheckFramebufferStatus(); if (status != gl_const::GLFramebufferComplete) @@ -85,7 +162,18 @@ void Framebuffer::SetSize(uint32_t width, uint32_t height) LOG(LWARNING, ("Framebuffer is unsupported. Framebuffer status =", status)); } - m_defaultContext->setDefaultFramebuffer(); + if (m_framebufferFallback != nullptr) + m_framebufferFallback(); +} + +void Framebuffer::SetDepthStencilRef(ref_ptr depthStencilRef) +{ + m_depthStencilRef = depthStencilRef; +} + +void Framebuffer::ApplyOwnDepthStencil() +{ + m_depthStencilRef = make_ref(m_depthStencil); } void Framebuffer::Enable() @@ -96,10 +184,15 @@ void Framebuffer::Enable() void Framebuffer::Disable() { - ASSERT(m_defaultContext, ()); ASSERT(m_isSupported, ()); - m_defaultContext->setDefaultFramebuffer(); + if (m_framebufferFallback != nullptr) + m_framebufferFallback(); } uint32_t Framebuffer::GetTextureId() const { return m_colorTextureId; } + +ref_ptr Framebuffer::GetDepthStencilRef() const +{ + return m_depthStencilRef; +} } // namespace dp diff --git a/drape/framebuffer.hpp b/drape/framebuffer.hpp index 39de064799..96a95c2dcb 100644 --- a/drape/framebuffer.hpp +++ b/drape/framebuffer.hpp @@ -1,37 +1,61 @@ #pragma once +#include "drape/pointers.hpp" + #include +#include namespace dp { -class OGLContext; +using FramebufferFallback = std::function; class Framebuffer { public: - Framebuffer() = default; + class DepthStencil + { + public: + DepthStencil(bool stencilEnabled); + ~DepthStencil(); + void SetSize(uint32_t width, uint32_t height); + void Destroy(); + uint32_t GetDepthAttachmentId() const; + uint32_t GetStencilAttachmentId() const; + private: + bool const m_stencilEnabled = false; + uint32_t m_layout = 0; + uint32_t m_pixelType = 0; + uint32_t m_textureId = 0; + }; + + Framebuffer(); + Framebuffer(uint32_t colorFormat); + Framebuffer(uint32_t colorFormat, bool stencilEnabled); ~Framebuffer(); - void SetDefaultContext(dp::OGLContext * context); + void SetFramebufferFallback(FramebufferFallback && fallback); void SetSize(uint32_t width, uint32_t height); + void SetDepthStencilRef(ref_ptr depthStencilRef); + void ApplyOwnDepthStencil(); void Enable(); void Disable(); uint32_t GetTextureId() const; + ref_ptr GetDepthStencilRef() const; + bool IsSupported() const { return m_isSupported; } private: void Destroy(); + drape_ptr m_depthStencil; + ref_ptr m_depthStencilRef; uint32_t m_width = 0; uint32_t m_height = 0; - uint32_t m_colorTextureId = 0; - uint32_t m_depthTextureId = 0; uint32_t m_framebufferId = 0; - - dp::OGLContext * m_defaultContext = 0; - + uint32_t m_colorFormat; + FramebufferFallback m_framebufferFallback; bool m_isSupported = true; }; } // namespace dp diff --git a/drape/glconstants.cpp b/drape/glconstants.cpp index 2d4d6a8adc..dfd0c6d2ca 100644 --- a/drape/glconstants.cpp +++ b/drape/glconstants.cpp @@ -104,6 +104,10 @@ const glConst GLRenderer = GL_RENDERER; const glConst GLVendor = GL_VENDOR; const glConst GLVersion = GL_VERSION; +const glConst GLColorBit = GL_COLOR_BUFFER_BIT; +const glConst GLDepthBit = GL_DEPTH_BUFFER_BIT; +const glConst GLStencilBit = GL_STENCIL_BUFFER_BIT; + const glConst GLMaxFragmentTextures = GL_MAX_TEXTURE_IMAGE_UNITS; const glConst GLMaxVertexTextures = GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS; const glConst GLMaxTextureSize = GL_MAX_TEXTURE_SIZE; @@ -138,6 +142,7 @@ const glConst GLAlpha = GL_ALPHA; const glConst GLLuminance = GL_LUMINANCE; const glConst GLAlphaLuminance = GL_LUMINANCE_ALPHA; const glConst GLDepthComponent = GL_DEPTH_COMPONENT; +const glConst GLDepthStencil = GL_DEPTH_STENCIL; const glConst GLRGBA8 = GL_RGBA8_OES; const glConst GLRGBA4 = GL_RGBA4_OES; @@ -146,6 +151,7 @@ const glConst GLLuminance8 = GL_LUMINANCE8_OES; const glConst GLAlphaLuminance8 = GL_LUMINANCE8_ALPHA8_OES; const glConst GLAlphaLuminance4 = GL_LUMINANCE8_ALPHA4_OES; const glConst GLRed = GL_RED; +const glConst GLRedGreen = GL_RG; const glConst GL8BitOnChannel = GL_UNSIGNED_BYTE; const glConst GL4BitOnChannel = GL_UNSIGNED_SHORT_4_4_4_4; @@ -173,6 +179,7 @@ const glConst GLUnsignedShortType = GL_UNSIGNED_SHORT; const glConst GLIntType = GL_INT; const glConst GLUnsignedIntType = GL_UNSIGNED_INT; const glConst GLFloatType = GL_FLOAT; +const glConst GLUnsignedInt24_8Type = GL_UNSIGNED_INT_24_8; const glConst GLFloatVec2 = GL_FLOAT_VEC2; const glConst GLFloatVec3 = GL_FLOAT_VEC3; @@ -203,6 +210,7 @@ const glConst GLDepthTest = GL_DEPTH_TEST; const glConst GLBlending = GL_BLEND; const glConst GLCullFace = GL_CULL_FACE; const glConst GLScissorTest = GL_SCISSOR_TEST; +const glConst GLStencilTest = GL_STENCIL_TEST; const glConst GLClockwise = GL_CW; const glConst GLCounterClockwise = GL_CCW; @@ -220,6 +228,14 @@ const glConst GLNotEqual = GL_NOTEQUAL; const glConst GLGreatOrEqual = GL_GEQUAL; const glConst GLAlways = GL_ALWAYS; +const glConst GLKeep = GL_KEEP; +const glConst GLIncr = GL_INCR; +const glConst GLDecr = GL_DECR; +const glConst GLInvert = GL_INVERT; +const glConst GLReplace = GL_REPLACE; +const glConst GLIncrWrap = GL_INCR_WRAP; +const glConst GLDecrWrap = GL_DECR_WRAP; + const glConst GLActiveUniforms = GL_ACTIVE_UNIFORMS; const glConst GLLines = GL_LINES; @@ -230,6 +246,7 @@ const glConst GLTriangleStrip = GL_TRIANGLE_STRIP; const glConst GLColorAttachment = GL_COLOR_ATTACHMENT0; const glConst GLDepthAttachment = GL_DEPTH_ATTACHMENT; const glConst GLStencilAttachment = GL_STENCIL_ATTACHMENT; +const glConst GLDepthStencilAttachment = GL_DEPTH_STENCIL_ATTACHMENT; const glConst GLFramebufferComplete = GL_FRAMEBUFFER_COMPLETE; diff --git a/drape/glconstants.hpp b/drape/glconstants.hpp index c67c181fa9..e44bd5656a 100644 --- a/drape/glconstants.hpp +++ b/drape/glconstants.hpp @@ -12,6 +12,11 @@ extern const glConst GLRenderer; extern const glConst GLVendor; extern const glConst GLVersion; +/// Clear bits +extern const glConst GLColorBit; +extern const glConst GLDepthBit; +extern const glConst GLStencilBit; + /// Hardware specific params extern const glConst GLMaxFragmentTextures; extern const glConst GLMaxVertexTextures; @@ -54,6 +59,7 @@ extern const glConst GLAlpha; extern const glConst GLLuminance; extern const glConst GLAlphaLuminance; extern const glConst GLDepthComponent; +extern const glConst GLDepthStencil; /// Texture layout size extern const glConst GLRGBA8; @@ -63,6 +69,7 @@ extern const glConst GLLuminance8; extern const glConst GLAlphaLuminance8; extern const glConst GLAlphaLuminance4; extern const glConst GLRed; +extern const glConst GLRedGreen; /// Pixel type for texture upload extern const glConst GL8BitOnChannel; @@ -97,6 +104,7 @@ extern const glConst GLUnsignedShortType; extern const glConst GLIntType; extern const glConst GLUnsignedIntType; extern const glConst GLFloatType; +extern const glConst GLUnsignedInt24_8Type; extern const glConst GLFloatVec2; extern const glConst GLFloatVec3; @@ -130,6 +138,7 @@ extern const glConst GLDepthTest; extern const glConst GLBlending; extern const glConst GLCullFace; extern const glConst GLScissorTest; +extern const glConst GLStencilTest; /// Triangle faces order extern const glConst GLClockwise; @@ -150,6 +159,15 @@ extern const glConst GLNotEqual; extern const glConst GLGreatOrEqual; extern const glConst GLAlways; +/// OpenGL stencil functions +extern const glConst GLKeep; +extern const glConst GLIncr; +extern const glConst GLDecr; +extern const glConst GLInvert; +extern const glConst GLReplace; +extern const glConst GLIncrWrap; +extern const glConst GLDecrWrap; + /// Program object parameter names extern const glConst GLActiveUniforms; @@ -163,6 +181,7 @@ extern const glConst GLTriangleStrip; extern const glConst GLColorAttachment; extern const glConst GLDepthAttachment; extern const glConst GLStencilAttachment; +extern const glConst GLDepthStencilAttachment; /// Framebuffer status extern const glConst GLFramebufferComplete; diff --git a/drape/glfunctions.cpp b/drape/glfunctions.cpp index 55c003194a..4cdc153b63 100644 --- a/drape/glfunctions.cpp +++ b/drape/glfunctions.cpp @@ -427,16 +427,10 @@ void GLFunctions::glClearColor(float r, float g, float b, float a) GLCHECK(glClearColorFn(r, g, b, a)); } -void GLFunctions::glClear() +void GLFunctions::glClear(uint32_t clearBits) { ASSERT(glClearFn != nullptr, ()); - GLCHECK(glClearFn(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); -} - -void GLFunctions::glClearDepth() -{ - ASSERT(glClearFn != nullptr, ()); - GLCHECK(glClearFn(GL_DEPTH_BUFFER_BIT)); + GLCHECK(glClearFn(clearBits)); } void GLFunctions::glViewport(uint32_t x, uint32_t y, uint32_t w, uint32_t h) @@ -458,8 +452,21 @@ void GLFunctions::glFlush() } void GLFunctions::glFinish() { GLCHECK(::glFinish()); } + void GLFunctions::glFrontFace(glConst mode) { GLCHECK(::glFrontFace(mode)); } + void GLFunctions::glCullFace(glConst face) { GLCHECK(::glCullFace(face)); } + +void GLFunctions::glStencilOpSeparate(glConst face, glConst sfail, glConst dpfail, glConst dppass) +{ + GLCHECK(::glStencilOpSeparate(face, sfail, dpfail, dppass)); +} + +void GLFunctions::glStencilFuncSeparate(glConst face, glConst func, int ref, uint32_t mask) +{ + GLCHECK(::glStencilFuncSeparate(face, func, ref, mask)); +} + void GLFunctions::glPixelStore(glConst name, uint32_t value) { GLCHECK(::glPixelStorei(name, value)); @@ -900,8 +907,10 @@ void GLFunctions::glTexImage2D(int width, int height, glConst layout, glConst pi { // In OpenGL ES3: // - we can't create unsized GL_RED texture, so we use GL_R8; + // - we can't create unsized GL_RG texture, so we use GL_RG8; // - we can't create unsized GL_DEPTH_COMPONENT texture, so we use GL_DEPTH_COMPONENT16 - // or GL_DEPTH_COMPONENT24 or GL_DEPTH_COMPONENT32F. + // or GL_DEPTH_COMPONENT24 or GL_DEPTH_COMPONENT32F; + // - we can't create unsized GL_DEPTH_STENCIL texture, so we use GL_DEPTH24_STENCIL8. glConst internalFormat = layout; if (CurrentApiVersion == dp::ApiVersion::OpenGLES3) { @@ -909,6 +918,10 @@ void GLFunctions::glTexImage2D(int width, int height, glConst layout, glConst pi { internalFormat = GL_R8; } + else if (layout == gl_const::GLRedGreen) + { + internalFormat = GL_RG8; + } else if (layout == gl_const::GLDepthComponent) { internalFormat = GL_DEPTH_COMPONENT16; @@ -917,10 +930,14 @@ void GLFunctions::glTexImage2D(int width, int height, glConst layout, glConst pi else if (pixelType == gl_const::GLFloatType) internalFormat = GL_DEPTH_COMPONENT32F; } + else if (layout == gl_const::GLDepthStencil) + { + internalFormat = GL_DEPTH24_STENCIL8; + } } - GLCHECK( - ::glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, layout, pixelType, data)); + GLCHECK(::glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, + 0, layout, pixelType, data)); } void GLFunctions::glTexSubImage2D(int x, int y, int width, int height, glConst layout, diff --git a/drape/glfunctions.hpp b/drape/glfunctions.hpp index 4c3e87c4d5..c950203e62 100644 --- a/drape/glfunctions.hpp +++ b/drape/glfunctions.hpp @@ -19,8 +19,7 @@ public: static bool glHasExtension(std::string const & name); static void glClearColor(float r, float g, float b, float a); - static void glClear(); - static void glClearDepth(); + static void glClear(uint32_t clearBits); static void glViewport(uint32_t x, uint32_t y, uint32_t w, uint32_t h); static void glScissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h); static void glFlush(); @@ -29,6 +28,9 @@ public: static void glFrontFace(glConst mode); static void glCullFace(glConst face); + static void glStencilOpSeparate(glConst face, glConst sfail, glConst dpfail, glConst dppass); + static void glStencilFuncSeparate(glConst face, glConst func, int ref, uint32_t mask); + static void glPixelStore(glConst name, uint32_t value); static int32_t glGetInteger(glConst pname); diff --git a/drape/hw_texture.cpp b/drape/hw_texture.cpp index 06a6c580b7..3fde271920 100644 --- a/drape/hw_texture.cpp +++ b/drape/hw_texture.cpp @@ -75,17 +75,23 @@ float HWTexture::GetT(uint32_t y) const void HWTexture::UnpackFormat(TextureFormat format, glConst & layout, glConst & pixelType) { + // Now we support only 1-byte-per-channel textures. + pixelType = gl_const::GL8BitOnChannel; + switch (format) { case RGBA8: layout = gl_const::GLRGBA; - pixelType = gl_const::GL8BitOnChannel; break; case ALPHA: // On OpenGL ES3 GLAlpha is not supported, we use GLRed instead. layout = GLFunctions::CurrentApiVersion == dp::ApiVersion::OpenGLES2 ? gl_const::GLAlpha : gl_const::GLRed; - pixelType = gl_const::GL8BitOnChannel; + break; + case RED_GREEN: + // On OpenGL ES2 2-channel textures are not supported. + layout = GLFunctions::CurrentApiVersion == dp::ApiVersion::OpenGLES2 ? gl_const::GLRGBA + : gl_const::GLRedGreen; break; default: ASSERT(false, ()); break; } @@ -109,6 +115,7 @@ void HWTexture::SetFilter(glConst filter) } int32_t HWTexture::GetID() const { return m_textureID; } + OpenGLHWTexture::~OpenGLHWTexture() { if (m_textureID != -1) diff --git a/drape/static_texture.cpp b/drape/static_texture.cpp index f131129b03..8638787e20 100644 --- a/drape/static_texture.cpp +++ b/drape/static_texture.cpp @@ -4,8 +4,8 @@ #include "platform/platform.hpp" -#include "coding/reader.hpp" #include "coding/parse_xml.hpp" +#include "coding/reader.hpp" #include "base/string_utils.hpp" @@ -14,26 +14,32 @@ #endif #include "3party/stb_image/stb_image.h" +#include + namespace dp { +std::string const StaticTexture::kDefaultResource = "default"; namespace { - using TLoadingCompletion = function; -using TLoadingFailure = function; +using TLoadingFailure = function; -bool LoadData(string const & textureName, string const & skinPathName, - TLoadingCompletion const & completionHandler, +bool LoadData(std::string const & textureName, std::string const & skinPathName, + uint8_t bytesPerPixel, TLoadingCompletion const & completionHandler, TLoadingFailure const & failureHandler) { ASSERT(completionHandler != nullptr, ()); ASSERT(failureHandler != nullptr, ()); - vector rawData; + std::vector rawData; try { - ReaderPtr reader = GetStyleReader().GetResourceReader(textureName + ".png", skinPathName); + std::string const fullName = textureName + ".png"; + ReaderPtr reader = skinPathName == StaticTexture::kDefaultResource ? + GetStyleReader().GetDefaultResourceReader(fullName) : + GetStyleReader().GetResourceReader(fullName, skinPathName); + CHECK_LESS(reader.Size(), static_cast(numeric_limits::max()), ()); size_t const size = static_cast(reader.Size()); rawData.resize(size); @@ -46,12 +52,30 @@ bool LoadData(string const & textureName, string const & skinPathName, } int w, h, bpp; - unsigned char * data = stbi_load_from_memory(&rawData[0], static_cast(rawData.size()), &w, &h, &bpp, 0); - ASSERT_EQUAL(bpp, 4, ("Incorrect texture format")); + unsigned char * data = + stbi_load_from_memory(&rawData[0], static_cast(rawData.size()), &w, &h, &bpp, 0); + + uint8_t constexpr kSupportedBPP = 4; + CHECK_EQUAL(bpp, kSupportedBPP, ("Incorrect texture format")); ASSERT(glm::isPowerOfTwo(w), (w)); ASSERT(glm::isPowerOfTwo(h), (h)); - completionHandler(data, static_cast(w), static_cast(h)); + if (bytesPerPixel != bpp) + { + std::vector convertedData(static_cast(w * h * bytesPerPixel)); + uint32_t const pixelsCount = static_cast(w * h); + for (uint32_t i = 0; i < pixelsCount; i++) + { + unsigned char const * p = data + i * bpp; + convertedData[i * bytesPerPixel] = p[0]; + convertedData[i * bytesPerPixel + 1] = p[1]; + } + stbi_image_free(data); + completionHandler(convertedData.data(), static_cast(w), static_cast(h)); + return true; + } + + completionHandler(data, static_cast(w), static_cast(h)); stbi_image_free(data); return true; } @@ -60,28 +84,29 @@ class StaticResourceInfo : public Texture::ResourceInfo { public: StaticResourceInfo() : Texture::ResourceInfo(m2::RectF(0.0f, 0.0f, 1.0f, 1.0f)) {} - virtual ~StaticResourceInfo(){} - + virtual ~StaticResourceInfo() {} Texture::ResourceType GetType() const override { return Texture::Static; } }; +} // namespace -} // namespace - -StaticTexture::StaticTexture(string const & textureName, string const & skinPathName, - ref_ptr allocator) +StaticTexture::StaticTexture(std::string const & textureName, std::string const & skinPathName, + dp::TextureFormat format, ref_ptr allocator) : m_textureName(textureName) + , m_skinPathName(skinPathName) + , m_format(format) , m_info(make_unique_dp()) { - m_isLoadingCorrect = Load(skinPathName, allocator); + m_isLoadingCorrect = Load(allocator); } -bool StaticTexture::Load(string const & skinPathName, ref_ptr allocator) +bool StaticTexture::Load(ref_ptr allocator) { - auto completionHandler = [this, &allocator](unsigned char * data, uint32_t width, uint32_t height) + auto completionHandler = [this, &allocator](unsigned char * data, uint32_t width, + uint32_t height) { Texture::Params p; p.m_allocator = allocator; - p.m_format = dp::RGBA8; + p.m_format = m_format; p.m_width = width; p.m_height = height; p.m_wrapSMode = gl_const::GLRepeate; @@ -90,22 +115,25 @@ bool StaticTexture::Load(string const & skinPathName, ref_ptr allocator) +void StaticTexture::Invalidate(ref_ptr allocator) { Destroy(); - m_isLoadingCorrect = Load(skinPathName, allocator); + m_isLoadingCorrect = Load(allocator); } -ref_ptr StaticTexture::FindResource(Texture::Key const & key, bool & newResource) +ref_ptr StaticTexture::FindResource(Texture::Key const & key, + bool & newResource) { newResource = false; if (key.GetType() != Texture::Static) @@ -115,14 +143,12 @@ ref_ptr StaticTexture::FindResource(Texture::Key const & void StaticTexture::Fail() { - int32_t alfaTexture = 0; + int32_t alphaTexture = 0; Texture::Params p; p.m_allocator = GetDefaultAllocator(); p.m_format = dp::RGBA8; p.m_width = 1; p.m_height = 1; - - Create(p, make_ref(&alfaTexture)); + Create(p, make_ref(&alphaTexture)); } - -} // namespace dp +} // namespace dp diff --git a/drape/static_texture.hpp b/drape/static_texture.hpp index aa2aa7e4c5..d7a6950445 100644 --- a/drape/static_texture.hpp +++ b/drape/static_texture.hpp @@ -2,11 +2,10 @@ #include "drape/texture.hpp" -#include "std/string.hpp" +#include namespace dp { - class StaticTexture : public Texture { public: @@ -16,23 +15,26 @@ public: ResourceType GetType() const override { return ResourceType::Static; } }; - StaticTexture(string const & textureName, string const & skinPathName, - ref_ptr allocator); + static std::string const kDefaultResource; + + StaticTexture(std::string const & textureName, std::string const & skinPathName, + dp::TextureFormat format, ref_ptr allocator); ref_ptr FindResource(Key const & key, bool & newResource) override; - void Invalidate(string const & skinPathName, ref_ptr allocator); + void Invalidate(ref_ptr allocator); bool IsLoadingCorrect() const { return m_isLoadingCorrect; } - private: void Fail(); - bool Load(string const & skinPathName, ref_ptr allocator); + bool Load(ref_ptr allocator); + + std::string const m_textureName; + std::string const m_skinPathName; + dp::TextureFormat const m_format; - string m_textureName; drape_ptr m_info; bool m_isLoadingCorrect; }; - -} // namespace dp +} // namespace dp diff --git a/drape/support_manager.cpp b/drape/support_manager.cpp index 6a68449086..bd745335f4 100644 --- a/drape/support_manager.cpp +++ b/drape/support_manager.cpp @@ -1,20 +1,24 @@ #include "drape/support_manager.hpp" #include "drape/glfunctions.hpp" -#include "base/logging.hpp" +#include "platform/settings.hpp" -#include "std/algorithm.hpp" -#include "std/vector.hpp" +#include "base/logging.hpp" #include "3party/Alohalytics/src/alohalytics.h" +#include +#include +#include + namespace dp { +char const * kSupportedAntialiasing = "Antialiasing"; void SupportManager::Init() { - string const renderer = GLFunctions::glGetString(gl_const::GLRenderer); - string const version = GLFunctions::glGetString(gl_const::GLVersion); + std::string const renderer = GLFunctions::glGetString(gl_const::GLRenderer); + std::string const version = GLFunctions::glGetString(gl_const::GLVersion); LOG(LINFO, ("Renderer =", renderer, "Api =", GLFunctions::CurrentApiVersion, "Driver version =", version)); // On Android the engine may be recreated. Here we guarantee that GPU info is sent once per session. @@ -29,12 +33,12 @@ void SupportManager::Init() if (m_isSamsungGoogleNexus) LOG(LINFO, ("Samsung Google Nexus detected.")); - if (renderer.find("Adreno") != string::npos) + if (renderer.find("Adreno") != std::string::npos) { - vector const models = { "200", "203", "205", "220", "225" }; + std::vector const models = { "200", "203", "205", "220", "225" }; for (auto const & model : models) { - if (renderer.find(model) != string::npos) + if (renderer.find(model) != std::string::npos) { LOG(LINFO, ("Adreno 200 device detected.")); m_isAdreno200 = true; @@ -43,32 +47,28 @@ void SupportManager::Init() } } - m_isTegra = (renderer.find("Tegra") != string::npos); + m_isTegra = (renderer.find("Tegra") != std::string::npos); if (m_isTegra) LOG(LINFO, ("NVidia Tegra device detected.")); - m_maxLineWidth = max(1, GLFunctions::glGetMaxLineWidth()); + m_maxLineWidth = std::max(1, GLFunctions::glGetMaxLineWidth()); LOG(LINFO, ("Max line width =", m_maxLineWidth)); -} -bool SupportManager::IsSamsungGoogleNexus() const -{ - return m_isSamsungGoogleNexus; -} - -bool SupportManager::IsAdreno200Device() const -{ - return m_isAdreno200; -} - -bool SupportManager::IsTegraDevice() const -{ - return m_isTegra; -} - -int SupportManager::GetMaxLineWidth() const -{ - return m_maxLineWidth; + // Set up default antialiasing value. + bool val; + if (!settings::Get(kSupportedAntialiasing, val)) + { +#ifdef OMIM_OS_ANDROID + std::vector const models = {"Mali-G71", "Mali-T880", + "Adreno (TM) 540", "Adreno (TM) 530", + "Adreno (TM) 430", "Adreno (TM) 330"}; + m_isAntialiasingEnabledByDefault = + (std::find(models.begin(), models.end(), renderer) != models.end()); +#else + m_isAntialiasingEnabledByDefault = true; +#endif + settings::Set(kSupportedAntialiasing, m_isAntialiasingEnabledByDefault); + } } SupportManager & SupportManager::Instance() @@ -76,5 +76,4 @@ SupportManager & SupportManager::Instance() static SupportManager manager; return manager; } - -} // namespace dp +} // namespace dp diff --git a/drape/support_manager.hpp b/drape/support_manager.hpp index c59fdbcbbe..e7a9df6aa9 100644 --- a/drape/support_manager.hpp +++ b/drape/support_manager.hpp @@ -1,11 +1,12 @@ #pragma once -#include "std/noncopyable.hpp" +#include "base/macros.hpp" namespace dp { +extern char const * kSupportedAntialiasing; -class SupportManager : public noncopyable +class SupportManager { public: // This singleton must be available only from rendering threads. @@ -14,21 +15,21 @@ public: // Initialization must be called only when OpenGL context is created. void Init(); - bool IsSamsungGoogleNexus() const; - bool IsAdreno200Device() const; - bool IsTegraDevice() const; - - int GetMaxLineWidth() const; + bool IsSamsungGoogleNexus() const { return m_isSamsungGoogleNexus; } + bool IsAdreno200Device() const { return m_isAdreno200; } + bool IsTegraDevice() const { return m_isTegra; } + int GetMaxLineWidth() const { return m_maxLineWidth; } + bool IsAntialiasingEnabledByDefault() const { return m_isAntialiasingEnabledByDefault; } private: SupportManager() = default; - ~SupportManager() = default; bool m_isSamsungGoogleNexus = false; bool m_isAdreno200 = false; bool m_isTegra = false; - int m_maxLineWidth = 1; -}; + bool m_isAntialiasingEnabledByDefault = false; -} // namespace dp + DISALLOW_COPY_AND_MOVE(SupportManager); +}; +} // namespace dp diff --git a/drape/texture_manager.cpp b/drape/texture_manager.cpp index 2811e4fa22..6c6acc5554 100644 --- a/drape/texture_manager.cpp +++ b/drape/texture_manager.cpp @@ -19,6 +19,8 @@ #include "std/vector.hpp" #include "std/bind.hpp" +#include + namespace dp { @@ -43,7 +45,6 @@ uint32_t const kDefaultSymbolsIndex = 0; namespace { - void MultilineTextToUniString(TextureManager::TMultilineText const & text, strings::UniString & outString) { size_t cnt = 0; @@ -117,8 +118,23 @@ void ParsePatternsList(string const & patternsFile, ToDo toDo) toDo(pattern); }); } +m2::PointU StipplePenTextureSize(size_t patternsCount, uint32_t maxTextureSize) +{ + uint32_t const sz = my::NextPowOf2(static_cast(patternsCount) + kReservedPatterns); + uint32_t const stippleTextureHeight = + std::min(maxTextureSize, std::max(sz, kMinStippleTextureHeight)); + return m2::PointU(kStippleTextureWidth, stippleTextureHeight); +} -} // namespace +m2::PointU ColorTextureSize(size_t colorsCount, uint32_t maxTextureSize) +{ + uint32_t const sz = static_cast(floor(sqrt(colorsCount + kReservedColors))); + uint32_t colorTextureSize = std::max(my::NextPowOf2(sz), kMinColorTextureSize); + colorTextureSize *= ColorTexture::GetColorSizeInPixels(); + colorTextureSize = std::min(maxTextureSize, colorTextureSize); + return m2::PointU(colorTextureSize, colorTextureSize); +} +} // namespace TextureManager::TextureManager() : m_maxTextureSize(0) @@ -178,8 +194,7 @@ m2::RectF const & TextureManager::BaseRegion::GetTexRect() const TextureManager::GlyphRegion::GlyphRegion() : BaseRegion() -{ -} +{} float TextureManager::GlyphRegion::GetOffsetX() const { @@ -228,6 +243,8 @@ void TextureManager::Release() m_trafficArrowTexture.reset(); m_hatchingTexture.reset(); + m_smaaAreaTexture.reset(); + m_smaaSearchTexture.reset(); m_glyphTextures.clear(); @@ -390,39 +407,46 @@ size_t TextureManager::FindHybridGlyphsGroup(TMultilineText const & text, int fi void TextureManager::Init(Params const & params) { + m_resPostfix = params.m_resPostfix; m_textureAllocator = CreateAllocator(); - m_maxTextureSize = min(kMaxTextureSize, (uint32_t)GLFunctions::glGetInteger(gl_const::GLMaxTextureSize)); + m_maxTextureSize = std::min(kMaxTextureSize, + static_cast(GLFunctions::glGetInteger(gl_const::GLMaxTextureSize))); GLFunctions::glPixelStore(gl_const::GLUnpackAlignment, 1); + // Initialize symbols. for (size_t i = 0; i < ARRAY_SIZE(kSymbolTextures); ++i) { - m_symbolTextures.push_back(make_unique_dp(params.m_resPostfix, kSymbolTextures[i], + m_symbolTextures.push_back(make_unique_dp(m_resPostfix, kSymbolTextures[i], make_ref(m_textureAllocator))); } - m_trafficArrowTexture = make_unique_dp("traffic-arrow", params.m_resPostfix, - make_ref(m_textureAllocator)); + // Initialize static textures. + m_trafficArrowTexture = make_unique_dp("traffic-arrow", m_resPostfix, + dp::RGBA8, make_ref(m_textureAllocator)); + m_hatchingTexture = make_unique_dp("area-hatching", m_resPostfix, + dp::RGBA8, make_ref(m_textureAllocator)); - m_hatchingTexture = make_unique_dp("area-hatching", params.m_resPostfix, - make_ref(m_textureAllocator)); + if (GLFunctions::CurrentApiVersion == dp::ApiVersion::OpenGLES3) + { + m_smaaAreaTexture = make_unique_dp("smaa-area", StaticTexture::kDefaultResource, + dp::RED_GREEN, make_ref(m_textureAllocator)); + m_smaaSearchTexture = make_unique_dp("smaa-search", StaticTexture::kDefaultResource, + dp::ALPHA, make_ref(m_textureAllocator)); + } - // initialize patterns + // Initialize patterns. buffer_vector, 64> patterns; double const visualScale = params.m_visualScale; ParsePatternsList(params.m_patterns, [&patterns, visualScale](buffer_vector const & pattern) { buffer_vector p; for (size_t i = 0; i < pattern.size(); i++) - p.push_back(pattern[i] * visualScale); + p.push_back(static_cast(pattern[i] * visualScale)); patterns.push_back(move(p)); }); - - uint32_t stippleTextureHeight = max(my::NextPowOf2(static_cast(patterns.size()) + kReservedPatterns), - kMinStippleTextureHeight); - stippleTextureHeight = min(m_maxTextureSize, stippleTextureHeight); - m_stipplePenTexture = make_unique_dp(m2::PointU(kStippleTextureWidth, stippleTextureHeight), + m_stipplePenTexture = make_unique_dp(StipplePenTextureSize(patterns.size(), m_maxTextureSize), make_ref(m_textureAllocator)); LOG(LDEBUG, ("Patterns texture size = ", m_stipplePenTexture->GetWidth(), m_stipplePenTexture->GetHeight())); @@ -430,17 +454,13 @@ void TextureManager::Init(Params const & params) for (auto it = patterns.begin(); it != patterns.end(); ++it) stipplePenTextureTex->ReservePattern(*it); - // initialize colors + // Initialize colors. buffer_vector colors; ParseColorsList(params.m_colors, [&colors](dp::Color const & color) { colors.push_back(color); }); - - uint32_t colorTextureSize = max(my::NextPowOf2(floor(sqrt(colors.size() + kReservedColors))), kMinColorTextureSize); - colorTextureSize *= ColorTexture::GetColorSizeInPixels(); - colorTextureSize = min(m_maxTextureSize, colorTextureSize); - m_colorTexture = make_unique_dp(m2::PointU(colorTextureSize, colorTextureSize), + m_colorTexture = make_unique_dp(ColorTextureSize(colors.size(), m_maxTextureSize), make_ref(m_textureAllocator)); LOG(LDEBUG, ("Colors texture size = ", m_colorTexture->GetWidth(), m_colorTexture->GetHeight())); @@ -448,15 +468,16 @@ void TextureManager::Init(Params const & params) for (auto it = colors.begin(); it != colors.end(); ++it) colorTex->ReserveColor(*it); - // initialize glyphs + // Initialize glyphs. m_glyphManager = make_unique_dp(params.m_glyphMngParams); uint32_t const textureSquare = m_maxTextureSize * m_maxTextureSize; - uint32_t const baseGlyphHeight = params.m_glyphMngParams.m_baseGlyphHeight * kGlyphAreaMultiplier; - uint32_t const avarageGlyphSquare = baseGlyphHeight * baseGlyphHeight; + uint32_t const baseGlyphHeight = + static_cast(params.m_glyphMngParams.m_baseGlyphHeight * kGlyphAreaMultiplier); + uint32_t const averageGlyphSquare = baseGlyphHeight * baseGlyphHeight; m_glyphGroups.push_back(GlyphGroup()); - m_maxGlypsCount = ceil(kGlyphAreaCoverage * textureSquare / avarageGlyphSquare); + m_maxGlypsCount = static_cast(ceil(kGlyphAreaCoverage * textureSquare / averageGlyphSquare)); m_glyphManager->ForEachUnicodeBlock([this](strings::UniChar const & start, strings::UniChar const & end) { if (m_glyphGroups.empty()) @@ -475,22 +496,27 @@ void TextureManager::Init(Params const & params) }); } -void TextureManager::Invalidate(string const & resPostfix) +void TextureManager::OnSwitchMapStyle() { + // Here we need invalidate only textures which can be changed in map style switch. for (size_t i = 0; i < m_symbolTextures.size(); ++i) { ASSERT(m_symbolTextures[i] != nullptr, ()); + ASSERT(dynamic_cast(m_symbolTextures[i].get()) != nullptr, ()); ref_ptr symbolsTexture = make_ref(m_symbolTextures[i]); - symbolsTexture->Invalidate(resPostfix, make_ref(m_textureAllocator)); + symbolsTexture->Invalidate(m_resPostfix, make_ref(m_textureAllocator)); } - ASSERT(m_trafficArrowTexture != nullptr, ()); - ref_ptr trafficArrowTexture = make_ref(m_trafficArrowTexture); - trafficArrowTexture->Invalidate(resPostfix, make_ref(m_textureAllocator)); - - ASSERT(m_hatchingTexture != nullptr, ()); - ref_ptr hatchingTexture = make_ref(m_hatchingTexture); - hatchingTexture->Invalidate(resPostfix, make_ref(m_textureAllocator)); + // Uncomment if static textures can be changed. + //ref_ptr staticTextures[] = {make_ref(m_trafficArrowTexture), + // make_ref(m_hatchingTexture)}; + //for (uint32_t i = 0; i < ARRAY_SIZE(staticTextures); i++) + //{ + // ASSERT(staticTextures[i] != nullptr, ()); + // ASSERT(dynamic_cast(staticTextures[i].get()) != nullptr, ()); + // ref_ptr t = staticTextures[i]; + // t->Invalidate(make_ref(m_textureAllocator)); + //} } void TextureManager::GetSymbolRegion(string const & symbolName, SymbolRegion & region) @@ -569,9 +595,18 @@ ref_ptr TextureManager::GetHatchingTexture() const return make_ref(m_hatchingTexture); } +ref_ptr TextureManager::GetSMAAAreaTexture() const +{ + return make_ref(m_smaaAreaTexture); +} + +ref_ptr TextureManager::GetSMAASearchTexture() const +{ + return make_ref(m_smaaSearchTexture); +} + constexpr size_t TextureManager::GetInvalidGlyphGroup() { return kInvalidGlyphGroup; } - -} // namespace dp +} // namespace dp diff --git a/drape/texture_manager.hpp b/drape/texture_manager.hpp index c0a9a74a9d..fa9edb7167 100644 --- a/drape/texture_manager.hpp +++ b/drape/texture_manager.hpp @@ -10,6 +10,7 @@ #include #include +#include #include namespace dp @@ -81,7 +82,7 @@ public: void Release(); void Init(Params const & params); - void Invalidate(string const & resPostfix); + void OnSwitchMapStyle(); void GetSymbolRegion(string const & symbolName, SymbolRegion & region); @@ -107,6 +108,8 @@ public: ref_ptr GetSymbolsTexture() const; ref_ptr GetTrafficArrowTexture() const; ref_ptr GetHatchingTexture() const; + ref_ptr GetSMAAAreaTexture() const; + ref_ptr GetSMAASearchTexture() const; private: struct GlyphGroup @@ -239,6 +242,7 @@ private: static constexpr size_t GetInvalidGlyphGroup(); private: + std::string m_resPostfix; std::vector> m_symbolTextures; drape_ptr m_stipplePenTexture; drape_ptr m_colorTexture; @@ -246,6 +250,8 @@ private: drape_ptr m_trafficArrowTexture; drape_ptr m_hatchingTexture; + drape_ptr m_smaaAreaTexture; + drape_ptr m_smaaSearchTexture; drape_ptr m_glyphManager; drape_ptr m_textureAllocator; diff --git a/drape_frontend/CMakeLists.txt b/drape_frontend/CMakeLists.txt index 3a130ac449..7b3f68f7bc 100644 --- a/drape_frontend/CMakeLists.txt +++ b/drape_frontend/CMakeLists.txt @@ -145,6 +145,8 @@ set( path_text_shape.hpp poi_symbol_shape.cpp poi_symbol_shape.hpp + postprocess_renderer.cpp + postprocess_renderer.hpp read_manager.cpp read_manager.hpp read_mwm_task.cpp @@ -274,6 +276,12 @@ set( shaders/screen_quad.vsh.glsl shaders/shader_index.txt shaders/shaders_lib.glsl + shaders/smaa_blending_weight.fsh.glsl + shaders/smaa_blending_weight.vsh.glsl + shaders/smaa_edges.fsh.glsl + shaders/smaa_edges.vsh.glsl + shaders/smaa_final.fsh.glsl + shaders/smaa_final.vsh.glsl shaders/solid_color.fsh.glsl shaders/text.fsh.glsl shaders/text.vsh.glsl diff --git a/drape_frontend/backend_renderer.cpp b/drape_frontend/backend_renderer.cpp index 0f6d844711..edc94f2874 100644 --- a/drape_frontend/backend_renderer.cpp +++ b/drape_frontend/backend_renderer.cpp @@ -308,9 +308,9 @@ void BackendRenderer::AcceptMessage(ref_ptr message) break; } - case Message::InvalidateTextures: + case Message::SwitchMapStyle: { - m_texMng->Invalidate(VisualParams::Instance().GetResourcePostfix()); + m_texMng->OnSwitchMapStyle(); RecacheMapShapes(); m_trafficGenerator->InvalidateTexturesCache(); break; @@ -536,6 +536,14 @@ void BackendRenderer::InitGLDependentResource() GetPlatform().GetFontNames(params.m_glyphMngParams.m_fonts); m_texMng->Init(params); + + // Send some textures to frontend renderer. + drape_ptr textures = make_unique_dp(); + textures->m_smaaAreaTexture = m_texMng->GetSMAAAreaTexture(); + textures->m_smaaSearchTexture = m_texMng->GetSMAASearchTexture(); + m_commutator->PostMessage(ThreadsCommutator::RenderThread, + make_unique_dp(std::move(textures)), + MessagePriority::High); } void BackendRenderer::RecacheMapShapes() diff --git a/drape_frontend/drape_engine.cpp b/drape_frontend/drape_engine.cpp index 115e0fade9..c95d101565 100644 --- a/drape_frontend/drape_engine.cpp +++ b/drape_frontend/drape_engine.cpp @@ -1,17 +1,19 @@ #include "drape_frontend/drape_engine.hpp" -#include "drape_frontend/message_subclasses.hpp" -#include "drape_frontend/visual_params.hpp" -#include "drape_frontend/my_position_controller.hpp" +#include "drape_frontend/message_subclasses.hpp" #include "drape_frontend/gui/drape_gui.hpp" +#include "drape_frontend/my_position_controller.hpp" +#include "drape_frontend/visual_params.hpp" + +#include "drape/support_manager.hpp" #include "platform/settings.hpp" namespace df { DrapeEngine::DrapeEngine(Params && params) - : m_myPositionModeChanged(move(params.m_myPositionModeChanged)) - , m_viewport(move(params.m_viewport)) + : m_myPositionModeChanged(std::move(params.m_myPositionModeChanged)) + , m_viewport(std::move(params.m_viewport)) { VisualParams::Init(params.m_vs, df::CalculateTileSize(m_viewport.GetWidth(), m_viewport.GetHeight())); @@ -32,8 +34,8 @@ DrapeEngine::DrapeEngine(Params && params) } else if (mode == location::FollowAndRotate) { - // If the screen rect setting in follow and rotate mode is missing or invalid, it could cause invalid animations, - // so the follow and rotate mode should be discarded. + // If the screen rect setting in follow and rotate mode is missing or invalid, it could cause + // invalid animations, so the follow and rotate mode should be discarded. m2::AnyRectD rect; if (!(settings::Get("ScreenClipRect", rect) && df::GetWorldRect().IsRectInside(rect.GetGlobalRect()))) mode = location::Follow; @@ -44,6 +46,18 @@ DrapeEngine::DrapeEngine(Params && params) if (settings::Get("LastEnterBackground", lastEnterBackground)) timeInBackground = my::Timer::LocalTime() - lastEnterBackground; + std::vector effects; + + bool enabledAntialiasing; + if (!settings::Get(dp::kSupportedAntialiasing, enabledAntialiasing)) + enabledAntialiasing = false; + + if (enabledAntialiasing) + { + LOG(LINFO, ("Antialiasing is enabled")); + effects.push_back(PostprocessRenderer::Antialiasing); + } + MyPositionController::Params mpParams(mode, timeInBackground, params.m_hints, @@ -55,18 +69,19 @@ DrapeEngine::DrapeEngine(Params && params) make_ref(m_threadCommutator), params.m_factory, make_ref(m_textureManager), - move(mpParams), + std::move(mpParams), m_viewport, bind(&DrapeEngine::ModelViewChanged, this, _1), bind(&DrapeEngine::TapEvent, this, _1), bind(&DrapeEngine::UserPositionChanged, this, _1), make_ref(m_requestedTiles), - move(params.m_overlaysShowStatsCallback), + std::move(params.m_overlaysShowStatsCallback), params.m_allow3dBuildings, params.m_trafficEnabled, - params.m_blockTapEvents); + params.m_blockTapEvents, + std::move(effects)); - m_frontend = make_unique_dp(move(frParams)); + m_frontend = make_unique_dp(std::move(frParams)); BackendRenderer::Params brParams(params.m_apiVersion, frParams.m_commutator, @@ -79,15 +94,15 @@ DrapeEngine::DrapeEngine(Params && params) params.m_trafficEnabled, params.m_simplifiedTrafficColors); - m_backend = make_unique_dp(move(brParams)); + m_backend = make_unique_dp(std::move(brParams)); - m_widgetsInfo = move(params.m_info); + m_widgetsInfo = std::move(params.m_info); RecacheGui(false); RecacheMapShapes(); if (params.m_showChoosePositionMark) - EnableChoosePositionMode(true, move(params.m_boundAreaTriangles), false, m2::PointD()); + EnableChoosePositionMode(true, std::move(params.m_boundAreaTriangles), false, m2::PointD()); ResizeImpl(m_viewport.GetWidth(), m_viewport.GetHeight()); } @@ -251,7 +266,7 @@ void DrapeEngine::RecacheGui(bool needResetOldGui) void DrapeEngine::AddUserEvent(drape_ptr && e) { - m_frontend->AddUserEvent(move(e)); + m_frontend->AddUserEvent(std::move(e)); } void DrapeEngine::ModelViewChanged(ScreenBase const & screen) @@ -293,7 +308,8 @@ void DrapeEngine::SetCompassInfo(location::CompassInfo const & info) MessagePriority::High); } -void DrapeEngine::SetGpsInfo(location::GpsInfo const & info, bool isNavigable, const location::RouteMatchingInfo & routeInfo) +void DrapeEngine::SetGpsInfo(location::GpsInfo const & info, bool isNavigable, + const location::RouteMatchingInfo & routeInfo) { m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread, make_unique_dp(info, isNavigable, routeInfo), @@ -302,45 +318,49 @@ void DrapeEngine::SetGpsInfo(location::GpsInfo const & info, bool isNavigable, c void DrapeEngine::SwitchMyPositionNextMode() { + using Mode = ChangeMyPositionModeMessage::EChangeType; m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread, - make_unique_dp(ChangeMyPositionModeMessage::SwitchNextMode), + make_unique_dp(Mode::SwitchNextMode), MessagePriority::High); } void DrapeEngine::LoseLocation() { + using Mode = ChangeMyPositionModeMessage::EChangeType; m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread, - make_unique_dp(ChangeMyPositionModeMessage::LoseLocation), + make_unique_dp(Mode::LoseLocation), MessagePriority::High); } void DrapeEngine::StopLocationFollow() { + using Mode = ChangeMyPositionModeMessage::EChangeType; m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread, - make_unique_dp(ChangeMyPositionModeMessage::StopFollowing), + make_unique_dp(Mode::StopFollowing), MessagePriority::High); } void DrapeEngine::FollowRoute(int preferredZoomLevel, int preferredZoomLevel3d, bool enableAutoZoom) { m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread, - make_unique_dp(preferredZoomLevel, preferredZoomLevel3d, enableAutoZoom), + make_unique_dp(preferredZoomLevel, + preferredZoomLevel3d, enableAutoZoom), MessagePriority::High); } void DrapeEngine::SetModelViewListener(TModelViewListenerFn && fn) { - m_modelViewChanged = move(fn); + m_modelViewChanged = std::move(fn); } void DrapeEngine::SetTapEventInfoListener(TTapEventInfoFn && fn) { - m_tapListener = move(fn); + m_tapListener = std::move(fn); } void DrapeEngine::SetUserPositionListener(TUserPositionChangedFn && fn) { - m_userPositionChanged = move(fn); + m_userPositionChanged = std::move(fn); } FeatureID DrapeEngine::GetVisiblePOI(m2::PointD const & glbPoint) @@ -355,7 +375,8 @@ FeatureID DrapeEngine::GetVisiblePOI(m2::PointD const & glbPoint) return result; } -void DrapeEngine::SelectObject(SelectionShape::ESelectedObject obj, m2::PointD const & pt, FeatureID const & featureId, bool isAnim) +void DrapeEngine::SelectObject(SelectionShape::ESelectedObject obj, m2::PointD const & pt, + FeatureID const & featureId, bool isAnim) { m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread, make_unique_dp(obj, pt, featureId, isAnim), @@ -393,12 +414,13 @@ bool DrapeEngine::GetMyPosition(m2::PointD & myPosition) return hasPosition; } -void DrapeEngine::AddRoute(m2::PolylineD const & routePolyline, vector const & turns, - df::ColorConstant color, vector const & traffic, +void DrapeEngine::AddRoute(m2::PolylineD const & routePolyline, std::vector const & turns, + df::ColorConstant color, std::vector const & traffic, df::RoutePattern pattern) { m_threadCommutator->PostMessage(ThreadsCommutator::ResourceUploadThread, - make_unique_dp(routePolyline, turns, color, traffic, pattern), + make_unique_dp(routePolyline, turns, + color, traffic, pattern), MessagePriority::Normal); } @@ -425,7 +447,7 @@ void DrapeEngine::SetRoutePoint(m2::PointD const & position, bool isStart, bool void DrapeEngine::SetWidgetLayout(gui::TWidgetsLayoutInfo && info) { - m_widgetsLayout = move(info); + m_widgetsLayout = std::move(info); for (auto const & layout : m_widgetsLayout) { auto const itInfo = m_widgetsInfo.find(layout.first); @@ -451,7 +473,8 @@ void DrapeEngine::Allow3dMode(bool allowPerspectiveInNavigation, bool allow3dBui MessagePriority::Normal); m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread, - make_unique_dp(allowPerspectiveInNavigation, allow3dBuildings), + make_unique_dp(allowPerspectiveInNavigation, + allow3dBuildings), MessagePriority::Normal); } @@ -462,10 +485,12 @@ void DrapeEngine::EnablePerspective() MessagePriority::Normal); } -void DrapeEngine::UpdateGpsTrackPoints(vector && toAdd, vector && toRemove) +void DrapeEngine::UpdateGpsTrackPoints(std::vector && toAdd, + std::vector && toRemove) { m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread, - make_unique_dp(move(toAdd), move(toRemove)), + make_unique_dp(std::move(toAdd), + std::move(toRemove)), MessagePriority::Normal); } @@ -476,7 +501,7 @@ void DrapeEngine::ClearGpsTrackPoints() MessagePriority::Normal); } -void DrapeEngine::EnableChoosePositionMode(bool enable, vector && boundAreaTriangles, +void DrapeEngine::EnableChoosePositionMode(bool enable, std::vector && boundAreaTriangles, bool hasPosition, m2::PointD const & position) { m_choosePositionMode = enable; @@ -494,7 +519,7 @@ void DrapeEngine::EnableChoosePositionMode(bool enable, vector && RecacheGui(true); } m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread, - make_unique_dp(enable, move(boundAreaTriangles), + make_unique_dp(enable, std::move(boundAreaTriangles), kineticScroll, hasPosition, position), MessagePriority::High); } @@ -531,7 +556,7 @@ void DrapeEngine::SetDisplacementMode(int mode) MessagePriority::Normal); } -void DrapeEngine::RequestSymbolsSize(vector const & symbols, +void DrapeEngine::RequestSymbolsSize(std::vector const & symbols, TRequestSymbolsSizeCallback const & callback) { m_threadCommutator->PostMessage(ThreadsCommutator::ResourceUploadThread, @@ -560,7 +585,7 @@ void DrapeEngine::UpdateTraffic(traffic::TrafficInfo const & info) segmentsColoring.emplace(info.GetMwmId(), info.GetColoring()); m_threadCommutator->PostMessage(ThreadsCommutator::ResourceUploadThread, - make_unique_dp(move(segmentsColoring)), + make_unique_dp(std::move(segmentsColoring)), MessagePriority::Normal); } @@ -594,7 +619,7 @@ void DrapeEngine::RunScenario(ScenarioManager::ScenarioData && scenarioData, { auto const & manager = m_frontend->GetScenarioManager(); if (manager != nullptr) - manager->RunScenario(move(scenarioData), onStartFn, onFinishFn); + manager->RunScenario(std::move(scenarioData), onStartFn, onFinishFn); } void DrapeEngine::AddCustomSymbols(CustomSymbols && symbols) @@ -618,4 +643,16 @@ void DrapeEngine::RemoveAllCustomSymbols() MessagePriority::Normal); } -} // namespace df +void DrapeEngine::SetPosteffectEnabled(PostprocessRenderer::Effect effect, bool enabled) +{ + if (effect == df::PostprocessRenderer::Antialiasing) + { + LOG(LINFO, ("Antialiasing is", (enabled ? "enabled" : "disabled"))); + settings::Set(dp::kSupportedAntialiasing, enabled); + } + + m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread, + make_unique_dp(effect, enabled), + MessagePriority::Normal); +} +} // namespace df diff --git a/drape_frontend/drape_engine.hpp b/drape_frontend/drape_engine.hpp index 64e9b5924e..debfbc2494 100644 --- a/drape_frontend/drape_engine.hpp +++ b/drape_frontend/drape_engine.hpp @@ -7,6 +7,7 @@ #include "drape_frontend/frontend_renderer.hpp" #include "drape_frontend/route_shape.hpp" #include "drape_frontend/overlays_tracker.hpp" +#include "drape_frontend/postprocess_renderer.hpp" #include "drape_frontend/scenario_manager.hpp" #include "drape_frontend/selection_shape.hpp" #include "drape_frontend/threads_commutator.hpp" @@ -25,8 +26,8 @@ #include "base/strings_bundle.hpp" -#include "std/map.hpp" -#include "std/mutex.hpp" +#include +#include namespace dp { @@ -58,7 +59,7 @@ public: bool trafficEnabled, bool blockTapEvents, bool showChoosePositionMark, - vector && boundAreaTriangles, + std::vector && boundAreaTriangles, bool isRoutingActive, bool isAutozoomEnabled, bool simplifiedTrafficColors, @@ -71,18 +72,18 @@ public: , m_hints(hints) , m_vs(vs) , m_fontsScaleFactor(fontsScaleFactor) - , m_info(move(info)) + , m_info(std::move(info)) , m_initialMyPositionMode(initialMyPositionMode) - , m_myPositionModeChanged(move(myPositionModeChanged)) + , m_myPositionModeChanged(std::move(myPositionModeChanged)) , m_allow3dBuildings(allow3dBuildings) , m_trafficEnabled(trafficEnabled) , m_blockTapEvents(blockTapEvents) , m_showChoosePositionMark(showChoosePositionMark) - , m_boundAreaTriangles(move(boundAreaTriangles)) + , m_boundAreaTriangles(std::move(boundAreaTriangles)) , m_isRoutingActive(isRoutingActive) , m_isAutozoomEnabled(isAutozoomEnabled) , m_simplifiedTrafficColors(simplifiedTrafficColors) - , m_overlaysShowStatsCallback(move(overlaysShowStatsCallback)) + , m_overlaysShowStatsCallback(std::move(overlaysShowStatsCallback)) {} dp::ApiVersion m_apiVersion; @@ -100,7 +101,7 @@ public: bool m_trafficEnabled; bool m_blockTapEvents; bool m_showChoosePositionMark; - vector m_boundAreaTriangles; + std::vector m_boundAreaTriangles; bool m_isRoutingActive; bool m_isAutozoomEnabled; bool m_simplifiedTrafficColors; @@ -120,7 +121,7 @@ public: void AddTouchEvent(TouchEvent const & event); void Scale(double factor, m2::PointD const & pxPoint, bool isAnim); - /// if zoom == -1, then current zoom will not change + // If zoom == -1 then current zoom will not be changed. void SetModelViewCenter(m2::PointD const & centerPt, int zoom, bool isAnim); void SetModelViewRect(m2::RectD const & rect, bool applyRotation, int zoom, bool isAnim); void SetModelViewAnyRect(m2::AnyRectD const & rect, bool isAnim); @@ -149,13 +150,14 @@ public: void SetUserPositionListener(TUserPositionChangedFn && fn); FeatureID GetVisiblePOI(m2::PointD const & glbPoint); - void SelectObject(SelectionShape::ESelectedObject obj, m2::PointD const & pt, FeatureID const & featureID, bool isAnim); + void SelectObject(SelectionShape::ESelectedObject obj, m2::PointD const & pt, + FeatureID const & featureID, bool isAnim); void DeselectObject(); bool GetMyPosition(m2::PointD & myPosition); SelectionShape::ESelectedObject GetSelectedObject(); - void AddRoute(m2::PolylineD const & routePolyline, vector const & turns, - df::ColorConstant color, vector const & traffic, + void AddRoute(m2::PolylineD const & routePolyline, std::vector const & turns, + df::ColorConstant color, std::vector const & traffic, df::RoutePattern pattern = df::RoutePattern()); void RemoveRoute(bool deactivateFollowing); void FollowRoute(int preferredZoomLevel, int preferredZoomLevel3d, bool enableAutoZoom); @@ -169,10 +171,11 @@ public: void Allow3dMode(bool allowPerspectiveInNavigation, bool allow3dBuildings); void EnablePerspective(); - void UpdateGpsTrackPoints(vector && toAdd, vector && toRemove); + void UpdateGpsTrackPoints(std::vector && toAdd, + std::vector && toRemove); void ClearGpsTrackPoints(); - void EnableChoosePositionMode(bool enable, vector && boundAreaTriangles, + void EnableChoosePositionMode(bool enable, std::vector && boundAreaTriangles, bool hasPosition, m2::PointD const & position); void BlockTapEvents(bool block); @@ -182,9 +185,9 @@ public: void SetDisplacementMode(int mode); - using TRequestSymbolsSizeCallback = function const &)>; + using TRequestSymbolsSizeCallback = std::function const &)>; - void RequestSymbolsSize(vector const & symbols, + void RequestSymbolsSize(std::vector const & symbols, TRequestSymbolsSizeCallback const & callback); void EnableTraffic(bool trafficEnabled); @@ -202,6 +205,8 @@ public: void RemoveCustomSymbols(MwmSet::MwmId const & mwmId); void RemoveAllCustomSymbols(); + void SetPosteffectEnabled(PostprocessRenderer::Effect effect, bool enabled); + private: void AddUserEvent(drape_ptr && e); void ModelViewChanged(ScreenBase const & screen); diff --git a/drape_frontend/drape_frontend.pro b/drape_frontend/drape_frontend.pro index 15e0f007bc..51077eb59f 100755 --- a/drape_frontend/drape_frontend.pro +++ b/drape_frontend/drape_frontend.pro @@ -71,6 +71,7 @@ SOURCES += \ path_symbol_shape.cpp \ path_text_shape.cpp \ poi_symbol_shape.cpp \ + postprocess_renderer.cpp \ read_manager.cpp \ read_mwm_task.cpp \ render_group.cpp \ @@ -179,6 +180,7 @@ HEADERS += \ path_symbol_shape.hpp \ path_text_shape.hpp \ poi_symbol_shape.hpp \ + postprocess_renderer.hpp \ read_manager.hpp \ read_mwm_task.hpp \ render_group.hpp \ @@ -267,6 +269,12 @@ OTHER_FILES += \ shaders/screen_quad.vsh.glsl \ shaders/shader_index.txt \ shaders/shaders_lib.glsl \ + shaders/smaa_blending_weight.fsh.glsl \ + shaders/smaa_blending_weight.vsh.glsl \ + shaders/smaa_edges.fsh.glsl \ + shaders/smaa_edges.vsh.glsl \ + shaders/smaa_final.fsh.glsl \ + shaders/smaa_final.vsh.glsl \ shaders/solid_color.fsh.glsl \ shaders/text.fsh.glsl \ shaders/text.vsh.glsl \ diff --git a/drape_frontend/frontend_renderer.cpp b/drape_frontend/frontend_renderer.cpp index ee52189864..fb9c01c920 100755 --- a/drape_frontend/frontend_renderer.cpp +++ b/drape_frontend/frontend_renderer.cpp @@ -6,6 +6,7 @@ #include "drape_frontend/gui/drape_gui.hpp" #include "drape_frontend/gui/ruler_helper.hpp" #include "drape_frontend/message_subclasses.hpp" +#include "drape_frontend/postprocess_renderer.hpp" #include "drape_frontend/scenario_manager.hpp" #include "drape_frontend/screen_operations.hpp" #include "drape_frontend/screen_quad_renderer.hpp" @@ -31,10 +32,9 @@ #include "base/logging.hpp" #include "base/stl_add.hpp" -#include "std/algorithm.hpp" -#include "std/bind.hpp" -#include "std/cmath.hpp" -#include "std/chrono.hpp" +#include +#include +#include namespace df { @@ -62,7 +62,7 @@ struct MergedGroupKey }; template -bool RemoveGroups(ToDo & filter, vector> & groups, +bool RemoveGroups(ToDo & filter, std::vector> & groups, ref_ptr tree) { size_t startCount = groups.size(); @@ -90,9 +90,9 @@ bool RemoveGroups(ToDo & filter, vector> & groups, struct RemoveTilePredicate { mutable bool m_deletionMark = false; - function const &)> const & m_predicate; + std::function const &)> const & m_predicate; - RemoveTilePredicate(function const &)> const & predicate) + RemoveTilePredicate(std::function const &)> const & predicate) : m_predicate(predicate) {} @@ -108,7 +108,6 @@ struct RemoveTilePredicate return false; } }; - } // namespace FrontendRenderer::FrontendRenderer(Params && params) @@ -116,10 +115,8 @@ FrontendRenderer::FrontendRenderer(Params && params) , m_gpuProgramManager(new dp::GpuProgramManager()) , m_routeRenderer(new RouteRenderer()) , m_trafficRenderer(new TrafficRenderer()) - , m_framebuffer(new dp::Framebuffer()) - , m_screenQuadRenderer(new ScreenQuadRenderer()) , m_gpsTrackRenderer( - new GpsTrackRenderer(bind(&FrontendRenderer::PrepareGpsTrackPoints, this, _1))) + new GpsTrackRenderer(std::bind(&FrontendRenderer::PrepareGpsTrackPoints, this, _1))) , m_drapeApiRenderer(new DrapeApiRenderer()) , m_overlayTree(new dp::OverlayTree()) , m_enablePerspectiveInNavigation(false) @@ -136,8 +133,9 @@ FrontendRenderer::FrontendRenderer(Params && params) , m_needRestoreSize(false) , m_trafficEnabled(params.m_trafficEnabled) , m_overlaysTracker(new OverlaysTracker()) - , m_overlaysShowStatsCallback(move(params.m_overlaysShowStatsCallback)) + , m_overlaysShowStatsCallback(std::move(params.m_overlaysShowStatsCallback)) , m_forceUpdateScene(false) + , m_postprocessRenderer(new PostprocessRenderer()) #ifdef SCENARIO_ENABLE , m_scenarioManager(new ScenarioManager(this)) #endif @@ -149,7 +147,11 @@ FrontendRenderer::FrontendRenderer(Params && params) ASSERT(m_tapEventInfoFn, ()); ASSERT(m_userPositionChangedFn, ()); - m_myPositionController.reset(new MyPositionController(move(params.m_myPositionParams))); + m_myPositionController.reset(new MyPositionController(std::move(params.m_myPositionParams))); + + for (auto const & effect : params.m_enabledEffects) + m_postprocessRenderer->SetEffectEnabled(effect, true /* enabled */); + StartThread(); } @@ -171,7 +173,7 @@ void FrontendRenderer::UpdateCanBeDeletedStatus() { m2::RectD const & screenRect = m_userEventStream.GetCurrentScreen().ClipRect(); - vector notFinishedTileRects; + std::vector notFinishedTileRects; notFinishedTileRects.reserve(m_notFinishedTiles.size()); for (auto const & tileKey : m_notFinishedTiles) notFinishedTileRects.push_back(tileKey.GetGlobalRect()); @@ -180,26 +182,18 @@ void FrontendRenderer::UpdateCanBeDeletedStatus() { for (auto & group : layer.m_renderGroups) { - if (group->IsPendingOnDelete()) + if (!group->IsPendingOnDelete()) + continue; + + bool canBeDeleted = true; + if (!notFinishedTileRects.empty()) { - bool canBeDeleted = true; - if (!notFinishedTileRects.empty()) - { - m2::RectD const tileRect = group->GetTileKey().GetGlobalRect(); - if (tileRect.IsIntersect(screenRect)) - { - for (auto const & notFinishedRect : notFinishedTileRects) - { - if (notFinishedRect.IsIntersect(tileRect)) - { - canBeDeleted = false; - break; - } - } - } - } - layer.m_isDirty |= group->UpdateCanBeDeletedStatus(canBeDeleted, m_currentZoomLevel, make_ref(m_overlayTree)); + m2::RectD const tileRect = group->GetTileKey().GetGlobalRect(); + if (tileRect.IsIntersect(screenRect)) + canBeDeleted = !HasIntersection(tileRect, notFinishedTileRects); } + layer.m_isDirty |= group->UpdateCanBeDeletedStatus(canBeDeleted, m_currentZoomLevel, + make_ref(m_overlayTree)); } } } @@ -217,7 +211,7 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) if (key.m_zoomLevel == m_currentZoomLevel && CheckTileGenerations(key)) { PrepareBucket(state, bucket); - AddToRenderGroup(state, move(bucket), key); + AddToRenderGroup(state, std::move(bucket), key); } break; } @@ -232,7 +226,8 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) CheckTileGenerations(overlayRenderData.m_tileKey)) { PrepareBucket(overlayRenderData.m_state, overlayRenderData.m_bucket); - AddToRenderGroup(overlayRenderData.m_state, move(overlayRenderData.m_bucket), overlayRenderData.m_tileKey); + AddToRenderGroup(overlayRenderData.m_state, std::move(overlayRenderData.m_bucket), + overlayRenderData.m_tileKey); } } UpdateCanBeDeletedStatus(); @@ -285,8 +280,8 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) auto program = m_gpuProgramManager->GetProgram(shape.m_state.GetProgramIndex()); auto program3d = m_gpuProgramManager->GetProgram(shape.m_state.GetProgram3dIndex()); auto group = make_unique_dp(layerId, shape.m_state, shape.m_tileKey, - move(shape.m_bucket)); - m_userMarkRenderGroups.push_back(move(group)); + std::move(shape.m_bucket)); + m_userMarkRenderGroups.push_back(std::move(group)); m_userMarkRenderGroups.back()->SetRenderParams(program, program3d, make_ref(&m_generalUniforms)); } break; @@ -301,9 +296,9 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) return g->GetLayerId() == layerId; }; - auto const iter = remove_if(m_userMarkRenderGroups.begin(), - m_userMarkRenderGroups.end(), - functor); + auto const iter = std::remove_if(m_userMarkRenderGroups.begin(), + m_userMarkRenderGroups.end(), + functor); m_userMarkRenderGroups.erase(iter, m_userMarkRenderGroups.end()); break; } @@ -321,12 +316,12 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) case Message::GuiLayerRecached: { ref_ptr msg = message; - drape_ptr renderer = move(msg->AcceptRenderer()); + drape_ptr renderer = std::move(msg->AcceptRenderer()); renderer->Build(make_ref(m_gpuProgramManager)); if (msg->NeedResetOldGui()) m_guiRenderer.release(); if (m_guiRenderer == nullptr) - m_guiRenderer = move(renderer); + m_guiRenderer = std::move(renderer); else m_guiRenderer->Merge(make_ref(renderer)); @@ -410,7 +405,7 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) m_routeRenderer->UpdateDistanceFromBegin(info.GetDistanceFromBegin()); // Here we have to recache route arrows. m_routeRenderer->UpdateRoute(m_userEventStream.GetCurrentScreen(), - bind(&FrontendRenderer::OnCacheRouteArrows, this, _1, _2)); + std::bind(&FrontendRenderer::OnCacheRouteArrows, this, _1, _2)); } break; @@ -467,10 +462,10 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) break; m2::PointD const finishPoint = routeData->m_sourcePolyline.Back(); - m_routeRenderer->SetRouteData(move(routeData), make_ref(m_gpuProgramManager)); + m_routeRenderer->SetRouteData(std::move(routeData), make_ref(m_gpuProgramManager)); // Here we have to recache route arrows. m_routeRenderer->UpdateRoute(m_userEventStream.GetCurrentScreen(), - bind(&FrontendRenderer::OnCacheRouteArrows, this, _1, _2)); + std::bind(&FrontendRenderer::OnCacheRouteArrows, this, _1, _2)); if (!m_routeRenderer->GetFinishPoint()) { @@ -497,7 +492,7 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) if (routeSignData->m_recacheId > 0 && routeSignData->m_recacheId < m_lastRecacheRouteId) break; - m_routeRenderer->SetRouteSign(move(routeSignData), make_ref(m_gpuProgramManager)); + m_routeRenderer->SetRouteSign(std::move(routeSignData), make_ref(m_gpuProgramManager)); break; } @@ -509,7 +504,7 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) if (routeArrowsData->m_recacheId > 0 && routeArrowsData->m_recacheId < m_lastRecacheRouteId) break; - m_routeRenderer->SetRouteArrows(move(routeArrowsData), make_ref(m_gpuProgramManager)); + m_routeRenderer->SetRouteArrows(std::move(routeArrowsData), make_ref(m_gpuProgramManager)); break; } @@ -536,8 +531,9 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) // receive FollowRoute message before FlushRoute message, so we need to postpone its processing. if (m_routeRenderer->GetRouteData() == nullptr) { - m_pendingFollowRoute.reset(new FollowRouteData(msg->GetPreferredZoomLevel(), msg->GetPreferredZoomLevelIn3d(), - msg->EnableAutoZoom())); + m_pendingFollowRoute = my::make_unique(msg->GetPreferredZoomLevel(), + msg->GetPreferredZoomLevelIn3d(), + msg->EnableAutoZoom()); break; } @@ -578,11 +574,11 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) blocker.Wait(); } - // Invalidate textures and wait for completion. + // Notify backend renderer and wait for completion. { BaseBlockingMessage::Blocker blocker; m_commutator->PostMessage(ThreadsCommutator::ResourceUploadThread, - make_unique_dp(blocker), + make_unique_dp(blocker), MessagePriority::High); blocker.Wait(); } @@ -784,6 +780,20 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) break; } + case Message::SetPostprocessStaticTextures: + { + ref_ptr msg = message; + m_postprocessRenderer->SetStaticTextures(msg->AcceptTextures()); + break; + } + + case Message::SetPosteffectEnabled: + { + ref_ptr msg = message; + m_postprocessRenderer->SetEffectEnabled(msg->GetEffect(), msg->IsEnabled()); + break; + } + default: ASSERT(false, ()); } @@ -824,7 +834,7 @@ void FrontendRenderer::UpdateGLResources() routeData->m_color, routeData->m_traffic, routeData->m_pattern, m_lastRecacheRouteId); m_routeRenderer->ClearGLDependentResources(); - m_commutator->PostMessage(ThreadsCommutator::ResourceUploadThread, move(recacheRouteMsg), + m_commutator->PostMessage(ThreadsCommutator::ResourceUploadThread, std::move(recacheRouteMsg), MessagePriority::Normal); } @@ -833,7 +843,8 @@ void FrontendRenderer::UpdateGLResources() // Request new tiles. ScreenBase screen = m_userEventStream.GetCurrentScreen(); m_lastReadedModelView = screen; - m_requestedTiles->Set(screen, m_isIsometry || screen.isPerspective(), m_forceUpdateScene, ResolveTileKeys(screen)); + m_requestedTiles->Set(screen, m_isIsometry || screen.isPerspective(), + m_forceUpdateScene, ResolveTileKeys(screen)); m_commutator->PostMessage(ThreadsCommutator::ResourceUploadThread, make_unique_dp(), MessagePriority::UberHighSingleton); @@ -841,10 +852,12 @@ void FrontendRenderer::UpdateGLResources() m_gpsTrackRenderer->Update(); } -void FrontendRenderer::FollowRoute(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom) +void FrontendRenderer::FollowRoute(int preferredZoomLevel, int preferredZoomLevelIn3d, + bool enableAutoZoom) { - m_myPositionController->ActivateRouting(!m_enablePerspectiveInNavigation ? preferredZoomLevel : preferredZoomLevelIn3d, - enableAutoZoom); + m_myPositionController->ActivateRouting( + !m_enablePerspectiveInNavigation ? preferredZoomLevel : preferredZoomLevelIn3d, + enableAutoZoom); if (m_enablePerspectiveInNavigation) AddUserEvent(make_unique_dp(true /* isAutoPerspective */)); @@ -904,16 +917,20 @@ void FrontendRenderer::OnResize(ScreenBase const & screen) m_myPositionController->OnUpdateScreen(screen); + uint32_t const sx = static_cast(viewportRect.SizeX()); + uint32_t const sy = static_cast(viewportRect.SizeY()); + if (viewportChanged) { m_myPositionController->UpdatePosition(); - m_viewport.SetViewport(0, 0, viewportRect.SizeX(), viewportRect.SizeY()); + m_viewport.SetViewport(0, 0, sx, sy); } if (viewportChanged || m_needRestoreSize) { - m_contextFactory->getDrawContext()->resize(viewportRect.SizeX(), viewportRect.SizeY()); - m_framebuffer->SetSize(viewportRect.SizeX(), viewportRect.SizeY()); + m_contextFactory->getDrawContext()->resize(sx, sy); + m_buildingsFramebuffer->SetSize(sx, sy); + m_postprocessRenderer->Resize(sx, sy); m_needRestoreSize = false; } @@ -932,7 +949,7 @@ void FrontendRenderer::AddToRenderGroup(dp::GLState const & state, { if (!g->IsPendingOnDelete() && g->GetState() == state && g->GetTileKey().EqualStrict(newTile)) { - g->AddBucket(move(renderBucket)); + g->AddBucket(std::move(renderBucket)); layer.m_isDirty = true; return; } @@ -942,7 +959,7 @@ void FrontendRenderer::AddToRenderGroup(dp::GLState const & state, ref_ptr program = m_gpuProgramManager->GetProgram(state.GetProgramIndex()); ref_ptr program3d = m_gpuProgramManager->GetProgram(state.GetProgram3dIndex()); group->SetRenderParams(program, program3d, make_ref(&m_generalUniforms)); - group->AddBucket(move(renderBucket)); + group->AddBucket(std::move(renderBucket)); layer.m_renderGroups.push_back(move(group)); layer.m_isDirty = true; @@ -1102,14 +1119,16 @@ void FrontendRenderer::RenderScene(ScreenBase const & modelView) DrapeMeasurer::Instance().BeforeRenderFrame(); #endif + m_postprocessRenderer->BeginFrame(); + GLFunctions::glEnable(gl_const::GLDepthTest); m_viewport.Apply(); RefreshBgColor(); - GLFunctions::glClear(); + GLFunctions::glClear(gl_const::GLColorBit | gl_const::GLDepthBit | gl_const::GLStencilBit); Render2dLayer(modelView); - if (m_framebuffer->IsSupported()) + if (m_buildingsFramebuffer->IsSupported()) { RenderTrafficAndRouteLayer(modelView); Render3dLayer(modelView, true /* useFramebuffer */); @@ -1122,7 +1141,7 @@ void FrontendRenderer::RenderScene(ScreenBase const & modelView) // After this line we do not use (almost) depth buffer. GLFunctions::glDisable(gl_const::GLDepthTest); - GLFunctions::glClearDepth(); + GLFunctions::glClear(gl_const::GLDepthBit); if (m_selectionShape != nullptr) { @@ -1131,38 +1150,56 @@ void FrontendRenderer::RenderScene(ScreenBase const & modelView) { ASSERT(m_myPositionController->IsModeHasPosition(), ()); m_selectionShape->SetPosition(m_myPositionController->Position()); - m_selectionShape->Render(modelView, m_currentZoomLevel, - make_ref(m_gpuProgramManager), m_generalUniforms); + m_selectionShape->Render(modelView, m_currentZoomLevel, make_ref(m_gpuProgramManager), + m_generalUniforms); } else if (selectedObject == SelectionShape::OBJECT_POI) { - m_selectionShape->Render(modelView, m_currentZoomLevel, make_ref(m_gpuProgramManager), m_generalUniforms); + m_selectionShape->Render(modelView, m_currentZoomLevel, make_ref(m_gpuProgramManager), + m_generalUniforms); } } - RenderOverlayLayer(modelView); + { + StencilWriterGuard guard(make_ref(m_postprocessRenderer)); + RenderOverlayLayer(modelView); + } - m_gpsTrackRenderer->RenderTrack(modelView, m_currentZoomLevel, make_ref(m_gpuProgramManager), m_generalUniforms); + m_gpsTrackRenderer->RenderTrack(modelView, m_currentZoomLevel, make_ref(m_gpuProgramManager), + m_generalUniforms); - if (m_selectionShape != nullptr && m_selectionShape->GetSelectedObject() == SelectionShape::OBJECT_USER_MARK) - m_selectionShape->Render(modelView, m_currentZoomLevel, make_ref(m_gpuProgramManager), m_generalUniforms); + if (m_selectionShape != nullptr && + m_selectionShape->GetSelectedObject() == SelectionShape::OBJECT_USER_MARK) + { + m_selectionShape->Render(modelView, m_currentZoomLevel, make_ref(m_gpuProgramManager), + m_generalUniforms); + } - RenderUserMarksLayer(modelView); + { + StencilWriterGuard guard(make_ref(m_postprocessRenderer)); + RenderUserMarksLayer(modelView); + m_routeRenderer->RenderRouteSigns(modelView, make_ref(m_gpuProgramManager), m_generalUniforms); + } - m_routeRenderer->RenderRouteSigns(modelView, make_ref(m_gpuProgramManager), m_generalUniforms); - - m_myPositionController->Render(modelView, m_currentZoomLevel, make_ref(m_gpuProgramManager), m_generalUniforms); + m_myPositionController->Render(modelView, m_currentZoomLevel, make_ref(m_gpuProgramManager), + m_generalUniforms); m_drapeApiRenderer->Render(modelView, make_ref(m_gpuProgramManager), m_generalUniforms); if (m_guiRenderer != nullptr) - m_guiRenderer->Render(make_ref(m_gpuProgramManager), m_myPositionController->IsInRouting(), modelView); + { + StencilWriterGuard guard(make_ref(m_postprocessRenderer)); + m_guiRenderer->Render(make_ref(m_gpuProgramManager), m_myPositionController->IsInRouting(), + modelView); + } #if defined(RENDER_DEBUG_RECTS) && defined(COLLECT_DISPLACEMENT_INFO) for (auto const & arrow : m_overlayTree->GetDisplacementInfo()) dp::DebugRectRenderer::Instance().DrawArrow(modelView, arrow); #endif + m_postprocessRenderer->EndFrame(make_ref(m_gpuProgramManager)); + #if defined(DRAPE_MEASURER) && (defined(RENDER_STATISTIC) || defined(TRACK_GPU_MEM)) DrapeMeasurer::Instance().AfterRenderFrame(); #endif @@ -1184,13 +1221,13 @@ void FrontendRenderer::Render3dLayer(ScreenBase const & modelView, bool useFrame float const kOpacity = 0.7f; if (useFramebuffer) { - ASSERT(m_framebuffer->IsSupported(), ()); - m_framebuffer->Enable(); + ASSERT(m_buildingsFramebuffer->IsSupported(), ()); + m_buildingsFramebuffer->Enable(); GLFunctions::glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - GLFunctions::glClear(); + GLFunctions::glClear(gl_const::GLColorBit); } - GLFunctions::glClearDepth(); + GLFunctions::glClear(gl_const::GLDepthBit); GLFunctions::glEnable(gl_const::GLDepthTest); RenderLayer & layer = m_layers[RenderLayer::Geometry3dID]; layer.Sort(make_ref(m_overlayTree)); @@ -1199,10 +1236,10 @@ void FrontendRenderer::Render3dLayer(ScreenBase const & modelView, bool useFrame if (useFramebuffer) { - m_framebuffer->Disable(); - GLFunctions::glDisable(gl_const::GLDepthTest); - m_screenQuadRenderer->RenderTexture(m_framebuffer->GetTextureId(), - make_ref(m_gpuProgramManager), kOpacity); + m_buildingsFramebuffer->Disable(); + m_screenQuadRenderer->RenderTexture(make_ref(m_gpuProgramManager), + m_buildingsFramebuffer->GetTextureId(), + kOpacity); } } @@ -1216,14 +1253,14 @@ void FrontendRenderer::RenderOverlayLayer(ScreenBase const & modelView) void FrontendRenderer::RenderTrafficAndRouteLayer(ScreenBase const & modelView) { - GLFunctions::glClearDepth(); + GLFunctions::glClear(gl_const::GLDepthBit); GLFunctions::glEnable(gl_const::GLDepthTest); if (m_trafficRenderer->HasRenderData()) { m_trafficRenderer->RenderTraffic(modelView, m_currentZoomLevel, 1.0f /* opacity */, make_ref(m_gpuProgramManager), m_generalUniforms); } - GLFunctions::glClearDepth(); + GLFunctions::glClear(gl_const::GLDepthBit); m_routeRenderer->RenderRoute(modelView, m_trafficRenderer->HasRenderData(), make_ref(m_gpuProgramManager), m_generalUniforms); } @@ -1282,10 +1319,10 @@ void FrontendRenderer::MergeBuckets() if (layer.m_renderGroups.empty()) return; - using TGroupMap = map>>; + using TGroupMap = map>>; TGroupMap forMerge; - vector> newGroups; + std::vector> newGroups; newGroups.reserve(layer.m_renderGroups.size()); size_t groupsCount = layer.m_renderGroups.size(); @@ -1296,23 +1333,24 @@ void FrontendRenderer::MergeBuckets() { dp::GLState state = group->GetState(); ASSERT_EQUAL(state.GetDepthLayer(), dp::GLState::GeometryLayer, ()); - forMerge[MergedGroupKey(state, group->GetTileKey())].push_back(move(layer.m_renderGroups[i])); + MergedGroupKey const k(state, group->GetTileKey()); + forMerge[k].push_back(std::move(layer.m_renderGroups[i])); } else { - newGroups.push_back(move(layer.m_renderGroups[i])); + newGroups.push_back(std::move(layer.m_renderGroups[i])); } } for (TGroupMap::value_type & node : forMerge) { if (node.second.size() < 2) - newGroups.emplace_back(move(node.second.front())); + newGroups.emplace_back(std::move(node.second.front())); else BatchMergeHelper::MergeBatches(node.second, newGroups, isPerspective); } - layer.m_renderGroups = move(newGroups); + layer.m_renderGroups = std::move(newGroups); layer.m_isDirty = true; }; @@ -1321,11 +1359,6 @@ void FrontendRenderer::MergeBuckets() mergeFn(m_layers[RenderLayer::Geometry3dID], isPerspective); } -bool FrontendRenderer::IsPerspective() const -{ - return m_userEventStream.GetCurrentScreen().isPerspective(); -} - void FrontendRenderer::RenderSingleGroup(ScreenBase const & modelView, ref_ptr group) { group->UpdateAnimation(); @@ -1334,15 +1367,14 @@ void FrontendRenderer::RenderSingleGroup(ScreenBase const & modelView, ref_ptr m; - + std::array m; dp::MakeProjection(m, 0.0f, screen.GetWidth(), screen.GetHeight(), 0.0f); m_generalUniforms.SetMatrix4x4Value("projection", m.data()); } void FrontendRenderer::RefreshZScale(ScreenBase const & screen) { - m_generalUniforms.SetFloatValue("zScale", screen.GetZScale()); + m_generalUniforms.SetFloatValue("zScale", static_cast(screen.GetZScale())); } void FrontendRenderer::RefreshPivotTransform(ScreenBase const & screen) @@ -1355,7 +1387,7 @@ void FrontendRenderer::RefreshPivotTransform(ScreenBase const & screen) else if (m_isIsometry) { math::Matrix transform(math::Identity()); - transform(2, 1) = -1.0f / tan(kIsometryAngle); + transform(2, 1) = -1.0f / static_cast(tan(kIsometryAngle)); transform(2, 2) = 1.0f / screen.GetHeight(); m_generalUniforms.SetMatrix4x4Value("pivotTransform", transform.m_data); } @@ -1449,7 +1481,7 @@ void FrontendRenderer::OnTwoFingersTap() bool FrontendRenderer::OnSingleTouchFiltrate(m2::PointD const & pt, TouchEvent::ETouchType type) { - // This method can be called before gui rendererer initialization. + // This method can be called before gui renderer initialization. if (m_guiRenderer == nullptr) return false; @@ -1526,11 +1558,12 @@ void FrontendRenderer::OnTouchMapAction() { m_myPositionController->ResetRoutingNotFollowTimer(); } - -bool FrontendRenderer::OnNewVisibleViewport(m2::RectD const & oldViewport, m2::RectD const & newViewport, m2::PointD & gOffset) +bool FrontendRenderer::OnNewVisibleViewport(m2::RectD const & oldViewport, + m2::RectD const & newViewport, m2::PointD & gOffset) { gOffset = m2::PointD(0, 0); - if (m_myPositionController->IsModeChangeViewport() || m_selectionShape == nullptr || oldViewport == newViewport) + if (m_myPositionController->IsModeChangeViewport() || m_selectionShape == nullptr || + oldViewport == newViewport) return false; ScreenBase const & screen = m_userEventStream.GetCurrentScreen(); @@ -1611,7 +1644,7 @@ TTilesCollection FrontendRenderer::ResolveTileKeys(ScreenBase const & screen) return group->GetTileKey().m_zoomLevel == m_currentZoomLevel && (key.m_x < result.m_minTileX || key.m_x >= result.m_maxTileX || key.m_y < result.m_minTileY || key.m_y >= result.m_maxTileY || - find(tilesToDelete.begin(), tilesToDelete.end(), key) != tilesToDelete.end()); + std::find(tilesToDelete.begin(), tilesToDelete.end(), key) != tilesToDelete.end()); }; for (RenderLayer & layer : m_layers) layer.m_isDirty |= RemoveGroups(removePredicate, layer.m_renderGroups, make_ref(m_overlayTree)); @@ -1647,7 +1680,7 @@ void FrontendRenderer::OnContextDestroy() m_userMarkRenderGroups.clear(); m_guiRenderer.reset(); m_selectionShape.reset(); - m_framebuffer.reset(); + m_buildingsFramebuffer.reset(); m_screenQuadRenderer.reset(); m_myPositionController->ResetRenderShape(); @@ -1655,6 +1688,7 @@ void FrontendRenderer::OnContextDestroy() m_gpsTrackRenderer->ClearRenderData(); m_trafficRenderer->ClearGLDependentResources(); m_drapeApiRenderer->Clear(); + m_postprocessRenderer->ClearGLDependentResources(); #ifdef RENDER_DEBUG_RECTS dp::DebugRectRenderer::Instance().Destroy(); @@ -1700,11 +1734,19 @@ void FrontendRenderer::OnContextCreate() dp::DebugRectRenderer::Instance().Init(make_ref(m_gpuProgramManager), gpu::DEBUG_RECT_PROGRAM); #endif - // resources recovering - m_framebuffer.reset(new dp::Framebuffer()); - m_framebuffer->SetDefaultContext(context); - + // Resources recovering. m_screenQuadRenderer.reset(new ScreenQuadRenderer()); + + m_postprocessRenderer->Init([context]() { context->setDefaultFramebuffer(); }); + m_postprocessRenderer->SetEnabled(m_apiVersion == dp::ApiVersion::OpenGLES3); + if (dp::SupportManager::Instance().IsAntialiasingEnabledByDefault()) + m_postprocessRenderer->SetEffectEnabled(PostprocessRenderer::Antialiasing, true); + + m_buildingsFramebuffer.reset(new dp::Framebuffer(gl_const::GLRGBA, false /* stencilEnabled */)); + m_buildingsFramebuffer->SetFramebufferFallback([this]() + { + m_postprocessRenderer->OnFramebufferFallback(); + }); } FrontendRenderer::Routine::Routine(FrontendRenderer & renderer) : m_renderer(renderer) {} @@ -1713,7 +1755,7 @@ void FrontendRenderer::Routine::Do() { LOG(LINFO, ("Start routine.")); - gui::DrapeGui::Instance().ConnectOnCompassTappedHandler(bind(&FrontendRenderer::OnCompassTapped, &m_renderer)); + gui::DrapeGui::Instance().ConnectOnCompassTappedHandler(std::bind(&FrontendRenderer::OnCompassTapped, &m_renderer)); m_renderer.m_myPositionController->SetListener(ref_ptr(&m_renderer)); m_renderer.m_userEventStream.SetListener(ref_ptr(&m_renderer)); @@ -1738,8 +1780,6 @@ void FrontendRenderer::Routine::Do() if (viewportChanged) m_renderer.OnResize(modelView); - context->setDefaultFramebuffer(); - if (modelViewChanged || viewportChanged) m_renderer.PrepareScene(modelView); @@ -1783,7 +1823,7 @@ void FrontendRenderer::Routine::Do() activityTimer.Reset(); availableTime = kVSyncInterval - timer.ElapsedSeconds(); } - while (availableTime > 0); + while (availableTime > 0.0); } context->present(); @@ -1796,7 +1836,7 @@ void FrontendRenderer::Routine::Do() m_renderer.m_myPositionController->IsRouteFollowingActive() && frameTime < kFrameTime) { uint32_t const ms = static_cast((kFrameTime - frameTime) * 1000); - this_thread::sleep_for(milliseconds(ms)); + this_thread::sleep_for(std::chrono::milliseconds(ms)); } if (m_renderer.m_overlaysTracker->IsValid() && @@ -1828,9 +1868,10 @@ void FrontendRenderer::ReleaseResources() m_myPositionController.reset(); m_selectionShape.release(); m_routeRenderer.reset(); - m_framebuffer.reset(); + m_buildingsFramebuffer.reset(); m_screenQuadRenderer.reset(); m_trafficRenderer.reset(); + m_postprocessRenderer.reset(); m_gpuProgramManager.reset(); m_contextFactory->getDrawContext()->doneCurrent(); @@ -1842,7 +1883,7 @@ void FrontendRenderer::AddUserEvent(drape_ptr && event) if (m_scenarioManager->IsRunning() && event->GetType() == UserEvent::EventType::Touch) return; #endif - m_userEventStream.AddEvent(move(event)); + m_userEventStream.AddEvent(std::move(event)); if (IsInInfinityWaiting()) CancelMessageWaiting(); } @@ -1852,17 +1893,20 @@ void FrontendRenderer::PositionChanged(m2::PointD const & position) m_userPositionChangedFn(position); } -void FrontendRenderer::ChangeModelView(m2::PointD const & center, int zoomLevel, TAnimationCreator const & parallelAnimCreator) +void FrontendRenderer::ChangeModelView(m2::PointD const & center, int zoomLevel, + TAnimationCreator const & parallelAnimCreator) { AddUserEvent(make_unique_dp(center, zoomLevel, true, parallelAnimCreator)); } -void FrontendRenderer::ChangeModelView(double azimuth, TAnimationCreator const & parallelAnimCreator) +void FrontendRenderer::ChangeModelView(double azimuth, + TAnimationCreator const & parallelAnimCreator) { AddUserEvent(make_unique_dp(azimuth, parallelAnimCreator)); } -void FrontendRenderer::ChangeModelView(m2::RectD const & rect, TAnimationCreator const & parallelAnimCreator) +void FrontendRenderer::ChangeModelView(m2::RectD const & rect, + TAnimationCreator const & parallelAnimCreator) { AddUserEvent(make_unique_dp(rect, true, kDoNotChangeZoom, true, parallelAnimCreator)); } @@ -1871,13 +1915,16 @@ void FrontendRenderer::ChangeModelView(m2::PointD const & userPos, double azimut m2::PointD const & pxZero, int preferredZoomLevel, TAnimationCreator const & parallelAnimCreator) { - AddUserEvent(make_unique_dp(userPos, pxZero, azimuth, preferredZoomLevel, true, parallelAnimCreator)); + AddUserEvent(make_unique_dp(userPos, pxZero, azimuth, preferredZoomLevel, + true, parallelAnimCreator)); } -void FrontendRenderer::ChangeModelView(double autoScale, m2::PointD const & userPos, double azimuth, m2::PointD const & pxZero, +void FrontendRenderer::ChangeModelView(double autoScale, m2::PointD const & userPos, double azimuth, + m2::PointD const & pxZero, TAnimationCreator const & parallelAnimCreator) { - AddUserEvent(make_unique_dp(userPos, pxZero, azimuth, autoScale, parallelAnimCreator)); + AddUserEvent(make_unique_dp(userPos, pxZero, azimuth, autoScale, + parallelAnimCreator)); } ScreenBase const & FrontendRenderer::ProcessEvents(bool & modelViewChanged, bool & viewportChanged) @@ -1894,7 +1941,7 @@ void FrontendRenderer::PrepareScene(ScreenBase const & modelView) RefreshPivotTransform(modelView); m_myPositionController->OnUpdateScreen(modelView); - m_routeRenderer->UpdateRoute(modelView, bind(&FrontendRenderer::OnCacheRouteArrows, this, _1, _2)); + m_routeRenderer->UpdateRoute(modelView, std::bind(&FrontendRenderer::OnCacheRouteArrows, this, _1, _2)); } void FrontendRenderer::UpdateScene(ScreenBase const & modelView) @@ -1931,7 +1978,7 @@ void FrontendRenderer::EmitModelViewChanged(ScreenBase const & modelView) const m_modelViewChangedFn(modelView); } -void FrontendRenderer::OnCacheRouteArrows(int routeIndex, vector const & borders) +void FrontendRenderer::OnCacheRouteArrows(int routeIndex, std::vector const & borders) { m_commutator->PostMessage(ThreadsCommutator::ResourceUploadThread, make_unique_dp(routeIndex, borders), @@ -1954,7 +2001,8 @@ FrontendRenderer::RenderLayer::RenderLayerID FrontendRenderer::RenderLayer::GetL if (state.GetDepthLayer() == dp::GLState::OverlayLayer) return OverlayID; - if (state.GetProgram3dIndex() == gpu::AREA_3D_PROGRAM || state.GetProgram3dIndex() == gpu::AREA_3D_OUTLINE_PROGRAM) + if (state.GetProgram3dIndex() == gpu::AREA_3D_PROGRAM || + state.GetProgram3dIndex() == gpu::AREA_3D_OUTLINE_PROGRAM) return Geometry3dID; return Geometry2dID; diff --git a/drape_frontend/frontend_renderer.hpp b/drape_frontend/frontend_renderer.hpp index 3fa2cebb33..309e7e6acc 100755 --- a/drape_frontend/frontend_renderer.hpp +++ b/drape_frontend/frontend_renderer.hpp @@ -14,27 +14,28 @@ #include "drape_frontend/render_group.hpp" #include "drape_frontend/requested_tiles.hpp" #include "drape_frontend/route_renderer.hpp" +#include "drape_frontend/postprocess_renderer.hpp" #include "drape_frontend/threads_commutator.hpp" #include "drape_frontend/tile_info.hpp" #include "drape_frontend/traffic_renderer.hpp" #include "drape_frontend/user_event_stream.hpp" -#include "drape/pointers.hpp" #include "drape/glstate.hpp" -#include "drape/vertex_array_buffer.hpp" #include "drape/gpu_program_manager.hpp" #include "drape/overlay_tree.hpp" +#include "drape/pointers.hpp" #include "drape/uniform_values_storage.hpp" +#include "drape/vertex_array_buffer.hpp" #include "platform/location.hpp" #include "geometry/screenbase.hpp" #include "geometry/triangle2d.hpp" -#include "std/function.hpp" -#include "std/map.hpp" -#include "std/array.hpp" -#include "std/unordered_set.hpp" +#include +#include +#include +#include namespace dp { @@ -58,42 +59,37 @@ struct TapInfo FeatureID const m_featureTapped; }; -class FrontendRenderer : public BaseRenderer - , public MyPositionController::Listener - , public UserEventStream::Listener +class FrontendRenderer : public BaseRenderer, + public MyPositionController::Listener, + public UserEventStream::Listener { public: - using TModelViewChanged = function; - using TTapEventInfoFn = function; - using TUserPositionChangedFn = function; + using TModelViewChanged = std::function; + using TTapEventInfoFn = std::function; + using TUserPositionChangedFn = std::function; struct Params : BaseRenderer::Params { - Params(dp::ApiVersion apiVersion, - ref_ptr commutator, - ref_ptr factory, - ref_ptr texMng, - MyPositionController::Params && myPositionParams, - dp::Viewport viewport, - TModelViewChanged const & modelViewChangedFn, - TTapEventInfoFn const & tapEventFn, - TUserPositionChangedFn const & positionChangedFn, - ref_ptr requestedTiles, + Params(dp::ApiVersion apiVersion, ref_ptr commutator, + ref_ptr factory, ref_ptr texMng, + MyPositionController::Params && myPositionParams, dp::Viewport viewport, + TModelViewChanged const & modelViewChangedFn, TTapEventInfoFn const & tapEventFn, + TUserPositionChangedFn const & positionChangedFn, ref_ptr requestedTiles, OverlaysShowStatsCallback && overlaysShowStatsCallback, - bool allow3dBuildings, - bool trafficEnabled, - bool blockTapEvents) + bool allow3dBuildings, bool trafficEnabled, bool blockTapEvents, + std::vector && enabledEffects) : BaseRenderer::Params(apiVersion, commutator, factory, texMng) - , m_myPositionParams(move(myPositionParams)) + , m_myPositionParams(std::move(myPositionParams)) , m_viewport(viewport) , m_modelViewChangedFn(modelViewChangedFn) , m_tapEventFn(tapEventFn) , m_positionChangedFn(positionChangedFn) , m_requestedTiles(requestedTiles) - , m_overlaysShowStatsCallback(move(overlaysShowStatsCallback)) + , m_overlaysShowStatsCallback(std::move(overlaysShowStatsCallback)) , m_allow3dBuildings(allow3dBuildings) , m_trafficEnabled(trafficEnabled) , m_blockTapEvents(blockTapEvents) + , m_enabledEffects(std::move(enabledEffects)) {} MyPositionController::Params m_myPositionParams; @@ -101,18 +97,12 @@ public: TModelViewChanged m_modelViewChangedFn; TTapEventInfoFn m_tapEventFn; TUserPositionChangedFn m_positionChangedFn; - location::TMyPositionModeChanged m_myPositionModeCallback; - location::EMyPositionMode m_initMyPositionMode; ref_ptr m_requestedTiles; OverlaysShowStatsCallback m_overlaysShowStatsCallback; - double m_timeInBackground; bool m_allow3dBuildings; bool m_trafficEnabled; bool m_blockTapEvents; - bool m_firstLaunch; - bool m_isLaunchByDeepLink; - bool m_isRoutingActive; - bool m_isAutozoomEnabled; + std::vector m_enabledEffects; }; FrontendRenderer(Params && params); @@ -122,14 +112,18 @@ public: void AddUserEvent(drape_ptr && event); - /// MyPositionController::Listener + // MyPositionController::Listener void PositionChanged(m2::PointD const & position) override; - void ChangeModelView(m2::PointD const & center, int zoomLevel, TAnimationCreator const & parallelAnimCreator) override; + void ChangeModelView(m2::PointD const & center, int zoomLevel, + TAnimationCreator const & parallelAnimCreator) override; void ChangeModelView(double azimuth, TAnimationCreator const & parallelAnimCreator) override; - void ChangeModelView(m2::RectD const & rect, TAnimationCreator const & parallelAnimCreator) override; + void ChangeModelView(m2::RectD const & rect, + TAnimationCreator const & parallelAnimCreator) override; void ChangeModelView(m2::PointD const & userPos, double azimuth, m2::PointD const & pxZero, - int preferredZoomLevel, TAnimationCreator const & parallelAnimCreator) override; - void ChangeModelView(double autoScale, m2::PointD const & userPos, double azimuth, m2::PointD const & pxZero, + int preferredZoomLevel, + TAnimationCreator const & parallelAnimCreator) override; + void ChangeModelView(double autoScale, m2::PointD const & userPos, double azimuth, + m2::PointD const & pxZero, TAnimationCreator const & parallelAnimCreator) override; drape_ptr const & GetScenarioManager() const; @@ -151,14 +145,13 @@ private: void RefreshPivotTransform(ScreenBase const & screen); void RefreshBgColor(); - ////// - /// Render part of scene + // Render part of scene void Render2dLayer(ScreenBase const & modelView); void Render3dLayer(ScreenBase const & modelView, bool useFramebuffer); void RenderOverlayLayer(ScreenBase const & modelView); void RenderUserMarksLayer(ScreenBase const & modelView); void RenderTrafficAndRouteLayer(ScreenBase const & modelView); - ////// + ScreenBase const & ProcessEvents(bool & modelViewChanged, bool & viewportChanged); void PrepareScene(ScreenBase const & modelView); void UpdateScene(ScreenBase const & modelView); @@ -189,14 +182,13 @@ private: void OnScaleEnded() override; void OnAnimatedScaleEnded() override; void OnTouchMapAction() override; - bool OnNewVisibleViewport(m2::RectD const & oldViewport, m2::RectD const & newViewport, m2::PointD & gOffset) override; + bool OnNewVisibleViewport(m2::RectD const & oldViewport, m2::RectD const & newViewport, + m2::PointD & gOffset) override; class Routine : public threads::IRoutine { public: Routine(FrontendRenderer & renderer); - - // threads::IRoutine overrides: void Do() override; private: @@ -210,11 +202,10 @@ private: void UpdateOverlayTree(ScreenBase const & modelView, drape_ptr & renderGroup); void EndUpdateOverlayTree(); - void AddToRenderGroup(dp::GLState const & state, - drape_ptr && renderBucket, + void AddToRenderGroup(dp::GLState const & state, drape_ptr && renderBucket, TileKey const & newTile); - using TRenderGroupRemovePredicate = function const &)>; + using TRenderGroupRemovePredicate = std::function const &)>; void RemoveRenderGroupsLater(TRenderGroupRemovePredicate const & predicate); void FollowRoute(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom); @@ -227,15 +218,13 @@ private: FeatureID GetVisiblePOI(m2::PointD const & pixelPoint); FeatureID GetVisiblePOI(m2::RectD const & pixelRect); - bool IsPerspective() const; - void PrepareGpsTrackPoints(uint32_t pointsCount); void PullToBoundArea(bool randomPlace, bool applyZoom); void ProcessSelection(ref_ptr msg); - void OnCacheRouteArrows(int routeIndex, vector const & borders); + void OnCacheRouteArrows(int routeIndex, std::vector const & borders); void CollectShowOverlaysEvents(); @@ -253,22 +242,22 @@ private: static RenderLayerID GetLayerID(dp::GLState const & renderGroup); - vector> m_renderGroups; + std::vector> m_renderGroups; bool m_isDirty = false; void Sort(ref_ptr overlayTree); }; - array m_layers; - vector> m_userMarkRenderGroups; - unordered_set m_userMarkVisibility; + std::array m_layers; + std::vector> m_userMarkRenderGroups; + std::unordered_set m_userMarkVisibility; drape_ptr m_guiRenderer; drape_ptr m_myPositionController; drape_ptr m_selectionShape; drape_ptr m_routeRenderer; drape_ptr m_trafficRenderer; - drape_ptr m_framebuffer; + drape_ptr m_buildingsFramebuffer; drape_ptr m_screenQuadRenderer; drape_ptr m_gpsTrackRenderer; drape_ptr m_drapeApiRenderer; @@ -295,7 +284,7 @@ private: TTilesCollection m_notFinishedTiles; int m_currentZoomLevel = -1; - + ref_ptr m_requestedTiles; uint64_t m_maxGeneration; int m_mergeBucketsCounter = 0; @@ -304,9 +293,7 @@ private: struct FollowRouteData { - FollowRouteData(int preferredZoomLevel, - int preferredZoomLevelIn3d, - bool enableAutoZoom) + FollowRouteData(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom) : m_preferredZoomLevel(preferredZoomLevel) , m_preferredZoomLevelIn3d(preferredZoomLevelIn3d) , m_enableAutoZoom(enableAutoZoom) @@ -319,7 +306,7 @@ private: unique_ptr m_pendingFollowRoute; - vector m_dragBoundArea; + std::vector m_dragBoundArea; drape_ptr m_selectObjectMessage; @@ -332,11 +319,12 @@ private: bool m_forceUpdateScene; + drape_ptr m_postprocessRenderer; + drape_ptr m_scenarioManager; #ifdef DEBUG bool m_isTeardowned; #endif }; - -} // namespace df +} // namespace df diff --git a/drape_frontend/message.hpp b/drape_frontend/message.hpp index 69ee6c4172..048f167290 100644 --- a/drape_frontend/message.hpp +++ b/drape_frontend/message.hpp @@ -46,7 +46,7 @@ public: FollowRoute, DeactivateRouteFollowing, UpdateMapStyle, - InvalidateTextures, + SwitchMapStyle, Invalidate, Allow3dMode, Allow3dBuildings, @@ -78,6 +78,8 @@ public: AddCustomSymbols, RemoveCustomSymbols, UpdateCustomSymbols, + SetPostprocessStaticTextures, + SetPosteffectEnabled, }; virtual ~Message() {} diff --git a/drape_frontend/message_subclasses.hpp b/drape_frontend/message_subclasses.hpp index 17b9ea5c3d..d322832e69 100644 --- a/drape_frontend/message_subclasses.hpp +++ b/drape_frontend/message_subclasses.hpp @@ -12,6 +12,7 @@ #include "drape_frontend/message.hpp" #include "drape_frontend/my_position.hpp" #include "drape_frontend/overlay_batcher.hpp" +#include "drape_frontend/postprocess_renderer.hpp" #include "drape_frontend/route_builder.hpp" #include "drape_frontend/selection_shape.hpp" #include "drape_frontend/tile_utils.hpp" @@ -785,14 +786,14 @@ private: bool const m_enableAutoZoom; }; -class InvalidateTexturesMessage : public BaseBlockingMessage +class SwitchMapStyleMessage : public BaseBlockingMessage { public: - InvalidateTexturesMessage(Blocker & blocker) + SwitchMapStyleMessage(Blocker & blocker) : BaseBlockingMessage(blocker) {} - Type GetType() const override { return Message::InvalidateTextures; } + Type GetType() const override { return Message::SwitchMapStyle; } }; class InvalidateMessage : public Message @@ -1198,4 +1199,35 @@ private: std::vector m_symbolsFeatures; }; -} // namespace df +class SetPostprocessStaticTexturesMessage : public Message +{ +public: + explicit SetPostprocessStaticTexturesMessage(drape_ptr && textures) + : m_textures(std::move(textures)) + {} + + Type GetType() const override { return Message::SetPostprocessStaticTextures; } + + drape_ptr && AcceptTextures() { return std::move(m_textures); } + +private: + drape_ptr m_textures; +}; + +class SetPosteffectEnabledMessage : public Message +{ +public: + SetPosteffectEnabledMessage(PostprocessRenderer::Effect effect, bool enabled) + : m_effect(effect) + , m_enabled(enabled) + {} + + Type GetType() const override { return Message::SetPosteffectEnabled; } + PostprocessRenderer::Effect GetEffect() const { return m_effect; } + bool IsEnabled() const { return m_enabled; } + +private: + PostprocessRenderer::Effect const m_effect; + bool const m_enabled; +}; +} // namespace df diff --git a/drape_frontend/postprocess_renderer.cpp b/drape_frontend/postprocess_renderer.cpp new file mode 100644 index 0000000000..37d77fc5dc --- /dev/null +++ b/drape_frontend/postprocess_renderer.cpp @@ -0,0 +1,416 @@ +#include "drape_frontend/postprocess_renderer.hpp" +#include "drape_frontend/screen_quad_renderer.hpp" +#include "drape_frontend/shader_def.hpp" + +#include "drape/glfunctions.hpp" +#include "drape/glstate.hpp" +#include "drape/gpu_program_manager.hpp" +#include "drape/texture_manager.hpp" +#include "drape/uniform_values_storage.hpp" + +#include "base/assert.hpp" + +namespace df +{ +namespace +{ +class SMAABaseRendererContext : public RendererContext +{ +protected: + void ApplyFramebufferMetrics(ref_ptr prg) + { + dp::UniformValuesStorage uniforms; + uniforms.SetFloatValue("u_framebufferMetrics", 1.0f / m_width, 1.0f / m_height, + m_width, m_height); + dp::ApplyUniforms(uniforms, prg); + } + + float m_width = 1.0f; + float m_height = 1.0f; +}; + +class EdgesRendererContext : public SMAABaseRendererContext +{ +public: + int GetGpuProgram() const override { return gpu::SMAA_EDGES_PROGRAM; } + + void PreRender(ref_ptr prg) override + { + GLFunctions::glClear(gl_const::GLColorBit); + + BindTexture(m_textureId, prg, "u_colorTex", 0 /* slotIndex */, + gl_const::GLLinear, gl_const::GLClampToEdge); + ApplyFramebufferMetrics(prg); + + GLFunctions::glDisable(gl_const::GLDepthTest); + GLFunctions::glDisable(gl_const::GLBlending); + } + + void SetParams(uint32_t textureId, uint32_t width, uint32_t height) + { + m_textureId = textureId; + m_width = static_cast(width); + m_height = static_cast(height); + } + +protected: + uint32_t m_textureId = 0; +}; + +class BlendingWeightRendererContext : public SMAABaseRendererContext +{ +public: + int GetGpuProgram() const override { return gpu::SMAA_BLENDING_WEIGHT_PROGRAM; } + + void PreRender(ref_ptr prg) override + { + GLFunctions::glClear(gl_const::GLColorBit); + + BindTexture(m_edgesTextureId, prg, "u_colorTex", 0 /* slotIndex */, + gl_const::GLLinear, gl_const::GLClampToEdge); + BindTexture(m_areaTextureId, prg, "u_smaaArea", 1 /* slotIndex */, + gl_const::GLLinear, gl_const::GLClampToEdge); + BindTexture(m_searchTextureId, prg, "u_smaaSearch", 2 /* slotIndex */, + gl_const::GLLinear, gl_const::GLClampToEdge); + ApplyFramebufferMetrics(prg); + + GLFunctions::glDisable(gl_const::GLDepthTest); + GLFunctions::glDisable(gl_const::GLBlending); + } + + void PostRender() override + { + GLFunctions::glActiveTexture(gl_const::GLTexture0 + 2); + GLFunctions::glBindTexture(0); + } + + void SetParams(uint32_t edgesTextureId, uint32_t areaTextureId, uint32_t searchTextureId, + uint32_t width, uint32_t height) + { + m_edgesTextureId = edgesTextureId; + m_areaTextureId = areaTextureId; + m_searchTextureId = searchTextureId; + m_width = static_cast(width); + m_height = static_cast(height); + } + +private: + uint32_t m_edgesTextureId = 0; + uint32_t m_areaTextureId = 0; + uint32_t m_searchTextureId = 0; +}; + +class SMAAFinalRendererContext : public SMAABaseRendererContext +{ +public: + int GetGpuProgram() const override { return gpu::SMAA_FINAL_PROGRAM; } + + void PreRender(ref_ptr prg) override + { + GLFunctions::glClear(gl_const::GLColorBit); + + BindTexture(m_colorTextureId, prg, "u_colorTex", 0 /* slotIndex */, + gl_const::GLLinear, gl_const::GLClampToEdge); + BindTexture(m_blendingWeightTextureId, prg, "u_blendingWeightTex", 1 /* slotIndex */, + gl_const::GLLinear, gl_const::GLClampToEdge); + ApplyFramebufferMetrics(prg); + + GLFunctions::glDisable(gl_const::GLDepthTest); + GLFunctions::glDisable(gl_const::GLBlending); + } + + void PostRender() override + { + GLFunctions::glActiveTexture(gl_const::GLTexture0 + 1); + GLFunctions::glBindTexture(0); + GLFunctions::glActiveTexture(gl_const::GLTexture0); + GLFunctions::glBindTexture(0); + } + + void SetParams(uint32_t colorTextureId, uint32_t blendingWeightTextureId, uint32_t width, + uint32_t height) + { + m_colorTextureId = colorTextureId; + m_blendingWeightTextureId = blendingWeightTextureId; + m_width = static_cast(width); + m_height = static_cast(height); + } + +private: + uint32_t m_colorTextureId = 0; + uint32_t m_blendingWeightTextureId = 0; +}; + +void InitFramebuffer(drape_ptr & framebuffer, uint32_t width, uint32_t height) +{ + if (framebuffer == nullptr) + framebuffer.reset(new dp::Framebuffer(gl_const::GLRGBA, true /* stencilEnabled */)); + framebuffer->SetSize(width, height); +} + +void InitFramebuffer(drape_ptr & framebuffer, uint32_t colorFormat, + ref_ptr depthStencilRef, + uint32_t width, uint32_t height) +{ + if (framebuffer == nullptr) + framebuffer.reset(new dp::Framebuffer(colorFormat)); + framebuffer->SetDepthStencilRef(depthStencilRef); + framebuffer->SetSize(width, height); +} + +bool IsSupported(drape_ptr const & framebuffer) +{ + return framebuffer != nullptr && framebuffer->IsSupported(); +} +} // namespace + +PostprocessRenderer::PostprocessRenderer() + : m_isEnabled(false) + , m_effects(0) + , m_width(0) + , m_height(0) + , m_edgesRendererContext(make_unique_dp()) + , m_bwRendererContext(make_unique_dp()) + , m_smaaFinalRendererContext(make_unique_dp()) + , m_frameStarted(false) +{} + +PostprocessRenderer::~PostprocessRenderer() +{ + ClearGLDependentResources(); +} + +void PostprocessRenderer::Init(dp::FramebufferFallback && fallback) +{ + m_screenQuadRenderer.reset(new ScreenQuadRenderer()); + m_framebufferFallback = std::move(fallback); + ASSERT(m_framebufferFallback != nullptr, ()); +} + +void PostprocessRenderer::ClearGLDependentResources() +{ + m_screenQuadRenderer.reset(); + m_framebufferFallback = nullptr; + m_staticTextures.reset(); + + m_mainFramebuffer.reset(); + m_edgesFramebuffer.reset(); + m_blendingWeightFramebuffer.reset(); +} + +void PostprocessRenderer::Resize(uint32_t width, uint32_t height) +{ + m_width = width; + m_height = height; + + UpdateFramebuffers(m_width, m_height); +} + +void PostprocessRenderer::SetStaticTextures(drape_ptr && textures) +{ + m_staticTextures = std::move(textures); +} + +void PostprocessRenderer::SetEnabled(bool enabled) +{ + m_isEnabled = enabled; + if (m_isEnabled && m_width != 0 && m_height != 0) + UpdateFramebuffers(m_width, m_height); +} + +bool PostprocessRenderer::IsEnabled() const +{ + if (!m_isEnabled || m_effects == 0 || m_staticTextures == nullptr) + return false; + + if (!IsSupported(m_mainFramebuffer)) + return false; + + if (IsEffectEnabled(Effect::Antialiasing) && + (!IsSupported(m_edgesFramebuffer) || !IsSupported(m_blendingWeightFramebuffer))) + { + return false; + } + + // Insert checking new effects here. + + return true; +} + +void PostprocessRenderer::SetEffectEnabled(Effect effect, bool enabled) +{ + uint32_t const oldValue = m_effects; + uint32_t const effectMask = static_cast(effect); + m_effects = (m_effects & ~effectMask) | (enabled ? effectMask : 0); + + if (m_width != 0 && m_height != 0 && oldValue != m_effects) + UpdateFramebuffers(m_width, m_height); +} + +bool PostprocessRenderer::IsEffectEnabled(Effect effect) const +{ + return (m_effects & static_cast(effect)) > 0; +} + +void PostprocessRenderer::BeginFrame() +{ + if (!IsEnabled()) + { + m_framebufferFallback(); + return; + } + + // Check if Subpixel Morphological Antialiasing (SMAA) is unavailable. + ASSERT(m_staticTextures != nullptr, ()); + if (m_staticTextures->m_smaaSearchTexture == nullptr || + m_staticTextures->m_smaaAreaTexture == nullptr || + m_staticTextures->m_smaaAreaTexture->GetID() < 0 || + m_staticTextures->m_smaaSearchTexture->GetID() < 0) + { + SetEffectEnabled(Effect::Antialiasing, false); + } + + m_mainFramebuffer->Enable(); + m_frameStarted = true; + + GLFunctions::glDisable(gl_const::GLStencilTest); +} + +void PostprocessRenderer::EndFrame(ref_ptr gpuProgramManager) +{ + if (!IsEnabled() && !m_frameStarted) + return; + + bool wasPostEffect = false; + + // Subpixel Morphological Antialiasing (SMAA). + if (IsEffectEnabled(Effect::Antialiasing)) + { + wasPostEffect = true; + + ASSERT(m_staticTextures->m_smaaAreaTexture != nullptr, ()); + ASSERT_GREATER_OR_EQUAL(m_staticTextures->m_smaaAreaTexture->GetID(), 0, ()); + + ASSERT(m_staticTextures->m_smaaSearchTexture != nullptr, ()); + ASSERT_GREATER_OR_EQUAL(m_staticTextures->m_smaaSearchTexture->GetID(), 0, ()); + + GLFunctions::glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + GLFunctions::glEnable(gl_const::GLStencilTest); + + // Render edges to texture. + { + m_edgesFramebuffer->Enable(); + + GLFunctions::glStencilFuncSeparate(gl_const::GLFrontAndBack, gl_const::GLNotEqual, 1, 1); + GLFunctions::glStencilOpSeparate(gl_const::GLFrontAndBack, gl_const::GLZero, + gl_const::GLZero, gl_const::GLReplace); + + ASSERT(dynamic_cast(m_edgesRendererContext.get()) != nullptr, ()); + auto context = static_cast(m_edgesRendererContext.get()); + context->SetParams(m_mainFramebuffer->GetTextureId(), m_width, m_height); + m_screenQuadRenderer->Render(gpuProgramManager, make_ref(m_edgesRendererContext)); + } + + // Render blending weight to texture. + { + m_blendingWeightFramebuffer->Enable(); + + GLFunctions::glStencilFuncSeparate(gl_const::GLFrontAndBack, gl_const::GLEqual, 1, 1); + GLFunctions::glStencilOpSeparate(gl_const::GLFrontAndBack, gl_const::GLKeep, + gl_const::GLKeep, gl_const::GLKeep); + + ASSERT(dynamic_cast(m_bwRendererContext.get()) != nullptr, ()); + auto context = static_cast(m_bwRendererContext.get()); + context->SetParams(m_edgesFramebuffer->GetTextureId(), + static_cast(m_staticTextures->m_smaaAreaTexture->GetID()), + static_cast(m_staticTextures->m_smaaSearchTexture->GetID()), + m_width, m_height); + m_screenQuadRenderer->Render(gpuProgramManager, make_ref(m_bwRendererContext)); + } + + // SMAA final pass. + GLFunctions::glDisable(gl_const::GLStencilTest); + { + m_framebufferFallback(); + ASSERT(dynamic_cast(m_smaaFinalRendererContext.get()) != nullptr, ()); + auto context = static_cast(m_smaaFinalRendererContext.get()); + context->SetParams(m_mainFramebuffer->GetTextureId(), + m_blendingWeightFramebuffer->GetTextureId(), + m_width, m_height); + m_screenQuadRenderer->Render(gpuProgramManager, make_ref(m_smaaFinalRendererContext)); + } + } + + if (!wasPostEffect) + { + m_framebufferFallback(); + GLFunctions::glClear(gl_const::GLColorBit); + m_screenQuadRenderer->RenderTexture(gpuProgramManager, m_mainFramebuffer->GetTextureId(), + 1.0f /* opacity */); + } + m_frameStarted = false; +} + +void PostprocessRenderer::EnableWritingToStencil() const +{ + if (!m_frameStarted) + return; + GLFunctions::glEnable(gl_const::GLStencilTest); + GLFunctions::glStencilFuncSeparate(gl_const::GLFrontAndBack, gl_const::GLAlways, 1, 1); + GLFunctions::glStencilOpSeparate(gl_const::GLFrontAndBack, gl_const::GLKeep, + gl_const::GLKeep, gl_const::GLReplace); +} + +void PostprocessRenderer::DisableWritingToStencil() const +{ + if (!m_frameStarted) + return; + GLFunctions::glDisable(gl_const::GLStencilTest); +} + +void PostprocessRenderer::UpdateFramebuffers(uint32_t width, uint32_t height) +{ + ASSERT_NOT_EQUAL(width, 0, ()); + ASSERT_NOT_EQUAL(height, 0, ()); + + if (m_effects != 0) + InitFramebuffer(m_mainFramebuffer, width, height); + else + m_mainFramebuffer.reset(); + + if (IsEffectEnabled(Effect::Antialiasing)) + { + InitFramebuffer(m_edgesFramebuffer, gl_const::GLRedGreen, + m_mainFramebuffer->GetDepthStencilRef(), + width, height); + InitFramebuffer(m_blendingWeightFramebuffer, gl_const::GLRGBA, + m_mainFramebuffer->GetDepthStencilRef(), + width, height); + } + else + { + m_edgesFramebuffer.reset(); + m_blendingWeightFramebuffer.reset(); + } +} + +void PostprocessRenderer::OnFramebufferFallback() +{ + if (m_frameStarted) + m_mainFramebuffer->Enable(); + else + m_framebufferFallback(); +} + +StencilWriterGuard::StencilWriterGuard(ref_ptr renderer) + : m_renderer(renderer) +{ + ASSERT(m_renderer != nullptr, ()); + m_renderer->EnableWritingToStencil(); +} + +StencilWriterGuard::~StencilWriterGuard() +{ + m_renderer->DisableWritingToStencil(); +} +} // namespace df diff --git a/drape_frontend/postprocess_renderer.hpp b/drape_frontend/postprocess_renderer.hpp new file mode 100644 index 0000000000..ce926218cf --- /dev/null +++ b/drape_frontend/postprocess_renderer.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "drape/framebuffer.hpp" +#include "drape/pointers.hpp" + +namespace dp +{ +class GpuProgramManager; +class Texture; +} // namespace dp + +namespace df +{ +class ScreenQuadRenderer; +class RendererContext; + +struct PostprocessStaticTextures +{ + ref_ptr m_smaaAreaTexture; + ref_ptr m_smaaSearchTexture; +}; + +class PostprocessRenderer +{ +public: + enum Effect + { + Antialiasing = 1 + }; + + PostprocessRenderer(); + ~PostprocessRenderer(); + + void Init(dp::FramebufferFallback && fallback); + void ClearGLDependentResources(); + void Resize(uint32_t width, uint32_t height); + void SetStaticTextures(drape_ptr && textures); + + void SetEnabled(bool enabled); + bool IsEnabled() const; + void SetEffectEnabled(Effect effect, bool enabled); + bool IsEffectEnabled(Effect effect) const; + + void OnFramebufferFallback(); + + void BeginFrame(); + void EndFrame(ref_ptr gpuProgramManager); + + void EnableWritingToStencil() const; + void DisableWritingToStencil() const; + +private: + void UpdateFramebuffers(uint32_t width, uint32_t height); + + bool m_isEnabled; + uint32_t m_effects; + + drape_ptr m_screenQuadRenderer; + dp::FramebufferFallback m_framebufferFallback; + drape_ptr m_staticTextures; + uint32_t m_width; + uint32_t m_height; + + drape_ptr m_mainFramebuffer; + drape_ptr m_edgesFramebuffer; + drape_ptr m_blendingWeightFramebuffer; + + drape_ptr m_edgesRendererContext; + drape_ptr m_bwRendererContext; + drape_ptr m_smaaFinalRendererContext; + + bool m_frameStarted; +}; + +class StencilWriterGuard +{ +public: + StencilWriterGuard(ref_ptr renderer); + ~StencilWriterGuard(); +private: + ref_ptr const m_renderer; +}; +} // namespace df diff --git a/drape_frontend/screen_quad_renderer.cpp b/drape_frontend/screen_quad_renderer.cpp index 22818bd365..7da2089685 100644 --- a/drape_frontend/screen_quad_renderer.cpp +++ b/drape_frontend/screen_quad_renderer.cpp @@ -13,10 +13,73 @@ namespace df { +namespace +{ +class TextureRendererContext : public RendererContext +{ +public: + int GetGpuProgram() const override { return gpu::SCREEN_QUAD_PROGRAM; } + + void PreRender(ref_ptr prg) override + { + BindTexture(m_textureId, prg, "u_colorTex", 0 /* slotIndex */, + gl_const::GLLinear, gl_const::GLClampToEdge); + + dp::UniformValuesStorage uniforms; + uniforms.SetFloatValue("u_opacity", m_opacity); + dp::ApplyUniforms(uniforms, prg); + + GLFunctions::glDisable(gl_const::GLDepthTest); + GLFunctions::glEnable(gl_const::GLBlending); + } + + void PostRender() override + { + GLFunctions::glDisable(gl_const::GLBlending); + GLFunctions::glBindTexture(0); + } + + void SetParams(uint32_t textureId, float opacity) + { + m_textureId = textureId; + m_opacity = opacity; + } + +private: + uint32_t m_textureId = 0; + float m_opacity = 1.0f; +}; +} // namespace + +void RendererContext::BindTexture(uint32_t textureId, ref_ptr prg, + std::string const & uniformName, uint8_t slotIndex, + uint32_t filteringMode, uint32_t wrappingMode) +{ + int8_t const textureLocation = prg->GetUniformLocation(uniformName); + ASSERT_NOT_EQUAL(textureLocation, -1, ()); + if (textureLocation < 0) + return; + + GLFunctions::glActiveTexture(gl_const::GLTexture0 + slotIndex); + GLFunctions::glBindTexture(textureId); + GLFunctions::glUniformValuei(textureLocation, slotIndex); + GLFunctions::glTexParameter(gl_const::GLMinFilter, filteringMode); + GLFunctions::glTexParameter(gl_const::GLMagFilter, filteringMode); + GLFunctions::glTexParameter(gl_const::GLWrapS, wrappingMode); + GLFunctions::glTexParameter(gl_const::GLWrapT, wrappingMode); +} + +ScreenQuadRenderer::ScreenQuadRenderer() + : m_textureRendererContext(make_unique_dp()) +{} + ScreenQuadRenderer::~ScreenQuadRenderer() { if (m_bufferId != 0) GLFunctions::glDeleteBuffer(m_bufferId); + + if (m_VAO != 0) + GLFunctions::glDeleteVertexArray(m_VAO); } void ScreenQuadRenderer::Build(ref_ptr prg) @@ -31,10 +94,7 @@ void ScreenQuadRenderer::Build(ref_ptr prg) m_attributeTexCoord = prg->GetAttributeLocation("a_tcoord"); ASSERT_NOT_EQUAL(m_attributeTexCoord, -1, ()); - - m_textureLocation = prg->GetUniformLocation("u_colorTex"); - ASSERT_NOT_EQUAL(m_textureLocation, -1, ()); - + std::vector vertices = {-1.0f, 1.0f, m_textureRect.minX(), m_textureRect.maxY(), 1.0f, 1.0f, m_textureRect.maxX(), m_textureRect.maxY(), -1.0f, -1.0f, m_textureRect.minX(), m_textureRect.minY(), @@ -50,29 +110,17 @@ void ScreenQuadRenderer::Build(ref_ptr prg) GLFunctions::glBindBuffer(0, gl_const::GLArrayBuffer); } -void ScreenQuadRenderer::RenderTexture(uint32_t textureId, ref_ptr mng, - float opacity) +void ScreenQuadRenderer::Render(ref_ptr mng, ref_ptr context) { - ref_ptr prg = mng->GetProgram(gpu::SCREEN_QUAD_PROGRAM); + ref_ptr prg = mng->GetProgram(context->GetGpuProgram()); prg->Bind(); if (m_bufferId == 0) Build(prg); - if (dp::GLExtensionsList::Instance().IsSupported(dp::GLExtensionsList::VertexArrayObject)) + if (m_VAO != 0) GLFunctions::glBindVertexArray(m_VAO); - if (m_textureLocation >= 0) - { - GLFunctions::glActiveTexture(gl_const::GLTexture0); - GLFunctions::glBindTexture(textureId); - GLFunctions::glUniformValuei(m_textureLocation, 0); - GLFunctions::glTexParameter(gl_const::GLMinFilter, gl_const::GLLinear); - GLFunctions::glTexParameter(gl_const::GLMagFilter, gl_const::GLLinear); - GLFunctions::glTexParameter(gl_const::GLWrapS, gl_const::GLClampToEdge); - GLFunctions::glTexParameter(gl_const::GLWrapT, gl_const::GLClampToEdge); - } - GLFunctions::glBindBuffer(m_bufferId, gl_const::GLArrayBuffer); GLFunctions::glEnableVertexAttribute(m_attributePosition); @@ -82,29 +130,39 @@ void ScreenQuadRenderer::RenderTexture(uint32_t textureId, ref_ptrPreRender(prg); GLFunctions::glDrawArrays(gl_const::GLTriangleStrip, 0, 4); - GLFunctions::glDisable(gl_const::GLBlending); + context->PostRender(); prg->Unbind(); - if (dp::GLExtensionsList::Instance().IsSupported(dp::GLExtensionsList::VertexArrayObject)) - GLFunctions::glBindVertexArray(0); - GLFunctions::glBindTexture(0); GLFunctions::glBindBuffer(0, gl_const::GLArrayBuffer); + + if (m_VAO != 0) + GLFunctions::glBindVertexArray(0); } -void ScreenQuadRenderer::SetTextureRect(m2::RectF const & rect, ref_ptr mng) +void ScreenQuadRenderer::RenderTexture(ref_ptr mng, uint32_t textureId, + float opacity) +{ + ASSERT(dynamic_cast(m_textureRendererContext.get()) != nullptr, ()); + + auto context = static_cast(m_textureRendererContext.get()); + context->SetParams(textureId, opacity); + + Render(mng, make_ref(m_textureRendererContext)); +} + +void ScreenQuadRenderer::SetTextureRect(m2::RectF const & rect, ref_ptr prg) { m_textureRect = rect; + Rebuild(prg); +} +void ScreenQuadRenderer::Rebuild(ref_ptr prg) +{ if (m_bufferId != 0) GLFunctions::glDeleteBuffer(m_bufferId); - ref_ptr prg = mng->GetProgram(gpu::SCREEN_QUAD_PROGRAM); prg->Bind(); Build(prg); prg->Unbind(); diff --git a/drape_frontend/screen_quad_renderer.hpp b/drape_frontend/screen_quad_renderer.hpp index 2231aab638..512ed29d89 100644 --- a/drape_frontend/screen_quad_renderer.hpp +++ b/drape_frontend/screen_quad_renderer.hpp @@ -4,6 +4,8 @@ #include "geometry/rect2d.hpp" +#include + namespace dp { class GpuProgram; @@ -12,16 +14,33 @@ class GpuProgramManager; namespace df { +class RendererContext +{ +public: + virtual ~RendererContext() {} + virtual int GetGpuProgram() const = 0; + virtual void PreRender(ref_ptr prg) {} + virtual void PostRender() {} +protected: + void BindTexture(uint32_t textureId, ref_ptr prg, + std::string const & uniformName, uint8_t slotIndex, + uint32_t filteringMode, uint32_t wrappingMode); +}; + class ScreenQuadRenderer { public: - ScreenQuadRenderer() = default; + ScreenQuadRenderer(); ~ScreenQuadRenderer(); - void SetTextureRect(m2::RectF const & rect, ref_ptr mng); + void SetTextureRect(m2::RectF const & rect, ref_ptr prg); + void Rebuild(ref_ptr prg); + bool IsInitialized() const { return m_bufferId != 0; } m2::RectF const & GetTextureRect() const { return m_textureRect; } - void RenderTexture(uint32_t textureId, ref_ptr mng, float opacity); + + void Render(ref_ptr mng, ref_ptr context); + void RenderTexture(ref_ptr mng, uint32_t textureId, float opacity); private: void Build(ref_ptr prg); @@ -30,7 +49,8 @@ private: uint32_t m_VAO = 0; int8_t m_attributePosition = -1; int8_t m_attributeTexCoord = -1; - int8_t m_textureLocation = -1; m2::RectF m_textureRect = m2::RectF(0.0f, 0.0f, 1.0f, 1.0f); + + drape_ptr m_textureRendererContext; }; } // namespace df diff --git a/drape_frontend/shaders/shader_index.txt b/drape_frontend/shaders/shader_index.txt index e0dea148f2..d3eabfc6c8 100644 --- a/drape_frontend/shaders/shader_index.txt +++ b/drape_frontend/shaders/shader_index.txt @@ -37,3 +37,6 @@ TEXT_FIXED_BILLBOARD_PROGRAM text_billboard.vsh.glsl text_fixed.fsh.glsl BOOKMARK_BILLBOARD_PROGRAM user_mark_billboard.vsh.glsl texturing.fsh.glsl TRAFFIC_PROGRAM traffic.vsh.glsl traffic.fsh.glsl TRAFFIC_LINE_PROGRAM traffic_line.vsh.glsl traffic_line.fsh.glsl +SMAA_EDGES_PROGRAM smaa_edges.vsh.glsl smaa_edges.fsh.glsl +SMAA_BLENDING_WEIGHT_PROGRAM smaa_blending_weight.vsh.glsl smaa_blending_weight.fsh.glsl +SMAA_FINAL_PROGRAM smaa_final.vsh.glsl smaa_final.fsh.glsl diff --git a/drape_frontend/shaders/smaa_blending_weight.fsh.glsl b/drape_frontend/shaders/smaa_blending_weight.fsh.glsl new file mode 100644 index 0000000000..284fe0a48d --- /dev/null +++ b/drape_frontend/shaders/smaa_blending_weight.fsh.glsl @@ -0,0 +1,189 @@ +// Implementation of Subpixel Morphological Antialiasing (SMAA) is based on https://github.com/iryoku/smaa + +uniform sampler2D u_colorTex; +uniform sampler2D u_smaaArea; +uniform sampler2D u_smaaSearch; + +uniform vec4 u_framebufferMetrics; + +varying vec4 v_coords; +varying vec4 v_offset0; +varying vec4 v_offset1; +varying vec4 v_offset2; + +#define SMAA_SEARCHTEX_SIZE vec2(66.0, 33.0) +#define SMAA_SEARCHTEX_PACKED_SIZE vec2(64.0, 16.0) +#define SMAA_AREATEX_MAX_DISTANCE 16.0 +#define SMAA_AREATEX_PIXEL_SIZE (vec2(1.0 / 256.0, 1.0 / 1024.0)) + +#ifdef GLES3 + #define SMAALoopBegin(condition) while (condition) { + #define SMAALoopEnd } + #define SMAASampleLevelZero(tex, coord) textureLod(tex, coord, 0.0) + #define SMAASampleLevelZeroOffset(tex, coord, offset) textureLodOffset(tex, coord, 0.0, offset) + #define SMAARound(v) round((v)) + #define SMAAOffset(x,y) ivec2(x,y) +#else + #define SMAA_MAX_SEARCH_STEPS 8 + #define SMAALoopBegin(condition) for (int i = 0; i < SMAA_MAX_SEARCH_STEPS; i++) { if (!(condition)) break; + #define SMAALoopEnd } + #define SMAASampleLevelZero(tex, coord) texture2D(tex, coord) + #define SMAASampleLevelZeroOffset(tex, coord, offset) texture2D(tex, coord + vec2(offset) * u_framebufferMetrics.xy) + #define SMAARound(v) floor((v) + 0.5) + #define SMAAOffset(x,y) vec2(x,y) +#endif + +const vec2 kAreaTexMaxDistance = vec2(SMAA_AREATEX_MAX_DISTANCE, SMAA_AREATEX_MAX_DISTANCE); +const float kActivationThreshold = 0.8281; + +float SMAASearchLength(vec2 e, float offset) +{ + // The texture is flipped vertically, with left and right cases taking half + // of the space horizontally. + vec2 scale = SMAA_SEARCHTEX_SIZE * vec2(0.5, -1.0); + vec2 bias = SMAA_SEARCHTEX_SIZE * vec2(offset, 1.0); + + // Scale and bias to access texel centers. + scale += vec2(-1.0, 1.0); + bias += vec2( 0.5, -0.5); + + // Convert from pixel coordinates to texcoords. + // (We use SMAA_SEARCHTEX_PACKED_SIZE because the texture is cropped). + scale *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + bias *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + + // Lookup the search texture. +#ifdef GLES3 + return SMAASampleLevelZero(u_smaaSearch, scale * e + bias).r; +#else + return SMAASampleLevelZero(u_smaaSearch, scale * e + bias).a; +#endif +} + +float SMAASearchXLeft(vec2 texcoord, float end) +{ + vec2 e = vec2(0.0, 1.0); + SMAALoopBegin(texcoord.x > end && e.g > kActivationThreshold && e.r == 0.0) + e = SMAASampleLevelZero(u_colorTex, texcoord).rg; + texcoord = vec2(-2.0, 0.0) * u_framebufferMetrics.xy + texcoord; + SMAALoopEnd + float offset = 3.25 - (255.0 / 127.0) * SMAASearchLength(e, 0.0); + return u_framebufferMetrics.x * offset + texcoord.x; +} + +float SMAASearchXRight(vec2 texcoord, float end) +{ + vec2 e = vec2(0.0, 1.0); + SMAALoopBegin(texcoord.x < end && e.g > kActivationThreshold && e.r == 0.0) + e = SMAASampleLevelZero(u_colorTex, texcoord).rg; + texcoord = vec2(2.0, 0.0) * u_framebufferMetrics.xy + texcoord; + SMAALoopEnd + float offset = 3.25 - (255.0 / 127.0) * SMAASearchLength(e, 0.5); + return -u_framebufferMetrics.x * offset + texcoord.x; +} + +float SMAASearchYUp(vec2 texcoord, float end) +{ + vec2 e = vec2(1.0, 0.0); + SMAALoopBegin(texcoord.y > end && e.r > kActivationThreshold && e.g == 0.0) + e = SMAASampleLevelZero(u_colorTex, texcoord).rg; + texcoord = vec2(0.0, -2.0) * u_framebufferMetrics.xy + texcoord; + SMAALoopEnd + float offset = 3.25 - (255.0 / 127.0) * SMAASearchLength(e.gr, 0.0); + return u_framebufferMetrics.y * offset + texcoord.y; +} + +float SMAASearchYDown(vec2 texcoord, float end) +{ + vec2 e = vec2(1.0, 0.0); + SMAALoopBegin(texcoord.y < end && e.r > kActivationThreshold && e.g == 0.0) + e = SMAASampleLevelZero(u_colorTex, texcoord).rg; + texcoord = vec2(0.0, 2.0) * u_framebufferMetrics.xy + texcoord; + SMAALoopEnd + float offset = 3.25 - (255.0 / 127.0) * SMAASearchLength(e.gr, 0.5); + return -u_framebufferMetrics.y * offset + texcoord.y; +} + +// Here, we have the distance and both crossing edges. So, what are the areas +// at each side of current edge? +vec2 SMAAArea(vec2 dist, float e1, float e2) +{ + // Rounding prevents precision errors of bilinear filtering. + vec2 texcoord = kAreaTexMaxDistance * SMAARound(4.0 * vec2(e1, e2)) + dist; + // We do a scale and bias for mapping to texel space. + texcoord = SMAA_AREATEX_PIXEL_SIZE * (texcoord + 0.5); + return SMAASampleLevelZero(u_smaaArea, texcoord).rg; +} + +void main() +{ + vec4 weights = vec4(0.0, 0.0, 0.0, 0.0); + vec2 e = texture2D(u_colorTex, v_coords.xy).rg; + + if (e.g > 0.0) // Edge at north + { + vec2 d; + + // Find the distance to the left. + vec3 coords; + coords.x = SMAASearchXLeft(v_offset0.xy, v_offset2.x); + coords.y = v_offset1.y; + d.x = coords.x; + + // Now fetch the left crossing edges, two at a time using bilinear + // filtering. Sampling at -0.25 enables to discern what value each edge has. + float e1 = SMAASampleLevelZero(u_colorTex, coords.xy).r; + + // Find the distance to the right. + coords.z = SMAASearchXRight(v_offset0.zw, v_offset2.y); + d.y = coords.z; + + // We want the distances to be in pixel units (doing this here allow to + // better interleave arithmetic and memory accesses). + d = abs(SMAARound(u_framebufferMetrics.zz * d - v_coords.zz)); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically. + vec2 sqrt_d = sqrt(d); + + // Fetch the right crossing edges. + float e2 = SMAASampleLevelZeroOffset(u_colorTex, coords.zy, SMAAOffset(1, 0)).r; + + // Here we know how this pattern looks like, now it is time for getting + // the actual area. + weights.rg = SMAAArea(sqrt_d, e1, e2); + } + + if (e.r > 0.0) // Edge at west + { + vec2 d; + + // Find the distance to the top. + vec3 coords; + coords.y = SMAASearchYUp(v_offset1.xy, v_offset2.z); + coords.x = v_offset0.x; + d.x = coords.y; + + // Fetch the top crossing edges. + float e1 = SMAASampleLevelZero(u_colorTex, coords.xy).g; + + // Find the distance to the bottom. + coords.z = SMAASearchYDown(v_offset1.zw, v_offset2.w); + d.y = coords.z; + + // We want the distances to be in pixel units. + d = abs(SMAARound(u_framebufferMetrics.ww * d - v_coords.ww)); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically. + vec2 sqrt_d = sqrt(d); + + // Fetch the bottom crossing edges. + float e2 = SMAASampleLevelZeroOffset(u_colorTex, coords.xz, SMAAOffset(0, 1)).g; + + // Get the area for this direction. + weights.ba = SMAAArea(sqrt_d, e1, e2); + } + + gl_FragColor = weights; +} diff --git a/drape_frontend/shaders/smaa_blending_weight.vsh.glsl b/drape_frontend/shaders/smaa_blending_weight.vsh.glsl new file mode 100644 index 0000000000..621cd2a332 --- /dev/null +++ b/drape_frontend/shaders/smaa_blending_weight.vsh.glsl @@ -0,0 +1,28 @@ +// Implementation of Subpixel Morphological Antialiasing (SMAA) is based on https://github.com/iryoku/smaa + +attribute vec2 a_pos; +attribute vec2 a_tcoord; + +uniform vec4 u_framebufferMetrics; + +varying vec4 v_coords; +varying vec4 v_offset0; +varying vec4 v_offset1; +varying vec4 v_offset2; + +// SMAA_MAX_SEARCH_STEPS specifies the maximum steps performed in the +// horizontal/vertical pattern searches, at each side of the pixel. +#define SMAA_MAX_SEARCH_STEPS 8.0 +const vec4 kMaxSearchSteps = vec4(-2.0 * SMAA_MAX_SEARCH_STEPS, 2.0 * SMAA_MAX_SEARCH_STEPS, + -2.0 * SMAA_MAX_SEARCH_STEPS, 2.0 * SMAA_MAX_SEARCH_STEPS); + +void main() +{ + v_coords = vec4(a_tcoord, a_tcoord * u_framebufferMetrics.zw); + // We will use these offsets for the searches. + v_offset0 = u_framebufferMetrics.xyxy * vec4(-0.25, -0.125, 1.25, -0.125) + a_tcoord.xyxy; + v_offset1 = u_framebufferMetrics.xyxy * vec4(-0.125, -0.25, -0.125, 1.25) + a_tcoord.xyxy; + // And these for the searches, they indicate the ends of the loops. + v_offset2 = u_framebufferMetrics.xxyy * kMaxSearchSteps + vec4(v_offset0.xz, v_offset1.yw); + gl_Position = vec4(a_pos, 0.0, 1.0); +} diff --git a/drape_frontend/shaders/smaa_edges.fsh.glsl b/drape_frontend/shaders/smaa_edges.fsh.glsl new file mode 100644 index 0000000000..69322a954d --- /dev/null +++ b/drape_frontend/shaders/smaa_edges.fsh.glsl @@ -0,0 +1,65 @@ +// Implementation of Subpixel Morphological Antialiasing (SMAA) is based on https://github.com/iryoku/smaa + +uniform sampler2D u_colorTex; + +varying vec2 v_colorTexCoords; +varying vec4 v_offset0; +varying vec4 v_offset1; +varying vec4 v_offset2; + +// SMAA_THRESHOLD specifies the threshold or sensitivity to edges. +// Lowering this value you will be able to detect more edges at the expense of +// performance. +// Range: [0, 0.5] +// 0.1 is a reasonable value, and allows to catch most visible edges. +// 0.05 is a rather overkill value, that allows to catch 'em all. +#define SMAA_THRESHOLD 0.05 +const vec2 kThreshold = vec2(SMAA_THRESHOLD, SMAA_THRESHOLD); + +// If there is an neighbor edge that has SMAA_LOCAL_CONTRAST_FACTOR times +// bigger contrast than current edge, current edge will be discarded. +// This allows to eliminate spurious crossing edges, and is based on the fact +// that, if there is too much contrast in a direction, that will hide +// perceptually contrast in the other neighbors. +#define SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR 2.0 + +// Standard relative luminance weights. +// https://en.wikipedia.org/wiki/Relative_luminance +const vec3 kWeights = vec3(0.2126, 0.7152, 0.0722); + +void main() +{ + // Calculate lumas. + float L = dot(texture2D(u_colorTex, v_colorTexCoords).rgb, kWeights); + float Lleft = dot(texture2D(u_colorTex, v_offset0.xy).rgb, kWeights); + float Ltop = dot(texture2D(u_colorTex, v_offset0.zw).rgb, kWeights); + + // We do the usual threshold. + vec4 delta; + delta.xy = abs(L - vec2(Lleft, Ltop)); + vec2 edges = step(kThreshold, delta.xy); + if (dot(edges, vec2(1.0, 1.0)) == 0.0) + discard; + + // Calculate right and bottom deltas. + float Lright = dot(texture2D(u_colorTex, v_offset1.xy).rgb, kWeights); + float Lbottom = dot(texture2D(u_colorTex, v_offset1.zw).rgb, kWeights); + delta.zw = abs(L - vec2(Lright, Lbottom)); + + // Calculate the maximum delta in the direct neighborhood. + vec2 maxDelta = max(delta.xy, delta.zw); + + // Calculate left-left and top-top deltas. + float Lleftleft = dot(texture2D(u_colorTex, v_offset2.xy).rgb, kWeights); + float Ltoptop = dot(texture2D(u_colorTex, v_offset2.zw).rgb, kWeights); + delta.zw = abs(vec2(Lleft, Ltop) - vec2(Lleftleft, Ltoptop)); + + // Calculate the final maximum delta. + maxDelta = max(maxDelta.xy, delta.zw); + float finalDelta = max(maxDelta.x, maxDelta.y); + + // Local contrast adaptation + edges *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); + + gl_FragColor = vec4(edges, 0.0, 1.0); +} diff --git a/drape_frontend/shaders/smaa_edges.vsh.glsl b/drape_frontend/shaders/smaa_edges.vsh.glsl new file mode 100644 index 0000000000..efcd5e118d --- /dev/null +++ b/drape_frontend/shaders/smaa_edges.vsh.glsl @@ -0,0 +1,21 @@ +// Implementation of Subpixel Morphological Antialiasing (SMAA) is based on https://github.com/iryoku/smaa + +attribute vec2 a_pos; +attribute vec2 a_tcoord; + +uniform vec4 u_framebufferMetrics; + +varying vec2 v_colorTexCoords; +varying vec4 v_offset0; +varying vec4 v_offset1; +varying vec4 v_offset2; + +void main() +{ + v_colorTexCoords = a_tcoord; + v_offset0 = u_framebufferMetrics.xyxy * vec4(-1.0, 0.0, 0.0, -1.0) + a_tcoord.xyxy; + v_offset1 = u_framebufferMetrics.xyxy * vec4( 1.0, 0.0, 0.0, 1.0) + a_tcoord.xyxy; + v_offset2 = u_framebufferMetrics.xyxy * vec4(-2.0, 0.0, 0.0, -2.0) + a_tcoord.xyxy; + gl_Position = vec4(a_pos, 0.0, 1.0); +} + diff --git a/drape_frontend/shaders/smaa_final.fsh.glsl b/drape_frontend/shaders/smaa_final.fsh.glsl new file mode 100644 index 0000000000..01054dedb4 --- /dev/null +++ b/drape_frontend/shaders/smaa_final.fsh.glsl @@ -0,0 +1,51 @@ +// Implementation of Subpixel Morphological Antialiasing (SMAA) is based on https://github.com/iryoku/smaa + +uniform sampler2D u_colorTex; +uniform sampler2D u_blendingWeightTex; + +uniform vec4 u_framebufferMetrics; + +varying vec2 v_colorTexCoords; +varying vec4 v_offset; + +#ifdef GLES3 + #define SMAASampleLevelZero(tex, coord) textureLod(tex, coord, 0.0) +#else + #define SMAASampleLevelZero(tex, coord) texture2D(tex, coord) +#endif + +void main() +{ + // Fetch the blending weights for current pixel. + vec4 a; + a.x = texture2D(u_blendingWeightTex, v_offset.xy).a; // Right + a.y = texture2D(u_blendingWeightTex, v_offset.zw).g; // Top + a.wz = texture2D(u_blendingWeightTex, v_colorTexCoords).xz; // Bottom / Left + + // Is there any blending weight with a value greater than 0.0? + if (dot(a, vec4(1.0, 1.0, 1.0, 1.0)) < 1e-5) + { + gl_FragColor = texture2D(u_colorTex, v_colorTexCoords); + } + else + { + // Calculate the blending offsets. + vec4 blendingOffset = vec4(0.0, a.y, 0.0, a.w); + vec2 blendingWeight = a.yw; + if (max(a.x, a.z) > max(a.y, a.w)) + { + blendingOffset = vec4(a.x, 0.0, a.z, 0.0); + blendingWeight = a.xz; + } + blendingWeight /= dot(blendingWeight, vec2(1.0, 1.0)); + + // Calculate the texture coordinates. + vec4 bc = blendingOffset * vec4(u_framebufferMetrics.xy, -u_framebufferMetrics.xy); + bc += v_colorTexCoords.xyxy; + + // We exploit bilinear filtering to mix current pixel with the chosen neighbor. + vec4 color = blendingWeight.x * SMAASampleLevelZero(u_colorTex, bc.xy); + color += blendingWeight.y * SMAASampleLevelZero(u_colorTex, bc.zw); + gl_FragColor = color; + } +} diff --git a/drape_frontend/shaders/smaa_final.vsh.glsl b/drape_frontend/shaders/smaa_final.vsh.glsl new file mode 100644 index 0000000000..7f872d4f1d --- /dev/null +++ b/drape_frontend/shaders/smaa_final.vsh.glsl @@ -0,0 +1,16 @@ +// Implementation of Subpixel Morphological Antialiasing (SMAA) is based on https://github.com/iryoku/smaa + +attribute vec2 a_pos; +attribute vec2 a_tcoord; + +uniform vec4 u_framebufferMetrics; + +varying vec2 v_colorTexCoords; +varying vec4 v_offset; + +void main() +{ + v_colorTexCoords = a_tcoord; + v_offset = u_framebufferMetrics.xyxy * vec4(1.0, 0.0, 0.0, 1.0) + a_tcoord.xyxy; + gl_Position = vec4(a_pos, 0.0, 1.0); +} diff --git a/geometry/rect2d.hpp b/geometry/rect2d.hpp index 4eb42e75a4..bb77e21a49 100644 --- a/geometry/rect2d.hpp +++ b/geometry/rect2d.hpp @@ -355,6 +355,17 @@ namespace m2 ); } + template + inline bool HasIntersection(m2::Rect const & rect, TCollection const & geometry) + { + for (auto const & g : geometry) + { + if (rect.IsIntersect(g)) + return true; + } + return false; + }; + template TArchive & operator >> (TArchive & ar, m2::Rect & rect) { diff --git a/indexer/map_style_reader.cpp b/indexer/map_style_reader.cpp index 68f38a7213..1ebedfaccb 100644 --- a/indexer/map_style_reader.cpp +++ b/indexer/map_style_reader.cpp @@ -1,16 +1,13 @@ #include "map_style_reader.hpp" -#include "base/logging.hpp" - #include "coding/file_name_utils.hpp" #include "platform/platform.hpp" -#include +#include "base/logging.hpp" namespace { - std::string const kSuffixDark = "_dark"; std::string const kSuffixClear = "_clear"; std::string const kSuffixVehicleDark = "_vehicle_dark"; @@ -18,7 +15,7 @@ std::string const kSuffixVehicleClear = "_vehicle_clear"; std::string const kStylesOverrideDir = "styles"; -string GetStyleRulesSuffix(MapStyle mapStyle) +std::string GetStyleRulesSuffix(MapStyle mapStyle) { switch (mapStyle) { @@ -31,7 +28,7 @@ string GetStyleRulesSuffix(MapStyle mapStyle) case MapStyleVehicleClear: return kSuffixVehicleClear; case MapStyleMerged: - return string(); + return std::string(); case MapStyleCount: break; @@ -40,7 +37,7 @@ string GetStyleRulesSuffix(MapStyle mapStyle) return kSuffixClear; } -string GetStyleResourcesSuffix(MapStyle mapStyle) +std::string GetStyleResourcesSuffix(MapStyle mapStyle) { // We use the same resources for default and vehicle styles // to avoid textures duplication and package size increasing. @@ -53,7 +50,7 @@ string GetStyleResourcesSuffix(MapStyle mapStyle) case MapStyleVehicleClear: return kSuffixClear; case MapStyleMerged: - return string(); + return std::string(); case MapStyleCount: break; @@ -61,13 +58,11 @@ string GetStyleResourcesSuffix(MapStyle mapStyle) LOG(LWARNING, ("Unknown map style", mapStyle)); return kSuffixClear; } - } // namespace StyleReader::StyleReader() : m_mapStyle(kDefaultMapStyle) -{ -} +{} void StyleReader::SetCurrentStyle(MapStyle mapStyle) { @@ -81,27 +76,37 @@ MapStyle StyleReader::GetCurrentStyle() ReaderPtr StyleReader::GetDrawingRulesReader() { - string rulesFile = string("drules_proto") + GetStyleRulesSuffix(GetCurrentStyle()) + ".bin"; + std::string rulesFile = + std::string("drules_proto") + GetStyleRulesSuffix(GetCurrentStyle()) + ".bin"; - auto overriddenRulesFile = my::JoinFoldersToPath({GetPlatform().WritableDir(), kStylesOverrideDir}, rulesFile); + auto overriddenRulesFile = + my::JoinFoldersToPath({GetPlatform().WritableDir(), kStylesOverrideDir}, rulesFile); if (GetPlatform().IsFileExistsByFullPath(overriddenRulesFile)) rulesFile = overriddenRulesFile; return GetPlatform().GetReader(rulesFile); } -ReaderPtr StyleReader::GetResourceReader(string const & file, string const & density) +ReaderPtr StyleReader::GetResourceReader(std::string const & file, + std::string const & density) { - string const resourceDir = string("resources-") + density + GetStyleResourcesSuffix(GetCurrentStyle()); - string resFile = my::JoinFoldersToPath(resourceDir, file); + std::string const resourceDir = + std::string("resources-") + density + GetStyleResourcesSuffix(GetCurrentStyle()); + std::string resFile = my::JoinFoldersToPath(resourceDir, file); - auto overriddenResFile = my::JoinFoldersToPath({GetPlatform().WritableDir(), kStylesOverrideDir}, resFile); + auto overriddenResFile = + my::JoinFoldersToPath({GetPlatform().WritableDir(), kStylesOverrideDir}, resFile); if (GetPlatform().IsFileExistsByFullPath(overriddenResFile)) resFile = overriddenResFile; return GetPlatform().GetReader(resFile); } +ReaderPtr StyleReader::GetDefaultResourceReader(std::string const & file) +{ + return GetPlatform().GetReader(my::JoinFoldersToPath("resources-default", file)); +} + StyleReader & GetStyleReader() { static StyleReader instance; diff --git a/indexer/map_style_reader.hpp b/indexer/map_style_reader.hpp index 63ff46a47a..5c488614d3 100644 --- a/indexer/map_style_reader.hpp +++ b/indexer/map_style_reader.hpp @@ -4,6 +4,8 @@ #include "map_style.hpp" +#include + class StyleReader { public: @@ -14,7 +16,8 @@ public: ReaderPtr GetDrawingRulesReader(); - ReaderPtr GetResourceReader(string const & file, string const & density); + ReaderPtr GetResourceReader(std::string const & file, std::string const & density); + ReaderPtr GetDefaultResourceReader(std::string const & file); private: MapStyle m_mapStyle; diff --git a/map/framework.cpp b/map/framework.cpp index 969cd06b19..15caf2ccab 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -3072,16 +3072,31 @@ bool Framework::ParseDrapeDebugCommand(std::string const & query) desiredStyle = MapStyleVehicleDark; else if (query == "?vlight" || query == "mapstyle:vehicle_light") desiredStyle = MapStyleVehicleClear; - else - return false; + if (desiredStyle != MapStyleCount) + { #if defined(OMIM_OS_ANDROID) - MarkMapStyle(desiredStyle); + MarkMapStyle(desiredStyle); #else - SetMapStyle(desiredStyle); + SetMapStyle(desiredStyle); #endif + return true; + } - return true; + if (query == "?aa" || query == "effect:antialiasing") + { + m_drapeEngine->SetPosteffectEnabled(df::PostprocessRenderer::Antialiasing, + true /* enabled */); + return true; + } + else if (query == "?no-aa" || query == "effect:no-antialiasing") + { + m_drapeEngine->SetPosteffectEnabled(df::PostprocessRenderer::Antialiasing, + false /* enabled */); + return true; + } + + return false; } bool Framework::ParseEditorDebugCommand(search::SearchParams const & params) diff --git a/qt/qt_common/map_widget.cpp b/qt/qt_common/map_widget.cpp index 4af959574b..bd2746a890 100644 --- a/qt/qt_common/map_widget.cpp +++ b/qt/qt_common/map_widget.cpp @@ -21,6 +21,8 @@ namespace qt { namespace common { +//#define ENABLE_AA_SWITCH + MapWidget::MapWidget(Framework & framework, bool apiOpenGLES3, QWidget * parent) : QOpenGLWidget(parent) , m_framework(framework) @@ -54,6 +56,11 @@ void MapWidget::BindHotkeys(QWidget & parent) {Qt::Key_Minus, SLOT(ScaleMinus())}, {Qt::ALT + Qt::Key_Equal, SLOT(ScalePlusLight())}, {Qt::ALT + Qt::Key_Minus, SLOT(ScaleMinusLight())}, + {Qt::ALT + Qt::Key_Minus, SLOT(ScaleMinusLight())}, +#ifdef ENABLE_AA_SWITCH + {Qt::ALT + Qt::Key_A, SLOT(AntialiasingOn())}, + {Qt::ALT + Qt::Key_S, SLOT(AntialiasingOff())}, +#endif }; for (auto const & hotkey : hotkeys) @@ -104,6 +111,20 @@ void MapWidget::ScalePlusLight() { m_framework.Scale(Framework::SCALE_MAG_LIGHT, void MapWidget::ScaleMinusLight() { m_framework.Scale(Framework::SCALE_MIN_LIGHT, true); } +void MapWidget::AntialiasingOn() +{ + auto engine = m_framework.GetDrapeEngine(); + if (engine != nullptr) + engine->SetPosteffectEnabled(df::PostprocessRenderer::Antialiasing, true); +} + +void MapWidget::AntialiasingOff() +{ + auto engine = m_framework.GetDrapeEngine(); + if (engine != nullptr) + engine->SetPosteffectEnabled(df::PostprocessRenderer::Antialiasing, false); +} + void MapWidget::ScaleChanged(int action) { if (!m_slider) diff --git a/qt/qt_common/map_widget.hpp b/qt/qt_common/map_widget.hpp index ce37dcf09e..4956b592a8 100644 --- a/qt/qt_common/map_widget.hpp +++ b/qt/qt_common/map_widget.hpp @@ -46,6 +46,9 @@ public Q_SLOTS: void SliderPressed(); void SliderReleased(); + void AntialiasingOn(); + void AntialiasingOff(); + public: Q_SIGNAL void BeforeEngineCreation(); diff --git a/xcode/drape_frontend/drape_frontend.xcodeproj/project.pbxproj b/xcode/drape_frontend/drape_frontend.xcodeproj/project.pbxproj index 40fb7faddc..bd30acd56a 100644 --- a/xcode/drape_frontend/drape_frontend.xcodeproj/project.pbxproj +++ b/xcode/drape_frontend/drape_frontend.xcodeproj/project.pbxproj @@ -45,6 +45,8 @@ 454C19BD1CCE3EC0002A2C86 /* animation_system.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 454C19BA1CCE3EC0002A2C86 /* animation_system.hpp */; }; 45580ABA1E28DB2600CD535D /* scenario_manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45580AB81E28DB2600CD535D /* scenario_manager.cpp */; }; 45580ABB1E28DB2600CD535D /* scenario_manager.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 45580AB91E28DB2600CD535D /* scenario_manager.hpp */; }; + 456B3F991ED464FE009B3D1F /* postprocess_renderer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 456B3F971ED464FE009B3D1F /* postprocess_renderer.cpp */; }; + 456B3F9A1ED464FE009B3D1F /* postprocess_renderer.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 456B3F981ED464FE009B3D1F /* postprocess_renderer.hpp */; }; 457D89251E7AE89500049500 /* custom_symbol.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 457D89241E7AE89500049500 /* custom_symbol.hpp */; }; 45B4B8CB1CF5C16B00A54761 /* screen_animations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45B4B8C71CF5C16B00A54761 /* screen_animations.cpp */; }; 45B4B8CC1CF5C16B00A54761 /* screen_animations.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 45B4B8C81CF5C16B00A54761 /* screen_animations.hpp */; }; @@ -275,6 +277,8 @@ 45580AB81E28DB2600CD535D /* scenario_manager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = scenario_manager.cpp; sourceTree = ""; }; 45580AB91E28DB2600CD535D /* scenario_manager.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = scenario_manager.hpp; sourceTree = ""; }; 4560692B1EB9F9D2009AB7B7 /* shaders_lib.glsl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = shaders_lib.glsl; path = shaders/shaders_lib.glsl; sourceTree = ""; }; + 456B3F971ED464FE009B3D1F /* postprocess_renderer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = postprocess_renderer.cpp; sourceTree = ""; }; + 456B3F981ED464FE009B3D1F /* postprocess_renderer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = postprocess_renderer.hpp; sourceTree = ""; }; 457D89241E7AE89500049500 /* custom_symbol.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = custom_symbol.hpp; sourceTree = ""; }; 45B4B8C71CF5C16B00A54761 /* screen_animations.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = screen_animations.cpp; sourceTree = ""; }; 45B4B8C81CF5C16B00A54761 /* screen_animations.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = screen_animations.hpp; sourceTree = ""; }; @@ -605,6 +609,8 @@ 670947411BDF9B99005014C0 /* drape_frontend */ = { isa = PBXGroup; children = ( + 456B3F971ED464FE009B3D1F /* postprocess_renderer.cpp */, + 456B3F981ED464FE009B3D1F /* postprocess_renderer.hpp */, 45BB025B1EB8BE5200FE5C0C /* shader_def.cpp */, 45BB025C1EB8BE5200FE5C0C /* shader_def.hpp */, 45BB02221EB8BE1400FE5C0C /* shaders */, @@ -859,6 +865,7 @@ 6709486A1BDF9C7F005014C0 /* brush_info.hpp in Headers */, 675D218E1BFB871D00717E4F /* rect.h in Headers */, 670948191BDF9C39005014C0 /* interpolations.hpp in Headers */, + 456B3F9A1ED464FE009B3D1F /* postprocess_renderer.hpp in Headers */, 670947CB1BDF9BE1005014C0 /* threads_commutator.hpp in Headers */, 347F52121DC2334A0064B273 /* drape_api.hpp in Headers */, 670948701BDF9C7F005014C0 /* feature_processor.hpp in Headers */, @@ -1106,6 +1113,7 @@ 347F52111DC2334A0064B273 /* drape_api.cpp in Sources */, 347F520F1DC2334A0064B273 /* drape_api_renderer.cpp in Sources */, 347F520D1DC2334A0064B273 /* drape_api_builder.cpp in Sources */, + 456B3F991ED464FE009B3D1F /* postprocess_renderer.cpp in Sources */, 670947CA1BDF9BE1005014C0 /* threads_commutator.cpp in Sources */, 670947981BDF9BE1005014C0 /* map_data_provider.cpp in Sources */, 670948181BDF9C39005014C0 /* interpolations.cpp in Sources */,