diff --git a/.gitignore b/.gitignore index e0c79f6..b5a4b67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,69 +1,51 @@ ``` -# Compiled and build artifacts +# Build artifacts *.o *.obj -*.exe +*.a +*.lib *.dll *.so -*.a +*.dylib +*.exe *.out -# Dependencies -venv/ -.venv/ -__pycache__/ -.mypy_cache/ -.pytest_cache/ -node_modules/ -dist/ +# CMake build directories build/ -target/ -.gradle/ +cmake-build-*/ +CMakeFiles/ +CMakeCache.txt +Makefile +compile_commands.json + +# Dependencies +vendor/ +external/ -# Logs and temp files +# Logs *.log + +# Temporary files *.tmp -*.swp +*.temp # Environment .env .env.local -*.env.* +.env.* # Editors .vscode/ .idea/ - -# System files -.DS_Store -Thumbs.db +*.swp +*.swo # Coverage coverage/ htmlcov/ .coverage -# Compressed files -*.zip -*.gz -*.tar -*.tgz -*.bz2 -*.xz -*.7z -*.rar -*.zst -*.lz4 -*.lzh -*.cab -*.arj -*.rpm -*.deb -*.Z -*.lz -*.lzo -*.tar.gz -*.tar.bz2 -*.tar.xz -*.tar.zst +# OS +.DS_Store +Thumbs.db ``` \ No newline at end of file diff --git a/FarmEngine/CMakeLists.txt b/FarmEngine/CMakeLists.txt index b666d99..3d5310a 100644 --- a/FarmEngine/CMakeLists.txt +++ b/FarmEngine/CMakeLists.txt @@ -96,6 +96,11 @@ endif() add_library(FarmEngineRenderer STATIC renderer/Renderer.cpp renderer/Renderer.h + # Render Graph System + rendering/graph/RenderGraph.h + rendering/graph/RenderGraph.cpp + rendering/graph/RenderPipeline.h + rendering/graph/RenderPipeline.cpp ) target_include_directories(FarmEngineRenderer PUBLIC diff --git a/FarmEngine/src/rendering/graph/ExampleUsage.h b/FarmEngine/src/rendering/graph/ExampleUsage.h new file mode 100644 index 0000000..8217e5c --- /dev/null +++ b/FarmEngine/src/rendering/graph/ExampleUsage.h @@ -0,0 +1,189 @@ +#pragma once + +/** + * @file ExampleUsage.h + * @brief Ejemplos de uso del Render Graph para diferentes casos del FarmEngine + */ + +#include "RenderGraph.h" +#include "RenderPipeline.h" + +namespace FarmEngine::Render::Examples { + +/** + * @brief Ejemplo: Pipeline completo para rendering de chunks de terreno + * + * Este es el caso de uso principal para FarmEngine - renderizar chunks + * voxel con iluminación dinámica y oclusión ambiental. + */ +inline void ChunkRenderingExample(RenderGraph& graph, VkExtent2D swapchainExtent) { + RenderGraphBuilder builder; + + // 1. Recursos principales + builder.addColorTarget("SceneColor", VK_FORMAT_R16G16B16A16_SFLOAT, swapchainExtent, ResourceLifetime::Frame); + builder.addDepthTarget("SceneDepth", VK_FORMAT_D32_SFLOAT, swapchainExtent, ResourceLifetime::Frame); + + // 2. Geometry Pass - Renderiza todos los chunks visibles + builder.addPass("ChunkGeometry", [](RenderPass& pass) { + pass.colorAttachments.push_back("SceneColor"); + pass.depthAttachment = "SceneDepth"; + pass.clearColor = true; + pass.clearDepth = true; + pass.clearColorValue = {{0.4f, 0.6f, 0.9f, 1.0f}}; // Sky blue + + // Callback que se ejecutará durante el render pass + pass.executeCallback = [](VkCommandBuffer cmd, const ResourceRegistry& registry) { + // Aquí se integrarían con ChunkManager::getVisibleChunks() + // for (auto* chunk : visibleChunks) { + // chunk->renderOpaque(cmd); + // } + }; + }); + + // 3. Transparency Pass - Plantas, agua, partículas + builder.addPass("Transparency", [](RenderPass& pass) { + pass.colorAttachments.push_back("SceneColor"); + pass.depthAttachment = "SceneDepth"; + pass.colorLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; // Preserve previous pass + pass.depthLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + + pass.executeCallback = [](VkCommandBuffer cmd, const ResourceRegistry& registry) { + // Render transparent crops, water, animals + }; + }); + + // 4. Post-Processing - Tonemapping a swapchain + builder.addPass("Tonemapping", [](RenderPass& pass) { + pass.inputAttachments = {"SceneColor"}; + pass.colorAttachments.push_back("Swapchain"); // External + pass.clearColor = false; + + pass.executeCallback = [](VkCommandBuffer cmd, const ResourceRegistry& registry) { + // ACES tonemapping + gamma correction + }; + }); + + graph.compile(std::move(builder)); +} + +/** + * @brief Ejemplo: Shadow mapping para iluminación dinámica + * + * Muestra cómo añadir un pass de shadow map antes del geometry pass + */ +inline void ShadowMappingExample(RenderGraph& graph, VkExtent2D extent) { + RenderGraphBuilder builder; + + // Shadow map resource (alta resolución para calidad) + builder.addDepthTarget("ShadowMap", VK_FORMAT_D32_SFLOAT, {2048, 2048}, ResourceLifetime::Frame); + + // Shadow pass - renderiza desde perspectiva de la luz + builder.addPass("ShadowPass", [](RenderPass& pass) { + pass.depthAttachment = "ShadowMap"; + pass.clearDepth = true; + pass.clearDepthValue = {1.0f, 0}; + + pass.executeCallback = [](VkCommandBuffer cmd, const ResourceRegistry& registry) { + // Bind shadow pipeline + // Set light view/projection matrices + // Render depth-only pass de chunks + }; + }); + + // Main geometry pass que usa el shadow map + builder.addPass("MainGeometry", [](RenderPass& pass) { + pass.colorAttachments.push_back("SceneColor"); + pass.depthAttachment = "SceneDepth"; + pass.inputAttachments.push_back("ShadowMap"); // Para sampling en shader + + pass.executeCallback = [](VkCommandBuffer cmd, const ResourceRegistry& registry) { + // Fragment shader samplea ShadowMap para calcular oclusión + }; + }); + + // Dependencia explícita: ShadowPass -> MainGeometry + builder.addDependency( + 0, 1, // From pass 0 to pass 1 + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_ACCESS_SHADER_READ_BIT + ); + + graph.compile(std::move(builder)); +} + +/** + * @brief Ejemplo: SSAO (Screen Space Ambient Occlusion) + * + * Técnica post-process para oclusión ambiental en tiempo real + */ +inline void SSAOExample(RenderGraph& graph, VkExtent2D extent) { + RenderGraphBuilder builder; + + // GBuffer básico + builder.addColorTarget("GBuffer_Albedo", VK_FORMAT_R8G8B8A8_UNORM, extent, ResourceLifetime::Frame); + builder.addColorTarget("GBuffer_Normals", VK_FORMAT_A2B10G10R10_UNORM_PACK32, extent, ResourceLifetime::Frame); + builder.addDepthTarget("SceneDepth", VK_FORMAT_D32_SFLOAT, extent, ResourceLifetime::Frame); + + // SSAO buffer (half-res para performance) + builder.addColorTarget("SSAO_Buffer", VK_FORMAT_R8_UNORM, {extent.width / 2, extent.height / 2}, ResourceLifetime::Frame); + + // 1. Geometry + builder.addPass("Geometry", [](RenderPass& pass) { + pass.colorAttachments = {"GBuffer_Albedo", "GBuffer_Normals"}; + pass.depthAttachment = "SceneDepth"; + // ... configurar callbacks + }); + + // 2. SSAO Pass + builder.addPass("SSAO", [](RenderPass& pass) { + pass.inputAttachments = {"GBuffer_Normals", "SceneDepth"}; + pass.colorAttachments.push_back("SSAO_Buffer"); + pass.clearColor = true; + + pass.executeCallback = [](VkCommandBuffer cmd, const ResourceRegistry& registry) { + // Full-screen quad con SSAO calculation + // Samplea normals + depth para calcular oclusión + }; + }); + + // 3. Lighting con SSAO aplicado + builder.addPass("Lighting", [](RenderPass& pass) { + pass.inputAttachments = {"GBuffer_Albedo", "GBuffer_Normals", "SSAO_Buffer"}; + pass.colorAttachments.push_back("FinalColor"); + + pass.executeCallback = [](VkCommandBuffer cmd, const ResourceRegistry& registry) { + // Lighting calculation con SSAO factor + }; + }); + + graph.compile(std::move(builder)); +} + +/** + * @brief Ejemplo: Minimizado para testing + * + * Pipeline más simple posible para validar integración + */ +inline void MinimalExample(RenderGraph& graph, VkExtent2D extent) { + RenderGraphBuilder builder; + + builder.addColorTarget("Backbuffer", VK_FORMAT_B8G8R8A8_SRGB, extent, ResourceLifetime::External); + builder.addDepthTarget("Depth", VK_FORMAT_D32_SFLOAT, extent, ResourceLifetime::Frame); + + builder.addPass("ClearAndDraw", [](RenderPass& pass) { + pass.colorAttachments.push_back("Backbuffer"); + pass.depthAttachment = "Depth"; + pass.clearColor = true; + pass.clearDepth = true; + + pass.executeCallback = [](VkCommandBuffer cmd, const ResourceRegistry& registry) { + // Draw single triangle or quad + }; + }); + + graph.compile(std::move(builder)); +} + +} // namespace FarmEngine::Render::Examples diff --git a/FarmEngine/src/rendering/graph/README.md b/FarmEngine/src/rendering/graph/README.md new file mode 100644 index 0000000..5348700 --- /dev/null +++ b/FarmEngine/src/rendering/graph/README.md @@ -0,0 +1,115 @@ +# Render Graph Module - FarmEngine + +## Overview + +Sistema de renderizado basado en **Render Graph** para Vulkan 1.2+, siguiendo las mejores prácticas de motores modernos (Unreal, Frostbite, id Tech). + +## Ventajas Clave + +### 1. Sincronización Automática +- Barreras de memoria generadas automáticamente +- Semaphores entre passes +- Transiciones de layout implícitas + +### 2. Optimización TBDR (Tile-Based Deferred Rendering) +- Reordenamiento de passes para maximizar coherencia de tiles +- Minimización de bandwidth de memoria +- Ideal para GPUs mobile (PowerVR, Adreno, Mali) + +### 3. Alias de Memoria +- Reutilización automática de recursos temporales +- Recursos con lifetime `Frame` se reciclan entre frames +- Reducción significativa de VRAM usage + +## Arquitectura + +``` +┌─────────────────────────────────────────────────────────────┐ +│ RenderGraph │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ GeometryPass │→ │ LightingPass │→ │ PostProcess │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ ↓ ↓ ↓ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ GBuffer │ │ HDR Buffer │ │ Swapchain │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Uso Básico + +```cpp +#include "rendering/graph/RenderGraph.h" +#include "rendering/graph/RenderPipeline.h" + +// Crear grafo +RenderGraph graph; + +// Configurar forward renderer +ForwardRenderer renderer; +renderer.buildGraph(graph, swapchainExtent); + +// Crear recursos Vulkan +graph.createRenderPasses(device); +graph.createFramebuffers(device, swapchainExtent); + +// Ejecutar cada frame +void renderFrame(VkCommandBuffer cmd) { + ResourceRegistry registry = graph.getRegistry(); + graph.execute(cmd, registry, frameIndex); +} +``` + +## Deferred Rendering Avanzado + +```cpp +DeferredRenderer deferred; +deferred.buildGraph(graph, swapchainExtent); + +// GBuffer structure: +// - Albedo: VK_FORMAT_R8G8B8A8_UNORM +// - Normals: VK_FORMAT_A2B10G10R10_UNORM_PACK32 +// - Depth: VK_FORMAT_R32_SFLOAT +// - Material: VK_FORMAT_R8G8B8A8_UNORM +// - HDR Light: VK_FORMAT_R16G16B16A16_SFLOAT +``` + +## Tipos de Recursos + +| Lifetime | Descripción | Ejemplo | +|----------|-------------|---------| +| `Frame` | Se crea/destruye cada frame | GBuffer temporal, HDR buffer | +| `Persistent` | Vive toda la vida del graph | Texturas cargadas, buffers estáticos | +| `External` | Gestionado externamente | Swapchain images, recursos del engine | + +## Integración con ChunkSystem + +El Render Graph se integra naturalmente con el sistema de chunks: + +```cpp +builder.addPass("ChunkGeometry", [](RenderPass& pass) { + pass.colorAttachments.push_back("GBuffer_Albedo"); + pass.depthAttachment = "Depth"; + + pass.executeCallback = [chunkManager](VkCommandBuffer cmd, const ResourceRegistry& reg) { + auto visibleChunks = chunkManager.getVisibleChunks(); + for (auto* chunk : visibleChunks) { + chunk->render(cmd); + } + }; +}); +``` + +## Próximas Mejoras + +- [ ] Inferencia automática de dependencias +- [ ] Async compute queue support +- [ ] Frame graph statistics & profiling +- [ ] Hot-reload de passes desde ImGui +- [ ] Memory alias analysis optimizer + +## Referencias + +- [GPUOpen Render Graph](https://gpuopen.com/) +- [Frostbite Render Graph Architecture](https://www.ea.com/seeds/pubs) +- [Vulkan Best Practices](https://github.com/KhronosGroup/Vulkan-Guide) diff --git a/FarmEngine/src/rendering/graph/RenderGraph.cpp b/FarmEngine/src/rendering/graph/RenderGraph.cpp new file mode 100644 index 0000000..3a93930 --- /dev/null +++ b/FarmEngine/src/rendering/graph/RenderGraph.cpp @@ -0,0 +1,364 @@ +#include "RenderGraph.h" +#include +#include + +namespace FarmEngine::Render { + +// ============================================================================ +// ResourceRegistry Implementation +// ============================================================================ + +const ResourceHandle* ResourceRegistry::getResource(const std::string& name) const { + auto it = nameToIndex.find(name); + if (it == nameToIndex.end()) { + return nullptr; + } + return &resources[it->second]; +} + +VkImageView ResourceRegistry::getImageView(const std::string& name) const { + const ResourceHandle* res = getResource(name); + return res ? res->imageView : VK_NULL_HANDLE; +} + +VkImage ResourceRegistry::getImage(const std::string& name) const { + const ResourceHandle* res = getResource(name); + return res ? res->image : VK_NULL_HANDLE; +} + +VkFormat ResourceRegistry::getFormat(const std::string& name) const { + const ResourceHandle* res = getResource(name); + return res ? res->format : VK_FORMAT_UNDEFINED; +} + +void ResourceRegistry::updateResourceState(const std::string& name, + VkImageLayout newLayout, + VkAccessFlags newAccess, + VkPipelineStageFlags newStage) { + auto it = nameToIndex.find(name); + if (it != nameToIndex.end()) { + resources[it->second].currentLayout = newLayout; + resources[it->second].currentAccess = newAccess; + resources[it->second].currentStage = newStage; + } +} + +// ============================================================================ +// RenderGraphBuilder Implementation +// ============================================================================ + +RenderGraphBuilder& RenderGraphBuilder::addColorTarget(const std::string& name, + VkFormat format, + VkExtent2D extent, + ResourceLifetime lifetime) { + ResourceHandle handle; + handle.name = name; + handle.type = ResourceType::ColorAttachment; + handle.lifetime = lifetime; + handle.format = format; + handle.extent = extent; + handle.currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; + resources.push_back(handle); + return *this; +} + +RenderGraphBuilder& RenderGraphBuilder::addDepthTarget(const std::string& name, + VkFormat format, + VkExtent2D extent, + ResourceLifetime lifetime) { + ResourceHandle handle; + handle.name = name; + handle.type = ResourceType::DepthAttachment; + handle.lifetime = lifetime; + handle.format = format; + handle.extent = extent; + handle.currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; + resources.push_back(handle); + return *this; +} + +RenderGraphBuilder& RenderGraphBuilder::addExternalImage(const std::string& name, + VkImage image, + VkImageView view, + VkFormat format, + VkExtent2D extent) { + ResourceHandle handle; + handle.name = name; + handle.type = ResourceType::ColorAttachment; + handle.lifetime = ResourceLifetime::External; + handle.image = image; + handle.imageView = view; + handle.format = format; + handle.extent = extent; + handle.isSwapchain = true; + resources.push_back(handle); + return *this; +} + +RenderGraphBuilder& RenderGraphBuilder::addPass(const std::string& name, + const std::function& configureFunc) { + RenderPass pass; + pass.name = name; + configureFunc(pass); + passes.push_back(pass); + return *this; +} + +RenderGraphBuilder& RenderGraphBuilder::addDependency(uint32_t from, uint32_t to, + VkPipelineStageFlags srcStage, + VkPipelineStageFlags dstStage, + VkAccessFlags srcAccess, + VkAccessFlags dstAccess) { + PassDependency dep; + dep.from = from; + dep.to = to; + dep.srcStageMask = srcStage; + dep.dstStageMask = dstStage; + dep.srcAccessMask = srcAccess; + dep.dstAccessMask = dstAccess; + explicitDependencies.push_back(dep); + return *this; +} + +// ============================================================================ +// RenderGraph Implementation +// ============================================================================ + +RenderGraph::~RenderGraph() { + // Destructor - cleanup se hace explícitamente en cleanup() +} + +void RenderGraph::compile(RenderGraphBuilder&& builder) { + device = VK_NULL_HANDLE; // Se seteará cuando tengamos contexto Vulkan + + // Copiar recursos y passes + compiledRegistry.resources = std::move(builder.resources); + compiledPasses.resize(builder.passes.size()); + + // Construir mapa nombre->índice + for (size_t i = 0; i < compiledRegistry.resources.size(); ++i) { + compiledRegistry.nameToIndex[compiledRegistry.resources[i].name] = i; + } + + // Compilar cada pass + for (size_t i = 0; i < builder.passes.size(); ++i) { + CompiledPass& compiled = compiledPasses[i]; + compiled.definition = builder.passes[i]; + + // Resolver attachments + uint32_t attachmentIndex = 0; + + // Color attachments + for (const auto& colorName : compiled.definition.colorAttachments) { + const ResourceHandle* res = compiledRegistry.getResource(colorName); + if (!res) { + throw std::runtime_error("Color attachment not found: " + colorName); + } + + VkAttachmentDescription desc{}; + desc.format = res->format; + desc.samples = VK_SAMPLE_COUNT_1_BIT; + desc.loadOp = compiled.definition.colorLoadOp; + desc.storeOp = compiled.definition.colorStoreOp; + desc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + desc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + desc.initialLayout = res->currentLayout; + desc.finalLayout = res->currentLayout; // Se actualizará con barreras + + compiled.attachments.push_back(desc); + + VkAttachmentReference ref{}; + ref.attachment = attachmentIndex++; + ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + compiled.colorRefs.push_back(ref); + } + + // Depth attachment + if (!compiled.definition.depthAttachment.empty()) { + const ResourceHandle* res = compiledRegistry.getResource(compiled.definition.depthAttachment); + if (!res) { + throw std::runtime_error("Depth attachment not found: " + compiled.definition.depthAttachment); + } + + VkAttachmentDescription desc{}; + desc.format = res->format; + desc.samples = VK_SAMPLE_COUNT_1_BIT; + desc.loadOp = compiled.definition.depthLoadOp; + desc.storeOp = compiled.definition.depthStoreOp; + desc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + desc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + desc.initialLayout = res->currentLayout; + desc.finalLayout = res->currentLayout; + + compiled.attachments.push_back(desc); + + compiled.depthRef.attachment = attachmentIndex++; + compiled.depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + } + + // Dependencias automáticas basadas en uso de recursos + // TODO: Implementar análisis de dependencias del grafo + } + + // Añadir dependencias explícitas + for (const auto& dep : builder.explicitDependencies) { + if (dep.from < compiledPasses.size() && dep.to < compiledPasses.size()) { + VkSubpassDependency vkDep{}; + vkDep.srcSubpass = dep.from; + vkDep.dstSubpass = dep.to; + vkDep.srcStageMask = dep.srcStageMask; + vkDep.dstStageMask = dep.dstStageMask; + vkDep.srcAccessMask = dep.srcAccessMask; + vkDep.dstAccessMask = dep.dstAccessMask; + vkDep.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; + + compiledPasses[dep.to].dependencies.push_back(vkDep); + } + } +} + +void RenderGraph::createRenderPasses(VkDevice dev) { + device = dev; + + for (auto& compiled : compiledPasses) { + VkRenderPassCreateInfo rpInfo{}; + rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + rpInfo.attachmentCount = static_cast(compiled.attachments.size()); + rpInfo.pAttachments = compiled.attachments.data(); + rpInfo.subpassCount = 1; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = static_cast(compiled.colorRefs.size()); + subpass.pColorAttachments = compiled.colorRefs.data(); + subpass.pDepthStencilAttachment = compiled.depthRef.attachment != VK_ATTACHMENT_UNUSED ? &compiled.depthRef : nullptr; + + rpInfo.pSubpasses = &subpass; + rpInfo.dependencyCount = static_cast(compiled.dependencies.size()); + rpInfo.pDependencies = compiled.dependencies.data(); + + if (vkCreateRenderPass(device, &rpInfo, nullptr, &compiled.vkRenderPass) != VK_SUCCESS) { + throw std::runtime_error("Failed to create render pass: " + compiled.definition.name); + } + } +} + +void RenderGraph::createFramebuffers(VkDevice dev, VkExtent2D swapchainExtent) { + for (auto& compiled : compiledPasses) { + std::vector attachments; + VkExtent2D extent = swapchainExtent; + + // Obtener views de todos los attachments + for (const auto& colorName : compiled.definition.colorAttachments) { + const ResourceHandle* res = compiledRegistry.getResource(colorName); + if (res) { + attachments.push_back(res->imageView); + if (!res->isSwapchain) { + extent = res->extent; + } + } + } + + if (!compiled.definition.depthAttachment.empty()) { + const ResourceHandle* res = compiledRegistry.getResource(compiled.definition.depthAttachment); + if (res) { + attachments.push_back(res->imageView); + } + } + + VkFramebufferCreateInfo fbInfo{}; + fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + fbInfo.renderPass = compiled.vkRenderPass; + fbInfo.attachmentCount = static_cast(attachments.size()); + fbInfo.pAttachments = attachments.data(); + fbInfo.width = extent.width; + fbInfo.height = extent.height; + fbInfo.layers = 1; + + if (vkCreateFramebuffer(device, &fbInfo, nullptr, &compiled.framebuffer) != VK_SUCCESS) { + throw std::runtime_error("Failed to create framebuffer: " + compiled.definition.name); + } + } +} + +void RenderGraph::recordBarriers(VkCommandBuffer cmd, const CompiledPass& pass) { + // Ejecutar barreras pre-pass si existen + if (!pass.prePassBarriers.empty()) { + vkCmdPipelineBarrier( + cmd, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, + 0, nullptr, + 0, nullptr, + static_cast(pass.prePassBarriers.size()), + pass.prePassBarriers.data() + ); + } +} + +void RenderGraph::execute(VkCommandBuffer cmd, ResourceRegistry& registry, uint32_t frameIndex) { + if (compiledPasses.empty()) { + return; + } + + for (const auto& compiled : compiledPasses) { + // 1. Record barreras de transición + recordBarriers(cmd, compiled); + + // 2. Begin render pass + VkRenderPassBeginInfo rpBegin{}; + rpBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + rpBegin.renderPass = compiled.vkRenderPass; + rpBegin.framebuffer = compiled.framebuffer; + rpBegin.renderArea.extent = {compiled.attachments.empty() ? 0 : + registry.resources[0].extent.width, + compiled.attachments.empty() ? 0 : + registry.resources[0].extent.height}; + + // Clear values + std::vector clearValues; + if (!compiled.definition.colorAttachments.empty()) { + VkClearValue colorClear{}; + colorClear.color = compiled.definition.clearColorValue; + clearValues.push_back(colorClear); + } + if (!compiled.definition.depthAttachment.empty()) { + VkClearValue depthClear{}; + depthClear.depthStencil = compiled.definition.clearDepthValue; + clearValues.push_back(depthClear); + } + + rpBegin.clearValueCount = static_cast(clearValues.size()); + rpBegin.pClearValues = clearValues.data(); + + vkCmdBeginRenderPass(cmd, &rpBegin, VK_SUBPASS_CONTENTS_INLINE); + + // 3. Ejecutar callbacks del pass + if (compiled.definition.executeCallback) { + compiled.definition.executeCallback(cmd, registry); + } + + // 4. End render pass + vkCmdEndRenderPass(cmd); + } +} + +void RenderGraph::cleanup(VkDevice device) { + for (auto& compiled : compiledPasses) { + if (compiled.framebuffer != VK_NULL_HANDLE) { + vkDestroyFramebuffer(device, compiled.framebuffer, nullptr); + compiled.framebuffer = VK_NULL_HANDLE; + } + if (compiled.vkRenderPass != VK_NULL_HANDLE) { + vkDestroyRenderPass(device, compiled.vkRenderPass, nullptr); + compiled.vkRenderPass = VK_NULL_HANDLE; + } + } + + compiledPasses.clear(); + compiledRegistry.resources.clear(); + compiledRegistry.nameToIndex.clear(); +} + +} // namespace FarmEngine::Render diff --git a/FarmEngine/src/rendering/graph/RenderGraph.h b/FarmEngine/src/rendering/graph/RenderGraph.h new file mode 100644 index 0000000..e98286f --- /dev/null +++ b/FarmEngine/src/rendering/graph/RenderGraph.h @@ -0,0 +1,167 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace FarmEngine::Render { + +// Forward declarations +class RenderGraph; +class ResourceRegistry; + +enum class ResourceType { + ColorAttachment, + DepthAttachment, + InputAttachment, + StorageImage, + UniformBuffer, + VertexBuffer, + IndexBuffer +}; + +enum class ResourceLifetime { + Frame, // Se crea/destruye cada frame (ej: GBuffer temporal) + Persistent, // Vive mientras viva el graph (ej: Swapchain, Texturas cargadas) + External // Gestionado externamente (ej: Swapchain images provistas por el engine) +}; + +struct ResourceHandle { + std::string name; + ResourceType type; + ResourceLifetime lifetime; + + VkImage image = VK_NULL_HANDLE; + VkImageView imageView = VK_NULL_HANDLE; + VkFormat format = VK_FORMAT_UNDEFINED; + VkExtent2D extent = {0, 0}; + uint32_t arrayLayers = 1; + uint32_t mipLevels = 1; + + // Estado actual + VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VkAccessFlags currentAccess = 0; + VkPipelineStageFlags currentStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + + bool isSwapchain = false; +}; + +struct PassDependency { + uint32_t from; + uint32_t to; + VkPipelineStageFlags srcStageMask; + VkPipelineStageFlags dstStageMask; + VkAccessFlags srcAccessMask; + VkAccessFlags dstAccessMask; +}; + +class RenderPass { +public: + using ExecuteFunc = std::function; + + std::string name; + std::vector colorAttachments; + std::string depthAttachment; + std::vector inputAttachments; + std::vector storageAttachments; + + ExecuteFunc executeCallback; + + // Configuración de clear/load/store + bool clearColor = false; + bool clearDepth = false; + VkClearColorValue clearColorValue = {{0.0f, 0.0f, 0.0f, 1.0f}}; + VkClearDepthStencilValue clearDepthValue = {1.0f, 0}; + + VkAttachmentLoadOp colorLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + VkAttachmentStoreOp colorStoreOp = VK_ATTACHMENT_STORE_OP_STORE; + VkAttachmentLoadOp depthLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + VkAttachmentStoreOp depthStoreOp = VK_ATTACHMENT_STORE_OP_STORE; +}; + +class RenderGraphBuilder { +public: + RenderGraphBuilder() = default; + + // Definir recursos + RenderGraphBuilder& addColorTarget(const std::string& name, VkFormat format, VkExtent2D extent, ResourceLifetime lifetime = ResourceLifetime::Frame); + RenderGraphBuilder& addDepthTarget(const std::string& name, VkFormat format, VkExtent2D extent, ResourceLifetime lifetime = ResourceLifetime::Frame); + RenderGraphBuilder& addExternalImage(const std::string& name, VkImage image, VkImageView view, VkFormat format, VkExtent2D extent); + + // Definir passes + RenderGraphBuilder& addPass(const std::string& name, + const std::function& configureFunc); + + // Definir dependencias explícitas (opcional, se pueden inferir) + RenderGraphBuilder& addDependency(uint32_t from, uint32_t to, + VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage, + VkAccessFlags srcAccess, VkAccessFlags dstAccess); + +private: + friend class RenderGraph; + std::vector resources; + std::vector passes; + std::vector explicitDependencies; +}; + +class ResourceRegistry { +public: + const ResourceHandle* getResource(const std::string& name) const; + VkImageView getImageView(const std::string& name) const; + VkImage getImage(const std::string& name) const; + VkFormat getFormat(const std::string& name) const; + + void updateResourceState(const std::string& name, + VkImageLayout newLayout, + VkAccessFlags newAccess, + VkPipelineStageFlags newStage); + +private: + friend class RenderGraph; + std::unordered_map nameToIndex; + std::vector resources; +}; + +class RenderGraph { +public: + RenderGraph() = default; + ~RenderGraph(); + + void compile(RenderGraphBuilder&& builder); + void execute(VkCommandBuffer cmd, ResourceRegistry& registry, uint32_t frameIndex); + + // Acceso a recursos compilados + const ResourceRegistry& getRegistry() const { return compiledRegistry; } + + void cleanup(VkDevice device); + +private: + struct CompiledPass { + RenderPass definition; + std::vector attachments; + std::vector colorRefs; + VkAttachmentReference depthRef = {VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED}; + std::vector dependencies; + VkRenderPass vkRenderPass = VK_NULL_HANDLE; + VkFramebuffer framebuffer = VK_NULL_HANDLE; + + // Barreras de transición antes del pass + std::vector prePassBarriers; + }; + + std::vector compiledPasses; + ResourceRegistry compiledRegistry; + std::vector persistentResources; + + VkDevice device = VK_NULL_HANDLE; + + void createRenderPasses(VkDevice dev); + void createFramebuffers(VkDevice dev, VkExtent2D swapchainExtent); + void recordBarriers(VkCommandBuffer cmd, const CompiledPass& pass); +}; + +} // namespace FarmEngine::Render diff --git a/FarmEngine/src/rendering/graph/RenderPipeline.cpp b/FarmEngine/src/rendering/graph/RenderPipeline.cpp new file mode 100644 index 0000000..bc3d6bc --- /dev/null +++ b/FarmEngine/src/rendering/graph/RenderPipeline.cpp @@ -0,0 +1,181 @@ +#include "RenderPipeline.h" + +namespace FarmEngine::Render { + +// ============================================================================ +// ForwardRenderer Implementation +// ============================================================================ + +ForwardRenderer::ForwardRenderer() : extent{0, 0} {} + +void ForwardRenderer::buildGraph(RenderGraph& graph, VkExtent2D swapchainExtent) { + extent = swapchainExtent; + + RenderGraphBuilder builder; + + // 1. Crear recursos + builder.addColorTarget("FinalColor", VK_FORMAT_B8G8R8A8_SRGB, extent, ResourceLifetime::External); + builder.addDepthTarget("Depth", VK_FORMAT_D32_SFLOAT, extent, ResourceLifetime::Frame); + + // 2. Configurar Geometry Pass + builder.addPass("Geometry", [](RenderPass& pass) { + pass.colorAttachments.push_back("FinalColor"); + pass.depthAttachment = "Depth"; + pass.clearColor = true; + pass.clearDepth = true; + pass.clearColorValue = {{0.0f, 0.0f, 0.0f, 1.0f}}; + pass.clearDepthValue = {1.0f, 0}; + + pass.executeCallback = [](VkCommandBuffer cmd, const ResourceRegistry& registry) { + // Aquí iría el draw call de geometría + // vkCmdDrawIndexed(...); + }; + }); + + // Compilar grafo + graph.compile(std::move(builder)); +} + +void ForwardRenderer::setupPasses() { + // Configuración específica de pipelines, descriptor sets, etc. +} + +// ============================================================================ +// DeferredRenderer Implementation +// ============================================================================ + +DeferredRenderer::DeferredRenderer() : extent{0, 0} {} + +void DeferredRenderer::buildGraph(RenderGraph& graph, VkExtent2D swapchainExtent) { + extent = swapchainExtent; + + RenderGraphBuilder builder; + + // Setup GBuffer (múltiples attachments) + setupGBuffer(builder); + + // Lighting Pass (lee del GBuffer, escribe en HDR buffer) + setupLightingPass(builder); + + // Post-Processing (tonemapping, bloom, etc.) + setupPostProcess(builder); + + graph.compile(std::move(builder)); +} + +void DeferredRenderer::setupGBuffer(RenderGraphBuilder& builder) { + // GBuffer attachments típicos: + // 1. Albedo (color + alpha) + builder.addColorTarget("GBuffer_Albedo", VK_FORMAT_R8G8B8A8_UNORM, extent, ResourceLifetime::Frame); + + // 2. Normales (empacadas en RGB) + builder.addColorTarget("GBuffer_Normals", VK_FORMAT_A2B10G10R10_UNORM_PACK32, extent, ResourceLifetime::Frame); + + // 3. Position/Depth + builder.addColorTarget("GBuffer_Depth", VK_FORMAT_R32_SFLOAT, extent, ResourceLifetime::Frame); + + // 4. Material properties (roughness, metallic, etc.) + builder.addColorTarget("GBuffer_Material", VK_FORMAT_R8G8B8A8_UNORM, extent, ResourceLifetime::Frame); + + // Depth buffer compartido + builder.addDepthTarget("Depth", VK_FORMAT_D32_SFLOAT, extent, ResourceLifetime::Frame); + + // Geometry Pass que escribe en todos los GBuffer + builder.addPass("GBuffer_Geometry", [](RenderPass& pass) { + pass.colorAttachments = {"GBuffer_Albedo", "GBuffer_Normals", "GBuffer_Depth", "GBuffer_Material"}; + pass.depthAttachment = "Depth"; + pass.clearColor = true; + pass.clearDepth = true; + + pass.executeCallback = [](VkCommandBuffer cmd, const ResourceRegistry& registry) { + // Draw geometry to all GBuffer attachments simultaneously + // Vulkan permite hasta 8 color attachments en un solo pass + }; + }); +} + +void DeferredRenderer::setupLightingPass(RenderGraphBuilder& builder) { + // HDR buffer para acumular luz + builder.addColorTarget("HDR_Lighting", VK_FORMAT_R16G16B16A16_SFLOAT, extent, ResourceLifetime::Frame); + + builder.addPass("Lighting", [](RenderPass& pass) { + // Lee del GBuffer como input attachments (texture sampling desde fragment shader) + pass.inputAttachments = {"GBuffer_Albedo", "GBuffer_Normals", "GBuffer_Depth", "GBuffer_Material"}; + pass.colorAttachments.push_back("HDR_Lighting"); + pass.clearColor = true; + pass.clearColorValue = {{0.0f, 0.0f, 0.0f, 1.0f}}; + + pass.executeCallback = [](VkCommandBuffer cmd, const ResourceRegistry& registry) { + // Full-screen quad con lighting calculations + // Fragment shader muestrea los input attachments para calcular iluminación + }; + }); +} + +void DeferredRenderer::setupPostProcess(RenderGraphBuilder& builder) { + // Buffer intermedio para post-processing + builder.addColorTarget("PostProcess_Temp", VK_FORMAT_R16G16B16A16_SFLOAT, extent, ResourceLifetime::Frame); + + // Salida final (swapchain) + // builder.addExternalImage se hace fuera, aquí solo referenciamos + + builder.addPass("Tonemapping", [](RenderPass& pass) { + pass.inputAttachments = {"HDR_Lighting"}; + pass.colorAttachments.push_back("FinalColor"); // External swapchain + pass.clearColor = false; + + pass.executeCallback = [](VkCommandBuffer cmd, const ResourceRegistry& registry) { + // ACES tonemapping, bloom, color grading, etc. + }; + }); +} + +// ============================================================================ +// ResourceFactory Implementation +// ============================================================================ + +VkImageCreateInfo ResourceFactory::createImageInfo( + VkFormat format, + VkExtent2D extent, + VkImageUsageFlags usage, + uint32_t arrayLayers, + uint32_t mipLevels +) { + VkImageCreateInfo info{}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + info.imageType = VK_IMAGE_TYPE_2D; + info.format = format; + info.extent.width = extent.width; + info.extent.height = extent.height; + info.extent.depth = 1; + info.mipLevels = mipLevels; + info.arrayLayers = arrayLayers; + info.samples = VK_SAMPLE_COUNT_1_BIT; + info.tiling = VK_IMAGE_TILING_OPTIMAL; + info.usage = usage; + info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + return info; +} + +VkImageViewCreateInfo ResourceFactory::createImageViewInfo( + VkImage image, + VkFormat format, + VkImageAspectFlags aspectMask +) { + VkImageViewCreateInfo info{}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.image = image; + info.viewType = VK_IMAGE_VIEW_TYPE_2D; + info.format = format; + info.subresourceRange.aspectMask = aspectMask; + info.subresourceRange.baseMipLevel = 0; + info.subresourceRange.levelCount = 1; + info.subresourceRange.baseArrayLayer = 0; + info.subresourceRange.layerCount = 1; + + return info; +} + +} // namespace FarmEngine::Render diff --git a/FarmEngine/src/rendering/graph/RenderPipeline.h b/FarmEngine/src/rendering/graph/RenderPipeline.h new file mode 100644 index 0000000..6f1fd1d --- /dev/null +++ b/FarmEngine/src/rendering/graph/RenderPipeline.h @@ -0,0 +1,87 @@ +#pragma once + +#include "RenderGraph.h" +#include + +namespace FarmEngine::Render { + +/** + * @brief Ejemplo de configuración de un pipeline de renderizado moderno + * + * Este ejemplo muestra cómo el Render Graph permite: + * 1. Sincronización automática entre passes + * 2. Reordenamiento para optimización TBDR (Tile-Based Deferred Rendering) + * 3. Alias de memoria para recursos temporales + */ +class ForwardRenderer { +public: + ForwardRenderer(); + + /** + * @brief Construye el grafo de render para forward rendering + * + * Pipeline: + * 1. GBuffer Pass (opcional, para deferred) + * 2. Geometry Pass + * 3. Lighting Pass + * 4. Post-Processing + * 5. Present + */ + void buildGraph(RenderGraph& graph, VkExtent2D swapchainExtent); + + /** + * @brief Configura los passes específicos del renderer + */ + void setupPasses(); + +private: + VkExtent2D extent; +}; + +/** + * @brief Ejemplo avanzado: Deferred Renderer con múltiples GBuffer + * + * Demuestra alias de memoria y reutilización de recursos temporales + */ +class DeferredRenderer { +public: + DeferredRenderer(); + + void buildGraph(RenderGraph& graph, VkExtent2D swapchainExtent); + + /** + * Estructura típica de GBuffer: + * - Albedo (RGBA8) + * - Normals (RGB10_A2 o RGBA16F) + * - Position/Depth (R32F o D24S8) + * - Material (RGBA8) + */ + void setupGBuffer(RenderGraphBuilder& builder); + void setupLightingPass(RenderGraphBuilder& builder); + void setupPostProcess(RenderGraphBuilder& builder); + +private: + VkExtent2D extent; +}; + +/** + * @brief Helper para crear recursos Vulkan compatibles con el Render Graph + */ +class ResourceFactory { +public: + static VkImageCreateInfo createImageInfo( + VkFormat format, + VkExtent2D extent, + VkImageUsageFlags usage, + uint32_t arrayLayers = 1, + uint32_t mipLevels = 1 + ); + + static VkImageViewCreateInfo createImageViewInfo( + VkImage image, + VkFormat format, + VkImageAspectFlags aspectMask + ); +}; + +} // namespace FarmEngine::Render diff --git a/FarmEngine/world/chunks/ChunkSystem.cpp b/FarmEngine/world/chunks/ChunkSystem.cpp index c18360d..5f88ccc 100644 --- a/FarmEngine/world/chunks/ChunkSystem.cpp +++ b/FarmEngine/world/chunks/ChunkSystem.cpp @@ -21,7 +21,7 @@ Chunk::Chunk(int32_t x, int32_t z, const ChunkConfig& config) , config(config) { const int32_t size = config.chunkSize; - const int32_t height = 64; // Default chunk height + const int32_t height = config.chunkHeight; const size_t totalBlocks = static_cast(size) * height * size; blocks.resize(totalBlocks, 0); @@ -72,7 +72,7 @@ bool Chunk::generate(int32_t seed) { Biome biome = biomeMap[z * size + x]; // Simple block placement logic - for (int32_t y = 0; y < 64; ++y) { + for (int32_t y = 0; y < config.chunkHeight; ++y) { uint8_t blockID = 0; // Air if (y < static_cast(height) - 3) { @@ -114,7 +114,7 @@ bool Chunk::saveToDisk(const std::string& path) { uint8_t Chunk::getBlock(int32_t x, int32_t y, int32_t z) const { if (x < 0 || x >= config.chunkSize || - y < 0 || y >= 64 || + y < 0 || y >= config.chunkHeight || z < 0 || z >= config.chunkSize) { return 0; } @@ -129,7 +129,7 @@ uint8_t Chunk::getBlock(int32_t x, int32_t y, int32_t z) const { void Chunk::setBlock(int32_t x, int32_t y, int32_t z, uint8_t blockID) { if (x < 0 || x >= config.chunkSize || - y < 0 || y >= 64 || + y < 0 || y >= config.chunkHeight || z < 0 || z >= config.chunkSize) { return; } @@ -156,7 +156,7 @@ bool Chunk::buildMesh() { const int32_t size = config.chunkSize; - for (int32_t y = 0; y < 64; ++y) { + for (int32_t y = 0; y < config.chunkHeight; ++y) { for (int32_t z = 0; z < size; ++z) { for (int32_t x = 0; x < size; ++x) { uint8_t block = getBlock(x, y, z); @@ -201,7 +201,7 @@ bool Chunk::buildMesh() { fx, fy+1, fz, -1, 0, 0, fx, fy, fz, -1, 0, 0 }); - uint32_t base = static_cast(opaqueMesh.vertices.size() / 3) - 4; + uint32_t base = static_cast(opaqueMesh.vertices.size() / 6) - 4; opaqueMesh.indices.insert(opaqueMesh.indices.end(), { base, base+1, base+2, base, base+2, base+3 @@ -216,7 +216,7 @@ bool Chunk::buildMesh() { fx+1, fy+1, fz+1, 0, 1, 0, fx+1, fy+1, fz, 0, 1, 0 }); - uint32_t base = static_cast(opaqueMesh.vertices.size() / 3) - 4; + uint32_t base = static_cast(opaqueMesh.vertices.size() / 6) - 4; opaqueMesh.indices.insert(opaqueMesh.indices.end(), { base, base+1, base+2, base, base+2, base+3 @@ -231,7 +231,7 @@ bool Chunk::buildMesh() { fx+1, fy, fz, 0, -1, 0, fx+1, fy, fz+1, 0, -1, 0 }); - uint32_t base = static_cast(opaqueMesh.vertices.size() / 3) - 4; + uint32_t base = static_cast(opaqueMesh.vertices.size() / 6) - 4; opaqueMesh.indices.insert(opaqueMesh.indices.end(), { base, base+1, base+2, base, base+2, base+3 @@ -246,7 +246,7 @@ bool Chunk::buildMesh() { fx+1, fy+1, fz+1, 0, 0, 1, fx+1, fy, fz+1, 0, 0, 1 }); - uint32_t base = static_cast(opaqueMesh.vertices.size() / 3) - 4; + uint32_t base = static_cast(opaqueMesh.vertices.size() / 6) - 4; opaqueMesh.indices.insert(opaqueMesh.indices.end(), { base, base+1, base+2, base, base+2, base+3 @@ -261,7 +261,7 @@ bool Chunk::buildMesh() { fx, fy+1, fz, 0, 0, -1, fx, fy, fz, 0, 0, -1 }); - uint32_t base = static_cast(opaqueMesh.vertices.size() / 3) - 4; + uint32_t base = static_cast(opaqueMesh.vertices.size() / 6) - 4; opaqueMesh.indices.insert(opaqueMesh.indices.end(), { base, base+1, base+2, base, base+2, base+3 @@ -333,8 +333,9 @@ void ChunkManager::update(const glm::vec3& playerPosition, float deltaTime) { Chunk* ChunkManager::getChunk(int32_t chunkX, int32_t chunkZ) { std::lock_guard lock(chunkMutex); - int64_t key = (static_cast(chunkX) << 32) | - (static_cast(chunkZ) & 0xFFFFFFFF); + // Use proper two's complement for negative coordinates + uint64_t key = (static_cast(static_cast(chunkX)) << 32) | + (static_cast(chunkZ) & 0xFFFFFFFFULL); auto it = chunks.find(key); if (it != chunks.end()) { @@ -384,8 +385,9 @@ void ChunkManager::loadChunksAroundPlayer(const glm::vec3& playerPosition) { glm::vec2 playerChunkPos(playerChunkX, playerChunkZ); std::sort(chunkOrder.begin(), chunkOrder.end(), [playerChunkPos](const auto& a, const auto& b) { - float distA = glm::length(glm::vec2(a.first, a.second) - playerChunkPos); - float distB = glm::length(glm::vec2(b.first, b.second) - playerChunkPos); + // Use squared distance to avoid expensive sqrt operations + float distA = glm::length2(glm::vec2(a.first, a.second) - playerChunkPos); + float distB = glm::length2(glm::vec2(b.first, b.second) - playerChunkPos); return distA < distB; }); @@ -409,7 +411,7 @@ void ChunkManager::unloadDistantChunks(const glm::vec3& playerPosition) { int32_t unloadDistance = config.renderDistance + 2; // Buffer zone - std::vector chunksToRemove; + std::vector chunksToRemove; std::lock_guard lock(chunkMutex); @@ -431,7 +433,7 @@ void ChunkManager::unloadDistantChunks(const glm::vec3& playerPosition) { } // Remove distant chunks - for (int64_t key : chunksToRemove) { + for (uint64_t key : chunksToRemove) { chunks.erase(key); } } @@ -483,26 +485,37 @@ glm::ivec3 ChunkManager::worldToLocal(const glm::vec3& worldPos) const { } void ChunkManager::processPendingGenerations() { - std::lock_guard lock(chunkMutex); + // Pop tasks from pendingGeneration under lock + std::vector> tasksToProcess; - const size_t maxPerFrame = 4; // Limit generations per frame - size_t processed = 0; - - auto it = pendingGeneration.begin(); - while (it != pendingGeneration.end() && processed < maxPerFrame) { - int32_t chunkX = it->first; - int32_t chunkZ = it->second; + { + std::lock_guard lock(chunkMutex); + + const size_t maxPerFrame = 4; // Limit generations per frame + size_t count = 0; - // Create and generate chunk + auto it = pendingGeneration.begin(); + while (it != pendingGeneration.end() && count < maxPerFrame) { + tasksToProcess.push_back(*it); + it = pendingGeneration.erase(it); + ++count; + } + } + + // Generate chunks without holding the lock + for (const auto& [chunkX, chunkZ] : tasksToProcess) { auto chunk = std::make_unique(chunkX, chunkZ, config); chunk->generate(12345); // Use default seed - int64_t key = (static_cast(chunkX) << 32) | - (static_cast(chunkZ) & 0xFFFFFFFF); - chunks[key] = std::move(chunk); + // Reacquire lock only to insert the finished chunk + // Use proper two's complement for negative coordinates + uint64_t key = (static_cast(static_cast(chunkX)) << 32) | + (static_cast(chunkZ) & 0xFFFFFFFFULL); - it = pendingGeneration.erase(it); - ++processed; + { + std::lock_guard lock(chunkMutex); + chunks[key] = std::move(chunk); + } } } @@ -558,8 +571,9 @@ void ChunkManager::updateVisibleChunks(const glm::vec3& cameraPosition) { glm::vec3 posA = a->getPosition(); glm::vec3 posB = b->getPosition(); - float distA = glm::length(posA - cameraPosition); - float distB = glm::length(posB - cameraPosition); + // Use squared distance to avoid expensive sqrt operations + float distA = glm::length2(posA - cameraPosition); + float distB = glm::length2(posB - cameraPosition); return distA < distB; }); @@ -763,13 +777,14 @@ CullingResult CullingSystem::cullChunks(const std::vector& chunks, glm::vec3 chunkPos = chunk->getPosition(); float chunkSize = static_cast(config.chunkSize); + float chunkHeight = static_cast(config.chunkHeight); // Create bounding box for chunk glm::vec3 minBound(chunkPos.x, 0, chunkPos.z); - glm::vec3 maxBound(chunkPos.x + chunkSize, 64, chunkPos.z + chunkSize); + glm::vec3 maxBound(chunkPos.x + chunkSize, chunkHeight, chunkPos.z + chunkSize); // Distance culling - glm::vec3 chunkCenter = chunkPos + glm::vec3(chunkSize * 0.5f, 32, chunkSize * 0.5f); + glm::vec3 chunkCenter = chunkPos + glm::vec3(chunkSize * 0.5f, chunkHeight * 0.5f, chunkSize * 0.5f); if (shouldCullByDistance(chunkCenter, cameraPosition, maxDistance)) { result.culled++; continue; diff --git a/FarmEngine/world/chunks/ChunkSystem.h b/FarmEngine/world/chunks/ChunkSystem.h index 290f7fa..dc4e21a 100644 --- a/FarmEngine/world/chunks/ChunkSystem.h +++ b/FarmEngine/world/chunks/ChunkSystem.h @@ -11,6 +11,7 @@ namespace FarmEngine { struct ChunkConfig { int32_t chunkSize = 32; + int32_t chunkHeight = 64; int32_t renderDistance = 8; float lodDistances[4] = {20.0f, 50.0f, 100.0f, 200.0f}; }; @@ -115,8 +116,8 @@ class ChunkManager { ChunkConfig config; - // Chunks cargados: key = (chunkX << 32) | chunkZ - std::unordered_map> chunks; + // Chunks cargados: key = packed uint64 from chunk coords + std::unordered_map> chunks; mutable std::mutex chunkMutex; // Cola de generación