/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2022 Blender Foundation */ /** \file * \ingroup gpu */ #include "vk_framebuffer.hh" #include "vk_memory.hh" #include "vk_texture.hh" namespace blender::gpu { /* -------------------------------------------------------------------- */ /** \name Creation & Deletion * \{ */ VKFrameBuffer::VKFrameBuffer(const char *name) : FrameBuffer(name) { immutable_ = false; } VKFrameBuffer::VKFrameBuffer(const char *name, VkFramebuffer vk_framebuffer, VkRenderPass vk_render_pass, VkExtent2D vk_extent) : FrameBuffer(name) { immutable_ = true; /* Never update an internal frame-buffer. */ dirty_attachments_ = false; width_ = vk_extent.width; height_ = vk_extent.height; vk_framebuffer_ = vk_framebuffer; vk_render_pass_ = vk_render_pass; viewport_[0] = scissor_[0] = 0; viewport_[1] = scissor_[1] = 0; viewport_[2] = scissor_[2] = width_; viewport_[3] = scissor_[3] = height_; } VKFrameBuffer::~VKFrameBuffer() { if (!immutable_) { render_pass_free(); } } /** \} */ void VKFrameBuffer::bind(bool /*enabled_srgb*/) { update_attachments(); VKContext &context = *VKContext::get(); context.activate_framebuffer(*this); } VkRect2D VKFrameBuffer::vk_render_area_get() const { VkRect2D render_area = {}; if (scissor_test_get()) { int scissor_rect[4]; scissor_get(scissor_rect); render_area.offset.x = scissor_rect[0]; render_area.offset.y = scissor_rect[1]; render_area.extent.width = scissor_rect[2]; render_area.extent.height = scissor_rect[3]; } else { render_area.offset.x = 0; render_area.offset.y = 0; render_area.extent.width = width_; render_area.extent.height = height_; } return render_area; } bool VKFrameBuffer::check(char /*err_out*/[256]) { return false; } void VKFrameBuffer::build_clear_attachments_depth_stencil( const eGPUFrameBufferBits buffers, float clear_depth, uint32_t clear_stencil, Vector &r_attachments) const { VkClearAttachment clear_attachment = {}; clear_attachment.aspectMask = (buffers & GPU_DEPTH_BIT ? VK_IMAGE_ASPECT_DEPTH_BIT : 0) | (buffers & GPU_STENCIL_BIT ? VK_IMAGE_ASPECT_STENCIL_BIT : 0); clear_attachment.clearValue.depthStencil.depth = clear_depth; clear_attachment.clearValue.depthStencil.stencil = clear_stencil; r_attachments.append(clear_attachment); } void VKFrameBuffer::build_clear_attachments_color(const float (*clear_colors)[4], const bool multi_clear_colors, Vector &r_attachments) const { int color_index = 0; for (int color_slot = 0; color_slot < GPU_FB_MAX_COLOR_ATTACHMENT; color_slot++) { const GPUAttachment &attachment = attachments_[GPU_FB_COLOR_ATTACHMENT0 + color_slot]; if (attachment.tex == nullptr) { continue; } VkClearAttachment clear_attachment = {}; clear_attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; clear_attachment.colorAttachment = color_slot; eGPUDataFormat data_format = to_data_format(GPU_texture_format(attachment.tex)); clear_attachment.clearValue.color = to_vk_clear_color_value(data_format, &clear_colors[color_index]); r_attachments.append(clear_attachment); color_index += multi_clear_colors ? 1 : 0; } } /* -------------------------------------------------------------------- */ /** \name Clear * \{ */ void VKFrameBuffer::clear(const Vector &attachments) const { VkClearRect clear_rect = {}; clear_rect.rect = vk_render_area_get(); clear_rect.baseArrayLayer = 0; clear_rect.layerCount = 1; VKContext &context = *VKContext::get(); VKCommandBuffer &command_buffer = context.command_buffer_get(); command_buffer.clear(attachments, Span(&clear_rect, 1)); } void VKFrameBuffer::clear(const eGPUFrameBufferBits buffers, const float clear_color[4], float clear_depth, uint clear_stencil) { Vector attachments; if (buffers & (GPU_DEPTH_BIT | GPU_STENCIL_BIT)) { build_clear_attachments_depth_stencil(buffers, clear_depth, clear_stencil, attachments); } if (buffers & GPU_COLOR_BIT) { float clear_color_single[4]; copy_v4_v4(clear_color_single, clear_color); build_clear_attachments_color(&clear_color_single, false, attachments); } clear(attachments); } void VKFrameBuffer::clear_multi(const float (*clear_color)[4]) { Vector attachments; build_clear_attachments_color(clear_color, true, attachments); clear(attachments); } void VKFrameBuffer::clear_attachment(GPUAttachmentType /*type*/, eGPUDataFormat /*data_format*/, const void * /*clear_value*/) { /* Clearing of a single attachment was added to implement `clear_multi` in OpenGL. As * `clear_multi` is supported in Vulkan it isn't needed to implement this method. */ BLI_assert_unreachable(); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Load/Store operations * \{ */ void VKFrameBuffer::attachment_set_loadstore_op(GPUAttachmentType /*type*/, eGPULoadOp /*load_action*/, eGPUStoreOp /*store_action*/) { } /** \} */ /* -------------------------------------------------------------------- */ /** \name Read back * \{ */ void VKFrameBuffer::read(eGPUFrameBufferBits /*planes*/, eGPUDataFormat /*format*/, const int /*area*/[4], int /*channel_len*/, int /*slot*/, void * /*r_data*/) { } /** \} */ /* -------------------------------------------------------------------- */ /** \name Blit operations * \{ */ void VKFrameBuffer::blit_to(eGPUFrameBufferBits /*planes*/, int /*src_slot*/, FrameBuffer * /*dst*/, int /*dst_slot*/, int /*dst_offset_x*/, int /*dst_offset_y*/) { } /** \} */ /* -------------------------------------------------------------------- */ /** \name Update attachments * \{ */ void VKFrameBuffer::update_attachments() { if (immutable_) { return; } if (!dirty_attachments_) { return; } render_pass_free(); render_pass_create(); dirty_attachments_ = false; } void VKFrameBuffer::render_pass_create() { BLI_assert(!immutable_); BLI_assert(vk_render_pass_ == VK_NULL_HANDLE); BLI_assert(vk_framebuffer_ == VK_NULL_HANDLE); VK_ALLOCATION_CALLBACKS /* Track first attachment for size. */ GPUAttachmentType first_attachment = GPU_FB_MAX_ATTACHMENT; std::array attachment_descriptions; std::array image_views; std::array attachment_references; /*Vector color_attachments; VkAttachmentReference depth_attachment = {}; */ bool has_depth_attachment = false; bool found_attachment = false; int depth_location = -1; for (int type = GPU_FB_MAX_ATTACHMENT - 1; type >= 0; type--) { GPUAttachment &attachment = attachments_[type]; if (attachment.tex == nullptr && !found_attachment) { /* Move the depth texture to the next binding point after all color textures. The binding * location of the color textures should be kept in sync between ShaderCreateInfos and the * framebuffer attachments. The depth buffer should be the last slot. */ depth_location = max_ii(type - GPU_FB_COLOR_ATTACHMENT0, 0); continue; } found_attachment |= attachment.tex != nullptr; /* Keep the first attachment to the first color attachment, or to the depth buffer when there * is no color attachment. */ if (attachment.tex != nullptr && (first_attachment == GPU_FB_MAX_ATTACHMENT || type >= GPU_FB_COLOR_ATTACHMENT0)) { first_attachment = static_cast(type); } int attachment_location = type >= GPU_FB_COLOR_ATTACHMENT0 ? type - GPU_FB_COLOR_ATTACHMENT0 : depth_location; if (attachment.tex) { /* Ensure texture is allocated to ensure the image view. */ VKTexture &texture = *static_cast(unwrap(attachment.tex)); texture.ensure_allocated(); image_views[attachment_location] = texture.vk_image_view_handle(); VkAttachmentDescription &attachment_description = attachment_descriptions[attachment_location]; attachment_description.flags = 0; attachment_description.format = to_vk_format(texture.format_get()); attachment_description.samples = VK_SAMPLE_COUNT_1_BIT; attachment_description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachment_description.storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachment_description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachment_description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachment_description.initialLayout = VK_IMAGE_LAYOUT_GENERAL; attachment_description.finalLayout = VK_IMAGE_LAYOUT_GENERAL; /* Create the attachment reference. */ const bool is_depth_attachment = ELEM( type, GPU_FB_DEPTH_ATTACHMENT, GPU_FB_DEPTH_STENCIL_ATTACHMENT); BLI_assert_msg(!is_depth_attachment || !has_depth_attachment, "There can only be one depth/stencil attachment."); has_depth_attachment |= is_depth_attachment; VkAttachmentReference &attachment_reference = attachment_references[attachment_location]; attachment_reference.attachment = attachment_location; attachment_reference.layout = is_depth_attachment ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_GENERAL; } } /* Update the size, viewport & scissor based on the first attachment. */ if (first_attachment != GPU_FB_MAX_ATTACHMENT) { GPUAttachment &attachment = attachments_[first_attachment]; BLI_assert(attachment.tex); int size[3]; GPU_texture_get_mipmap_size(attachment.tex, attachment.mip, size); size_set(size[0], size[1]); } else { this->size_set(0, 0); } viewport_reset(); scissor_reset(); /* Create render pass. */ const int attachment_len = has_depth_attachment ? depth_location + 1 : depth_location; const int color_attachment_len = depth_location; VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = color_attachment_len; subpass.pColorAttachments = attachment_references.data(); if (has_depth_attachment) { subpass.pDepthStencilAttachment = &attachment_references[depth_location]; } VkRenderPassCreateInfo render_pass_info = {}; render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; render_pass_info.attachmentCount = attachment_len; render_pass_info.pAttachments = attachment_descriptions.data(); render_pass_info.subpassCount = 1; render_pass_info.pSubpasses = &subpass; VKContext &context = *VKContext::get(); vkCreateRenderPass( context.device_get(), &render_pass_info, vk_allocation_callbacks, &vk_render_pass_); /* We might want to split frame-buffer and render pass. */ VkFramebufferCreateInfo framebuffer_create_info = {}; framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebuffer_create_info.renderPass = vk_render_pass_; framebuffer_create_info.attachmentCount = attachment_len; framebuffer_create_info.pAttachments = image_views.data(); framebuffer_create_info.width = width_; framebuffer_create_info.height = height_; framebuffer_create_info.layers = 1; vkCreateFramebuffer( context.device_get(), &framebuffer_create_info, vk_allocation_callbacks, &vk_framebuffer_); } void VKFrameBuffer::render_pass_free() { BLI_assert(!immutable_); if (vk_render_pass_ == VK_NULL_HANDLE) { return; } VK_ALLOCATION_CALLBACKS VKContext &context = *VKContext::get(); vkDestroyRenderPass(context.device_get(), vk_render_pass_, vk_allocation_callbacks); vkDestroyFramebuffer(context.device_get(), vk_framebuffer_, vk_allocation_callbacks); vk_render_pass_ = VK_NULL_HANDLE; vk_framebuffer_ = VK_NULL_HANDLE; } /** \} */ } // namespace blender::gpu