// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "Mesh.h" #include "MeshDeformation.h" #include "ModelInstanceEntry.h" #include "Engine/Content/Assets/Material.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Core/Log.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Scripting/ManagedCLR/MCore.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Threading/Task.h" #include "Engine/Threading/Threading.h" #if USE_EDITOR #include "Engine/Renderer/GBufferPass.h" #endif GPUVertexLayout* VB0ElementType18::GetLayout() { return GPUVertexLayout::Get({ { VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32B32_Float }, }); } GPUVertexLayout* VB1ElementType18::GetLayout() { return GPUVertexLayout::Get({ { VertexElement::Types::TexCoord, 1, 0, 0, PixelFormat::R16G16_Float }, { VertexElement::Types::Normal, 1, 0, 0, PixelFormat::R10G10B10A2_UNorm }, { VertexElement::Types::Tangent, 1, 0, 0, PixelFormat::R10G10B10A2_UNorm }, { VertexElement::Types::TexCoord1, 1, 0, 0, PixelFormat::R16G16_Float }, }); } GPUVertexLayout* VB2ElementType18::GetLayout() { return GPUVertexLayout::Get({ { VertexElement::Types::Color, 2, 0, 0, PixelFormat::R8G8B8A8_UNorm }, }); } namespace { template bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const IndexType* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) { auto model = mesh->GetModel(); CHECK_RETURN(model && model->IsVirtual(), true); CHECK_RETURN(triangles && vertices, true); // Pack mesh data into vertex buffers Array vb1; Array vb2; vb1.Resize(vertexCount); if (normals) { if (tangents) { for (uint32 i = 0; i < vertexCount; i++) { const Float3 normal = normals[i]; const Float3 tangent = tangents[i]; auto& v = vb1.Get()[i]; RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal, tangent); } } else { for (uint32 i = 0; i < vertexCount; i++) { const Float3 normal = normals[i]; auto& v = vb1.Get()[i]; RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal); } } } else { // Set default tangent frame const auto n = Float1010102(Float3::UnitZ); const auto t = Float1010102(Float3::UnitX); for (uint32 i = 0; i < vertexCount; i++) { vb1.Get()[i].Normal = n; vb1.Get()[i].Tangent = t; } } if (uvs) { for (uint32 i = 0; i < vertexCount; i++) vb1.Get()[i].TexCoord = Half2(uvs[i]); } else { auto v = Half2::Zero; for (uint32 i = 0; i < vertexCount; i++) vb1.Get()[i].TexCoord = v; } { auto v = Half2::Zero; for (uint32 i = 0; i < vertexCount; i++) vb1.Get()[i].LightmapUVs = v; } if (colors) { vb2.Resize(vertexCount); for (uint32 i = 0; i < vertexCount; i++) vb2.Get()[i].Color = colors[i]; } return mesh->UpdateMesh(vertexCount, triangleCount, (VB0ElementType*)vertices, vb1.Get(), vb2.HasItems() ? vb2.Get() : nullptr, triangles); } #if !COMPILE_WITHOUT_CSHARP template bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj) { ASSERT((uint32)MCore::Array::GetLength(verticesObj) >= vertexCount); ASSERT((uint32)MCore::Array::GetLength(trianglesObj) / 3 >= triangleCount); auto vertices = MCore::Array::GetAddress(verticesObj); auto triangles = MCore::Array::GetAddress(trianglesObj); const auto normals = normalsObj ? MCore::Array::GetAddress(normalsObj) : nullptr; const auto tangents = tangentsObj ? MCore::Array::GetAddress(tangentsObj) : nullptr; const auto uvs = uvObj ? MCore::Array::GetAddress(uvObj) : nullptr; const auto colors = colorsObj ? MCore::Array::GetAddress(colorsObj) : nullptr; return UpdateMesh(mesh, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); } #endif } bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices) { Release(); // Setup GPU resources const bool failed = Load(vertexCount, triangleCount, vb0, vb1, vb2, ib, use16BitIndices); if (!failed) { // Calculate mesh bounds BoundingBox bounds; BoundingBox::FromPoints((const Float3*)vb0, vertexCount, bounds); SetBounds(bounds); } return failed; } bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) { return ::UpdateMesh(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); } bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) { return ::UpdateMesh(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); } bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer) { Array> vbData; vbData.Add(vb0); if (vb1) vbData.Add(vb1); if (vb2) vbData.Add(vb2); Array> vbLayout; vbLayout.Add(VB0ElementType::GetLayout()); vbLayout.Add(VB1ElementType::GetLayout()); vbLayout.Add(VB2ElementType::GetLayout()); return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout); } void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, const Matrix& world, StaticFlags flags, bool receiveDecals, DrawPass drawModes, float perInstanceRandom, int8 sortOrder) const { if (!material || !material->IsSurface() || !IsInitialized()) return; drawModes &= material->GetDrawModes(); if (drawModes == DrawPass::None) return; // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; drawCall.Geometry.VertexBuffers[1] = _vertexBuffers[1]; drawCall.Geometry.VertexBuffers[2] = _vertexBuffers[2]; drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; drawCall.Material = material; drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); drawCall.ObjectRadius = (float)_sphere.Radius * drawCall.World.GetScaleVector().GetAbsolute().MaxValue(); drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = world; drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World); drawCall.PerInstanceRandom = perInstanceRandom; #if USE_EDITOR const ViewMode viewMode = renderContext.View.Mode; if (viewMode == ViewMode::LightmapUVsDensity || viewMode == ViewMode::LODPreview) GBufferPass::AddIndexBufferToModelLOD(_indexBuffer, &((Model*)_model)->LODs[_lodIndex]); #endif // Push draw call to the render list renderContext.List->AddDrawCall(renderContext, drawModes, flags, drawCall, receiveDecals, sortOrder); } void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const { const auto& entry = info.Buffer->At(_materialSlotIndex); if (!entry.Visible || !IsInitialized()) return; const MaterialSlot& slot = _model->MaterialSlots[_materialSlotIndex]; // Select material MaterialBase* material; if (entry.Material && entry.Material->IsLoaded()) material = entry.Material; else if (slot.Material && slot.Material->IsLoaded()) material = slot.Material; else material = GPUDevice::Instance->GetDefaultMaterial(); if (!material || !material->IsSurface()) return; // Check if skip rendering const auto shadowsMode = entry.ShadowsMode & slot.ShadowsMode; const auto drawModes = info.DrawModes & renderContext.View.Pass & renderContext.View.GetShadowsDrawPassMask(shadowsMode) & material->GetDrawModes(); if (drawModes == DrawPass::None) return; // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; drawCall.Geometry.VertexBuffers[1] = _vertexBuffers[1]; drawCall.Geometry.VertexBuffers[2] = _vertexBuffers[2]; if (info.Deformation) { info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]); info.Deformation->RunDeformers(this, MeshBufferType::Vertex1, drawCall.Geometry.VertexBuffers[1]); } if (info.VertexColors && info.VertexColors[_lodIndex]) { // TODO: cache vertexOffset within the model LOD per-mesh uint32 vertexOffset = 0; for (int32 meshIndex = 0; meshIndex < _index; meshIndex++) vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount(); drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex]; drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType); } drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); drawCall.ObjectRadius = (float)info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr; drawCall.Surface.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Rectangle::Empty; drawCall.Surface.LODDitherFactor = lodDitherFactor; drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World); drawCall.PerInstanceRandom = info.PerInstanceRandom; #if USE_EDITOR const ViewMode viewMode = renderContext.View.Mode; if (viewMode == ViewMode::LightmapUVsDensity || viewMode == ViewMode::LODPreview) GBufferPass::AddIndexBufferToModelLOD(_indexBuffer, &((Model*)_model)->LODs[_lodIndex]); if (viewMode == ViewMode::LightmapUVsDensity) drawCall.Surface.LODDitherFactor = info.LightmapScale; // See LightmapUVsDensityMaterialShader #endif // Push draw call to the render list renderContext.List->AddDrawCall(renderContext, drawModes, info.Flags, drawCall, entry.ReceiveDecals, info.SortOrder); } void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& info, float lodDitherFactor) const { const auto& entry = info.Buffer->At(_materialSlotIndex); if (!entry.Visible || !IsInitialized()) return; const MaterialSlot& slot = _model->MaterialSlots[_materialSlotIndex]; // Select material MaterialBase* material; if (entry.Material && entry.Material->IsLoaded()) material = entry.Material; else if (slot.Material && slot.Material->IsLoaded()) material = slot.Material; else material = GPUDevice::Instance->GetDefaultMaterial(); if (!material || !material->IsSurface()) return; // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; drawCall.Geometry.VertexBuffers[1] = _vertexBuffers[1]; drawCall.Geometry.VertexBuffers[2] = _vertexBuffers[2]; if (info.Deformation) { info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]); info.Deformation->RunDeformers(this, MeshBufferType::Vertex1, drawCall.Geometry.VertexBuffers[1]); } if (info.VertexColors && info.VertexColors[_lodIndex]) { // TODO: cache vertexOffset within the model LOD per-mesh uint32 vertexOffset = 0; for (int32 meshIndex = 0; meshIndex < _index; meshIndex++) vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount(); drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex]; drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType); } drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); drawCall.ObjectRadius = (float)info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr; drawCall.Surface.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Rectangle::Empty; drawCall.Surface.LODDitherFactor = lodDitherFactor; drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World); drawCall.PerInstanceRandom = info.PerInstanceRandom; #if USE_EDITOR const ViewMode viewMode = renderContextBatch.GetMainContext().View.Mode; if (viewMode == ViewMode::LightmapUVsDensity || viewMode == ViewMode::LODPreview) GBufferPass::AddIndexBufferToModelLOD(_indexBuffer, &((Model*)_model)->LODs[_lodIndex]); if (viewMode == ViewMode::LightmapUVsDensity) drawCall.Surface.LODDitherFactor = info.LightmapScale; // See LightmapUVsDensityMaterialShader #endif // Push draw call to the render lists const auto shadowsMode = entry.ShadowsMode & slot.ShadowsMode; const auto drawModes = info.DrawModes & material->GetDrawModes(); if (drawModes != DrawPass::None) renderContextBatch.GetMainContext().List->AddDrawCall(renderContextBatch, drawModes, info.Flags, shadowsMode, info.Bounds, drawCall, entry.ReceiveDecals, info.SortOrder); } bool Mesh::Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout) { if (MeshBase::Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout)) return true; auto model = (Model*)_model; if (model) model->LODs[_lodIndex]._verticesCount += _vertices; return false; } void Mesh::Release() { auto model = (Model*)_model; if (model) model->LODs[_lodIndex]._verticesCount -= _vertices; MeshBase::Release(); } bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const { if (_cachedVertexBuffers[0].IsEmpty()) { PROFILE_CPU(); auto model = GetModel(); ScopeLock lock(model->Locker); if (model->IsVirtual()) { LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download."); return true; } // Fetch chunk with data from drive/memory const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(GetLODIndex()); if (model->LoadChunk(chunkIndex)) return true; const auto chunk = model->GetChunk(chunkIndex); if (!chunk) { LOG(Error, "Missing chunk."); return true; } MemoryReadStream stream(chunk->Get(), chunk->Size()); // Seek to find mesh location for (int32 i = 0; i <= _index; i++) { // #MODEL_DATA_FORMAT_USAGE uint32 vertices; stream.ReadUint32(&vertices); uint32 triangles; stream.ReadUint32(&triangles); uint32 indicesCount = triangles * 3; bool use16BitIndexBuffer = indicesCount <= MAX_uint16; uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); if (vertices == 0 || triangles == 0) { LOG(Error, "Invalid mesh data."); return true; } auto vb0 = stream.Move(vertices); auto vb1 = stream.Move(vertices); bool hasColors = stream.ReadBool(); VB2ElementType18* vb2 = nullptr; if (hasColors) { vb2 = stream.Move(vertices); } auto ib = stream.Move(indicesCount * ibStride); if (i != _index) continue; // Cache mesh data _cachedIndexBufferCount = indicesCount; _cachedIndexBuffer.Set(ib, indicesCount * ibStride); _cachedVertexBuffers[0].Set((const byte*)vb0, vertices * sizeof(VB0ElementType)); _cachedVertexBuffers[1].Set((const byte*)vb1, vertices * sizeof(VB1ElementType)); if (hasColors) _cachedVertexBuffers[2].Set((const byte*)vb2, vertices * sizeof(VB2ElementType)); break; } } switch (type) { case MeshBufferType::Index: result.Link(_cachedIndexBuffer); count = _cachedIndexBufferCount; break; case MeshBufferType::Vertex0: result.Link(_cachedVertexBuffers[0]); count = _cachedVertexBuffers[0].Count() / sizeof(VB0ElementType); break; case MeshBufferType::Vertex1: result.Link(_cachedVertexBuffers[1]); count = _cachedVertexBuffers[1].Count() / sizeof(VB1ElementType); break; case MeshBufferType::Vertex2: result.Link(_cachedVertexBuffers[2]); count = _cachedVertexBuffers[2].Count() / sizeof(VB2ElementType); break; default: return true; } return false; } #if !COMPILE_WITHOUT_CSHARP bool Mesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj) { return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj); } bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj) { return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj); } enum class InternalBufferType { VB0 = 0, VB1 = 1, VB2 = 2, IB16 = 3, IB32 = 4, }; MArray* Mesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI) { auto mesh = this; auto type = (InternalBufferType)typeI; auto model = mesh->GetModel(); ScopeLock lock(model->Locker); // Virtual assets always fetch from GPU memory forceGpu |= model->IsVirtual(); if (!mesh->IsInitialized() && forceGpu) { LOG(Error, "Cannot load mesh data from GPU if it's not loaded."); return nullptr; } MeshBufferType bufferType; switch (type) { case InternalBufferType::VB0: bufferType = MeshBufferType::Vertex0; break; case InternalBufferType::VB1: bufferType = MeshBufferType::Vertex1; break; case InternalBufferType::VB2: bufferType = MeshBufferType::Vertex2; break; case InternalBufferType::IB16: case InternalBufferType::IB32: bufferType = MeshBufferType::Index; break; default: return nullptr; } BytesContainer data; int32 dataCount; if (forceGpu) { // Get data from GPU // TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer auto task = mesh->DownloadDataGPUAsync(bufferType, data); if (task == nullptr) return nullptr; task->Start(); model->Locker.Unlock(); if (task->Wait()) { LOG(Error, "Task failed."); return nullptr; } model->Locker.Lock(); // Extract elements count from result data switch (bufferType) { case MeshBufferType::Index: dataCount = data.Length() / (Use16BitIndexBuffer() ? sizeof(uint16) : sizeof(uint32)); break; case MeshBufferType::Vertex0: dataCount = data.Length() / sizeof(VB0ElementType); break; case MeshBufferType::Vertex1: dataCount = data.Length() / sizeof(VB1ElementType); break; case MeshBufferType::Vertex2: dataCount = data.Length() / sizeof(VB2ElementType); break; } } else { // Get data from CPU if (DownloadDataCPU(bufferType, data, dataCount)) return nullptr; } // Convert into managed array MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), dataCount); void* managedArrayPtr = MCore::Array::GetAddress(result); const int32 elementSize = data.Length() / dataCount; switch (type) { case InternalBufferType::VB0: case InternalBufferType::VB1: case InternalBufferType::VB2: { Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length()); break; } case InternalBufferType::IB16: { if (elementSize == sizeof(uint16)) { Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length()); } else { auto dst = (uint16*)managedArrayPtr; auto src = (uint32*)data.Get(); for (int32 i = 0; i < dataCount; i++) dst[i] = src[i]; } break; } case InternalBufferType::IB32: { if (elementSize == sizeof(uint16)) { auto dst = (uint32*)managedArrayPtr; auto src = (uint16*)data.Get(); for (int32 i = 0; i < dataCount; i++) dst[i] = src[i]; } else { Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length()); } break; } } return result; } #endif