[Gameplay] Reduce loom cost
[0ad.git] / source / graphics / ParticleEmitter.cpp
blob6f3e9c9c2e2fd4d076cf1bd4a890ce8765c97c65
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 "ParticleEmitter.h"
22 #include "graphics/LightEnv.h"
23 #include "graphics/LOSTexture.h"
24 #include "graphics/ParticleEmitterType.h"
25 #include "graphics/ParticleManager.h"
26 #include "graphics/ShaderProgram.h"
27 #include "graphics/TextureManager.h"
28 #include "ps/CStrInternStatic.h"
29 #include "renderer/Renderer.h"
30 #include "renderer/SceneRenderer.h"
32 CParticleEmitter::CParticleEmitter(const CParticleEmitterTypePtr& type) :
33 m_Type(type), m_Active(true), m_NextParticleIdx(0), m_EmissionRoundingError(0.f),
34 m_LastUpdateTime(type->m_Manager.GetCurrentTime()),
35 m_IndexArray(false),
36 m_VertexArray(Renderer::Backend::IBuffer::Type::VERTEX, true),
37 m_LastFrameNumber(-1)
39 // If we should start with particles fully emitted, pretend that we
40 // were created in the past so the first update will produce lots of
41 // particles.
42 // TODO: instead of this, maybe it would make more sense to do a full
43 // lifetime-length update of all emitters when the game first starts
44 // (so that e.g. buildings constructed later on won't have fully-started
45 // emitters, but those at the start will)?
46 if (m_Type->m_StartFull)
47 m_LastUpdateTime -= m_Type->m_MaxLifetime;
49 m_Particles.reserve(m_Type->m_MaxParticles);
51 m_AttributePos.format = Renderer::Backend::Format::R32G32B32_SFLOAT;
52 m_VertexArray.AddAttribute(&m_AttributePos);
54 m_AttributeAxis.format = Renderer::Backend::Format::R32G32_SFLOAT;
55 m_VertexArray.AddAttribute(&m_AttributeAxis);
57 m_AttributeUV.format = Renderer::Backend::Format::R32G32_SFLOAT;
58 m_VertexArray.AddAttribute(&m_AttributeUV);
60 m_AttributeColor.format = Renderer::Backend::Format::R8G8B8A8_UNORM;
61 m_VertexArray.AddAttribute(&m_AttributeColor);
63 m_VertexArray.SetNumberOfVertices(m_Type->m_MaxParticles * 4);
64 m_VertexArray.Layout();
66 m_IndexArray.SetNumberOfVertices(m_Type->m_MaxParticles * 6);
67 m_IndexArray.Layout();
68 VertexArrayIterator<u16> index = m_IndexArray.GetIterator();
69 for (u16 i = 0; i < m_Type->m_MaxParticles; ++i)
71 *index++ = i*4 + 0;
72 *index++ = i*4 + 1;
73 *index++ = i*4 + 2;
74 *index++ = i*4 + 2;
75 *index++ = i*4 + 3;
76 *index++ = i*4 + 0;
78 m_IndexArray.Upload();
79 m_IndexArray.FreeBackingStore();
81 const uint32_t stride = m_VertexArray.GetStride();
82 const std::array<Renderer::Backend::SVertexAttributeFormat, 4> attributes{{
83 {Renderer::Backend::VertexAttributeStream::POSITION,
84 m_AttributePos.format, m_AttributePos.offset, stride,
85 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
86 {Renderer::Backend::VertexAttributeStream::COLOR,
87 m_AttributeColor.format, m_AttributeColor.offset, stride,
88 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
89 {Renderer::Backend::VertexAttributeStream::UV0,
90 m_AttributeUV.format, m_AttributeUV.offset, stride,
91 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
92 {Renderer::Backend::VertexAttributeStream::UV1,
93 m_AttributeAxis.format, m_AttributeAxis.offset, stride,
94 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
95 }};
96 m_VertexInputLayout = g_Renderer.GetVertexInputLayout(attributes);
99 void CParticleEmitter::UpdateArrayData(int frameNumber)
101 if (m_LastFrameNumber == frameNumber)
102 return;
104 m_LastFrameNumber = frameNumber;
106 // Update m_Particles
107 m_Type->UpdateEmitter(*this, m_Type->m_Manager.GetCurrentTime() - m_LastUpdateTime);
108 m_LastUpdateTime = m_Type->m_Manager.GetCurrentTime();
110 // Regenerate the vertex array data:
112 VertexArrayIterator<CVector3D> attrPos = m_AttributePos.GetIterator<CVector3D>();
113 VertexArrayIterator<float[2]> attrAxis = m_AttributeAxis.GetIterator<float[2]>();
114 VertexArrayIterator<float[2]> attrUV = m_AttributeUV.GetIterator<float[2]>();
115 VertexArrayIterator<SColor4ub> attrColor = m_AttributeColor.GetIterator<SColor4ub>();
117 ENSURE(m_Particles.size() <= m_Type->m_MaxParticles);
119 CBoundingBoxAligned bounds;
121 for (size_t i = 0; i < m_Particles.size(); ++i)
123 // TODO: for more efficient rendering, maybe we should replace this with
124 // a degenerate quad if alpha is 0
126 bounds += m_Particles[i].pos;
128 *attrPos++ = m_Particles[i].pos;
129 *attrPos++ = m_Particles[i].pos;
130 *attrPos++ = m_Particles[i].pos;
131 *attrPos++ = m_Particles[i].pos;
133 // Compute corner offsets, split into sin/cos components so the vertex
134 // shader can multiply by the camera-right (or left?) and camera-up vectors
135 // to get rotating billboards:
137 float s = sin(m_Particles[i].angle) * m_Particles[i].size/2.f;
138 float c = cos(m_Particles[i].angle) * m_Particles[i].size/2.f;
140 (*attrAxis)[0] = c;
141 (*attrAxis)[1] = s;
142 ++attrAxis;
143 (*attrAxis)[0] = s;
144 (*attrAxis)[1] = -c;
145 ++attrAxis;
146 (*attrAxis)[0] = -c;
147 (*attrAxis)[1] = -s;
148 ++attrAxis;
149 (*attrAxis)[0] = -s;
150 (*attrAxis)[1] = c;
151 ++attrAxis;
153 (*attrUV)[0] = 1;
154 (*attrUV)[1] = 0;
155 ++attrUV;
156 (*attrUV)[0] = 0;
157 (*attrUV)[1] = 0;
158 ++attrUV;
159 (*attrUV)[0] = 0;
160 (*attrUV)[1] = 1;
161 ++attrUV;
162 (*attrUV)[0] = 1;
163 (*attrUV)[1] = 1;
164 ++attrUV;
166 SColor4ub color = m_Particles[i].color;
168 // Special case: If the blending depends on the source color, not the source alpha,
169 // then pre-multiply by the alpha. (This is kind of a hack.)
170 if (m_Type->m_BlendMode == CParticleEmitterType::BlendMode::OVERLAY ||
171 m_Type->m_BlendMode == CParticleEmitterType::BlendMode::MULTIPLY)
173 color.R = (color.R * color.A) / 255;
174 color.G = (color.G * color.A) / 255;
175 color.B = (color.B * color.A) / 255;
178 *attrColor++ = color;
179 *attrColor++ = color;
180 *attrColor++ = color;
181 *attrColor++ = color;
184 m_ParticleBounds = bounds;
186 m_VertexArray.Upload();
189 void CParticleEmitter::PrepareForRendering()
191 m_VertexArray.PrepareForRendering();
194 void CParticleEmitter::UploadData(
195 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
197 m_VertexArray.UploadIfNeeded(deviceCommandContext);
200 void CParticleEmitter::Bind(
201 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
202 Renderer::Backend::IShaderProgram* shader)
204 m_Type->m_Texture->UploadBackendTextureIfNeeded(deviceCommandContext);
206 CLOSTexture& los = g_Renderer.GetSceneRenderer().GetScene().GetLOSTexture();
207 deviceCommandContext->SetTexture(
208 shader->GetBindingSlot(str_losTex), los.GetTextureSmooth());
209 deviceCommandContext->SetUniform(
210 shader->GetBindingSlot(str_losTransform),
211 los.GetTextureMatrix()[0], los.GetTextureMatrix()[12]);
213 const CLightEnv& lightEnv = g_Renderer.GetSceneRenderer().GetLightEnv();
215 deviceCommandContext->SetUniform(
216 shader->GetBindingSlot(str_sunColor), lightEnv.m_SunColor.AsFloatArray());
217 deviceCommandContext->SetUniform(
218 shader->GetBindingSlot(str_fogColor), lightEnv.m_FogColor.AsFloatArray());
219 deviceCommandContext->SetUniform(
220 shader->GetBindingSlot(str_fogParams), lightEnv.m_FogFactor, lightEnv.m_FogMax);
222 deviceCommandContext->SetTexture(
223 shader->GetBindingSlot(str_baseTex), m_Type->m_Texture->GetBackendTexture());
226 void CParticleEmitter::RenderArray(
227 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
229 if (m_Particles.empty())
230 return;
232 const uint32_t stride = m_VertexArray.GetStride();
233 const uint32_t firstVertexOffset = m_VertexArray.GetOffset() * stride;
235 deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout);
237 deviceCommandContext->SetVertexBuffer(
238 0, m_VertexArray.GetBuffer(), firstVertexOffset);
239 deviceCommandContext->SetIndexBuffer(m_IndexArray.GetBuffer());
241 deviceCommandContext->DrawIndexed(m_IndexArray.GetOffset(), m_Particles.size() * 6, 0);
243 g_Renderer.GetStats().m_DrawCalls++;
244 g_Renderer.GetStats().m_Particles += m_Particles.size();
247 void CParticleEmitter::Unattach(const CParticleEmitterPtr& self)
249 m_Active = false;
250 m_Type->m_Manager.AddUnattachedEmitter(self);
253 void CParticleEmitter::AddParticle(const SParticle& particle)
255 if (m_NextParticleIdx >= m_Particles.size())
256 m_Particles.push_back(particle);
257 else
258 m_Particles[m_NextParticleIdx] = particle;
260 m_NextParticleIdx = (m_NextParticleIdx + 1) % m_Type->m_MaxParticles;
263 void CParticleEmitter::SetEntityVariable(const std::string& name, float value)
265 m_EntityVariables[name] = value;
268 CModelParticleEmitter::CModelParticleEmitter(const CParticleEmitterTypePtr& type) :
269 m_Type(type)
271 m_Emitter = CParticleEmitterPtr(new CParticleEmitter(m_Type));
274 CModelParticleEmitter::~CModelParticleEmitter()
276 m_Emitter->Unattach(m_Emitter);
279 void CModelParticleEmitter::SetEntityVariable(const std::string& name, float value)
281 m_Emitter->SetEntityVariable(name, value);
284 std::unique_ptr<CModelAbstract> CModelParticleEmitter::Clone() const
286 return std::make_unique<CModelParticleEmitter>(m_Type);
289 void CModelParticleEmitter::CalcBounds()
291 // TODO: we ought to compute sensible bounds here, probably based on the
292 // current computed particle positions plus the emitter type's largest
293 // potential bounding box at the current position
295 m_WorldBounds = m_Type->CalculateBounds(m_Emitter->GetPosition(), m_Emitter->GetParticleBounds());
298 void CModelParticleEmitter::ValidatePosition()
300 // TODO: do we need to do anything here?
302 // This is a convenient (though possibly not particularly appropriate) place
303 // to invalidate bounds so they'll be recomputed from the recent particle data
304 InvalidateBounds();
307 void CModelParticleEmitter::InvalidatePosition()
311 void CModelParticleEmitter::SetTransform(const CMatrix3D& transform)
313 if (m_Transform == transform)
314 return;
316 m_Emitter->SetPosition(transform.GetTranslation());
317 m_Emitter->SetRotation(transform.GetRotation());
319 // call base class to set transform on this object
320 CRenderableObject::SetTransform(transform);