#include "rendering/vk_render_target.hpp" #include "rendering/vk_context.hpp" #include "core/logger.hpp" namespace wowee { namespace rendering { VkRenderTarget::~VkRenderTarget() { // Must call destroy() explicitly with device/allocator before destruction } bool VkRenderTarget::create(VkContext& ctx, uint32_t width, uint32_t height, VkFormat format) { VkDevice device = ctx.getDevice(); VmaAllocator allocator = ctx.getAllocator(); // Create color image (COLOR_ATTACHMENT + SAMPLED for reading in subsequent passes) colorImage_ = createImage(device, allocator, width, height, format, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); if (!colorImage_.image) { LOG_ERROR("VkRenderTarget: failed to create color image (", width, "x", height, ")"); return false; } // Create sampler (linear filtering, clamp to edge) VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.minFilter = VK_FILTER_LINEAR; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.minLod = 0.0f; samplerInfo.maxLod = 0.0f; if (vkCreateSampler(device, &samplerInfo, nullptr, &sampler_) != VK_SUCCESS) { LOG_ERROR("VkRenderTarget: failed to create sampler"); destroy(device, allocator); return false; } // Create render pass // Color attachment: UNDEFINED -> COLOR_ATTACHMENT_OPTIMAL (during pass) // -> SHADER_READ_ONLY_OPTIMAL (final layout, ready for sampling) VkAttachmentDescription colorAttachment{}; colorAttachment.format = format; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; VkAttachmentReference colorRef{}; colorRef.attachment = 0; colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorRef; // Dependency: external -> subpass 0 (wait for previous reads to finish) VkSubpassDependency dependency{}; dependency.srcSubpass = VK_SUBPASS_EXTERNAL; dependency.dstSubpass = 0; dependency.srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; VkRenderPassCreateInfo rpInfo{}; rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; rpInfo.attachmentCount = 1; rpInfo.pAttachments = &colorAttachment; rpInfo.subpassCount = 1; rpInfo.pSubpasses = &subpass; rpInfo.dependencyCount = 1; rpInfo.pDependencies = &dependency; if (vkCreateRenderPass(device, &rpInfo, nullptr, &renderPass_) != VK_SUCCESS) { LOG_ERROR("VkRenderTarget: failed to create render pass"); destroy(device, allocator); return false; } // Create framebuffer VkFramebufferCreateInfo fbInfo{}; fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; fbInfo.renderPass = renderPass_; fbInfo.attachmentCount = 1; fbInfo.pAttachments = &colorImage_.imageView; fbInfo.width = width; fbInfo.height = height; fbInfo.layers = 1; if (vkCreateFramebuffer(device, &fbInfo, nullptr, &framebuffer_) != VK_SUCCESS) { LOG_ERROR("VkRenderTarget: failed to create framebuffer"); destroy(device, allocator); return false; } LOG_INFO("VkRenderTarget created (", width, "x", height, ")"); return true; } void VkRenderTarget::destroy(VkDevice device, VmaAllocator allocator) { if (framebuffer_) { vkDestroyFramebuffer(device, framebuffer_, nullptr); framebuffer_ = VK_NULL_HANDLE; } if (renderPass_) { vkDestroyRenderPass(device, renderPass_, nullptr); renderPass_ = VK_NULL_HANDLE; } if (sampler_) { vkDestroySampler(device, sampler_, nullptr); sampler_ = VK_NULL_HANDLE; } destroyImage(device, allocator, colorImage_); } void VkRenderTarget::beginPass(VkCommandBuffer cmd, const VkClearColorValue& clear) { VkRenderPassBeginInfo rpBegin{}; rpBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; rpBegin.renderPass = renderPass_; rpBegin.framebuffer = framebuffer_; rpBegin.renderArea.offset = {0, 0}; rpBegin.renderArea.extent = getExtent(); VkClearValue clearValue{}; clearValue.color = clear; rpBegin.clearValueCount = 1; rpBegin.pClearValues = &clearValue; vkCmdBeginRenderPass(cmd, &rpBegin, VK_SUBPASS_CONTENTS_INLINE); // Set viewport and scissor to match render target VkViewport viewport{}; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = static_cast(colorImage_.extent.width); viewport.height = static_cast(colorImage_.extent.height); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; vkCmdSetViewport(cmd, 0, 1, &viewport); VkRect2D scissor{}; scissor.offset = {0, 0}; scissor.extent = getExtent(); vkCmdSetScissor(cmd, 0, 1, &scissor); } void VkRenderTarget::endPass(VkCommandBuffer cmd) { vkCmdEndRenderPass(cmd); // Image is now in SHADER_READ_ONLY_OPTIMAL (from render pass finalLayout) } VkDescriptorImageInfo VkRenderTarget::descriptorInfo() const { VkDescriptorImageInfo info{}; info.sampler = sampler_; info.imageView = colorImage_.imageView; info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; return info; } } // namespace rendering } // namespace wowee