1#include "SpriteRenderSystem.h"
3#include "Foundation/Logging/Logger.h"
4#include "Rendering/ICapabilities.h"
8#include "Resources/MaterialManager.h"
9#include "Resources/TextureManager.h"
10#include "Resources/MeshManager.h"
11#include "Resources/DefaultMaterial.h"
13#include "Scene/PickingFlags.h"
14#include "Scene/RayPick.h"
16#include "Systems/Core/CameraSystem.h"
23Cogs::Core::SpriteRenderSystem::SpriteRenderSystem(Memory::Allocator * allocator, SizeType capacity) :
24 ComponentSystemWithDataPool(allocator, capacity)
33 expandSpriteGeometry = !
context->device->getCapabilities()->getDeviceCapabilities().GeometryShaders;
35 if (expandSpriteGeometry) {
36 spriteMaterial =
context->materialManager->loadMaterial(
"Materials/SpriteNoGsMaterial.material");
38 spriteMaterial =
context->materialManager->loadMaterial(
"Materials/SpriteMaterial.material");
40 context->materialManager->processLoading();
44 registerSpriteMaterial(spriteMaterial);
46 spriteBatches.reserve(1024);
51 currentBatch =
nullptr;
52 spriteBatches.clear();
54 for (
auto & m : materials) {
55 m.freeInstances.insert(m.freeInstances.end(), m.frameInstances.begin(), m.frameInstances.end());
56 m.frameInstances.clear();
61 for (
auto & sprite : pool) {
62 auto & spriteData = getData(&sprite);
63 spriteData.batchIndex = NoBatchIndex;
66 freeMeshes.insert(freeMeshes.end(), frameMeshes.begin(), frameMeshes.end());
72 for (
auto & sprite : pool) {
73 auto & spriteData = getData(&sprite);
75 if (!sprite.isVisible()) {
76 spriteData.batchIndex = NoBatchIndex;
80 if (spriteData.batchIndex == NoBatchIndex)
continue;
82 auto batch = getSpriteBatch(spriteData.batchIndex);
84 auto spritePosition = glm::vec3(batch->transform * glm::vec4(spriteData.position, 1));
86 const glm::vec3 kEpsilon = glm::vec3(0.01f);
87 spriteData.boundingBox = { spritePosition - kEpsilon, spritePosition + kEpsilon };
90 base::postUpdate(context);
97 if(pool.size() == 1) {
98 assert(picker ==
nullptr);
100 context->rayPicking->addPickable(picker);
102 LOG_DEBUG(logger,
"Registered sprint picker extension");
105 return componentHandle;
110 base::destroyComponent(component);
112 if (pool.size() == 0) {
113 assert(picker !=
nullptr);
114 context->rayPicking->removePickable(picker);
118 LOG_DEBUG(logger,
"Last component destroyed, unregistered sprite picker extension");
122size_t Cogs::Core::SpriteRenderSystem::registerSpriteMaterial(
MaterialHandle material)
124 materials.resize(materials.size() + 1);
125 auto & spriteMaterial = materials.back();
127 spriteMaterial.material = material;
129 if (material->isDefaultMaterial()) {
130 spriteMaterial.textureKey = DefaultMaterial::DiffuseMap;
131 spriteMaterial.colorKey = DefaultMaterial::DiffuseColor;
133 spriteMaterial.textureKey = material->getTextureKey(
"spriteTexture");
135 if (spriteMaterial.textureKey == NoProperty) {
136 LOG_ERROR(logger,
"Sprite material does not contain texture property.");
137 return NoSpriteMaterial;
140 spriteMaterial.colorKey = material->getVec4Key(
"spriteColor");
142 if (spriteMaterial.colorKey == NoProperty) {
143 LOG_WARNING(logger,
"Sprite material missing color property.");
146 spriteMaterial.alwaysOnTopKey = material->getBoolKey(
"alwaysOnTop");
148 if (spriteMaterial.alwaysOnTopKey == NoProperty) {
149 LOG_WARNING(logger,
"Sprite material missing 'alwaysOnTop' property.");
153 return materials.size() - 1;
156Cogs::Core::SpriteBatch * Cogs::Core::SpriteRenderSystem::begin(SpriteMaterialKey materialKey,
const StringView & permutation, TextureHandle texture, glm::vec4 color,
bool alwaysOnTop)
158 if (!currentBatch || currentBatch->count != 0) {
159 if (currentBatch && !currentBatch->count) {
160 LOG_WARNING(logger,
"Empty sprite batch detected.");
163 spriteBatches.resize(spriteBatches.size() + 1);
164 currentBatch = &spriteBatches.back();
165 currentBatch->index = spriteBatches.size() - 1;
170 if (materialKey == NoSpriteMaterial) {
171 LOG_WARNING(logger,
"Invalid sprite material key, falling back to default.");
173 materialKey = DefaultSpriteMaterial;
176 auto & spriteMaterial = materials[materialKey];
178 if (spriteMaterial.freeInstances.size()) {
179 currentBatch->material = spriteMaterial.freeInstances.back();
180 spriteMaterial.freeInstances.pop_back();
181 currentBatch->material->reset();
183 currentBatch->material = context->materialInstanceManager->createMaterialInstance(spriteMaterial.material);
186 spriteMaterial.frameInstances.push_back(currentBatch->material);
188 if (!currentBatch->material->isDefaultMaterial()) {
189 currentBatch->material->setPermutation(permutation);
191 color = glm::vec4(glm::vec3(color) * color.a, color.a);
193 currentBatch->material->setBoolProperty(DefaultMaterial::EnableLighting,
false);
195 currentBatch->material->options.depthWriteEnabled =
false;
198 currentBatch->material->setTextureProperty(spriteMaterial.textureKey, texture);
200 if (spriteMaterial.colorKey != NoProperty) {
201 currentBatch->material->setVec4Property(spriteMaterial.colorKey, color);
204 if (spriteMaterial.alwaysOnTopKey != NoProperty) {
205 currentBatch->material->setBoolProperty(spriteMaterial.alwaysOnTopKey, alwaysOnTop);
208 currentBatch->flags |= alwaysOnTop ? RenderItemFlags::AlwaysOnTop : RenderItemFlags::None;
214 const StringView & postfix,
217 TextureHandle texture,
221 const auto permutation = getPermutation(positionMode, sizeMode).to_string() + postfix.to_string();
223 auto batch = begin(material, permutation, texture, color, alwaysOnTop);
225 batch->positionMode = positionMode;
226 batch->sizeMode = sizeMode;
231Cogs::Core::SpriteBatch * Cogs::Core::SpriteRenderSystem::begin(SpriteMaterialKey material, TextureHandle texture, glm::vec4 color)
233 return begin(material,
"", texture, color);
238 return begin(DefaultSpriteMaterial, texture, color);
241void Cogs::Core::SpriteRenderSystem::addSprite(SpriteRenderComponent * spriteRenderer, glm::vec3 position, glm::vec2 size, glm::vec4 texCoords)
243 addSprite(spriteRenderer, position, glm::vec2(0, 0), size, texCoords);
246void Cogs::Core::SpriteRenderSystem::addSprite(SpriteRenderComponent * spriteRenderer, glm::vec3 position, glm::vec2 offset, glm::vec2 size, glm::vec4 texCoords)
249 LOG_ERROR(logger,
"Cannot add sprite without active batch.");
253 if (!spriteRenderer) {
254 LOG_ERROR(logger,
"Cannot add sprite without SpriteRenderComponent.");
258 if (!spriteRenderer->isVisible())
return;
260 if (!expandSpriteGeometry) {
261 currentBatch->count++;
263 const size_t spriteIndex = currentBatch->start + currentBatch->count - 1;
265 auto & spriteData = getData(spriteRenderer);
266 spriteData.batchIndex = currentBatch->index;
267 spriteData.spriteIndex = spriteIndex;
269 spriteData.position = position;
270 spriteData.offset = offset;
271 spriteData.size = size;
273 if (currentBatch->count == 1) {
276 currentBatch->lod = spriteRenderer->lod;
279 auto mesh = currentBatch->mesh.resolve();
281 const size_t meshCount = currentBatch->start + currentBatch->count;
282 auto vertexData = mesh->map<SpriteVertex>(
283 VertexDataType::Interleaved0,
284 SpriteVertex::getVertexFormat(),
286 meshCount < kMinBatchSize ? kMinBatchSize : meshCount);
288 vertexData[spriteIndex].position = glm::vec4(position, 1);
289 vertexData[spriteIndex].parameters = glm::vec4(size, offset);
290 vertexData[spriteIndex].texCoords = texCoords;
292 mesh->setCount(meshCount);
294 currentBatch->expanded =
true;
296 const size_t spriteIndex = currentBatch->start + currentBatch->count;
297 currentBatch->count += 6;
299 auto & spriteData = getData(spriteRenderer);
300 spriteData.batchIndex = currentBatch->index;
301 spriteData.spriteIndex = spriteIndex/6;
303 spriteData.position = position;
304 spriteData.offset = offset;
305 spriteData.size = size;
307 if (currentBatch->count == 6) {
310 currentBatch->lod = spriteRenderer->lod;
313 auto mesh = currentBatch->mesh.resolve();
315 const size_t meshCount = currentBatch->start + currentBatch->count;
316 auto vertexData = mesh->map<SpriteVertex>(
317 VertexDataType::Interleaved0,
318 SpriteVertex::getVertexFormat(),
320 meshCount < kMinBatchSize ? kMinBatchSize : meshCount);
322 const float indices[6] = { 0, 1, 2, 2, 1, 3 };
323 for (
unsigned i = 0; i < 6; i++) {
324 vertexData[spriteIndex + i].position = glm::vec4(position, indices[i]);
325 vertexData[spriteIndex + i].parameters = glm::vec4(size, offset);
326 vertexData[spriteIndex + i].texCoords = texCoords;
329 mesh->setCount(meshCount);
335 if (index >= spriteBatches.size())
return nullptr;
337 return &spriteBatches[index];
340std::span<const Cogs::Core::SpriteBatch> Cogs::Core::SpriteRenderSystem::getSpriteBatches()
const
342 return spriteBatches;
348 switch (positionMode)
351 return "NoTransformRelativeSize";
353 return "PixelTransformRelativeSize";
355 case PositionMode::Local:
356 return "ViewTransformRelativeSize";
361 switch (positionMode)
364 return "PixelTransformPixelSize";
366 case PositionMode::Local:
367 return "ViewTransformPixelSize";
376void Cogs::Core::SpriteRenderSystem::updateBatchMesh()
378 const size_t kMeshMinBatchCount = 1024;
380 if (freeMeshes.size()) {
381 currentBatch->mesh = freeMeshes.back();
382 freeMeshes.pop_back();
383 frameMeshes.push_back(currentBatch->mesh);
385 for (
auto & m : frameMeshes) {
386 if (m->getCount() < kMeshMinBatchCount) {
387 currentBatch->mesh = m;
388 currentBatch->start = m->getCount();
393 if (!currentBatch->mesh) {
394 currentBatch->mesh = context->meshManager->create();
397 currentBatch->mesh->setName(
"SpriteMesh");
398 frameMeshes.push_back(currentBatch->mesh);
404Cogs::Core::SpritePicker::SpritePicker(SpriteRenderSystem* spriteSystem)
405 : spriteSystem(spriteSystem)
411 const glm::vec2& normPosition,
417 std::vector<RayPicking::RayPickHit>& hits)
426 const CameraData& cameraData = context->cameraSystem->getData(&camera);
427 const glm::mat4 invProjection = glm::inverse(cameraData.rawViewProjection);
429 bool hitSomething =
false;
439 const SpriteRenderData& data = context->spriteRenderSystem->getData(&sprite);
440 const SpriteBatch* batch = context->spriteRenderSystem->getSpriteBatch(data.batchIndex);
445 const size_t meshCount = batch->start + batch->count;
447 SpriteVertex::getVertexFormat(),
449 meshCount < kMinBatchSize ? kMinBatchSize : meshCount);
450 if (!meshData.isValid()) {
455 const SpriteVertex& spriteVertex = meshData[batch->start];
457 const glm::vec3 position = spriteVertex.position;
458 const glm::vec2 offset = glm::vec2(spriteVertex.parameters.z, spriteVertex.parameters.w);
459 const glm::vec2 size = glm::vec2(spriteVertex.parameters.x, spriteVertex.parameters.y);
461 glm::vec4 normPos = glm::vec4(0, 0, 0, 1);
462 glm::vec2 normOffset = offset / (0.5f * cameraData.viewportSize);
465 normPos = glm::vec4(glm::vec2(position) / (0.5f * cameraData.viewportSize) - glm::vec2(1, 1) + normOffset, position.z, 1);
468 normPos = glm::vec4(glm::vec2(position) + normOffset, position.z, 1);
471 const glm::vec3 worldPos = glm::vec3(batch->transform * glm::vec4(position, 1));
472 normPos = cameraData.rawViewProjection * glm::vec4(worldPos, 1);
473 normPos /= normPos.w;
474 normPos = glm::vec4(glm::vec2(normPos) + normOffset, normPos.z, 1);
477 const glm::vec2 normSize = size / cameraData.viewportSize;
479 const float xMin = normPos.x - glm::abs(normSize.x);
480 const float xMax = normPos.x + glm::abs(normSize.x);
481 const float yMin = normPos.y - glm::abs(normSize.y);
482 const float yMax = normPos.y + glm::abs(normSize.y);
484 if (normPosition.x > xMin && normPosition.x < xMax && normPosition.y > yMin && normPosition.y < yMax) {
485 glm::vec4 worldPos = invProjection * glm::vec4(normPosition.x, normPosition.y, normPos.z, 1.0f);
486 worldPos /= worldPos.w;
488 glm::vec4 clipPos = cameraData.rawViewProjection * glm::vec4(glm::vec3(worldPos), 1.f);
489 if ((-clipPos.w <= clipPos.z) && (clipPos.z <= clipPos.w)) {
491 const glm::vec4 viewPos= cameraData.viewMatrix * worldPos;
492 const float viewDist = -viewPos.z;
495 if (viewDist < hits[0].distance) {
496 hits[0] = {sprite, returnChildEntity, position, viewDist};
502 hits.emplace_back(sprite, returnChildEntity, position, viewDist);
510 std::sort(hits.begin(), hits.end());
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 preUpdate()
Run the pre-update method of the system.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
constexpr bool isVisibleInLayer(RenderLayers layerMask) const
Check if the entity should be visible in the given layer mask.
constexpr bool isPickable() const
Check if the entity is pickable or not.
bool pickCamera(Context *context, const CameraComponent &camera, const glm::vec2 &normPosition, float, float, PickingFlags pickingFlags, PicksReturned returnFlag, const RayPicking::RayPickFilter &filter, std::vector< RayPicking::RayPickHit > &hits) override
Do a ray pick from a normalized screen space position in the camera direction and return all hits.
void initialize(Context *context) override
Initialize the system.
ComponentHandle createComponent() override
void destroyComponent(ComponentHandle component) override
Log implementation class.
Provides a weakly referenced view over the contents of a string.
PositionMode
Positioning mode.
@ Relative
Position given in normalized device coordinates, with [-1, -1] corresponding to the lower left of the...
@ Pixels
Position given in screen pixels ranging from [0, 0] in the lower left corner to [viewport....
@ World
Position given in world space coordinates.
PicksReturned
* Options for returning picking hits.
@ Closest
Return just the closest hit.
@ AllSorted
Return all hits sorted based on distance from the ray start, closest first.
PickingFlags
Options for COGS picking.
@ ReturnChildEntity
Return ID if sub-entity picked, not set: return root parent entity.
@ PickSprites
Check picking for entities with SpriteRenderComponent. I.e. Text, Annotation, Billboard,...
@ Blend
Render with regular alpha blending.
@ Relative
Size given in normalized device coordinates.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Handle to a Component instance.
Contains data describing a Camera instance and its derived data structured such as matrix data and vi...
@ Sprite
The material is used for sprite rendering only.
BlendMode blendMode
Blend mode to use when rendering geometry with the material instance.
bool depthWriteEnabled
If depth writes should be enabled rendering the geometry with the material instance.
MaterialOptions options
Material rendering options.
MappedStreamReadOnly< Element > mapReadOnly(const VertexDataType::EVertexDataType type, VertexFormatHandle format, const size_t start, const size_t count)
Maps the data stream corresponding to the given type for read-only access.
bool isUnwantedType(const ComponentModel::Component &comp) const
Helper function used to determine if a given component belongs to an accepted entity type.
RenderLayers layerMask
Limit picking to the specified render layers. Pick all layers by default.
uint8_t currentLod
The assigned LoD of the current component.
uint8_t selectedLod
The selected LoD of the composite entity.
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
@ PointList
List of points.
@ TriangleList
List of triangles.