// // Created by Bradley Austin Davis on 2016/08/07 // Adapted for Vulkan in 2022-2025 by dr Karol Suprynowicz. // Copyright 2013-2018 High Fidelity, Inc. // Copyright 2023-2025 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // #include "VKFramebuffer.h" #include "VKBackend.h" #include "VKTexture.h" #include "VKShared.h" // VKTODO: this part needs a lot of work void gpu::vk::VKFramebuffer::update() { auto backend = _backend.lock(); VkDevice device = backend->getContext().device->logicalDevice; // VKTODO: this is wrong, most of framebuffer code will need to be rewritten if (vkFramebuffer != VK_NULL_HANDLE) { // VKTODO: don't destroy immediately, recycle instead for deletion after current frame completes. vkDestroyFramebuffer(device, vkFramebuffer, nullptr); } if (vkRenderPass) { backend->_currentFrame->_renderPasses.push_back(vkRenderPass); } // VKTODO: free all attachments too VKTexture* vkTexture = nullptr; TexturePointer surface; bool lastTextureWasNull = false; if (_gpuObject.getColorStamps() != _colorStamps) { if (_gpuObject.hasColor()) { // VKTODO: Do these need to be deleted? attachments.clear(); //int unit = 0; for (auto& b : _gpuObject.getRenderBuffers()) { surface = b._texture; if (surface) { Q_ASSERT(TextureUsageType::RENDERBUFFER == surface->getUsageType()); vkTexture = backend->syncGPUObject(surface); } else { vkTexture = nullptr; } if (vkTexture) { if (lastTextureWasNull) { Q_ASSERT(false); } if (vkTexture->_target == VK_IMAGE_VIEW_TYPE_2D) { VKAttachmentCreateInfo attachmentCI {}; attachmentCI.width = vkTexture->_gpuObject.getWidth(); attachmentCI.height = vkTexture->_gpuObject.getHeight(); attachmentCI.layerCount = 1; attachmentCI.format = gpu::vk::evalTexelFormatInternal(vkTexture->_gpuObject.getTexelFormat(), backend->getContext()); attachmentCI.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; attachmentCI.imageSampleCount = VK_SAMPLE_COUNT_1_BIT; addAttachment(attachmentCI, vkTexture); //glNamedFramebufferTexture(_id, colorAttachments[unit], gltexture->_texture, 0); // VKTODO: how to do this? /*} else if (vkTexture->_target == GL_TEXTURE_2D_MULTISAMPLE) { glNamedFramebufferTexture(_id, colorAttachments[unit], gltexture->_texture, 0);*/ } else if (vkTexture->_target == VK_IMAGE_VIEW_TYPE_2D_ARRAY) { // VKTODO: what about layers? VKAttachmentCreateInfo attachmentCI {}; attachmentCI.width = vkTexture->_gpuObject.getWidth(); attachmentCI.height = vkTexture->_gpuObject.getHeight(); attachmentCI.layerCount = vkTexture->_gpuObject.getNumSlices() - b._subresource; attachmentCI.format = gpu::vk::evalTexelFormatInternal(vkTexture->_gpuObject.getTexelFormat(), backend->getContext()); attachmentCI.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; attachmentCI.imageSampleCount = VK_SAMPLE_COUNT_1_BIT; addAttachment(attachmentCI, vkTexture, b._subresource); }else { // VKTODO: what is subresource? Q_ASSERT(false); //glNamedFramebufferTextureLayer(_id, colorAttachments[unit], gltexture->_texture, 0, b._subresource); } //_colorBuffers.push_back(unit); } else { lastTextureWasNull = true; // VKTODO: what to do here? //glNamedFramebufferTexture(_id, colorAttachments[unit], 0, 0); } //unit++; } } _colorStamps = _gpuObject.getColorStamps(); } /*GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT; if (!_gpuObject.hasStencil()) { attachement = GL_DEPTH_ATTACHMENT; } else if (!_gpuObject.hasDepth()) { attachement = GL_STENCIL_ATTACHMENT; }*/ if (_gpuObject.getDepthStamp() != _depthStamp) { auto surface = _gpuObject.getDepthStencilBuffer(); auto subresource = _gpuObject.getDepthStencilBufferSubresource(); auto backend = _backend.lock(); if (_gpuObject.hasDepthStencil() && surface) { Q_ASSERT(TextureUsageType::RENDERBUFFER == surface->getUsageType()); vkTexture = backend->syncGPUObject(surface); } if (vkTexture) { if (vkTexture->_target == VK_IMAGE_VIEW_TYPE_2D) { VKAttachmentCreateInfo attachmentCI {}; attachmentCI.width = vkTexture->_gpuObject.getWidth(); attachmentCI.height = vkTexture->_gpuObject.getHeight(); attachmentCI.layerCount = 1; attachmentCI.format = gpu::vk::evalTexelFormatInternal(vkTexture->_gpuObject.getTexelFormat(), backend->getContext()); attachmentCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; attachmentCI.imageSampleCount = VK_SAMPLE_COUNT_1_BIT; addAttachment(attachmentCI, vkTexture); /*} else if (vkTexture->_target == GL_TEXTURE_2D_MULTISAMPLE) { glNamedFramebufferTexture(_id, attachement, gltexture->_texture, 0);*/ } else if (vkTexture->_target == VK_IMAGE_VIEW_TYPE_2D_ARRAY) { VKAttachmentCreateInfo attachmentCI {}; attachmentCI.width = vkTexture->_gpuObject.getWidth(); attachmentCI.height = vkTexture->_gpuObject.getHeight(); attachmentCI.layerCount = vkTexture->_gpuObject.getNumSlices() - subresource; attachmentCI.format = gpu::vk::evalTexelFormatInternal(vkTexture->_gpuObject.getTexelFormat(), backend->getContext()); attachmentCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; attachmentCI.imageSampleCount = VK_SAMPLE_COUNT_1_BIT; addAttachment(attachmentCI, vkTexture, subresource); } else { Q_ASSERT(false); // VKTODO //glNamedFramebufferTextureLayer(_id, attachement, gltexture->_texture, 0, // _gpuObject.getDepthStencilBufferSubresource()); } } else { //Q_ASSERT(false); // VKTODO //glNamedFramebufferTexture(_id, attachement, 0, 0); } _depthStamp = _gpuObject.getDepthStamp(); } // Last but not least, define where we draw if (!attachments.empty()) { VK_CHECK_RESULT(createFramebuffer()); //glNamedFramebufferDrawBuffers(_id, (GLsizei)_colorBuffers.size(), _colorBuffers.data()); } else { Q_ASSERT(false); // VKTODO //glNamedFramebufferDrawBuffer(_id, GL_NONE); } // Now check for completness //_status = glCheckNamedFramebufferStatus(_id, GL_DRAW_FRAMEBUFFER); // restore the current framebuffer //checkStatus(); } // From VKS VkResult gpu::vk::VKFramebuffer::createFramebuffer() { std::vector attachmentDescriptions; for (auto& attachment : attachments) { attachmentDescriptions.push_back(attachment.description); }; // Collect attachment references std::vector colorReferences; VkAttachmentReference depthReference = {}; bool hasDepth = false; bool hasColor = false; uint32_t attachmentIndex = 0; for (auto& attachment : attachments) { if (attachment.isDepthStencil()) { // Only one depth attachment allowed assert(!hasDepth); depthReference.attachment = attachmentIndex; depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; hasDepth = true; } else { colorReferences.push_back({ attachmentIndex, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); hasColor = true; } attachmentIndex++; }; // Default render pass setup uses only one subpass VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; if (hasColor) { subpass.pColorAttachments = colorReferences.data(); subpass.colorAttachmentCount = static_cast(colorReferences.size()); } if (hasDepth) { subpass.pDepthStencilAttachment = &depthReference; } // Use subpass dependencies for attachment layout transitions //std::array dependencies; // VKTODO: what are these for? /*dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; dependencies[0].dstSubpass = 0; dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; dependencies[1].srcSubpass = 0; dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;*/ // Create render pass VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.pAttachments = attachmentDescriptions.data(); renderPassInfo.attachmentCount = static_cast(attachmentDescriptions.size()); renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; // VKTODO //renderPassInfo.dependencyCount = 2; //renderPassInfo.pDependencies = dependencies.data(); renderPassInfo.dependencyCount = 0; VK_CHECK_RESULT(vkCreateRenderPass(_backend.lock()->_context.device->logicalDevice, &renderPassInfo, nullptr, &vkRenderPass)); std::vector attachmentViews; for (auto attachment : attachments) { attachmentViews.push_back(attachment.view); } // Find. max number of layers across attachments uint32_t maxLayers = 0; for (auto attachment : attachments) { if (attachment.subresourceRange.layerCount > maxLayers) { maxLayers = attachment.subresourceRange.layerCount; } } VkFramebufferCreateInfo framebufferInfo = {}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = vkRenderPass; framebufferInfo.pAttachments = attachmentViews.data(); framebufferInfo.attachmentCount = static_cast(attachmentViews.size()); framebufferInfo.width = _gpuObject.getWidth(); framebufferInfo.height = _gpuObject.getHeight(); framebufferInfo.layers = maxLayers; VK_CHECK_RESULT(vkCreateFramebuffer(_backend.lock()->_context.device->logicalDevice, &framebufferInfo, nullptr, &vkFramebuffer)); return VK_SUCCESS; } //bool gpu::vk::VKFramebuffer::checkStatus(gpu::vk::VKFramebuffer::FramebufferStatus target) const { // VKTODO /*switch (_status) { case GL_FRAMEBUFFER_COMPLETE: // Success ! return true; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; break; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; break; case GL_FRAMEBUFFER_UNSUPPORTED: qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; break; #if !defined(USE_GLES) case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; break; case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; break; #endif default: break; } return false; */ //} gpu::vk::VKFramebuffer::~VKFramebuffer() { auto backend = _backend.lock(); if (backend) { auto& recycler = backend->getContext().recycler; // VKTODO: these sometimes get destroyed after backend was destroyed? recycler.trashVkRenderPass(vkRenderPass); recycler.trashVkFramebuffer(vkFramebuffer); for (auto &attachment : attachments) { recycler.trashVkImageView(attachment.view); } recycler.framebufferDeleted(this); } else { Q_ASSERT(false); } //VKTODO /*if (_id) { auto backend = _backend.lock(); if (backend) { backend->releaseFramebuffer(_id); } }*/ } // From VKS uint32_t gpu::vk::VKFramebuffer::addAttachment(VKAttachmentCreateInfo createinfo, VKTexture *texture, uint32_t subresource) { auto *attachmentTexture = dynamic_cast(texture); Q_ASSERT(attachmentTexture); FramebufferAttachment attachment {}; attachment.format = createinfo.format; VkImageAspectFlags aspectMask = VK_FLAGS_NONE; // Select aspect mask and layout depending on usage // Color attachment if (createinfo.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) { Q_ASSERT(attachment.format != VK_FORMAT_D24_UNORM_S8_UINT); aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; } // Depth (and/or stencil) attachment if (createinfo.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) { if (attachment.hasDepth()) { aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; } if (attachment.hasStencil()) { aspectMask = aspectMask | VK_IMAGE_ASPECT_STENCIL_BIT; } } assert(aspectMask > 0); attachment.image = attachmentTexture->_vkImage; attachment.subresourceRange = {}; attachment.subresourceRange.aspectMask = aspectMask; attachment.subresourceRange.baseArrayLayer = subresource; attachment.subresourceRange.levelCount = 1; attachment.subresourceRange.layerCount = 1; // Even though texture can have multiple layers, we only render to one of them currently. VkImageViewCreateInfo imageView = vks::initializers::imageViewCreateInfo(); imageView.viewType = (createinfo.layerCount == 1) ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY; imageView.format = createinfo.format; imageView.subresourceRange = attachment.subresourceRange; imageView.subresourceRange.aspectMask = attachment.hasDepth() ? VkImageAspectFlags{VK_IMAGE_ASPECT_DEPTH_BIT} : aspectMask; imageView.image = attachment.image; VK_CHECK_RESULT(vkCreateImageView(_backend.lock()->_context.device->logicalDevice, &imageView, nullptr, &attachment.view)); // Fill attachment description attachment.description = {}; attachment.description.samples = createinfo.imageSampleCount; attachment.description.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachment.description.storeOp = (createinfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE; attachment.description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachment.description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachment.description.format = createinfo.format; attachment.description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // Final layout // If not, final layout depends on attachment type if (attachment.hasDepth() || attachment.hasStencil()) { attachment.description.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; // VKTODO: this is tricky, because it depends on what the image will be used for } else { attachment.description.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; // VKTODO: this is tricky, because it depends on what the image will be used for } attachments.push_back(attachment); return static_cast(attachments.size() - 1); } // VKTODO: get rid of _backend.lock() #if 0 using namespace gpu; using namespace gpu::gl; VKFramebuffer::~VKFramebuffer() { if (_id) { auto backend = _backend.lock(); if (backend) { backend->releaseFramebuffer(_id); } } } bool VKFramebuffer::checkStatus(VKenum target) const { bool result = false; switch (_status) { case VK_FRAMEBUFFER_COMPLETE: // Success ! result = true; break; case VK_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: qCDebug(gpu_vk_logging) << "VKFramebuffer::syncGPUObject : Framebuffer not valid, VK_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; break; case VK_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: qCDebug(gpu_vk_logging) << "VKFramebuffer::syncGPUObject : Framebuffer not valid, VK_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; break; case VK_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: qCDebug(gpu_vk_logging) << "VKFramebuffer::syncGPUObject : Framebuffer not valid, VK_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; break; case VK_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: qCDebug(gpu_vk_logging) << "VKFramebuffer::syncGPUObject : Framebuffer not valid, VK_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; break; case VK_FRAMEBUFFER_UNSUPPORTED: qCDebug(gpu_vk_logging) << "VKFramebuffer::syncGPUObject : Framebuffer not valid, VK_FRAMEBUFFER_UNSUPPORTED."; break; } return result; } #endif