diff --git a/.gitignore b/.gitignore index 318f799..46c1c67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ ``` -# Compiled and binary files +# Compiled and build artifacts *.o *.obj *.exe @@ -13,27 +13,32 @@ node_modules/ venv/ .venv/ __pycache__/ -dist/ -build/ +.mypy_cache/ +.pytest_cache/ target/ .gradle/ -# Editors/IDE -.vscode/ -.idea/ +# Build directories +build/ +dist/ + +# Logs and temp files +*.log +*.tmp *.swp -*.swo -# System/Environment -.DS_Store -Thumbs.db +# Environment .env .env.local *.env.* -# Logs and temp files -*.log -*.tmp +# Editors +.vscode/ +.idea/ + +# System files +.DS_Store +Thumbs.db # Coverage coverage/ @@ -59,8 +64,4 @@ htmlcov/ *.Z *.lz *.lzo -*.tar.gz -*.tar.bz2 -*.tar.xz -*.tar.zst ``` \ No newline at end of file diff --git a/FarmEngine/src/rendering/graph/ExampleUsage.h b/FarmEngine/src/rendering/graph/ExampleUsage.h index 8217e5c..dc14c03 100644 --- a/FarmEngine/src/rendering/graph/ExampleUsage.h +++ b/FarmEngine/src/rendering/graph/ExampleUsage.h @@ -16,7 +16,7 @@ namespace FarmEngine::Render::Examples { * 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) { +inline void ChunkRenderingExample(RenderGraph& graph, VkDevice device, VkExtent2D swapchainExtent) { RenderGraphBuilder builder; // 1. Recursos principales @@ -64,6 +64,7 @@ inline void ChunkRenderingExample(RenderGraph& graph, VkExtent2D swapchainExtent }); graph.compile(std::move(builder)); + graph.build(device, swapchainExtent); } /** @@ -71,7 +72,7 @@ inline void ChunkRenderingExample(RenderGraph& graph, VkExtent2D swapchainExtent * * Muestra cómo añadir un pass de shadow map antes del geometry pass */ -inline void ShadowMappingExample(RenderGraph& graph, VkExtent2D extent) { +inline void ShadowMappingExample(RenderGraph& graph, VkDevice device, VkExtent2D extent) { RenderGraphBuilder builder; // Shadow map resource (alta resolución para calidad) @@ -111,6 +112,7 @@ inline void ShadowMappingExample(RenderGraph& graph, VkExtent2D extent) { ); graph.compile(std::move(builder)); + graph.build(device, extent); } /** @@ -118,7 +120,7 @@ inline void ShadowMappingExample(RenderGraph& graph, VkExtent2D extent) { * * Técnica post-process para oclusión ambiental en tiempo real */ -inline void SSAOExample(RenderGraph& graph, VkExtent2D extent) { +inline void SSAOExample(RenderGraph& graph, VkDevice device, VkExtent2D extent) { RenderGraphBuilder builder; // GBuffer básico @@ -159,6 +161,7 @@ inline void SSAOExample(RenderGraph& graph, VkExtent2D extent) { }); graph.compile(std::move(builder)); + graph.build(device, extent); } /** @@ -166,7 +169,7 @@ inline void SSAOExample(RenderGraph& graph, VkExtent2D extent) { * * Pipeline más simple posible para validar integración */ -inline void MinimalExample(RenderGraph& graph, VkExtent2D extent) { +inline void MinimalExample(RenderGraph& graph, VkDevice device, VkExtent2D extent) { RenderGraphBuilder builder; builder.addColorTarget("Backbuffer", VK_FORMAT_B8G8R8A8_SRGB, extent, ResourceLifetime::External); @@ -184,6 +187,7 @@ inline void MinimalExample(RenderGraph& graph, VkExtent2D extent) { }); graph.compile(std::move(builder)); + graph.build(device, extent); } } // namespace FarmEngine::Render::Examples diff --git a/FarmEngine/src/rendering/graph/RenderGraph.cpp b/FarmEngine/src/rendering/graph/RenderGraph.cpp index 8b4c11b..b74b912 100644 --- a/FarmEngine/src/rendering/graph/RenderGraph.cpp +++ b/FarmEngine/src/rendering/graph/RenderGraph.cpp @@ -41,6 +41,10 @@ void ResourceRegistry::updateResourceState(const std::string& name, VkPipelineStageFlags newStage) { auto it = nameToIndex.find(name); if (it != nameToIndex.end()) { + // Defensive bounds check to prevent out-of-bounds access with stale indices + if (it->second >= resources.size()) { + return; + } resources[it->second].currentLayout = newLayout; resources[it->second].currentAccess = newAccess; resources[it->second].currentStage = newStage; @@ -124,6 +128,30 @@ RenderGraphBuilder& RenderGraphBuilder::addDependency(uint32_t from, uint32_t to return *this; } +RenderGraphBuilder& RenderGraphBuilder::addResourceDependency(uint32_t from, uint32_t to, + const std::string& resourceName, + VkPipelineStageFlags srcStage, + VkPipelineStageFlags dstStage, + VkAccessFlags srcAccess, + VkAccessFlags dstAccess, + VkImageLayout oldLayout, + VkImageLayout newLayout, + VkImageAspectFlags aspectMask) { + PassDependency dep; + dep.from = from; + dep.to = to; + dep.srcStageMask = srcStage; + dep.dstStageMask = dstStage; + dep.srcAccessMask = srcAccess; + dep.dstAccessMask = dstAccess; + dep.resourceName = resourceName; + dep.oldLayout = oldLayout; + dep.newLayout = newLayout; + dep.aspectMask = aspectMask; + explicitDependencies.push_back(dep); + return *this; +} + // ============================================================================ // RenderGraph Implementation // ============================================================================ @@ -206,19 +234,50 @@ void RenderGraph::compile(RenderGraphBuilder&& builder) { // Procesar dependencias explícitas y convertirlas en barreras por-pass for (const auto& dep : builder.explicitDependencies) { - if (dep.to < compiledPasses.size()) { + // Validate dependency indices + if (dep.from >= compiledPasses.size()) { + throw std::runtime_error("Invalid dependency: 'from' index " + std::to_string(dep.from) + + " is out of range (pass count: " + std::to_string(compiledPasses.size()) + ")"); + } + if (dep.to >= compiledPasses.size()) { + throw std::runtime_error("Invalid dependency: 'to' index " + std::to_string(dep.to) + + " is out of range (pass count: " + std::to_string(compiledPasses.size()) + ")"); + } + + if (!dep.resourceName.empty()) { + // Resource-specific dependency: create VkImageMemoryBarrier + const ResourceHandle* res = compiledRegistry.getResource(dep.resourceName); + if (!res) { + throw std::runtime_error("Resource dependency references unknown resource: " + dep.resourceName); + } + VkImageMemoryBarrier barrier{}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.srcAccessMask = dep.srcAccessMask; barrier.dstAccessMask = dep.dstAccessMask; - barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - barrier.newLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.oldLayout = dep.oldLayout; + barrier.newLayout = dep.newLayout; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = VK_NULL_HANDLE; // Se establecerá durante la ejecución - barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + barrier.image = res->image; + barrier.subresourceRange = { + dep.aspectMask, + 0, res->mipLevels, + 0, res->arrayLayers + }; compiledPasses[dep.to].prePassBarriers.push_back(barrier); + } else { + // Pass-level execution dependency: store in CompiledPass for use with VkMemoryBarrier + compiledPasses[dep.to].dependencies.push_back({ + dep.from, + dep.to, + dep.srcStageMask, + dep.dstStageMask, + dep.srcAccessMask, + dep.dstAccessMask, + VK_DEPENDENCY_BY_REGION_BIT + }); } } } @@ -306,11 +365,11 @@ void RenderGraph::createFramebuffers(VkDevice dev, VkExtent2D swapchainExtent) { } void RenderGraph::recordBarriers(VkCommandBuffer cmd, const CompiledPass& pass) { - // Ejecutar barreras pre-pass si existen + // Ejecutar barreras de imagen pre-pass si existen if (!pass.prePassBarriers.empty()) { vkCmdPipelineBarrier( cmd, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, @@ -327,6 +386,16 @@ void RenderGraph::execute(VkCommandBuffer cmd, ResourceRegistry& registry, uint3 } for (const auto& compiled : compiledPasses) { + // Defensive guard: ensure render pass and framebuffer are valid + if (compiled.vkRenderPass == VK_NULL_HANDLE) { + throw std::runtime_error("Execute failed: vkRenderPass is VK_NULL_HANDLE for pass '" + + compiled.definition.name + "'. Call build() before execute()."); + } + if (compiled.framebuffer == VK_NULL_HANDLE) { + throw std::runtime_error("Execute failed: framebuffer is VK_NULL_HANDLE for pass '" + + compiled.definition.name + "'. Call build() before execute()."); + } + // 1. Record barreras de transición recordBarriers(cmd, compiled); @@ -336,24 +405,7 @@ void RenderGraph::execute(VkCommandBuffer cmd, ResourceRegistry& registry, uint3 rpBegin.renderPass = compiled.vkRenderPass; rpBegin.framebuffer = compiled.framebuffer; // Usar el extent del CompiledPass (calculado en createFramebuffers) - // Si es {0,0}, calcularlo dinámicamente desde los attachments como fallback - if (compiled.extent.width == 0 || compiled.extent.height == 0) { - VkExtent2D fallbackExtent = {800, 600}; // Default fallback - if (!compiled.definition.colorAttachments.empty()) { - const ResourceHandle* res = compiledRegistry.getResource(compiled.definition.colorAttachments[0]); - if (res && !res->isSwapchain) { - fallbackExtent = res->extent; - } - } else if (!compiled.definition.depthAttachment.empty()) { - const ResourceHandle* res = compiledRegistry.getResource(compiled.definition.depthAttachment); - if (res) { - fallbackExtent = res->extent; - } - } - rpBegin.renderArea.extent = fallbackExtent; - } else { - rpBegin.renderArea.extent = compiled.extent; - } + rpBegin.renderArea.extent = compiled.extent; // Clear values - uno por cada attachment (color attachments primero, luego depth) std::vector clearValues; diff --git a/FarmEngine/src/rendering/graph/RenderGraph.h b/FarmEngine/src/rendering/graph/RenderGraph.h index dcb78e1..a7a4432 100644 --- a/FarmEngine/src/rendering/graph/RenderGraph.h +++ b/FarmEngine/src/rendering/graph/RenderGraph.h @@ -57,6 +57,14 @@ struct PassDependency { VkPipelineStageFlags dstStageMask; VkAccessFlags srcAccessMask; VkAccessFlags dstAccessMask; + + // Optional: resource-specific synchronization + // If empty, this is a pass-level execution dependency (uses VkMemoryBarrier) + // If set, this is a resource-specific dependency (uses VkImageMemoryBarrier) + std::string resourceName; + VkImageLayout oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImageLayout newLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; }; class RenderPass { @@ -100,6 +108,14 @@ class RenderGraphBuilder { RenderGraphBuilder& addDependency(uint32_t from, uint32_t to, VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage, VkAccessFlags srcAccess, VkAccessFlags dstAccess); + + // Definir dependencias específicas de recursos (para barreras de imagen) + RenderGraphBuilder& addResourceDependency(uint32_t from, uint32_t to, + const std::string& resourceName, + VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage, + VkAccessFlags srcAccess, VkAccessFlags dstAccess, + VkImageLayout oldLayout, VkImageLayout newLayout, + VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_COLOR_BIT); private: friend class RenderGraph; diff --git a/FarmEngine/src/rendering/graph/RenderPipeline.cpp b/FarmEngine/src/rendering/graph/RenderPipeline.cpp index bc3d6bc..f4fbaf6 100644 --- a/FarmEngine/src/rendering/graph/RenderPipeline.cpp +++ b/FarmEngine/src/rendering/graph/RenderPipeline.cpp @@ -8,7 +8,7 @@ namespace FarmEngine::Render { ForwardRenderer::ForwardRenderer() : extent{0, 0} {} -void ForwardRenderer::buildGraph(RenderGraph& graph, VkExtent2D swapchainExtent) { +void ForwardRenderer::buildGraph(RenderGraph& graph, VkDevice device, VkExtent2D swapchainExtent) { extent = swapchainExtent; RenderGraphBuilder builder; @@ -34,6 +34,9 @@ void ForwardRenderer::buildGraph(RenderGraph& graph, VkExtent2D swapchainExtent) // Compilar grafo graph.compile(std::move(builder)); + + // Construir Vulkan objects (render passes y framebuffers) + graph.build(device, swapchainExtent); } void ForwardRenderer::setupPasses() { @@ -46,7 +49,7 @@ void ForwardRenderer::setupPasses() { DeferredRenderer::DeferredRenderer() : extent{0, 0} {} -void DeferredRenderer::buildGraph(RenderGraph& graph, VkExtent2D swapchainExtent) { +void DeferredRenderer::buildGraph(RenderGraph& graph, VkDevice device, VkExtent2D swapchainExtent) { extent = swapchainExtent; RenderGraphBuilder builder; @@ -61,6 +64,9 @@ void DeferredRenderer::buildGraph(RenderGraph& graph, VkExtent2D swapchainExtent setupPostProcess(builder); graph.compile(std::move(builder)); + + // Construir Vulkan objects (render passes y framebuffers) + graph.build(device, swapchainExtent); } void DeferredRenderer::setupGBuffer(RenderGraphBuilder& builder) { diff --git a/FarmEngine/src/rendering/graph/RenderPipeline.h b/FarmEngine/src/rendering/graph/RenderPipeline.h index 6f1fd1d..39b9438 100644 --- a/FarmEngine/src/rendering/graph/RenderPipeline.h +++ b/FarmEngine/src/rendering/graph/RenderPipeline.h @@ -27,7 +27,7 @@ class ForwardRenderer { * 4. Post-Processing * 5. Present */ - void buildGraph(RenderGraph& graph, VkExtent2D swapchainExtent); + void buildGraph(RenderGraph& graph, VkDevice device, VkExtent2D swapchainExtent); /** * @brief Configura los passes específicos del renderer @@ -47,7 +47,7 @@ class DeferredRenderer { public: DeferredRenderer(); - void buildGraph(RenderGraph& graph, VkExtent2D swapchainExtent); + void buildGraph(RenderGraph& graph, VkDevice device, VkExtent2D swapchainExtent); /** * Estructura típica de GBuffer: