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
236 // If set, rayPicking handled by elsewhere by some dedicated system
237 if (comp.customRayPickHandling) { continue; }
238
239 // Filter out unwanted, invisible or disabled entities early to speed up queries.
240 if (filter.isUnwantedType(comp) || comp.lod.currentLod != comp.lod.selectedLod) { continue; }
241
242 // ForcePickable flag overrides visibility and flags.
244 if (!comp.isVisible() || !comp.isVisibleInLayer(filter.layerMask) || !comp.isPickable()) {
245 continue;
246 }
247 }
248
249 const RayPicking::Ordinal ordinal = filter.getOrdinal(comp);
250
251 const MeshRenderData& renderData = system->getData<MeshRenderData>(&comp);
252 if (!renderData.meshComponent) {
253 continue;
254 }
255
256 const Geometry::BoundingBox& bboxWorld = system->getWorldBounds(&comp);
257 const Mesh* mesh = renderData.meshComponent.resolveComponent<MeshComponent>()->meshHandle.resolve();
258 if (!mesh) {
259 continue;
260 }
261 TextureCoordinateInfo texcoordInfo = getTextureCoordinateInfo(*mesh);
262
263 // Note that the bbox test must be 'fuzzy' (including tolerance) for
264 // line picking not to prematurely fail in bbox test (bbox of single
265 // line is a single line).
266 if (isEmpty(bboxWorld)) {
267 continue;
268 }
269
270 if (frustumClassifyBoundingBox(worldPickMatrix, bboxWorld.min, bboxWorld.max) != FrustumPlanes::InsideAll) {
271 continue;
272 }
273
274 // Create transform from local frame to pick frustum, where pick ray
275 // is negative Z and ray fuzziness is +/- w.
276 glm::mat4 localPickMatrix = worldPickMatrix * renderData.localToWorld;
277
278 std::vector<RayIntersectionHit> meshHits;
279 std::vector<FrustumPlanes> scratch;
280 if (!intersectMesh(meshHits,
281 scratch,
282 localPickMatrix,
283 *mesh,
284 comp.startIndex,
285 comp.vertexCount,
286 static_cast<uint32_t>(-1))) {
287 continue;
288 }
289
290 if (meshHits.empty()) {
291 continue;
292 }
293
294 for (const RayIntersectionHit& meshHit : meshHits) {
295 const glm::vec4 position = renderData.localToWorld * glm::vec4(meshHit.pos_, 1.f);
296
297 if (const ClipShapeComponent* clipComp = comp.clipShapeComponent.resolveComponent<ClipShapeComponent>(); clipComp) {
298 const ClipShapeData& clipData = context->clipShapeSystem->getData(clipComp);
299 if (clippedByClipComp(clipData, position)) {
300 continue;
301 }
302 }
303
304 const glm::vec4 clipPos = rawViewProjection * glm::vec4(position.x, position.y, position.z, 1.f);
305 // use clip space to see if the point falls within the view frustum
306 if ((-clipPos.w < clipPos.z) && (clipPos.z < clipPos.w)) {
307
308 glm::vec4 viewPos = viewMatrix * position;
309 float viewDist = -viewPos.z;
310
311 if (returnFlag == PicksReturned::Closest && !batchPickResults.empty()) {
312 if (batchPickResults[0].isBehind(ordinal, viewDist)) {
313 glm::vec2 textureCoords = getTextureCoords(texcoordInfo, meshHit, pickingFlags);
314 batchPickResults[0] = {comp, returnChildEntity, position, ordinal, viewDist, textureCoords};
315 }
316 // else, the intersection we found is further, so don't do anything
317 }
318 else {
319 glm::vec2 textureCoords = getTextureCoords(texcoordInfo, meshHit, pickingFlags);
320 batchPickResults.emplace_back(comp, returnChildEntity, position, ordinal, viewDist, textureCoords);
321 }
322 }
323 }
324 }
325
326 if (!batchPickResults.empty()) {
327 LockGuard guard(batchLock);
328 if (returnFlag == PicksReturned::Closest) {
329 if (hits.empty()) {
330 hits = std::move(batchPickResults);
331 }
332 else if (batchPickResults[0] < hits[0]) {
333 hits[0] = batchPickResults[0];
334 }
335 //else don't insert anything because we found a hit further away than what we already had
336 }
337 else {
338 hits.insert(hits.end(),
339 std::make_move_iterator(batchPickResults.begin()),
340 std::make_move_iterator(batchPickResults.end()));
341 }
342 }
343 }
344 );
345 }
346 context->taskManager->destroy(group);
347
348 return hitsBefore != hits.size();
349}
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.
ComponentModel::ComponentHandle customRayPickHandling
Handle to the component that manages custom raypicking.
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:140
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.
@ 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:181
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:202
RenderLayers layerMask
Limit picking to the specified render layers. Pick all layers by default.
Definition: RayPick.h:62
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