Index: source/graphics/Canvas2D.cpp =================================================================== --- source/graphics/Canvas2D.cpp +++ source/graphics/Canvas2D.cpp @@ -36,7 +36,7 @@ { // Array of 2D elements unrolled into 1D array. -using PlaneArray2D = std::array; +using PlaneArray2D = std::array; inline void DrawTextureImpl( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, @@ -54,7 +54,6 @@ uvs[idx + 1] /= texture->GetHeight(); } - shader->Uniform(str_transform, GetDefaultGuiMatrix()); shader->Uniform(str_colorAdd, add); shader->Uniform(str_colorMul, multiply); shader->Uniform(str_grayscaleFactor, grayscaleFactor); @@ -62,7 +61,7 @@ shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, uvs.data()); shader->AssertPointersBound(); - glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size() / 2); + glDrawArrays(GL_TRIANGLES, 0, vertices.size() / 2); } } // anonymous namespace @@ -87,6 +86,8 @@ Tech->BeginPass(); DeviceCommandContext->SetGraphicsPipelineState( Tech->GetGraphicsPipelineStateDesc()); + const CShaderProgramPtr& shader = Tech->GetShader(); + shader->Uniform(str_transform, GetDefaultGuiMatrix()); } void UnbindTech() @@ -114,47 +115,161 @@ void CCanvas2D::DrawLine(const std::vector& points, const float width, const CColor& color) { - m->BindTechIfNeeded(); + if (points.empty()) + return; + + // We could reuse the terrain line building, but it uses 3D space instead of + // 2D. So it can be less optimal for a canvas. - std::vector vertices; - std::vector uvs(points.size() * 2, 0.0f); - vertices.reserve(points.size() * 2); - for (const CVector2D& point : points) - { - vertices.emplace_back(point.X); - vertices.emplace_back(point.Y); + // Adding a single pixel line with alpha gradient to reduce the aliasing + // effect. + const float halfWidth = width * 0.5f + 1.0f; + + struct PointIndex + { + size_t index; + float length; + CVector2D normal; + }; + // Normal for the last index is undefined. + std::vector pointsIndices; + pointsIndices.reserve(points.size()); + pointsIndices.emplace_back(PointIndex{0, 0.0f, CVector2D()}); + for (size_t index = 0; index < points.size();) + { + size_t nextIndex = index + 1; + CVector2D direction; + float length = 0.0f; + while (nextIndex < points.size()) + { + direction = points[nextIndex] - points[pointsIndices.back().index]; + length = direction.Length(); + if (length >= halfWidth * 2.0f) + { + direction /= length; + break; + } + ++nextIndex; + } + if (nextIndex == points.size()) + break; + pointsIndices.back().length = length; + pointsIndices.back().normal = CVector2D(-direction.Y, direction.X); + pointsIndices.emplace_back(PointIndex{nextIndex, 0.0f, CVector2D()}); + index = nextIndex; } - CShaderProgramPtr shader = m->Tech->GetShader(); - shader->BindTexture(str_tex, g_Renderer.GetTextureManager().GetTransparentTexture()->GetBackendTexture()); - shader->Uniform(str_transform, GetDefaultGuiMatrix()); - shader->Uniform(str_colorAdd, color); - shader->Uniform(str_colorMul, CColor(0.0f, 0.0f, 0.0f, 0.0f)); + if (pointsIndices.size() <= 1) + return; + + std::vector> vertices; + std::vector> uvs; + std::vector indices; + const size_t reserveSize = 2 * pointsIndices.size() - 1; + vertices.reserve(reserveSize); + uvs.reserve(reserveSize); + indices.reserve(reserveSize * 12); + + auto addVertices = [&vertices, &uvs, &indices, &halfWidth](const CVector2D& p1, const CVector2D& p2) + { + if (!vertices.empty()) + { + const u16 lastVertexIndex = static_cast(vertices.size() * 3 - 1); + ENSURE(lastVertexIndex >= 2); + // First vertical half of the segment. + indices.emplace_back(lastVertexIndex - 2); + indices.emplace_back(lastVertexIndex - 1); + indices.emplace_back(lastVertexIndex + 2); + indices.emplace_back(lastVertexIndex - 2); + indices.emplace_back(lastVertexIndex + 2); + indices.emplace_back(lastVertexIndex + 1); + // Second vertical half of the segment. + indices.emplace_back(lastVertexIndex - 1); + indices.emplace_back(lastVertexIndex); + indices.emplace_back(lastVertexIndex + 3); + indices.emplace_back(lastVertexIndex - 1); + indices.emplace_back(lastVertexIndex + 3); + indices.emplace_back(lastVertexIndex + 2); + } + vertices.emplace_back(std::array{p1, (p1 + p2) / 2.0f, p2}); + uvs.emplace_back(std::array{ + CVector2D(0.0f, 0.0f), + CVector2D(std::max(1.0f, halfWidth - 1.0f), 0.0f), + CVector2D(0.0f, 0.0f)}); + }; + + addVertices( + points[pointsIndices.front().index] - pointsIndices.front().normal * halfWidth, + points[pointsIndices.front().index] + pointsIndices.front().normal * halfWidth); + // For each pair of adjacent segments we need to add smooth transition. + for (size_t index = 0; index + 2 < pointsIndices.size(); ++index) + { + const PointIndex& pointIndex = pointsIndices[index]; + const PointIndex& nextPointIndex = pointsIndices[index + 1]; + // Angle between adjacent segments. + const float cosAlpha = pointIndex.normal.Dot(nextPointIndex.normal); + constexpr float EPS = 1e-3f; + // Use a simple segment if adjacent segments are almost codirectional. + if (cosAlpha > 1.0f - EPS) + { + addVertices( + points[pointIndex.index] - pointIndex.normal * halfWidth, + points[pointIndex.index] + pointIndex.normal * halfWidth); + } + else + { + addVertices( + points[nextPointIndex.index] - pointIndex.normal * halfWidth, + points[nextPointIndex.index] + pointIndex.normal * halfWidth); + // Average normal between adjacent segments. We might want to rotate it but + // for now we assume that it's enough for current line widths. + const CVector2D normal = cosAlpha < -1.0f + EPS + ? CVector2D(pointIndex.normal.Y, -pointIndex.normal.X) + : ((pointIndex.normal + nextPointIndex.normal) / 2.0f).Normalized(); + addVertices( + points[nextPointIndex.index] - normal * halfWidth, + points[nextPointIndex.index] + normal * halfWidth); + addVertices( + points[nextPointIndex.index] - nextPointIndex.normal * halfWidth, + points[nextPointIndex.index] + nextPointIndex.normal * halfWidth); + } + // We use 16-bit indices, it means that we can't use more than 64K vertices. + const size_t requiredFreeSpace = 3 * 4; + if (vertices.size() * 3 + requiredFreeSpace >= 65536) + break; + } + addVertices( + points[pointsIndices.back().index] - pointsIndices[pointsIndices.size() - 2].normal * halfWidth, + points[pointsIndices.back().index] + pointsIndices[pointsIndices.size() - 2].normal * halfWidth); + + m->BindTechIfNeeded(); + + const CShaderProgramPtr& shader = m->Tech->GetShader(); + shader->BindTexture(str_tex, g_Renderer.GetTextureManager().GetAlphaGradientTexture()->GetBackendTexture()); + shader->Uniform(str_colorAdd, CColor(0.0f, 0.0f, 0.0f, 0.0f)); + shader->Uniform(str_colorMul, color); shader->Uniform(str_grayscaleFactor, 0.0f); shader->VertexPointer(2, GL_FLOAT, 0, vertices.data()); shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, uvs.data()); shader->AssertPointersBound(); -#if !CONFIG2_GLES - glEnable(GL_LINE_SMOOTH); -#endif - glLineWidth(width); - glDrawArrays(GL_LINE_STRIP, 0, vertices.size() / 2); - glLineWidth(1.0f); -#if !CONFIG2_GLES - glDisable(GL_LINE_SMOOTH); -#endif + glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, indices.data()); } void CCanvas2D::DrawRect(const CRect& rect, const CColor& color) { - const PlaneArray2D uvs = { - 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f + const PlaneArray2D uvs + { + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; - const PlaneArray2D vertices = { + const PlaneArray2D vertices = + { rect.left, rect.bottom, rect.right, rect.bottom, rect.right, rect.top, + rect.left, rect.bottom, + rect.right, rect.top, rect.left, rect.top }; @@ -176,15 +291,21 @@ CTexturePtr texture, const CRect& destination, const CRect& source, const CColor& multiply, const CColor& add, const float grayscaleFactor) { - const PlaneArray2D uvs = { + const PlaneArray2D uvs = + { source.left, source.bottom, source.right, source.bottom, source.right, source.top, + source.left, source.bottom, + source.right, source.top, source.left, source.top }; - const PlaneArray2D vertices = { + const PlaneArray2D vertices = + { destination.left, destination.bottom, destination.right, destination.bottom, + destination.right, destination.top, + destination.left, destination.bottom, destination.right, destination.top, destination.left, destination.top }; Index: source/graphics/TextureManager.h =================================================================== --- source/graphics/TextureManager.h +++ source/graphics/TextureManager.h @@ -84,12 +84,18 @@ ~CTextureManager(); /** - * Create a texture with the given GL properties. + * Create a texture with the given properties. * The texture data will not be loaded immediately. */ CTexturePtr CreateTexture(const CTextureProperties& props); /** + * Wraps a backend texture. + */ + CTexturePtr WrapBackendTexture( + std::unique_ptr backendTexture); + + /** * Returns a magenta texture. Use this for highlighting errors * (e.g. missing terrain textures). */ @@ -106,6 +112,11 @@ const CTexturePtr& GetTransparentTexture(); /** + * Returns an alpha gradient RGBA texture. + */ + const CTexturePtr& GetAlphaGradientTexture(); + + /** * Work on asynchronous texture loading operations, if any. * Returns true if it did any work. * The caller should typically loop this per frame until it returns @@ -313,7 +324,7 @@ private: friend class CTextureManagerImpl; - friend class SingleColorTexture; + friend class CPredefinedTexture; friend struct TextureCacheCmp; friend struct TPequal_to; friend struct TPhash; Index: source/graphics/TextureManager.cpp =================================================================== --- source/graphics/TextureManager.cpp +++ source/graphics/TextureManager.cpp @@ -26,6 +26,7 @@ #include "lib/hash.h" #include "lib/timer.h" #include "maths/MD5.h" +#include "maths/MathUtil.h" #include "ps/CacheLoader.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" @@ -103,11 +104,35 @@ } // anonymous namespace -class SingleColorTexture +class CPredefinedTexture { public: - SingleColorTexture(const CColor& color, const VfsPath& pathPlaceholder, - const bool disableGL, CTextureManagerImpl* textureManager) + const CTexturePtr& GetTexture() + { + return m_Texture; + } + + void CreateTexture( + std::unique_ptr backendTexture, + CTextureManagerImpl* textureManager) + { + Renderer::Backend::GL::CTexture* fallback = backendTexture.get(); + CTextureProperties props(VfsPath{}); + m_Texture = CTexturePtr(new CTexture( + std::move(backendTexture), fallback, props, textureManager)); + m_Texture->m_State = CTexture::UPLOADED; + m_Texture->m_Self = m_Texture; + } + +private: + CTexturePtr m_Texture; +}; + +class CSingleColorTexture final : public CPredefinedTexture +{ +public: + CSingleColorTexture(const CColor& color, const bool disableGL, + CTextureManagerImpl* textureManager) : m_Color(color) { if (disableGL) @@ -115,10 +140,10 @@ std::stringstream textureName; textureName << "SingleColorTexture ("; - textureName << "R: " << m_Color.r << ","; - textureName << "G: " << m_Color.g << ","; - textureName << "B: " << m_Color.b << ","; - textureName << "A: " << m_Color.a << ")"; + textureName << "R:" << m_Color.r << ", "; + textureName << "G:" << m_Color.g << ", "; + textureName << "B:" << m_Color.b << ", "; + textureName << "A:" << m_Color.a << ")"; std::unique_ptr backendTexture = g_VideoMode.GetBackendDevice()->CreateTexture2D( @@ -127,23 +152,12 @@ 1, 1, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::REPEAT)); - Renderer::Backend::GL::CTexture* fallback = backendTexture.get(); - - CTextureProperties props(pathPlaceholder); - m_Texture = CTexturePtr(new CTexture( - std::move(backendTexture), fallback, props, textureManager)); - m_Texture->m_State = CTexture::UPLOADED; - m_Texture->m_Self = m_Texture; - } - - const CTexturePtr& GetTexture() - { - return m_Texture; + CreateTexture(std::move(backendTexture), textureManager); } void Upload(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { - if (!m_Texture || !m_Texture->GetBackendTexture()) + if (!GetTexture() || !GetTexture()->GetBackendTexture()) return; const SColor4ub color32 = m_Color.AsSColor4ub(); @@ -155,15 +169,91 @@ color32.B, color32.A }; - deviceCommandContext->UploadTexture(m_Texture->GetBackendTexture(), - Renderer::Backend::Format::R8G8B8A8, data, std::size(data)); + deviceCommandContext->UploadTexture(GetTexture()->GetBackendTexture(), + Renderer::Backend::Format::R8G8B8A8, &data, std::size(data)); } private: - CTexturePtr m_Texture; CColor m_Color; }; +class CGradientTexture final : public CPredefinedTexture +{ +public: + static const uint32_t WIDTH = 256; + static const uint32_t NUMBER_OF_LEVELS = 9; + + CGradientTexture(const CColor& colorFrom, const CColor& colorTo, + const bool disableGL, CTextureManagerImpl* textureManager) + : m_ColorFrom(colorFrom), m_ColorTo(colorTo) + { + if (disableGL) + return; + + std::stringstream textureName; + textureName << "GradientTexture"; + textureName << " From ("; + textureName << "R:" << m_ColorFrom.r << ", "; + textureName << "G:" << m_ColorFrom.g << ", "; + textureName << "B:" << m_ColorFrom.b << ", "; + textureName << "A:" << m_ColorFrom.a << ")"; + textureName << " To ("; + textureName << "R:" << m_ColorTo.r << ","; + textureName << "G:" << m_ColorTo.g << ","; + textureName << "B:" << m_ColorTo.b << ","; + textureName << "A:" << m_ColorTo.a << ")"; + + std::unique_ptr backendTexture = + g_VideoMode.GetBackendDevice()->CreateTexture2D( + textureName.str().c_str(), + Renderer::Backend::Format::R8G8B8A8, + WIDTH, 1, Renderer::Backend::Sampler::MakeDefaultSampler( + Renderer::Backend::Sampler::Filter::LINEAR, + Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), + NUMBER_OF_LEVELS); + CreateTexture(std::move(backendTexture), textureManager); + } + + void Upload(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) + { + if (!GetTexture() || !GetTexture()->GetBackendTexture()) + return; + + std::array, WIDTH> data; + for (uint32_t x = 0; x < WIDTH; ++x) + { + const float t = static_cast(x) / (WIDTH - 1); + const CColor color( + Interpolate(m_ColorFrom.r, m_ColorTo.r, t), + Interpolate(m_ColorFrom.g, m_ColorTo.g, t), + Interpolate(m_ColorFrom.b, m_ColorTo.b, t), + Interpolate(m_ColorFrom.a, m_ColorTo.a, t)); + const SColor4ub color32 = color.AsSColor4ub(); + data[x][0] = color32.R; + data[x][1] = color32.G; + data[x][2] = color32.B; + data[x][3] = color32.A; + } + for (uint32_t level = 0; level < NUMBER_OF_LEVELS; ++level) + { + deviceCommandContext->UploadTexture(GetTexture()->GetBackendTexture(), + Renderer::Backend::Format::R8G8B8A8, data.data(), (WIDTH >> level) * data[0].size(), level); + // Prepare data for the next level. + const uint32_t nextLevelWidth = (WIDTH >> (level + 1)); + if (nextLevelWidth > 0) + { + for (uint32_t x = 0; x < nextLevelWidth; ++x) + data[x] = data[(x << 1)]; + // Border values should be the same. + data[nextLevelWidth - 1] = data[(WIDTH >> level) - 1]; + } + } + } + +private: + CColor m_ColorFrom, m_ColorTo; +}; + struct TPhash { std::size_t operator()(const CTextureProperties& textureProperties) const @@ -213,10 +303,12 @@ CTextureManagerImpl(PIVFS vfs, bool highQuality, bool disableGL) : m_VFS(vfs), m_CacheLoader(vfs, L".dds"), m_DisableGL(disableGL), m_TextureConverter(vfs, highQuality), - m_DefaultTexture(CColor(0.25f, 0.25f, 0.25f, 1.0f), L"(default texture)", disableGL, this), - m_ErrorTexture(CColor(1.0f, 0.0f, 1.0f, 1.0f), L"(error texture)", disableGL, this), - m_WhiteTexture(CColor(1.0f, 1.0f, 1.0f, 1.0f), L"(white texture)", disableGL, this), - m_TransparentTexture(CColor(0.0f, 0.0f, 0.0f, 0.0f), L"(transparent texture)", disableGL, this) + m_DefaultTexture(CColor(0.25f, 0.25f, 0.25f, 1.0f), disableGL, this), + m_ErrorTexture(CColor(1.0f, 0.0f, 1.0f, 1.0f), disableGL, this), + m_WhiteTexture(CColor(1.0f, 1.0f, 1.0f, 1.0f), disableGL, this), + m_TransparentTexture(CColor(0.0f, 0.0f, 0.0f, 0.0f), disableGL, this), + m_AlphaGradientTexture( + CColor(1.0f, 1.0f, 1.0f, 0.0f), CColor(1.0f, 1.0f, 1.0f, 1.0f), disableGL, this) { // Allow hotloading of textures RegisterFileReloadFunc(ReloadChangedFileCB, this); @@ -252,6 +344,11 @@ return m_TransparentTexture.GetTexture(); } + const CTexturePtr& GetAlphaGradientTexture() + { + return m_AlphaGradientTexture.GetTexture(); + } + /** * See CTextureManager::CreateTexture */ @@ -275,6 +372,20 @@ return texture; } + CTexturePtr WrapBackendTexture( + std::unique_ptr backendTexture) + { + ENSURE(backendTexture); + Renderer::Backend::GL::CTexture* fallback = backendTexture.get(); + + CTextureProperties props(VfsPath{}); + CTexturePtr texture(new CTexture( + std::move(backendTexture), fallback, props, this)); + texture->m_State = CTexture::UPLOADED; + texture->m_Self = texture; + return texture; + } + /** * Load the given file into the texture object and upload it to OpenGL. * Assumes the file already exists. @@ -574,13 +685,14 @@ bool MakeUploadProgress( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { - if (!m_SingleColorTexturesUploaded) + if (!m_PredefinedTexturesUploaded) { m_DefaultTexture.Upload(deviceCommandContext); m_ErrorTexture.Upload(deviceCommandContext); m_WhiteTexture.Upload(deviceCommandContext); m_TransparentTexture.Upload(deviceCommandContext); - m_SingleColorTexturesUploaded = true; + m_AlphaGradientTexture.Upload(deviceCommandContext); + m_PredefinedTexturesUploaded = true; return true; } return false; @@ -692,11 +804,12 @@ bool m_DisableGL; CTextureConverter m_TextureConverter; - SingleColorTexture m_DefaultTexture; - SingleColorTexture m_ErrorTexture; - SingleColorTexture m_WhiteTexture; - SingleColorTexture m_TransparentTexture; - bool m_SingleColorTexturesUploaded = false; + CSingleColorTexture m_DefaultTexture; + CSingleColorTexture m_ErrorTexture; + CSingleColorTexture m_WhiteTexture; + CSingleColorTexture m_TransparentTexture; + CGradientTexture m_AlphaGradientTexture; + bool m_PredefinedTexturesUploaded = false; // Cache of all loaded textures using TextureCache = @@ -856,6 +969,12 @@ return m->CreateTexture(props); } +CTexturePtr CTextureManager::WrapBackendTexture( + std::unique_ptr backendTexture) +{ + return m->WrapBackendTexture(std::move(backendTexture)); +} + bool CTextureManager::TextureExists(const VfsPath& path) const { return m->TextureExists(path); @@ -874,6 +993,11 @@ const CTexturePtr& CTextureManager::GetTransparentTexture() { return m->GetTransparentTexture(); +} + +const CTexturePtr& CTextureManager::GetAlphaGradientTexture() +{ + return m->GetAlphaGradientTexture(); } bool CTextureManager::MakeProgress() Index: source/gui/ObjectTypes/CChart.cpp =================================================================== --- source/gui/ObjectTypes/CChart.cpp +++ source/gui/ObjectTypes/CChart.cpp @@ -103,12 +103,12 @@ } else { - canvas.DrawLine(linePoints, 1.1f, data.m_Color); + canvas.DrawLine(linePoints, 2.0f, data.m_Color); linePoints.clear(); } } if (!linePoints.empty()) - canvas.DrawLine(linePoints, 1.1f, data.m_Color); + canvas.DrawLine(linePoints, 2.0f, data.m_Color); } if (m_AxisWidth > 0)