1 /* Copyright (C) 2023 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "LOSTexture.h"
22 #include "graphics/ShaderManager.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"
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"
41 The LOS bitmap is computed with one value per LOS vertex, based on
42 CCmpRangeManager's visibility information.
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.
50 The blurred bitmap is then uploaded into a GL texture for use by the renderer.
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;
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;
65 CLOSTexture::CLOSTexture(CSimulation2
& simulation
)
66 : m_Simulation(simulation
)
68 if (CRenderer::IsInitialised() && g_RenderingOptions
.GetSmoothLOS())
72 CLOSTexture::~CLOSTexture()
74 m_SmoothFramebuffers
[0].reset();
75 m_SmoothFramebuffers
[1].reset();
81 // Create the LOS texture engine. Should be ran only once.
82 bool CLOSTexture::CreateShader()
84 m_SmoothTech
= g_Renderer
.GetShaderManager().LoadEffect(str_los_interp
);
85 m_ShaderInitialized
= m_SmoothTech
&& m_SmoothTech
->GetShader();
87 if (!m_ShaderInitialized
)
89 LOGERROR("Failed to load SmoothLOS shader, disabling.");
90 g_RenderingOptions
.SetSmoothLOS(false);
94 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}
102 m_VertexInputLayout
= g_Renderer
.GetVertexInputLayout(attributes
);
107 void CLOSTexture::DeleteTexture()
110 m_SmoothTextures
[0].reset();
111 m_SmoothTextures
[1].reset();
114 void CLOSTexture::MakeDirty()
119 Renderer::Backend::ITexture
* CLOSTexture::GetTextureSmooth()
121 if (CRenderer::IsInitialised() && !g_RenderingOptions
.GetSmoothLOS())
124 return m_SmoothTextures
[m_WhichTexture
].get();
127 void CLOSTexture::InterpolateLOS(Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
)
129 const bool skipSmoothLOS
= CRenderer::IsInitialised() && !g_RenderingOptions
.GetSmoothLOS();
130 if (!skipSmoothLOS
&& !m_ShaderInitialized
)
135 // RecomputeTexture will not cause the ConstructTexture to run.
136 // Force the textures to be created.
138 ConstructTexture(deviceCommandContext
);
144 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 m_LastTextureRecomputeTime
= timer_Time() - g_Renderer
.GetTimeManager().GetFrameDelta();
148 m_WhichTexture
= 1u - m_WhichTexture
;
155 GPU_SCOPED_LABEL(deviceCommandContext
, "Render LOS texture");
156 deviceCommandContext
->BeginFramebufferPass(m_SmoothFramebuffers
[m_WhichTexture
].get());
158 deviceCommandContext
->SetGraphicsPipelineState(
159 m_SmoothTech
->GetGraphicsPipelineState());
160 deviceCommandContext
->BeginPass();
162 Renderer::Backend::IShaderProgram
* shader
= m_SmoothTech
->GetShader();
164 deviceCommandContext
->SetTexture(
165 shader
->GetBindingSlot(str_losTex1
), m_Texture
.get());
166 deviceCommandContext
->SetTexture(
167 shader
->GetBindingSlot(str_losTex2
), m_SmoothTextures
[1u - m_WhichTexture
].get());
169 const float delta
= Clamp
<float>(
170 (timer_Time() - m_LastTextureRecomputeTime
) * 2.0f
, 0.0f
, 1.0f
);
171 deviceCommandContext
->SetUniform(shader
->GetBindingSlot(str_delta
), delta
);
173 Renderer::Backend::IDeviceCommandContext::Rect viewportRect
{};
174 viewportRect
.width
= m_Texture
->GetWidth();
175 viewportRect
.height
= m_Texture
->GetHeight();
176 deviceCommandContext
->SetViewports(1, &viewportRect
);
179 deviceCommandContext
->GetDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN
;
180 const float bottomV
= flip
? 1.0 : 0.0f
;
181 const float topV
= flip
? 0.0f
: 1.0f
;
204 deviceCommandContext
->SetVertexInputLayout(m_VertexInputLayout
);
206 deviceCommandContext
->SetVertexBufferData(
207 0, quadVerts
, std::size(quadVerts
) * sizeof(quadVerts
[0]));
208 deviceCommandContext
->SetVertexBufferData(
209 1, quadTex
, std::size(quadTex
) * sizeof(quadTex
[0]));
211 deviceCommandContext
->Draw(0, 6);
213 deviceCommandContext
->EndPass();
214 deviceCommandContext
->EndFramebufferPass();
218 Renderer::Backend::ITexture
* CLOSTexture::GetTexture()
221 return m_Texture
.get();
224 const CMatrix3D
& CLOSTexture::GetTextureMatrix()
227 return m_TextureMatrix
;
230 const CMatrix3D
& CLOSTexture::GetMinimapTextureMatrix()
233 return m_MinimapTextureMatrix
;
236 void CLOSTexture::ConstructTexture(Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
)
238 CmpPtr
<ICmpRangeManager
> cmpRangeManager(m_Simulation
, SYSTEM_ENTITY
);
239 if (!cmpRangeManager
)
242 m_MapSize
= cmpRangeManager
->GetVerticesPerSide();
244 const size_t textureSize
= round_up_to_pow2(round_up((size_t)m_MapSize
+ g_BlurSize
- 1, g_SubTextureAlignment
));
246 Renderer::Backend::IDevice
* backendDevice
= deviceCommandContext
->GetDevice();
248 const Renderer::Backend::Sampler::Desc defaultSamplerDesc
=
249 Renderer::Backend::Sampler::MakeDefaultSampler(
250 Renderer::Backend::Sampler::Filter::LINEAR
,
251 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE
);
253 if (backendDevice
->IsFramebufferFormatSupported(Renderer::Backend::Format::R8_UNORM
))
255 m_TextureFormat
= Renderer::Backend::Format::R8_UNORM
;
256 m_TextureFormatStride
= 1;
260 m_TextureFormat
= Renderer::Backend::Format::R8G8B8A8_UNORM
;
261 m_TextureFormatStride
= 4;
264 m_Texture
= backendDevice
->CreateTexture2D("LOSTexture",
265 Renderer::Backend::ITexture::Usage::TRANSFER_DST
|
266 Renderer::Backend::ITexture::Usage::SAMPLED
,
267 m_TextureFormat
, textureSize
, textureSize
, defaultSamplerDesc
);
269 // Initialise texture with SoD color, for the areas we don't
270 // overwrite with uploading later.
271 const size_t textureDataSize
= textureSize
* textureSize
* m_TextureFormatStride
;
272 std::unique_ptr
<u8
[]> texData
= std::make_unique
<u8
[]>(textureDataSize
);
273 memset(texData
.get(), 0x00, textureDataSize
);
275 if (CRenderer::IsInitialised() && g_RenderingOptions
.GetSmoothLOS())
277 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 m_SmoothTextures
[0] = backendDevice
->CreateTexture2D("LOSSmoothTexture0",
282 usage
, m_TextureFormat
, textureSize
, textureSize
, defaultSamplerDesc
);
283 m_SmoothTextures
[1] = backendDevice
->CreateTexture2D("LOSSmoothTexture1",
284 usage
, m_TextureFormat
, textureSize
, textureSize
, defaultSamplerDesc
);
286 Renderer::Backend::SColorAttachment colorAttachment
{};
287 colorAttachment
.texture
= m_SmoothTextures
[0].get();
288 colorAttachment
.loadOp
= Renderer::Backend::AttachmentLoadOp::DONT_CARE
;
289 colorAttachment
.storeOp
= Renderer::Backend::AttachmentStoreOp::STORE
;
290 colorAttachment
.clearColor
= CColor
{0.0f
, 0.0f
, 0.0f
, 0.0f
};
291 m_SmoothFramebuffers
[0] = backendDevice
->CreateFramebuffer(
292 "LOSSmoothFramebuffer0", &colorAttachment
, nullptr);
293 colorAttachment
.texture
= m_SmoothTextures
[1].get();
294 m_SmoothFramebuffers
[1] = backendDevice
->CreateFramebuffer(
295 "LOSSmoothFramebuffer1", &colorAttachment
, nullptr);
296 if (!m_SmoothFramebuffers
[0] || !m_SmoothFramebuffers
[1])
298 LOGERROR("Failed to create LOS framebuffers");
299 g_RenderingOptions
.SetSmoothLOS(false);
302 deviceCommandContext
->UploadTexture(
303 m_SmoothTextures
[0].get(), m_TextureFormat
, texData
.get(), textureDataSize
);
304 deviceCommandContext
->UploadTexture(
305 m_SmoothTextures
[1].get(), m_TextureFormat
, texData
.get(), textureDataSize
);
308 deviceCommandContext
->UploadTexture(
309 m_Texture
.get(), m_TextureFormat
, texData
.get(), textureDataSize
);
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)
320 float s
= (m_MapSize
-1) / static_cast<float>(textureSize
* (m_MapSize
-1) * LOS_TILE_SIZE
);
321 float t
= 0.5f
/ textureSize
;
322 m_TextureMatrix
.SetZero();
323 m_TextureMatrix
._11
= s
;
324 m_TextureMatrix
._23
= s
;
325 m_TextureMatrix
._14
= t
;
326 m_TextureMatrix
._24
= t
;
327 m_TextureMatrix
._44
= 1;
331 // Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize)
333 float s
= m_MapSize
/ (float)textureSize
;
334 m_MinimapTextureMatrix
.SetZero();
335 m_MinimapTextureMatrix
._11
= s
;
336 m_MinimapTextureMatrix
._22
= s
;
337 m_MinimapTextureMatrix
._44
= 1;
341 void CLOSTexture::RecomputeTexture(Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
)
343 // If the map was resized, delete and regenerate the texture
346 CmpPtr
<ICmpRangeManager
> cmpRangeManager(m_Simulation
, SYSTEM_ENTITY
);
347 if (!cmpRangeManager
|| m_MapSize
!= cmpRangeManager
->GetVerticesPerSide())
351 bool recreated
= false;
354 ConstructTexture(deviceCommandContext
);
358 PROFILE("recompute LOS texture");
360 CmpPtr
<ICmpRangeManager
> cmpRangeManager(m_Simulation
, SYSTEM_ENTITY
);
361 if (!cmpRangeManager
)
365 const size_t dataSize
= GetBitmapSize(m_MapSize
, m_MapSize
, &pitch
);
366 ENSURE(pitch
* m_MapSize
<= dataSize
);
367 std::unique_ptr
<u8
[]> losData
= std::make_unique
<u8
[]>(
368 dataSize
* m_TextureFormatStride
);
370 CLosQuerier
los(cmpRangeManager
->GetLosQuerier(m_Simulation
.GetSimContext().GetCurrentDisplayedPlayer()));
372 GenerateBitmap(los
, &losData
[0], m_MapSize
, m_MapSize
, pitch
);
374 // GenerateBitmap writes data tightly packed and we need to offset it to fit
375 // into the texture format properly.
376 const size_t textureDataPitch
= pitch
* m_TextureFormatStride
;
377 if (m_TextureFormatStride
> 1)
379 // We skip the last byte because it will be first in our order and we
380 // don't need to move it.
381 for (size_t index
= 0; index
+ 1 < dataSize
; ++index
)
383 const size_t oldAddress
= dataSize
- 1 - index
;
384 const size_t newAddress
= oldAddress
* m_TextureFormatStride
;
385 losData
[newAddress
] = losData
[oldAddress
];
386 losData
[oldAddress
] = 0;
390 if (CRenderer::IsInitialised() && g_RenderingOptions
.GetSmoothLOS() && recreated
)
392 deviceCommandContext
->UploadTextureRegion(
393 m_SmoothTextures
[0].get(), m_TextureFormat
, losData
.get(),
394 textureDataPitch
* m_MapSize
, 0, 0, pitch
, m_MapSize
);
395 deviceCommandContext
->UploadTextureRegion(
396 m_SmoothTextures
[1].get(), m_TextureFormat
, losData
.get(),
397 textureDataPitch
* m_MapSize
, 0, 0, pitch
, m_MapSize
);
400 deviceCommandContext
->UploadTextureRegion(
401 m_Texture
.get(), m_TextureFormat
, losData
.get(),
402 textureDataPitch
* m_MapSize
, 0, 0, pitch
, m_MapSize
);
405 size_t CLOSTexture::GetBitmapSize(size_t w
, size_t h
, size_t* pitch
)
407 *pitch
= round_up(w
+ g_BlurSize
- 1, g_SubTextureAlignment
);
408 return *pitch
* (h
+ g_BlurSize
- 1);
411 void CLOSTexture::GenerateBitmap(const CLosQuerier
& los
, u8
* losData
, size_t w
, size_t h
, size_t pitch
)
413 u8
*dataPtr
= losData
;
415 // Initialise the top padding
416 for (size_t j
= 0; j
< g_BlurSize
/2; ++j
)
417 for (size_t i
= 0; i
< pitch
; ++i
)
420 for (size_t j
= 0; j
< h
; ++j
)
422 // Initialise the left padding
423 for (size_t i
= 0; i
< g_BlurSize
/2; ++i
)
426 // Fill in the visibility data
427 for (size_t i
= 0; i
< w
; ++i
)
429 if (los
.IsVisible_UncheckedRange(i
, j
))
431 else if (los
.IsExplored_UncheckedRange(i
, j
))
437 // Initialise the right padding
438 for (size_t i
= 0; i
< pitch
- w
- g_BlurSize
/2; ++i
)
442 // Initialise the bottom padding
443 for (size_t j
= 0; j
< g_BlurSize
/2; ++j
)
444 for (size_t i
= 0; i
< pitch
; ++i
)
449 for (size_t j
= g_BlurSize
/2; j
< h
+ g_BlurSize
/2; ++j
)
451 for (size_t i
= 0; i
< w
; ++i
)
453 u8
* d
= &losData
[i
+j
*pitch
];
468 for (size_t j
= 0; j
< h
; ++j
)
470 for (size_t i
= 0; i
< w
; ++i
)
472 u8
* d
= &losData
[i
+j
*pitch
];