Cogs.Core
RenderSystem.cpp
1#include "RenderSystem.h"
2
3#include "Components/Core/MeshRenderComponent.h"
4#include "Context.h"
5
6#include "Components/Core/TransformComponent.h"
7#include "Components/Core/MeshComponent.h"
8#include "Components/Core/NearLimitComponent.h"
9#include "Components/Core/ClipShapeComponent.h"
10
11#include "Scene/RayPick.h"
12#include "Systems/Core/TransformSystem.h"
13#include "Systems/Core/CameraSystem.h"
14#include "Systems/Core/ClipShapeSystem.h"
15
16#include "Resources/Mesh.h"
17#include "Resources/MeshManager.h"
18
19#include "Services/Variables.h"
20#include "Services/TaskManager.h"
21
22#include "Utilities/Parallel.h"
23#include "Math/RayIntersection.h"
24
25#include "Scene/GetBounds.h"
26
27
28#include "Foundation/Logging/Logger.h"
29
30using namespace Cogs::Core;
31using namespace Cogs::Geometry;
32
33
34using namespace Cogs::Core;
35
36Cogs::Logging::Log logger = Cogs::Logging::getLogger("RenderSystem");
37
39{
41
42 geometryGroup = context->taskManager->createGroup();
43}
44
46{
47 const bool workParallel = context->engine->workParallel();
48
49 context->taskManager->wait(geometryGroup);
50
51 needsPost = false;
52
53 auto updateComponent = [this](MeshRenderComponent & renderComponent, size_t)
54 {
55 needsPost |= renderComponent.hasChanged();
56
57 auto & data = this->getData<MeshRenderData>(&renderComponent);
58 auto & localBounds = getLocalBounds(&renderComponent);
59
60 if (!data.transformComponent) {
61 data.transformComponent = renderComponent.getComponentHandle<TransformComponent>();
62 data.meshComponent = renderComponent.getComponentHandle<MeshComponent>();
63 }
64
65 // Update local bounds if necessary.
66 {
67 if ((data.flags & MeshRenderDataFlags::LocalBoundsOverride) == 0) {
68 auto meshComponent = data.meshComponent.resolveComponent<MeshComponent>();
69
70 if (!meshComponent->meshHandle) return;
71
72 auto mesh = meshComponent->meshHandle.resolve();
73
74 if (meshComponent->hasChanged() || data.meshBoundsGeneration != mesh->getGeneration() || mesh->boundsDirty()) {
75 if (mesh->boundsDirty() || (isEmpty(mesh->boundingBox) && mesh->getCount())) {
76 mesh->boundingBox = calculateBounds(mesh);
77
78 if (!isEmpty(mesh->boundingBox) && !mesh->isInitialized()) {
79 mesh->setChanged();
80 }
81 }
82
83 if (mesh->boundingBox.min.x <= mesh->boundingBox.max.x) {
84 localBounds = mesh->boundingBox;
85 } else {
86 localBounds = calculateBounds(mesh, renderComponent.startIndex, renderComponent.vertexCount);
87 }
88
89 data.meshBoundsGeneration = (uint8_t)mesh->getGeneration();
90 ++data.localBoundsGeneration;
91 }
92 }
93 }
94
95 // Check world bounds
96 {
97 auto transform = data.transformComponent.resolveComponent<TransformComponent>();
98
99 if (data.worldBoundsGeneration != data.localBoundsGeneration || transformSystem->hasChanged(transform)) {
100 data.localToWorld = transformSystem->getLocalToWorld(transform);
101
102 if (!isEmpty(localBounds)) {
103 getWorldBounds(&renderComponent) = Bounds::getTransformedBounds(localBounds, data.localToWorld);
104 } else {
105 getWorldBounds(&renderComponent) = localBounds;
106 }
107
108 if (data.worldBoundsGeneration != data.localBoundsGeneration) {
109 data.worldBoundsGeneration = data.localBoundsGeneration;
110 //FIXME: If the local bounding box has changed the current frame, the world bounds used for culling will lag
111 // a frame behind, potentially being empty or invalid. To ensure the object is not culled we reset the
112 // culling index, disable culling. The culling index will be set to a valid slot the next frame.
113 // This behavior will result in sub-optimal performance for e.g dynamic geometry out of frame.
114 data.cullingIndex = NoCullingIndex;
115 }
116 }
117 }
118 };
119
120 if (workParallel) {
121 CpuInstrumentationScope(SCOPE_SYSTEMS, "RenderSystem::update");
122
123 Parallel::processComponents(context, pool, "RenderSystem::updateComponent", updateComponent, geometryGroup);
124
125 context->taskManager->wait(geometryGroup);
126 } else {
127 Serial::processComponents(pool, updateComponent);
128 }
129
130 ++generation;
131}
132
134{
135 if (needsPost) {
137 }
138}
139
141{
142 if (geometryGroup.isValid()) {
143 context->taskManager->destroy(geometryGroup);
144 }
145}
146
148{
149 ComponentHandle handle = base_type::createComponent();
150
151 // Ensure consistent Bounds.
152 if (pool.size() == 1u) {
153 assert(picker == nullptr);
154 picker = std::make_unique<MeshRenderPicker>(this);
155 context->rayPicking->addPickable(picker.get());
156 }
157
158 return handle;
159}
161{
162 base_type::destroyComponent(component);
163
164 if (pool.size() == 0u) {
165 context->rayPicking->removePickable(picker.get());
166 picker.reset();
167 }
168}
169
170void Cogs::Core::RenderSystem::initializeCulling(CullingSource* cullSource)
171{
172 const auto offset = cullSource->count;
173 const auto count = pool.size();
174 const auto total = offset + count;
175 if (!count) return;
176
177 cullSource->count = total;
178 cullSource->bbMinWorld.resize(total);
179 cullSource->bbMaxWorld.resize(total);
180
181 if (count < 2048) {
182 for (SizeType i = 0; i < count; ++i) {
183 auto & meshData = this->getData<MeshRenderData>(&pool[i]);
184 SizeType j = (SizeType)offset + i;
185 meshData.cullingIndex = j;
186 cullSource->bbMinWorld[j] = getWorldBounds(&pool[i]).min;
187 cullSource->bbMaxWorld[j] = getWorldBounds(&pool[i]).max;
188 }
189 }
190 else {
191 auto scope = Parallel::forEach(context, count, [this, offset, cullSource](size_t i) {
192 auto & meshData = this->getData<MeshRenderData>(&pool[(SizeType)i]);
193 SizeType j = (SizeType)offset + (SizeType)i;
194 meshData.cullingIndex = j;
195 cullSource->bbMinWorld[j] = getWorldBounds(&pool[(SizeType)i]).min;
196 cullSource->bbMaxWorld[j] = getWorldBounds(&pool[(SizeType)i]).max;
197 }, "MeshRenderSystem::initializeCulling");
198 scope.Wait();
199 }
200}
201
203 const glm::mat4& worldPickMatrix,
204 const glm::mat4& rawViewProjection,
205 const glm::mat4& viewMatrix,
206 const RayPicking::RayPickFilter& filter,
207 PickingFlags pickingFlags,
208 PicksReturned returnFlag,
209 std::vector<RayPicking::RayPickHit>& hits)
210{
211 const bool returnChildEntity = (pickingFlags & PickingFlags::ReturnChildEntity) == PickingFlags::ReturnChildEntity;
212
213 const size_t hitsBefore = hits.size();
214
215 const Cogs::SizeType itemCount = system->pool.size();
216 const Cogs::SizeType batchSize = 1024;
217
218 TaskId group = context->taskManager->createGroup();
219 Mutex batchLock;
220
221 for (Cogs::SizeType start = 0; start < itemCount; start += batchSize) {
222 const Cogs::SizeType end = std::min(start + batchSize, itemCount);
223
224 context->taskManager->enqueueChild(group,
225 [&, start, end]() {
226 CpuInstrumentationScope(SCOPE_RAYPICK, "executePickingQueryTask");
227
228 std::vector<RayPicking::RayPickHit> batchPickResults;
229 if (returnFlag != PicksReturned::Closest) {
230 batchPickResults.reserve(10); // Reserve space for 10 hits in the current batch based purely on guessing.
231 }
232
233 for (Cogs::SizeType i = start; i < end; i++) {
234 const MeshRenderComponent& comp = system->pool[i];
235 // Filter out unwanted, invisible or disabled entities early to speed up queries.
236 if (filter.isUnwantedType(comp) || comp.lod.currentLod != comp.lod.selectedLod) { continue; }
237
238 // ForcePickable flag overrides visibility and flags.
240 if (!comp.isVisible() || !comp.isVisibleInLayer(filter.layerMask) || !comp.isPickable()) {
241 continue;
242 }
243 }
244
245 const MeshRenderData& renderData = system->getData<MeshRenderData>(&comp);
246 if (!renderData.meshComponent) {
247 continue;
248 }
249
250 const Geometry::BoundingBox& bboxWorld = system->getWorldBounds(&comp);
251 const Mesh* mesh = renderData.meshComponent.resolveComponent<MeshComponent>()->meshHandle.resolve();
252 if (!mesh) {
253 continue;
254 }
255 TextureCoordinateInfo texcoordInfo = getTextureCoordinateInfo(*mesh);
256
257 // Note that the bbox test must be 'fuzzy' (including tolerance) for
258 // line picking not to prematurely fail in bbox test (bbox of single
259 // line is a single line).
260 if (isEmpty(bboxWorld)) {
261 continue;
262 }
263
264 if (frustumClassifyBoundingBox(worldPickMatrix, bboxWorld.min, bboxWorld.max) != FrustumPlanes::InsideAll) {
265 continue;
266 }
267
268 // Create transform from local frame to pick frustum, where pick ray
269 // is negative Z and ray fuzziness is +/- w.
270 glm::mat4 localPickMatrix = worldPickMatrix * renderData.localToWorld;
271
272 std::vector<RayIntersectionHit> meshHits;
273 std::vector<FrustumPlanes> scratch;
274 if (!intersectMesh(meshHits,
275 scratch,
276 localPickMatrix,
277 *mesh,
278 comp.startIndex,
279 comp.vertexCount,
280 static_cast<uint32_t>(-1))) {
281 continue;
282 }
283
284 if (meshHits.empty()) {
285 continue;
286 }
287
288 for (const RayIntersectionHit& meshHit : meshHits) {
289 const glm::vec4 position = renderData.localToWorld * glm::vec4(meshHit.pos_, 1.f);
290
291 if (const ClipShapeComponent* clipComp = comp.clipShapeComponent.resolveComponent<ClipShapeComponent>(); clipComp) {
292 const ClipShapeData& clipData = context->clipShapeSystem->getData(clipComp);
293 if (clippedByClipComp(clipData, position)) {
294 continue;
295 }
296 }
297
298 const glm::vec4 clipPos = rawViewProjection * glm::vec4(position.x, position.y, position.z, 1.f);
299 // use clip space to see if the point falls within the view frustum
300 if ((-clipPos.w < clipPos.z) && (clipPos.z < clipPos.w)) {
301
302 glm::vec4 viewPos = viewMatrix * position;
303 float viewDist = -viewPos.z;
304
305 if (returnFlag == PicksReturned::Closest && !batchPickResults.empty()) {
306 if (viewDist < batchPickResults[0].distance) {
307 glm::vec2 textureCoords = getTextureCoords(texcoordInfo, meshHit, pickingFlags);
308 batchPickResults[0] = {comp, returnChildEntity, position, viewDist, textureCoords};
309 }
310 // else, the intersection we found is further, so don't do anything
311 }
312 else {
313 glm::vec2 textureCoords = getTextureCoords(texcoordInfo, meshHit, pickingFlags);
314 batchPickResults.emplace_back(comp, returnChildEntity, position, viewDist, textureCoords);
315 }
316 }
317 }
318 }
319
320 if (!batchPickResults.empty()) {
321 LockGuard guard(batchLock);
322 if (returnFlag == PicksReturned::Closest) {
323 if (hits.empty()) {
324 hits = std::move(batchPickResults);
325 }
326 else if (hits[0].distance > batchPickResults[0].distance) {
327 hits[0] = batchPickResults[0];
328 }
329 //else don't insert anything because we found a hit further away than what we already had
330 }
331 else {
332 hits.insert(hits.end(),
333 std::make_move_iterator(batchPickResults.begin()),
334 std::make_move_iterator(batchPickResults.end()));
335 }
336 }
337 }
338 );
339 }
340 context->taskManager->destroy(group);
341
342 if (returnFlag == PicksReturned::AllSorted) {
343 std::sort(hits.begin(), hits.end());
344 }
345 return hitsBefore != hits.size();
346}
ComponentHandle getComponentHandle() const
Definition: Component.h:177
Sets up a clipping shape that can be used by multiple entities.
Context * context
Pointer to the Context instance the system lives in.
void postUpdate()
Perform post update logic in the system.
virtual void initialize(Context *context)
Initialize the system.
void update()
Updates the system state to that of the current frame.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class TaskManager > taskManager
TaskManager service instance.
Definition: Context.h:186
std::unique_ptr< class Engine > engine
Engine instance.
Definition: Context.h:222
Contains a handle to a Mesh resource to use when rendering using the MeshRenderComponent.
Definition: MeshComponent.h:15
MeshHandle meshHandle
Handle to a Mesh resource to use when rendering.
Definition: MeshComponent.h:29
Renders the contents of a MeshComponent using the given materials.
uint32_t startIndex
Start vertex index to render from.
uint32_t vertexCount
Number of vertexes to draw.
ComponentModel::ComponentHandle clipShapeComponent
Handle to the currently active clip component, if any.
constexpr bool isVisibleInLayer(RenderLayers layerMask) const
Check if the entity should be visible in the given layer mask.
constexpr bool isVisible() const
Check if the entity is visible or not.
constexpr bool isRenderFlagSet(RenderFlags flag) const
Check if the given flag is currently set.
constexpr bool isPickable() const
Check if the entity is pickable or not.
void initialize(Context *context) override
Initialize the system.
ComponentHandle createComponent() override
Create a new component instance.
void cleanup(Context *context) override
Provided for custom cleanup logic in derived systems.
void destroyComponent(ComponentHandle component) override
Destroy the component held by the given handle.
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
Log implementation class.
Definition: LogManager.h:139
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
PicksReturned
  * Options for returning picking hits.
Definition: PickingFlags.h:40
@ Closest
Return just the closest hit.
@ AllSorted
Return all hits sorted based on distance from the ray start, closest first.
@ ForcePickable
Ensure component is pickable though it is not rendered.
PickingFlags
Options for COGS picking.
Definition: PickingFlags.h:12
@ ReturnChildEntity
Return ID if sub-entity picked, not set: return root parent entity.
Cogs::Geometry::BoundingBox COGSCORE_DLL_API calculateBounds(Mesh *mesh)
Calculate a bounding box for the given mesh.
Definition: MeshHelper.cpp:283
Contains geometry calculations and generation.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
ComponentIndex SizeType
Type used to track the size of pools.
Definition: Component.h:19
Handle to a Component instance.
Definition: Component.h:67
ComponentType * resolveComponent() const
Definition: Component.h:90
bool pickImpl(Context *context, const glm::mat4 &worldPickMatrix, const glm::mat4 &rawViewProjection, const glm::mat4 &viewMatrix, const RayPicking::RayPickFilter &filter, PickingFlags pickingFlags, PicksReturned returnFlag, std::vector< RayPicking::RayPickHit > &hits) override
Each mesh rendering system should implement this function that goes through all components and calls ...
Meshes contain streams of vertex data in addition to index data and options defining geometry used fo...
Definition: Mesh.h:265
bool isUnwantedType(const ComponentModel::Component &comp) const
Helper function used to determine if a given component belongs to an accepted entity type.
Definition: RayPick.cpp:187
RenderLayers layerMask
Limit picking to the specified render layers. Pick all layers by default.
Definition: RayPick.h:70
uint8_t currentLod
The assigned LoD of the current component.
uint8_t selectedLod
The selected LoD of the composite entity.
ResourceType * resolve() const
Resolve the handle, returning a pointer to the actual resource.
Task id struct used to identify unique Task instances.
Definition: TaskManager.h:20