Cogs.Core
MaterialSystem.cpp
1#include "MaterialSystem.h"
2
3#include "Context.h"
4
5#include "Resources/MaterialManager.h"
6#include "Resources/DefaultMaterial.h"
7
8#include "Components/Core/MeshRenderComponent.h"
9#include "Components/Core/SubMeshRenderComponent.h"
10
11#include "Foundation/ComponentModel/Entity.h"
12
13namespace Cogs
14{
15 namespace Core
16 {
25 size_t getMaterialHash(const MaterialComponent & component)
26 {
27 const uint8_t * beginByte = reinterpret_cast<const uint8_t *>(&component.diffuseMap);
28 const uint8_t * endByte = reinterpret_cast<const uint8_t *>(&component.enableTextureOverride) + sizeof(component.enableTextureOverride);
29
30 return fnv1a(beginByte, endByte - beginByte);
31 }
32
37 void applyRenderMaterial(const MaterialComponent & component, const MaterialData& data)
38 {
39 auto renderComponent = component.getComponent<MeshRenderComponent>();
40
41 // If the material component for example is located in an entity of type Material, there will
42 // be no render component, so we must check for its availability here.
43 if (renderComponent) {
44 if (renderComponent->material != data.instance && !renderComponent->customMaterial()) {
45 renderComponent->material = data.instance;
46 }
47 }
48
49 auto subMeshRenderComponent = component.getComponent<SubMeshRenderComponent>();
50
51 if (subMeshRenderComponent) {
52 if (subMeshRenderComponent->materials.empty()) {
53 subMeshRenderComponent->materials.resize(1);
54 }
55
56 if (subMeshRenderComponent->materials.front() != data.instance && !subMeshRenderComponent->customMaterial()) {
57 subMeshRenderComponent->materials.front() = data.instance;
58 }
59 }
60 }
61 }
62}
63
65{
66 auto & materialManager = *context->materialManager;
67 auto & materialInstanceManager = *context->materialInstanceManager;
68
69 for (auto & component : pool) {
70 auto & data = getData(&component);
71 auto masterMaterial = component.material.lock();
72
73 if (masterMaterial) {
74 continue;
75 } else if (component.hasChanged() || data.instance == MaterialInstanceHandle::NoHandle) {
76 bool needsPropertyUpdate = false;
77 bool needsRenderChange = false;
78
79 if (!data.lockInstance) {
80 const size_t materialHash = getMaterialHash(component);
81
82 // See if we can find a matching material instance in the shared instances.
83 const auto mIt = instances.find(materialHash);
84
85 if (mIt != instances.end()) {
86 // If the current instance is a custom material, we don't override it.
87 if (HandleIsValid(data.instance)) {
88 auto instance = materialInstanceManager.get(data.instance);
89
90 if (!instance->isDefaultMaterial()) {
91 continue;
92 }
93 }
94
95 if (mIt->second != data.instance) {
96 data.instance = mIt->second;
97 data.sharedInstance = true;
98 component.setChanged();
99 needsRenderChange = true;
100 }
101 } else {
102 if (HandleIsValid(data.instance)) {
103 auto instance = materialInstanceManager.get(data.instance);
104
105 if (!instance->isDefaultMaterial()) {
106 continue;
107 } else if (instance->referenceCount() == 1) {
108 instances[materialHash] = data.instance;
109 data.sharedInstance = true;
110 needsPropertyUpdate = true;
111 } else {
112 data.instance = materialInstanceManager.createMaterialInstance(materialManager.getDefaultMaterial());
113 data.sharedInstance = true;
114 component.setChanged();
115
116 instances[materialHash] = data.instance;
117 needsPropertyUpdate = true;
118 needsRenderChange = true;
119 }
120 } else {
121 data.instance = materialInstanceManager.createMaterialInstance(materialManager.getDefaultMaterial());
122 data.sharedInstance = true;
123 component.setChanged();
124
125 instances[materialHash] = data.instance;
126 needsPropertyUpdate = true;
127 needsRenderChange = true;
128 }
129 }
130 } else {
131 if (!HandleIsValid(data.instance)) {
132 data.instance = materialInstanceManager.createMaterialInstance(materialManager.getDefaultMaterial());
133 needsRenderChange = true;
134 }
135
136 needsPropertyUpdate = true;
137 }
138
139 if (needsPropertyUpdate) {
140 auto materialInstance = materialInstanceManager.get(data.instance);
141
142 if (!materialInstance->isDefaultMaterial()) continue;
143
144 auto diffuseColor = component.transparency != 0 ? glm::vec4(glm::vec3(component.diffuseColor), 1.0f - component.transparency) : component.diffuseColor;
145
146 materialInstance->setVec4Property(DefaultMaterial::DiffuseColor, diffuseColor);
147 materialInstance->setVec3Property(DefaultMaterial::EmissiveColor, glm::vec3(component.emissiveColor));
148 materialInstance->setVec3Property(DefaultMaterial::SpecularColor, glm::vec3(component.specularColor));
149
150 materialInstance->setFloatProperty(DefaultMaterial::SpecularPower, component.specularPower);
151
152 if (component.backdrop) {
153 materialInstance->setBackdrop();
154 } else {
155 materialInstance->unsetBackdrop();
156 }
157
158 if (component.transparency || component.diffuseColor.a != 1.0f) {
159 materialInstance->setTransparent();
160 } else {
161 materialInstance->setOpaque();
162 }
163
164 materialInstance->setBoolProperty(DefaultMaterial::EnableLighting, component.enableLighting);
165
166 materialInstance->setFloatProperty(DefaultMaterial::LineWidth, component.lineWidth);
167
168 if (component.diffuseMapScale != glm::vec2(1, 1)) {
169 materialInstance->setVec2Property(DefaultMaterial::DiffuseMapScale, component.diffuseMapScale);
170 }
171
172 materialInstance->setTextureProperty(DefaultMaterial::DiffuseMap, component.diffuseMap);
173 materialInstance->setTextureAddressMode(DefaultMaterial::DiffuseMap, component.addressMode);
174 materialInstance->setTextureFilterMode(DefaultMaterial::DiffuseMap, component.filterMode);
175
176 materialInstance->setTextureProperty(DefaultMaterial::NormalMap, component.normalMap);
177 materialInstance->setTextureAddressMode(DefaultMaterial::NormalMap, component.addressMode);
178 materialInstance->setVec2Property(DefaultMaterial::NormalMapScale, component.normalMapScale);
179 materialInstance->setFloatProperty(DefaultMaterial::NormalMapFactor, component.normalMapFactor);
180
181 materialInstance->setTextureProperty(DefaultMaterial::SpecularMap, component.specularMap);
182 materialInstance->setTextureAddressMode(DefaultMaterial::SpecularMap, component.addressMode);
183 materialInstance->setVec2Property(DefaultMaterial::SpecularMapScale, component.specularMapScale);
184
185 materialInstance->options.blendMode = component.blendMode;
186 materialInstance->options.drawOrder = component.drawOrder;
187
188 bool hasAdjacency = component.primitiveType == PrimitiveType::LineListAdjacency || component.primitiveType == PrimitiveType::LineStripAdjacency;
189 bool isLine = hasAdjacency || component.primitiveType == PrimitiveType::LineList || component.primitiveType == PrimitiveType::LineStrip || component.primitiveType == PrimitiveType::LineStripAdjacency;
190 bool isPoints = component.primitiveType == PrimitiveType::PointList;
191
192 if (isLine) {
193 if (component.instancedLine) {
194 materialInstance->setPermutation("InstancedLine");
195 }
196 else {
197 std::string linePermutation = hasAdjacency ? "LineAdj" : "Line";
198 materialInstance->setPermutation(linePermutation);
199 }
200 materialInstance->options.blendMode = BlendMode::Blend;
201 materialInstance->setVariant("Textured", HandleIsValid(component.diffuseMap));
202 materialInstance->setUIntProperty(materialInstance->material->getUIntKey("stipplePattern"), component.stipplePattern);
203 materialInstance->setUIntProperty(materialInstance->material->getUIntKey("stippleFactor"), component.stippleFactor);
204 }
205 else if (isPoints) {
206 materialInstance->setPermutation("Points");
207 materialInstance->options.blendMode = BlendMode::Blend;
208 materialInstance->setFloatProperty(DefaultMaterial::PointSize, component.pointSize);
209 }
210 else {
211 materialInstance->setPermutation("Default");
212
213 materialInstance->setVariant("EnableLighting", component.enableLighting);
214 materialInstance->setVariant("LightModel", component.enableLighting ? "Phong" : "BaseColor");
215
216 materialInstance->setVariant("ShadowReceiver", component.shadowReceiver);
217
218 if (component.diffuseMap || component.normalMap || component.specularMap) {
219 materialInstance->setVariant("Textured", true);
220
221 if (component.normalMap) {
222 materialInstance->setVariant("TangentSpaceNormalMap", true);
223 }
224
225 if (component.specularMap) {
226 materialInstance->setVariant("SpecularMap", true);
227 }
228 }
229 }
230
231 if (component.vertexColor) {
232 materialInstance->setVariant("VertexColor", true);
233 }
234
235 materialInstance->options.cullMode = component.cullMode;
236
237 materialInstance->options.depthWriteEnabled = component.depthWriteEnabled;
238 materialInstance->options.depthTestEnabled = component.depthTestEnabled;
239 materialInstance->options.depthBiasEnabled = component.depthBiasEnable;
240 materialInstance->options.depthBias.constant = component.depthBiasConstant;
241 materialInstance->options.depthBias.slope = component.depthBiasSlope;
242 materialInstance->options.depthBias.clamp = component.depthBiasClamp;
243 materialInstance->options.depthFunc = component.depthTestAlwaysPass ? DepthFunc::Always : DepthFunc::Less;
244
245 if (component.enableOverride) {
246 materialInstance->setMaterialFlag(MaterialFlags::OverrideColor, component.enableColorOverride);
247 materialInstance->setMaterialFlag(MaterialFlags::OverrideAlpha, component.enableAlphaOverride);
248 materialInstance->setMaterialFlag(MaterialFlags::OverrideTextures, component.enableTextureOverride);
249 } else {
250 materialInstance->unsetMaterialFlag(MaterialFlags::Override);
251 }
252
253 materialInstance->setChanged();
254 }
255
256 if (needsRenderChange) {
257 applyRenderMaterial(component, data);
258 }
259 }
260 }
261
262 for (auto & component : pool) {
263 auto& data = getData(&component);
264 auto masterMaterial = component.material.lock();
265
266 if (masterMaterial) {
267 auto masterComponent = masterMaterial->getComponent<MaterialComponent>();
268
269 assert(masterComponent && "Entity without material components cannot be used as master material.");
270
271 if (component.hasChanged() || masterComponent->hasChanged()) {
272 auto& masterData = getData(masterComponent);
273 auto masterInstance = masterData.instance;
274
275 if (data.instance != masterInstance) {
276 // Only apply changes if the master material updated its actual material instance.
277 data.instance = masterInstance;
278
279 component.setChanged();
280
281 applyRenderMaterial(component, data);
282 }
283 }
284 }
285 }
286
288}
289
291{
292 std::vector<size_t> removal;
293
294 for (auto & sharedInstance : instances) {
295 // If the reference count of an instance is equal to 1, the only handle to the instance
296 // must be the one we are holding in the \a instances array, and it can therefore be safely
297 // removed.
298 if (sharedInstance.second.resolve()->referenceCount() == 1) {
299 removal.push_back(sharedInstance.first);
300 }
301 }
302
303 for (auto & r : removal) {
304 instances.erase(r);
305 }
306}
307
309{
310 instances.clear();
311}
ComponentType * getComponent() const
Definition: Component.h:159
Context * context
Pointer to the Context instance the system lives in.
void update()
Updates the system state to that of the current frame.
ComponentPool< ComponentType > pool
Pool of components managed by the system.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
void removeUnusedSharedInstances()
Removes any shared material instances no longer in use.
std::unordered_map< size_t, MaterialInstanceHandle > instances
Holds shared material instances used by one or more MaterialComponent instances.
void cleanup(Context *context) override
Cleanup the material system, releasing any shared material instances held.
Renders the contents of a MeshComponent using the given materials.
MaterialInstanceHandle material
Material used to render the mesh.
Renders a mesh with flexible submesh usage.
std::vector< MaterialInstanceHandle > materials
Materials used to render individual sub-meshes.
void applyRenderMaterial(const MaterialComponent &component, const MaterialData &data)
Apply the material instance from the given component to the mesh render component (if found) on the s...
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
bool isLine(PrimitiveType::EPrimitiveType primitiveType)
Check if the given primitive type represents lines.
Definition: Mesh.h:241
@ Blend
Render with regular alpha blending.
size_t getMaterialHash(const MaterialComponent &component)
Creates a hash value from the material properties in the given component.
Contains all Cogs related functionality.
Definition: FieldSetter.h:23
constexpr size_t fnv1a(uint8_t data, size_t hashValue) noexcept
Hashes a single byte using the fnv1a algorithm.
Definition: HashFunctions.h:37
Exposes material properties for legacy entities and code.
@ Override
Override all properties of any inheriting materials.
Definition: Material.h:60
@ OverrideAlpha
Override alpha of any inheriting materials.
Definition: Material.h:54
@ OverrideColor
Override color of any inheriting materials.
Definition: Material.h:52
@ OverrideTextures
Override textures of any inheriting materials.
Definition: Material.h:56
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
@ LineStrip
Line strip.
Definition: Common.h:122
@ LineStripAdjacency
Line strip with adjacency.
Definition: Common.h:132
@ LineList
List of lines.
Definition: Common.h:120
@ PointList
List of points.
Definition: Common.h:124
@ LineListAdjacency
List of lines with adjacency.
Definition: Common.h:130