Index: ps/trunk/binaries/data/mods/public/shaders/arb/model_common.fp =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/arb/model_common.fp (revision 26921) +++ ps/trunk/binaries/data/mods/public/shaders/arb/model_common.fp (revision 26922) @@ -1,157 +1,159 @@ !!ARBfp1.0 #if USE_FP_SHADOW OPTION ARB_fragment_program_shadow; #endif ATTRIB v_tex = fragment.texcoord[0]; ATTRIB v_shadow = fragment.texcoord[1]; ATTRIB v_los = fragment.texcoord[2]; #if USE_OBJECTCOLOR PARAM objectColor = program.local[0]; #else #if USE_PLAYERCOLOR PARAM playerColor = program.local[0]; #endif #endif PARAM shadingColor = program.local[1]; PARAM ambient = program.local[2]; #if USE_FP_SHADOW && USE_SHADOW_PCF PARAM shadowScale = program.local[3]; TEMP offset, size, weight, depthSample; #endif #if USE_SPECULAR ATTRIB v_normal = fragment.texcoord[3]; ATTRIB v_half = fragment.texcoord[4]; PARAM specularPower = program.local[4]; PARAM specularColor = program.local[5]; PARAM sunColor = program.local[6]; #endif TEMP tex; TEMP texdiffuse; TEMP sundiffuse; TEMP temp; TEMP color; TEMP shadow; TEX tex, v_tex, texture[0], 2D; // Alpha-test as early as possible #ifdef REQUIRE_ALPHA_GEQUAL SUB temp.x, tex.a, REQUIRE_ALPHA_GEQUAL; KIL temp.x; // discard if < 0.0 #endif #if USE_TRANSPARENT MOV result.color.a, tex; #endif // Apply coloring based on texture alpha #if USE_OBJECTCOLOR LRP temp.rgb, objectColor, 1.0, tex.a; MUL texdiffuse.rgb, tex, temp; #else #if USE_PLAYERCOLOR LRP temp.rgb, playerColor, 1.0, tex.a; MUL texdiffuse.rgb, tex, temp; #else MOV texdiffuse.rgb, tex; #endif #endif #if USE_SPECULAR // specular = sunColor * specularColor * pow(max(0.0, dot(normalize(v_normal), v_half)), specularPower); TEMP specular; TEMP normal; MUL specular.rgb, specularColor, sunColor; DP3 normal.w, v_normal, v_normal; RSQ normal.w, normal.w; MUL normal.xyz, v_normal, normal.w; DP3_SAT temp.y, normal, v_half; // temp^p = (2^lg2(temp))^p = 2^(lg2(temp)*p) LG2 temp.y, temp.y; MUL temp.y, temp.y, specularPower.x; EX2 temp.y, temp.y; // TODO: why not just use POW here? (should test performance first) MUL specular.rgb, specular, temp.y; #endif // color = (texdiffuse * sundiffuse + specular) * get_shadow() + texdiffuse * ambient; // (sundiffuse is 2*fragment.color due to clamp-avoidance in the vertex program) #if USE_SHADOW && !DISABLE_RECEIVE_SHADOWS TEMP shadowBias; TEMP biasedShdw; MOV shadowBias.x, 0.003; MOV biasedShdw, v_shadow; SUB biasedShdw.z, v_shadow.z, shadowBias.x; #if USE_FP_SHADOW #if USE_SHADOW_PCF SUB offset.xy, v_shadow, 0.5; FRC offset.xy, offset; ADD size.xy, offset, 1.0; SUB size.zw, 2.0, offset.xyxy; MAD offset.xy, -0.5, offset, v_shadow; MOV offset.z, biasedShdw.z; ADD weight, { 1.0, 1.0, -0.5, -0.5 }, offset.xyxy; MUL weight, weight, shadowScale.zwzw; MOV offset.xy, weight.zwww; TEX depthSample.r, offset, texture[1], SHADOW2D; MOV temp.x, depthSample.r; MOV offset.x, weight.x; TEX depthSample.r, offset, texture[1], SHADOW2D; MOV temp.y, depthSample.r; MOV offset.xy, weight.zyyy; TEX depthSample.r, offset, texture[1], SHADOW2D; MOV temp.z, depthSample.r; MOV offset.x, weight.x; TEX depthSample.r, offset, texture[1], SHADOW2D; MOV temp.w, depthSample.r; MUL size, size.zxzx, size.wwyy; DP4 shadow.x, temp, size; MUL shadow.x, shadow.x, 0.111111; #else TEX shadow.x, biasedShdw, texture[1], SHADOW2D; #endif #else TEX tex, v_shadow, texture[1], 2D; MOV_SAT temp.z, biasedShdw.z; SGE shadow.x, tex.x, temp.z; #endif MUL sundiffuse.rgb, fragment.color, 2.0; #if USE_SPECULAR MAD color.rgb, texdiffuse, sundiffuse, specular; MUL temp.rgb, texdiffuse, ambient; MAD color.rgb, color, shadow.x, temp; #else MAD temp.rgb, sundiffuse, shadow.x, ambient; MUL color.rgb, texdiffuse, temp; #endif #else #if USE_SPECULAR MAD temp.rgb, fragment.color, 2.0, ambient; MAD color.rgb, texdiffuse, temp, specular; #else MAD temp.rgb, fragment.color, 2.0, ambient; MUL color.rgb, texdiffuse, temp; #endif #endif #if !IGNORE_LOS // Multiply everything by the LOS texture TEX tex.r, v_los, texture[2], 2D; + SUB tex.r, tex.r, 0.03; + MUL tex.r, tex.r, 0.97; MUL color.rgb, color, tex.r; #endif MUL result.color.rgb, color, shadingColor; END Index: ps/trunk/binaries/data/mods/public/shaders/arb/overlayline.fp =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/arb/overlayline.fp (revision 26921) +++ ps/trunk/binaries/data/mods/public/shaders/arb/overlayline.fp (revision 26922) @@ -1,32 +1,34 @@ !!ARBfp1.0 PARAM objectColor = program.local[0]; TEMP base; TEMP mask; TEMP color; // Combine base texture and color, using mask texture TEX base, fragment.texcoord[0], texture[0], 2D; TEX mask, fragment.texcoord[0], texture[1], 2D; #if USE_OBJECTCOLOR LRP color.rgb, mask, objectColor, base; #else LRP color.rgb, mask, fragment.color, base; #endif #if IGNORE_LOS MOV result.color.rgb, color; #else // Multiply RGB by LOS texture (red channel) TEMP los; TEX los, fragment.texcoord[1], texture[2], 2D; + SUB los.r, los.r, 0.03; + MUL los.r, los.r, 0.97; MUL result.color.rgb, color, los.r; #endif // Use alpha from base texture, combined with the object color/fragment alpha. #if USE_OBJECTCOLOR MUL result.color.a, objectColor.a, base.a; #else MUL result.color.a, fragment.color.a, base.a; #endif END Index: ps/trunk/binaries/data/mods/public/shaders/arb/particle.fp =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/arb/particle.fp (revision 26921) +++ ps/trunk/binaries/data/mods/public/shaders/arb/particle.fp (revision 26922) @@ -1,23 +1,25 @@ !!ARBfp1.0 ATTRIB v_los = fragment.texcoord[1]; PARAM sunColor = program.local[0]; TEMP tex, losTex, color; TEX tex, fragment.texcoord[0], texture[0], 2D; TEMP temp; MOV temp, 0.5; ADD color.rgb, fragment.color, sunColor; MUL color.rgb, color, temp; MUL color.rgb, color, tex; // Multiply everything by the LOS texture TEX losTex, v_los, texture[1], 2D; +SUB losTex.r, losTex.r, 0.03; +MUL losTex.r, losTex.r, 0.97; MUL result.color.rgb, color, losTex.r; MUL result.color.a, tex, fragment.color; END Index: ps/trunk/binaries/data/mods/public/shaders/arb/terrain_common.fp =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/arb/terrain_common.fp (revision 26921) +++ ps/trunk/binaries/data/mods/public/shaders/arb/terrain_common.fp (revision 26922) @@ -1,99 +1,101 @@ !!ARBfp1.0 #if USE_FP_SHADOW OPTION ARB_fragment_program_shadow; #endif PARAM ambient = program.local[0]; #if DECAL PARAM shadingColor = program.local[1]; #endif #if USE_FP_SHADOW && USE_SHADOW_PCF PARAM shadowScale = program.local[2]; TEMP offset, size, weight, depthSample; #endif TEMP tex; TEMP temp; TEMP diffuse; TEMP color; #if BLEND // Use alpha from blend texture // TODO: maybe we should invert the texture instead of doing SUB here? TEX tex.a, fragment.texcoord[1], texture[1], 2D; SUB result.color.a, 1.0, tex.a; #endif // Load diffuse color TEX color, fragment.texcoord[0], texture[0], 2D; #if DECAL // Use alpha from main texture MOV result.color.a, color; #endif // Compute color = texture * (ambient + diffuse*shadow) // (diffuse is 2*fragment.color due to clamp-avoidance in the vertex program) #if USE_SHADOW && !DISABLE_RECEIVE_SHADOWS TEMP shadowBias; TEMP biasedShdw; MOV shadowBias.x, 0.0005; MOV biasedShdw, fragment.texcoord[2]; SUB biasedShdw.z, fragment.texcoord[2].z, shadowBias.x; #if USE_FP_SHADOW #if USE_SHADOW_PCF SUB offset.xy, fragment.texcoord[2], 0.5; FRC offset.xy, offset; ADD size.xy, offset, 1.0; SUB size.zw, 2.0, offset.xyxy; MAD offset.xy, -0.5, offset, fragment.texcoord[2]; MOV offset.z, biasedShdw.z; ADD weight, { 1.0, 1.0, -0.5, -0.5 }, offset.xyxy; MUL weight, weight, shadowScale.zwzw; MOV offset.xy, weight.zwww; TEX depthSample.r, offset, texture[2], SHADOW2D; MOV temp.x, depthSample.r; MOV offset.x, weight.x; TEX depthSample.r, offset, texture[2], SHADOW2D; MOV temp.y, depthSample.r; MOV offset.xy, weight.zyyy; TEX depthSample.r, offset, texture[2], SHADOW2D; MOV temp.z, depthSample.r; MOV offset.x, weight.x; TEX depthSample.r, offset, texture[2], SHADOW2D; MOV temp.w, depthSample.r; MUL size, size.zxzx, size.wwyy; DP4 temp.x, temp, size; MUL temp.x, temp.x, 0.111111; #else TEX temp.x, biasedShdw, texture[2], SHADOW2D; #endif #else TEX tex, fragment.texcoord[2], texture[2], 2D; MOV_SAT temp.z, biasedShdw.z; SGE temp.x, tex.x, temp.z; #endif MUL diffuse.rgb, fragment.color, 2.0; MAD temp.rgb, diffuse, temp.x, ambient; MUL color.rgb, color, temp; #else MAD temp.rgb, fragment.color, 2.0, ambient; MUL color.rgb, color, temp; #endif // Multiply everything by the LOS texture TEX tex.r, fragment.texcoord[3], texture[3], 2D; +SUB tex.r, tex.r, 0.03; +MUL tex.r, tex.r, 0.97; MUL color.rgb, color, tex.r; #if DECAL MUL result.color.rgb, color, shadingColor; #else MOV result.color.rgb, color; #endif END Index: ps/trunk/binaries/data/mods/public/shaders/arb/water_simple.fp =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/arb/water_simple.fp (revision 26921) +++ ps/trunk/binaries/data/mods/public/shaders/arb/water_simple.fp (revision 26922) @@ -1,17 +1,19 @@ !!ARBfp1.0 PARAM color = program.local[2]; ATTRIB v_coords = fragment.texcoord[0]; ATTRIB v_losCoords = fragment.texcoord[1]; TEMP diffuse; TEX diffuse, v_coords, texture[0], 2D; MUL diffuse, diffuse, color; TEMP los; TEX los, v_losCoords, texture[1], 2D; +SUB los.r, los.r, 0.03; +MUL los.r, los.r, 0.97; MUL diffuse, diffuse, los.r; MOV result.color, diffuse; END Index: ps/trunk/source/graphics/LOSTexture.cpp =================================================================== --- ps/trunk/source/graphics/LOSTexture.cpp (revision 26921) +++ ps/trunk/source/graphics/LOSTexture.cpp (revision 26922) @@ -1,470 +1,472 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "LOSTexture.h" #include "graphics/ShaderManager.h" #include "lib/bits.h" #include "lib/config2.h" +#include "lib/timer.h" +#include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/Profile.h" #include "renderer/backend/IDevice.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/TimeManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/helpers/Los.h" /* The LOS bitmap is computed with one value per LOS vertex, based on CCmpRangeManager's visibility information. The bitmap is then blurred using an NxN filter (in particular a 7-tap Binomial filter as an efficient integral approximation of a Gaussian). To implement the blur efficiently without using extra memory for a second copy of the bitmap, we generate the bitmap with (N-1)/2 pixels of padding on each side, then the blur shifts the image back into the corner. The blurred bitmap is then uploaded into a GL texture for use by the renderer. */ // Blur with a NxN filter, where N = g_BlurSize must be an odd number. // Keep it in relation to the number of impassable tiles in MAP_EDGE_TILES. static const size_t g_BlurSize = 7; // Alignment (in bytes) of the pixel data passed into texture uploading. // This must be a multiple of GL_UNPACK_ALIGNMENT, which ought to be 1 (since // that's what we set it to) but in some weird cases appears to have a different // value. (See Trac #2594). Multiples of 4 are possibly good for performance anyway. static const size_t g_SubTextureAlignment = 4; CLOSTexture::CLOSTexture(CSimulation2& simulation) : m_Simulation(simulation) { if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS()) CreateShader(); } CLOSTexture::~CLOSTexture() { m_SmoothFramebuffers[0].reset(); m_SmoothFramebuffers[1].reset(); if (m_Texture) DeleteTexture(); } // Create the LOS texture engine. Should be ran only once. bool CLOSTexture::CreateShader() { m_SmoothTech = g_Renderer.GetShaderManager().LoadEffect(str_los_interp); Renderer::Backend::IShaderProgram* shader = m_SmoothTech->GetShader(); m_ShaderInitialized = m_SmoothTech && shader; if (!m_ShaderInitialized) { LOGERROR("Failed to load SmoothLOS shader, disabling."); g_RenderingOptions.SetSmoothLOS(false); return false; } return true; } void CLOSTexture::DeleteTexture() { m_Texture.reset(); m_SmoothTextures[0].reset(); m_SmoothTextures[1].reset(); } void CLOSTexture::MakeDirty() { m_Dirty = true; } Renderer::Backend::ITexture* CLOSTexture::GetTextureSmooth() { if (CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS()) return GetTexture(); else return m_SmoothTextures[m_WhichTexture].get(); } void CLOSTexture::InterpolateLOS(Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { const bool skipSmoothLOS = CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS(); if (!skipSmoothLOS && !m_ShaderInitialized) { if (!CreateShader()) return; // RecomputeTexture will not cause the ConstructTexture to run. // Force the textures to be created. DeleteTexture(); ConstructTexture(deviceCommandContext); m_Dirty = true; } if (m_Dirty) { RecomputeTexture(deviceCommandContext); + m_LastTextureRecomputeTime = timer_Time(); + m_WhichTexture = 1u - m_WhichTexture; m_Dirty = false; } if (skipSmoothLOS) return; GPU_SCOPED_LABEL(deviceCommandContext, "Render LOS texture"); deviceCommandContext->SetFramebuffer(m_SmoothFramebuffers[m_WhichTexture].get()); deviceCommandContext->SetGraphicsPipelineState( m_SmoothTech->GetGraphicsPipelineStateDesc()); deviceCommandContext->BeginPass(); Renderer::Backend::IShaderProgram* shader = m_SmoothTech->GetShader(); deviceCommandContext->SetTexture( shader->GetBindingSlot(str_losTex1), m_Texture.get()); deviceCommandContext->SetTexture( - shader->GetBindingSlot(str_losTex2), m_SmoothTextures[m_WhichTexture].get()); + shader->GetBindingSlot(str_losTex2), m_SmoothTextures[1u - m_WhichTexture].get()); - deviceCommandContext->SetUniform( - shader->GetBindingSlot(str_delta), - static_cast(g_Renderer.GetTimeManager().GetFrameDelta() * 4.0f)); + const float delta = Clamp( + (timer_Time() - m_LastTextureRecomputeTime) * 2.0f, 0.0f, 1.0f); + deviceCommandContext->SetUniform(shader->GetBindingSlot(str_delta), delta); const SViewPort oldVp = g_Renderer.GetViewport(); const SViewPort vp = { 0, 0, static_cast(m_Texture->GetWidth()), static_cast(m_Texture->GetHeight()) }; g_Renderer.SetViewport(vp); float quadVerts[] = { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; float quadTex[] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::POSITION, Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0); deviceCommandContext->SetVertexAttributeFormat( Renderer::Backend::VertexAttributeStream::UV0, Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1); deviceCommandContext->SetVertexBufferData( 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); deviceCommandContext->SetVertexBufferData( 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); deviceCommandContext->Draw(0, 6); g_Renderer.SetViewport(oldVp); deviceCommandContext->EndPass(); deviceCommandContext->SetFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer()); - - m_WhichTexture = 1u - m_WhichTexture; } Renderer::Backend::ITexture* CLOSTexture::GetTexture() { ENSURE(!m_Dirty); return m_Texture.get(); } const CMatrix3D& CLOSTexture::GetTextureMatrix() { ENSURE(!m_Dirty); return m_TextureMatrix; } const CMatrix3D& CLOSTexture::GetMinimapTextureMatrix() { ENSURE(!m_Dirty); return m_MinimapTextureMatrix; } void CLOSTexture::ConstructTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager) return; m_MapSize = cmpRangeManager->GetVerticesPerSide(); const size_t textureSize = round_up_to_pow2(round_up((size_t)m_MapSize + g_BlurSize - 1, g_SubTextureAlignment)); Renderer::Backend::IDevice* backendDevice = deviceCommandContext->GetDevice(); const Renderer::Backend::Sampler::Desc defaultSamplerDesc = Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE); if (backendDevice->IsFramebufferFormatSupported(Renderer::Backend::Format::R8_UNORM)) { m_TextureFormat = Renderer::Backend::Format::R8_UNORM; m_TextureFormatStride = 1; } else { m_TextureFormat = Renderer::Backend::Format::R8G8B8A8_UNORM; m_TextureFormatStride = 4; } m_Texture = backendDevice->CreateTexture2D("LOSTexture", m_TextureFormat, textureSize, textureSize, defaultSamplerDesc); // Initialise texture with SoD color, for the areas we don't // overwrite with uploading later. const size_t textureDataSize = textureSize * textureSize * m_TextureFormatStride; std::unique_ptr texData = std::make_unique(textureDataSize); memset(texData.get(), 0x00, textureDataSize); if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS()) { m_SmoothTextures[0] = backendDevice->CreateTexture2D("LOSSmoothTexture0", m_TextureFormat, textureSize, textureSize, defaultSamplerDesc); m_SmoothTextures[1] = backendDevice->CreateTexture2D("LOSSmoothTexture1", m_TextureFormat, textureSize, textureSize, defaultSamplerDesc); m_SmoothFramebuffers[0] = backendDevice->CreateFramebuffer( "LOSSmoothFramebuffer0", m_SmoothTextures[0].get(), nullptr); m_SmoothFramebuffers[1] = backendDevice->CreateFramebuffer( "LOSSmoothFramebuffer1", m_SmoothTextures[1].get(), nullptr); if (!m_SmoothFramebuffers[0] || !m_SmoothFramebuffers[1]) { LOGERROR("Failed to create LOS framebuffers"); g_RenderingOptions.SetSmoothLOS(false); } deviceCommandContext->UploadTexture( m_SmoothTextures[0].get(), m_TextureFormat, texData.get(), textureDataSize); deviceCommandContext->UploadTexture( m_SmoothTextures[1].get(), m_TextureFormat, texData.get(), textureDataSize); } deviceCommandContext->UploadTexture( m_Texture.get(), m_TextureFormat, texData.get(), textureDataSize); texData.reset(); { // Texture matrix: We want to map // world pos (0, y, 0) (i.e. first vertex) // onto texcoord (0.5/texsize, 0.5/texsize) (i.e. middle of first texel); // world pos ((mapsize-1)*cellsize, y, (mapsize-1)*cellsize) (i.e. last vertex) // onto texcoord ((mapsize-0.5) / texsize, (mapsize-0.5) / texsize) (i.e. middle of last texel) float s = (m_MapSize-1) / static_cast(textureSize * (m_MapSize-1) * LOS_TILE_SIZE); float t = 0.5f / textureSize; m_TextureMatrix.SetZero(); m_TextureMatrix._11 = s; m_TextureMatrix._23 = s; m_TextureMatrix._14 = t; m_TextureMatrix._24 = t; m_TextureMatrix._44 = 1; } { // Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize) float s = m_MapSize / (float)textureSize; m_MinimapTextureMatrix.SetZero(); m_MinimapTextureMatrix._11 = s; m_MinimapTextureMatrix._22 = s; m_MinimapTextureMatrix._44 = 1; } } void CLOSTexture::RecomputeTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { // If the map was resized, delete and regenerate the texture if (m_Texture) { CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager || m_MapSize != cmpRangeManager->GetVerticesPerSide()) DeleteTexture(); } bool recreated = false; if (!m_Texture) { ConstructTexture(deviceCommandContext); recreated = true; } PROFILE("recompute LOS texture"); CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager) return; size_t pitch; const size_t dataSize = GetBitmapSize(m_MapSize, m_MapSize, &pitch); ENSURE(pitch * m_MapSize <= dataSize); std::unique_ptr losData = std::make_unique( dataSize * m_TextureFormatStride); CLosQuerier los(cmpRangeManager->GetLosQuerier(g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer())); GenerateBitmap(los, &losData[0], m_MapSize, m_MapSize, pitch); // GenerateBitmap writes data tightly packed and we need to offset it to fit // into the texture format properly. const size_t textureDataPitch = pitch * m_TextureFormatStride; if (m_TextureFormatStride > 1) { // We skip the last byte because it will be first in our order and we // don't need to move it. for (size_t index = 0; index + 1 < dataSize; ++index) { const size_t oldAddress = dataSize - 1 - index; const size_t newAddress = oldAddress * m_TextureFormatStride; losData[newAddress] = losData[oldAddress]; losData[oldAddress] = 0; } } if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS() && recreated) { deviceCommandContext->UploadTextureRegion( m_SmoothTextures[0].get(), m_TextureFormat, losData.get(), textureDataPitch * m_MapSize, 0, 0, pitch, m_MapSize); deviceCommandContext->UploadTextureRegion( m_SmoothTextures[1].get(), m_TextureFormat, losData.get(), textureDataPitch * m_MapSize, 0, 0, pitch, m_MapSize); } deviceCommandContext->UploadTextureRegion( m_Texture.get(), m_TextureFormat, losData.get(), textureDataPitch * m_MapSize, 0, 0, pitch, m_MapSize); } size_t CLOSTexture::GetBitmapSize(size_t w, size_t h, size_t* pitch) { *pitch = round_up(w + g_BlurSize - 1, g_SubTextureAlignment); return *pitch * (h + g_BlurSize - 1); } void CLOSTexture::GenerateBitmap(const CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch) { u8 *dataPtr = losData; // Initialise the top padding for (size_t j = 0; j < g_BlurSize/2; ++j) for (size_t i = 0; i < pitch; ++i) *dataPtr++ = 0; for (size_t j = 0; j < h; ++j) { // Initialise the left padding for (size_t i = 0; i < g_BlurSize/2; ++i) *dataPtr++ = 0; // Fill in the visibility data for (size_t i = 0; i < w; ++i) { if (los.IsVisible_UncheckedRange(i, j)) *dataPtr++ = 255; else if (los.IsExplored_UncheckedRange(i, j)) *dataPtr++ = 127; else *dataPtr++ = 0; } // Initialise the right padding for (size_t i = 0; i < pitch - w - g_BlurSize/2; ++i) *dataPtr++ = 0; } // Initialise the bottom padding for (size_t j = 0; j < g_BlurSize/2; ++j) for (size_t i = 0; i < pitch; ++i) *dataPtr++ = 0; // Horizontal blur: for (size_t j = g_BlurSize/2; j < h + g_BlurSize/2; ++j) { for (size_t i = 0; i < w; ++i) { u8* d = &losData[i+j*pitch]; *d = ( 1*d[0] + 6*d[1] + 15*d[2] + 20*d[3] + 15*d[4] + 6*d[5] + 1*d[6] ) / 64; } } // Vertical blur: for (size_t j = 0; j < h; ++j) { for (size_t i = 0; i < w; ++i) { u8* d = &losData[i+j*pitch]; *d = ( 1*d[0*pitch] + 6*d[1*pitch] + 15*d[2*pitch] + 20*d[3*pitch] + 15*d[4*pitch] + 6*d[5*pitch] + 1*d[6*pitch] ) / 64; } } } Index: ps/trunk/source/graphics/LOSTexture.h =================================================================== --- ps/trunk/source/graphics/LOSTexture.h (revision 26921) +++ ps/trunk/source/graphics/LOSTexture.h (revision 26922) @@ -1,115 +1,116 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_LOSTEXTURE #define INCLUDED_LOSTEXTURE #include "graphics/ShaderTechniquePtr.h" #include "maths/Matrix3D.h" #include "renderer/backend/Format.h" #include "renderer/backend/IDeviceCommandContext.h" #include "renderer/backend/IFramebuffer.h" #include "renderer/backend/ITexture.h" #include class CLosQuerier; class CSimulation2; /** * Maintains the LOS (fog-of-war / shroud-of-darkness) texture, used for * rendering and for the minimap. */ class CLOSTexture { NONCOPYABLE(CLOSTexture); friend class TestLOSTexture; public: CLOSTexture(CSimulation2& simulation); ~CLOSTexture(); /** * Marks the LOS texture as needing recomputation. Call this after each * simulation update, to ensure responsive updates. */ void MakeDirty(); /** * Recomputes the LOS texture if necessary, and returns the texture handle. * Also potentially switches the current active texture unit, and enables texturing on it. * The texture is in 8-bit ALPHA format. */ Renderer::Backend::ITexture* GetTexture(); Renderer::Backend::ITexture* GetTextureSmooth(); void InterpolateLOS(Renderer::Backend::IDeviceCommandContext* deviceCommandContext); /** * Returns a matrix to map (x,y,z) world coordinates onto (u,v) LOS texture * coordinates, in the form expected by a matrix uniform. * This must only be called after InterpolateLOS. */ const CMatrix3D& GetTextureMatrix(); /** * Returns a matrix to map (0,0)-(1,1) texture coordinates onto LOS texture * coordinates, in the form expected by a matrix uniform. * This must only be called after InterpolateLOS. */ const CMatrix3D& GetMinimapTextureMatrix(); private: void DeleteTexture(); bool CreateShader(); void ConstructTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext); void RecomputeTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext); size_t GetBitmapSize(size_t w, size_t h, size_t* pitch); void GenerateBitmap(const CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch); CSimulation2& m_Simulation; bool m_Dirty = true; bool m_ShaderInitialized = false; // We need to choose the smallest format. We always use the red channel but // R8_UNORM might be unavailable on some platforms. So we fallback to // R8G8B8A8_UNORM. Renderer::Backend::Format m_TextureFormat = Renderer::Backend::Format::UNDEFINED; size_t m_TextureFormatStride = 0; std::unique_ptr m_Texture, m_SmoothTextures[2]; uint32_t m_WhichTexture = 0; + double m_LastTextureRecomputeTime = 0.0; // We update textures once a frame, so we change a Framebuffer once a frame. // That allows us to use two ping-pong FBOs instead of checking completeness // of Framebuffer each frame. std::unique_ptr m_SmoothFramebuffers[2]; CShaderTechniquePtr m_SmoothTech; size_t m_MapSize = 0; // vertexes per side CMatrix3D m_TextureMatrix; CMatrix3D m_MinimapTextureMatrix; }; #endif // INCLUDED_LOSTEXTURE