LCOV - code coverage report
Current view: top level - source/graphics - LOSTexture.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 55 227 24.2 %
Date: 2023-01-19 00:18:29 Functions: 6 16 37.5 %

          Line data    Source code
       1             : /* Copyright (C) 2023 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "LOSTexture.h"
      21             : 
      22             : #include "graphics/ShaderManager.h"
      23             : #include "lib/bits.h"
      24             : #include "lib/config2.h"
      25             : #include "lib/timer.h"
      26             : #include "maths/MathUtil.h"
      27             : #include "ps/CLogger.h"
      28             : #include "ps/CStrInternStatic.h"
      29             : #include "ps/Game.h"
      30             : #include "ps/Profile.h"
      31             : #include "renderer/backend/IDevice.h"
      32             : #include "renderer/Renderer.h"
      33             : #include "renderer/RenderingOptions.h"
      34             : #include "renderer/TimeManager.h"
      35             : #include "simulation2/Simulation2.h"
      36             : #include "simulation2/components/ICmpRangeManager.h"
      37             : #include "simulation2/helpers/Los.h"
      38             : 
      39             : /*
      40             : 
      41             : The LOS bitmap is computed with one value per LOS vertex, based on
      42             : CCmpRangeManager's visibility information.
      43             : 
      44             : The bitmap is then blurred using an NxN filter (in particular a
      45             : 7-tap Binomial filter as an efficient integral approximation of a Gaussian).
      46             : To implement the blur efficiently without using extra memory for a second copy
      47             : of the bitmap, we generate the bitmap with (N-1)/2 pixels of padding on each side,
      48             : then the blur shifts the image back into the corner.
      49             : 
      50             : The blurred bitmap is then uploaded into a GL texture for use by the renderer.
      51             : 
      52             : */
      53             : 
      54             : 
      55             : // Blur with a NxN filter, where N = g_BlurSize must be an odd number.
      56             : // Keep it in relation to the number of impassable tiles in MAP_EDGE_TILES.
      57             : static const size_t g_BlurSize = 7;
      58             : 
      59             : // Alignment (in bytes) of the pixel data passed into texture uploading.
      60             : // This must be a multiple of GL_UNPACK_ALIGNMENT, which ought to be 1 (since
      61             : // that's what we set it to) but in some weird cases appears to have a different
      62             : // value. (See Trac #2594). Multiples of 4 are possibly good for performance anyway.
      63             : static const size_t g_SubTextureAlignment = 4;
      64             : 
      65           1 : CLOSTexture::CLOSTexture(CSimulation2& simulation)
      66           1 :     : m_Simulation(simulation)
      67             : {
      68           1 :     if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS())
      69           0 :         CreateShader();
      70           1 : }
      71             : 
      72           2 : CLOSTexture::~CLOSTexture()
      73             : {
      74           1 :     m_SmoothFramebuffers[0].reset();
      75           1 :     m_SmoothFramebuffers[1].reset();
      76             : 
      77           1 :     if (m_Texture)
      78           0 :         DeleteTexture();
      79           1 : }
      80             : 
      81             : // Create the LOS texture engine. Should be ran only once.
      82           0 : bool CLOSTexture::CreateShader()
      83             : {
      84           0 :     m_SmoothTech = g_Renderer.GetShaderManager().LoadEffect(str_los_interp);
      85           0 :     m_ShaderInitialized = m_SmoothTech && m_SmoothTech->GetShader();
      86             : 
      87           0 :     if (!m_ShaderInitialized)
      88             :     {
      89           0 :         LOGERROR("Failed to load SmoothLOS shader, disabling.");
      90           0 :         g_RenderingOptions.SetSmoothLOS(false);
      91           0 :         return false;
      92             :     }
      93             : 
      94           0 :     const std::array<Renderer::Backend::SVertexAttributeFormat, 2> attributes{{
      95             :         {Renderer::Backend::VertexAttributeStream::POSITION,
      96             :             Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
      97             :             Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
      98             :         {Renderer::Backend::VertexAttributeStream::UV0,
      99             :             Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
     100             :             Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1}
     101             :     }};
     102           0 :     m_VertexInputLayout = g_Renderer.GetVertexInputLayout(attributes);
     103             : 
     104           0 :     return true;
     105             : }
     106             : 
     107           0 : void CLOSTexture::DeleteTexture()
     108             : {
     109           0 :     m_Texture.reset();
     110           0 :     m_SmoothTextures[0].reset();
     111           0 :     m_SmoothTextures[1].reset();
     112           0 : }
     113             : 
     114           0 : void CLOSTexture::MakeDirty()
     115             : {
     116           0 :     m_Dirty = true;
     117           0 : }
     118             : 
     119           0 : Renderer::Backend::ITexture* CLOSTexture::GetTextureSmooth()
     120             : {
     121           0 :     if (CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS())
     122           0 :         return GetTexture();
     123             :     else
     124           0 :         return m_SmoothTextures[m_WhichTexture].get();
     125             : }
     126             : 
     127           0 : void CLOSTexture::InterpolateLOS(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
     128             : {
     129           0 :     const bool skipSmoothLOS = CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS();
     130           0 :     if (!skipSmoothLOS && !m_ShaderInitialized)
     131             :     {
     132           0 :         if (!CreateShader())
     133           0 :             return;
     134             : 
     135             :         // RecomputeTexture will not cause the ConstructTexture to run.
     136             :         // Force the textures to be created.
     137           0 :         DeleteTexture();
     138           0 :         ConstructTexture(deviceCommandContext);
     139           0 :         m_Dirty = true;
     140             :     }
     141             : 
     142           0 :     if (m_Dirty)
     143             :     {
     144           0 :         RecomputeTexture(deviceCommandContext);
     145             :         // We need to subtract the frame time because without it we have the
     146             :         // same output images for the current and previous frames.
     147           0 :         m_LastTextureRecomputeTime = timer_Time() - g_Renderer.GetTimeManager().GetFrameDelta();
     148           0 :         m_WhichTexture = 1u - m_WhichTexture;
     149           0 :         m_Dirty = false;
     150             :     }
     151             : 
     152           0 :     if (skipSmoothLOS)
     153           0 :         return;
     154             : 
     155           0 :     GPU_SCOPED_LABEL(deviceCommandContext, "Render LOS texture");
     156           0 :     deviceCommandContext->BeginFramebufferPass(m_SmoothFramebuffers[m_WhichTexture].get());
     157             : 
     158           0 :     deviceCommandContext->SetGraphicsPipelineState(
     159           0 :         m_SmoothTech->GetGraphicsPipelineState());
     160           0 :     deviceCommandContext->BeginPass();
     161             : 
     162           0 :     Renderer::Backend::IShaderProgram* shader = m_SmoothTech->GetShader();
     163             : 
     164           0 :     deviceCommandContext->SetTexture(
     165           0 :         shader->GetBindingSlot(str_losTex1), m_Texture.get());
     166           0 :     deviceCommandContext->SetTexture(
     167           0 :         shader->GetBindingSlot(str_losTex2), m_SmoothTextures[1u - m_WhichTexture].get());
     168             : 
     169           0 :     const float delta = Clamp<float>(
     170           0 :         (timer_Time() - m_LastTextureRecomputeTime) * 2.0f, 0.0f, 1.0f);
     171           0 :     deviceCommandContext->SetUniform(shader->GetBindingSlot(str_delta), delta);
     172             : 
     173           0 :     Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
     174           0 :     viewportRect.width = m_Texture->GetWidth();
     175           0 :     viewportRect.height = m_Texture->GetHeight();
     176           0 :     deviceCommandContext->SetViewports(1, &viewportRect);
     177             : 
     178             :     const bool flip =
     179           0 :         deviceCommandContext->GetDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN;
     180           0 :     const float bottomV = flip ? 1.0 : 0.0f;
     181           0 :     const float topV = flip ? 0.0f : 1.0f;
     182             : 
     183           0 :     float quadVerts[] =
     184             :     {
     185             :         1.0f, 1.0f,
     186             :         -1.0f, 1.0f,
     187             :         -1.0f, -1.0f,
     188             : 
     189             :         -1.0f, -1.0f,
     190             :         1.0f, -1.0f,
     191             :         1.0f, 1.0f
     192             :     };
     193           0 :     float quadTex[] =
     194             :     {
     195             :         1.0f, topV,
     196             :         0.0f, topV,
     197             :         0.0f, bottomV,
     198             : 
     199             :         0.0f, bottomV,
     200             :         1.0f, bottomV,
     201             :         1.0f, topV
     202           0 :     };
     203             : 
     204           0 :     deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout);
     205             : 
     206           0 :     deviceCommandContext->SetVertexBufferData(
     207           0 :         0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0]));
     208           0 :     deviceCommandContext->SetVertexBufferData(
     209           0 :         1, quadTex, std::size(quadTex) * sizeof(quadTex[0]));
     210             : 
     211           0 :     deviceCommandContext->Draw(0, 6);
     212             : 
     213           0 :     deviceCommandContext->EndPass();
     214           0 :     deviceCommandContext->EndFramebufferPass();
     215             : }
     216             : 
     217             : 
     218           0 : Renderer::Backend::ITexture* CLOSTexture::GetTexture()
     219             : {
     220           0 :     ENSURE(!m_Dirty);
     221           0 :     return m_Texture.get();
     222             : }
     223             : 
     224           0 : const CMatrix3D& CLOSTexture::GetTextureMatrix()
     225             : {
     226           0 :     ENSURE(!m_Dirty);
     227           0 :     return m_TextureMatrix;
     228             : }
     229             : 
     230           0 : const CMatrix3D& CLOSTexture::GetMinimapTextureMatrix()
     231             : {
     232           0 :     ENSURE(!m_Dirty);
     233           0 :     return m_MinimapTextureMatrix;
     234             : }
     235             : 
     236           0 : void CLOSTexture::ConstructTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
     237             : {
     238           0 :     CmpPtr<ICmpRangeManager> cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
     239           0 :     if (!cmpRangeManager)
     240           0 :         return;
     241             : 
     242           0 :     m_MapSize = cmpRangeManager->GetVerticesPerSide();
     243             : 
     244           0 :     const size_t textureSize = round_up_to_pow2(round_up((size_t)m_MapSize + g_BlurSize - 1, g_SubTextureAlignment));
     245             : 
     246           0 :     Renderer::Backend::IDevice* backendDevice = deviceCommandContext->GetDevice();
     247             : 
     248             :     const Renderer::Backend::Sampler::Desc defaultSamplerDesc =
     249             :         Renderer::Backend::Sampler::MakeDefaultSampler(
     250             :             Renderer::Backend::Sampler::Filter::LINEAR,
     251           0 :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
     252             : 
     253           0 :     if (backendDevice->IsFramebufferFormatSupported(Renderer::Backend::Format::R8_UNORM))
     254             :     {
     255           0 :         m_TextureFormat = Renderer::Backend::Format::R8_UNORM;
     256           0 :         m_TextureFormatStride = 1;
     257             :     }
     258             :     else
     259             :     {
     260           0 :         m_TextureFormat = Renderer::Backend::Format::R8G8B8A8_UNORM;
     261           0 :         m_TextureFormatStride = 4;
     262             :     }
     263             : 
     264           0 :     m_Texture = backendDevice->CreateTexture2D("LOSTexture",
     265             :         Renderer::Backend::ITexture::Usage::TRANSFER_DST |
     266             :             Renderer::Backend::ITexture::Usage::SAMPLED,
     267           0 :         m_TextureFormat, textureSize, textureSize, defaultSamplerDesc);
     268             : 
     269             :     // Initialise texture with SoD color, for the areas we don't
     270             :     // overwrite with uploading later.
     271           0 :     const size_t textureDataSize = textureSize * textureSize * m_TextureFormatStride;
     272           0 :     std::unique_ptr<u8[]> texData = std::make_unique<u8[]>(textureDataSize);
     273           0 :     memset(texData.get(), 0x00, textureDataSize);
     274             : 
     275           0 :     if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS())
     276             :     {
     277           0 :         const uint32_t usage =
     278             :             Renderer::Backend::ITexture::Usage::TRANSFER_DST |
     279             :             Renderer::Backend::ITexture::Usage::SAMPLED |
     280             :             Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT;
     281           0 :         m_SmoothTextures[0] = backendDevice->CreateTexture2D("LOSSmoothTexture0",
     282           0 :             usage, m_TextureFormat, textureSize, textureSize, defaultSamplerDesc);
     283           0 :         m_SmoothTextures[1] = backendDevice->CreateTexture2D("LOSSmoothTexture1",
     284           0 :             usage, m_TextureFormat, textureSize, textureSize, defaultSamplerDesc);
     285             : 
     286           0 :         Renderer::Backend::SColorAttachment colorAttachment{};
     287           0 :         colorAttachment.texture = m_SmoothTextures[0].get();
     288           0 :         colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::DONT_CARE;
     289           0 :         colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
     290           0 :         colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
     291           0 :         m_SmoothFramebuffers[0] = backendDevice->CreateFramebuffer(
     292           0 :             "LOSSmoothFramebuffer0", &colorAttachment, nullptr);
     293           0 :         colorAttachment.texture = m_SmoothTextures[1].get();
     294           0 :         m_SmoothFramebuffers[1] = backendDevice->CreateFramebuffer(
     295           0 :             "LOSSmoothFramebuffer1", &colorAttachment, nullptr);
     296           0 :         if (!m_SmoothFramebuffers[0] || !m_SmoothFramebuffers[1])
     297             :         {
     298           0 :             LOGERROR("Failed to create LOS framebuffers");
     299           0 :             g_RenderingOptions.SetSmoothLOS(false);
     300             :         }
     301             : 
     302           0 :         deviceCommandContext->UploadTexture(
     303           0 :             m_SmoothTextures[0].get(), m_TextureFormat, texData.get(), textureDataSize);
     304           0 :         deviceCommandContext->UploadTexture(
     305           0 :             m_SmoothTextures[1].get(), m_TextureFormat, texData.get(), textureDataSize);
     306             :     }
     307             : 
     308           0 :     deviceCommandContext->UploadTexture(
     309           0 :         m_Texture.get(), m_TextureFormat, texData.get(), textureDataSize);
     310             : 
     311           0 :     texData.reset();
     312             : 
     313             :     {
     314             :         // Texture matrix: We want to map
     315             :         //   world pos (0, y, 0)  (i.e. first vertex)
     316             :         //     onto texcoord (0.5/texsize, 0.5/texsize)  (i.e. middle of first texel);
     317             :         //   world pos ((mapsize-1)*cellsize, y, (mapsize-1)*cellsize)  (i.e. last vertex)
     318             :         //     onto texcoord ((mapsize-0.5) / texsize, (mapsize-0.5) / texsize)  (i.e. middle of last texel)
     319             : 
     320           0 :         float s = (m_MapSize-1) / static_cast<float>(textureSize * (m_MapSize-1) * LOS_TILE_SIZE);
     321           0 :         float t = 0.5f / textureSize;
     322           0 :         m_TextureMatrix.SetZero();
     323           0 :         m_TextureMatrix._11 = s;
     324           0 :         m_TextureMatrix._23 = s;
     325           0 :         m_TextureMatrix._14 = t;
     326           0 :         m_TextureMatrix._24 = t;
     327           0 :         m_TextureMatrix._44 = 1;
     328             :     }
     329             : 
     330             :     {
     331             :         // Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize)
     332             : 
     333           0 :         float s = m_MapSize / (float)textureSize;
     334           0 :         m_MinimapTextureMatrix.SetZero();
     335           0 :         m_MinimapTextureMatrix._11 = s;
     336           0 :         m_MinimapTextureMatrix._22 = s;
     337           0 :         m_MinimapTextureMatrix._44 = 1;
     338             :     }
     339             : }
     340             : 
     341           0 : void CLOSTexture::RecomputeTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
     342             : {
     343             :     // If the map was resized, delete and regenerate the texture
     344           0 :     if (m_Texture)
     345             :     {
     346           0 :         CmpPtr<ICmpRangeManager> cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
     347           0 :         if (!cmpRangeManager || m_MapSize != cmpRangeManager->GetVerticesPerSide())
     348           0 :             DeleteTexture();
     349             :     }
     350             : 
     351           0 :     bool recreated = false;
     352           0 :     if (!m_Texture)
     353             :     {
     354           0 :         ConstructTexture(deviceCommandContext);
     355           0 :         recreated = true;
     356             :     }
     357             : 
     358           0 :     PROFILE("recompute LOS texture");
     359             : 
     360           0 :     CmpPtr<ICmpRangeManager> cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
     361           0 :     if (!cmpRangeManager)
     362           0 :         return;
     363             : 
     364             :     size_t pitch;
     365           0 :     const size_t dataSize = GetBitmapSize(m_MapSize, m_MapSize, &pitch);
     366           0 :     ENSURE(pitch * m_MapSize <= dataSize);
     367             :     std::unique_ptr<u8[]> losData = std::make_unique<u8[]>(
     368           0 :         dataSize * m_TextureFormatStride);
     369             : 
     370           0 :     CLosQuerier los(cmpRangeManager->GetLosQuerier(m_Simulation.GetSimContext().GetCurrentDisplayedPlayer()));
     371             : 
     372           0 :     GenerateBitmap(los, &losData[0], m_MapSize, m_MapSize, pitch);
     373             : 
     374             :     // GenerateBitmap writes data tightly packed and we need to offset it to fit
     375             :     // into the texture format properly.
     376           0 :     const size_t textureDataPitch = pitch * m_TextureFormatStride;
     377           0 :     if (m_TextureFormatStride > 1)
     378             :     {
     379             :         // We skip the last byte because it will be first in our order and we
     380             :         // don't need to move it.
     381           0 :         for (size_t index = 0; index + 1 < dataSize; ++index)
     382             :         {
     383           0 :             const size_t oldAddress = dataSize - 1 - index;
     384           0 :             const size_t newAddress = oldAddress * m_TextureFormatStride;
     385           0 :             losData[newAddress] = losData[oldAddress];
     386           0 :             losData[oldAddress] = 0;
     387             :         }
     388             :     }
     389             : 
     390           0 :     if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS() && recreated)
     391             :     {
     392           0 :         deviceCommandContext->UploadTextureRegion(
     393           0 :             m_SmoothTextures[0].get(), m_TextureFormat, losData.get(),
     394           0 :             textureDataPitch * m_MapSize, 0, 0, pitch, m_MapSize);
     395           0 :         deviceCommandContext->UploadTextureRegion(
     396           0 :             m_SmoothTextures[1].get(), m_TextureFormat, losData.get(),
     397           0 :             textureDataPitch * m_MapSize, 0, 0, pitch, m_MapSize);
     398             :     }
     399             : 
     400           0 :     deviceCommandContext->UploadTextureRegion(
     401           0 :         m_Texture.get(), m_TextureFormat, losData.get(),
     402           0 :         textureDataPitch * m_MapSize, 0, 0, pitch, m_MapSize);
     403             : }
     404             : 
     405           1 : size_t CLOSTexture::GetBitmapSize(size_t w, size_t h, size_t* pitch)
     406             : {
     407           1 :     *pitch = round_up(w + g_BlurSize - 1, g_SubTextureAlignment);
     408           1 :     return *pitch * (h + g_BlurSize - 1);
     409             : }
     410             : 
     411           1 : void CLOSTexture::GenerateBitmap(const CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch)
     412             : {
     413           1 :     u8 *dataPtr = losData;
     414             : 
     415             :     // Initialise the top padding
     416           4 :     for (size_t j = 0; j < g_BlurSize/2; ++j)
     417          51 :         for (size_t i = 0; i < pitch; ++i)
     418          48 :             *dataPtr++ = 0;
     419             : 
     420           9 :     for (size_t j = 0; j < h; ++j)
     421             :     {
     422             :         // Initialise the left padding
     423          32 :         for (size_t i = 0; i < g_BlurSize/2; ++i)
     424          24 :             *dataPtr++ = 0;
     425             : 
     426             :         // Fill in the visibility data
     427          72 :         for (size_t i = 0; i < w; ++i)
     428             :         {
     429          64 :             if (los.IsVisible_UncheckedRange(i, j))
     430          10 :                 *dataPtr++ = 255;
     431          54 :             else if (los.IsExplored_UncheckedRange(i, j))
     432           0 :                 *dataPtr++ = 127;
     433             :             else
     434          54 :                 *dataPtr++ = 0;
     435             :         }
     436             : 
     437             :         // Initialise the right padding
     438          48 :         for (size_t i = 0; i < pitch - w - g_BlurSize/2; ++i)
     439          40 :             *dataPtr++ = 0;
     440             :     }
     441             : 
     442             :     // Initialise the bottom padding
     443           4 :     for (size_t j = 0; j < g_BlurSize/2; ++j)
     444          51 :         for (size_t i = 0; i < pitch; ++i)
     445          48 :             *dataPtr++ = 0;
     446             : 
     447             :     // Horizontal blur:
     448             : 
     449           9 :     for (size_t j = g_BlurSize/2; j < h + g_BlurSize/2; ++j)
     450             :     {
     451          72 :         for (size_t i = 0; i < w; ++i)
     452             :         {
     453          64 :             u8* d = &losData[i+j*pitch];
     454          64 :             *d = (
     455         128 :                 1*d[0] +
     456         128 :                 6*d[1] +
     457         128 :                 15*d[2] +
     458         128 :                 20*d[3] +
     459         128 :                 15*d[4] +
     460         128 :                 6*d[5] +
     461          64 :                 1*d[6]
     462          64 :             ) / 64;
     463             :         }
     464             :     }
     465             : 
     466             :     // Vertical blur:
     467             : 
     468           9 :     for (size_t j = 0; j < h; ++j)
     469             :     {
     470          72 :         for (size_t i = 0; i < w; ++i)
     471             :         {
     472          64 :             u8* d = &losData[i+j*pitch];
     473          64 :             *d = (
     474         128 :                 1*d[0*pitch] +
     475         128 :                 6*d[1*pitch] +
     476         128 :                 15*d[2*pitch] +
     477         128 :                 20*d[3*pitch] +
     478         128 :                 15*d[4*pitch] +
     479         128 :                 6*d[5*pitch] +
     480          64 :                 1*d[6*pitch]
     481          64 :             ) / 64;
     482             :         }
     483             :     }
     484           4 : }

Generated by: LCOV version 1.13