Source code for simvx.graphics.renderer.passes

"""Shared render pass definitions."""


from __future__ import annotations

import logging
from typing import Any

import vulkan as vk

__all__ = ["create_render_pass", "create_offscreen_pass", "create_pick_pass"]

log = logging.getLogger(__name__)


[docs] def create_render_pass( device: Any, color_format: int, depth_format: int = 0, ) -> Any: """Create a render pass with colour and optional depth attachment.""" attachments = [] color_attachment = vk.VkAttachmentDescription( format=color_format, samples=vk.VK_SAMPLE_COUNT_1_BIT, loadOp=vk.VK_ATTACHMENT_LOAD_OP_CLEAR, storeOp=vk.VK_ATTACHMENT_STORE_OP_STORE, stencilLoadOp=vk.VK_ATTACHMENT_LOAD_OP_DONT_CARE, stencilStoreOp=vk.VK_ATTACHMENT_STORE_OP_DONT_CARE, initialLayout=vk.VK_IMAGE_LAYOUT_UNDEFINED, finalLayout=vk.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, ) attachments.append(color_attachment) color_ref = vk.VkAttachmentReference( attachment=0, layout=vk.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, ) depth_ref = None if depth_format: depth_attachment = vk.VkAttachmentDescription( format=depth_format, samples=vk.VK_SAMPLE_COUNT_1_BIT, loadOp=vk.VK_ATTACHMENT_LOAD_OP_CLEAR, storeOp=vk.VK_ATTACHMENT_STORE_OP_DONT_CARE, stencilLoadOp=vk.VK_ATTACHMENT_LOAD_OP_DONT_CARE, stencilStoreOp=vk.VK_ATTACHMENT_STORE_OP_DONT_CARE, initialLayout=vk.VK_IMAGE_LAYOUT_UNDEFINED, finalLayout=vk.VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, ) attachments.append(depth_attachment) depth_ref = vk.VkAttachmentReference( attachment=1, layout=vk.VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, ) subpass = vk.VkSubpassDescription( pipelineBindPoint=vk.VK_PIPELINE_BIND_POINT_GRAPHICS, colorAttachmentCount=1, pColorAttachments=[color_ref], pDepthStencilAttachment=depth_ref, ) src_stages = vk.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT dst_stages = vk.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT dst_access = vk.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT if depth_format: src_stages |= vk.VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT dst_stages |= vk.VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT dst_access |= vk.VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT dependency = vk.VkSubpassDependency( srcSubpass=vk.VK_SUBPASS_EXTERNAL, dstSubpass=0, srcStageMask=src_stages, srcAccessMask=0, dstStageMask=dst_stages, dstAccessMask=dst_access, ) create_info = vk.VkRenderPassCreateInfo( attachmentCount=len(attachments), pAttachments=attachments, subpassCount=1, pSubpasses=[subpass], dependencyCount=1, pDependencies=[dependency], ) render_pass = vk.vkCreateRenderPass(device, create_info, None) log.debug("Render pass created (depth=%s)", bool(depth_format)) return render_pass
[docs] def create_offscreen_pass( device: Any, color_format: int, depth_format: int = vk.VK_FORMAT_D32_SFLOAT, *, samplable_depth: bool = False, ) -> Any: """Create a render pass for offscreen rendering (colour -> SHADER_READ_ONLY). When *samplable_depth* is True, the depth attachment transitions to SHADER_READ_ONLY_OPTIMAL so it can be sampled in a later pass (e.g. motion blur). """ attachments = [ vk.VkAttachmentDescription( format=color_format, samples=vk.VK_SAMPLE_COUNT_1_BIT, loadOp=vk.VK_ATTACHMENT_LOAD_OP_CLEAR, storeOp=vk.VK_ATTACHMENT_STORE_OP_STORE, stencilLoadOp=vk.VK_ATTACHMENT_LOAD_OP_DONT_CARE, stencilStoreOp=vk.VK_ATTACHMENT_STORE_OP_DONT_CARE, initialLayout=vk.VK_IMAGE_LAYOUT_UNDEFINED, finalLayout=vk.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, ), ] color_ref = vk.VkAttachmentReference( attachment=0, layout=vk.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, ) depth_ref = None if depth_format: depth_final = ( vk.VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL if samplable_depth else vk.VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL ) depth_store = ( vk.VK_ATTACHMENT_STORE_OP_STORE if samplable_depth else vk.VK_ATTACHMENT_STORE_OP_DONT_CARE ) attachments.append(vk.VkAttachmentDescription( format=depth_format, samples=vk.VK_SAMPLE_COUNT_1_BIT, loadOp=vk.VK_ATTACHMENT_LOAD_OP_CLEAR, storeOp=depth_store, stencilLoadOp=vk.VK_ATTACHMENT_LOAD_OP_DONT_CARE, stencilStoreOp=vk.VK_ATTACHMENT_STORE_OP_DONT_CARE, initialLayout=vk.VK_IMAGE_LAYOUT_UNDEFINED, finalLayout=depth_final, )) depth_ref = vk.VkAttachmentReference( attachment=1, layout=vk.VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, ) subpass = vk.VkSubpassDescription( pipelineBindPoint=vk.VK_PIPELINE_BIND_POINT_GRAPHICS, colorAttachmentCount=1, pColorAttachments=[color_ref], pDepthStencilAttachment=depth_ref, ) src_stages = vk.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT dst_stages = vk.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT dst_access = vk.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT if depth_format: src_stages |= vk.VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT dst_stages |= vk.VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT dst_access |= vk.VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT dependencies = [ vk.VkSubpassDependency( srcSubpass=vk.VK_SUBPASS_EXTERNAL, dstSubpass=0, srcStageMask=src_stages, srcAccessMask=0, dstStageMask=dst_stages, dstAccessMask=dst_access, ), ] # When depth is samplable, add a dependency so the fragment shader can read it if samplable_depth and depth_format: dependencies.append(vk.VkSubpassDependency( srcSubpass=0, dstSubpass=vk.VK_SUBPASS_EXTERNAL, srcStageMask=vk.VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, srcAccessMask=vk.VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, dstStageMask=vk.VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, dstAccessMask=vk.VK_ACCESS_SHADER_READ_BIT, )) create_info = vk.VkRenderPassCreateInfo( attachmentCount=len(attachments), pAttachments=attachments, subpassCount=1, pSubpasses=[subpass], dependencyCount=len(dependencies), pDependencies=dependencies, ) render_pass = vk.vkCreateRenderPass(device, create_info, None) log.debug("Offscreen render pass created (samplable_depth=%s)", samplable_depth) return render_pass
[docs] def create_shadow_pass(device: Any, depth_format: int = vk.VK_FORMAT_D32_SFLOAT) -> Any: """Create a depth-only render pass for shadow mapping.""" depth_attachment = vk.VkAttachmentDescription( format=depth_format, samples=vk.VK_SAMPLE_COUNT_1_BIT, loadOp=vk.VK_ATTACHMENT_LOAD_OP_CLEAR, storeOp=vk.VK_ATTACHMENT_STORE_OP_STORE, stencilLoadOp=vk.VK_ATTACHMENT_LOAD_OP_DONT_CARE, stencilStoreOp=vk.VK_ATTACHMENT_STORE_OP_DONT_CARE, initialLayout=vk.VK_IMAGE_LAYOUT_UNDEFINED, finalLayout=vk.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, ) depth_ref = vk.VkAttachmentReference( attachment=0, layout=vk.VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, ) subpass = vk.VkSubpassDescription( pipelineBindPoint=vk.VK_PIPELINE_BIND_POINT_GRAPHICS, colorAttachmentCount=0, pDepthStencilAttachment=depth_ref, ) # Two dependencies: write → read transition dependencies = [ vk.VkSubpassDependency( srcSubpass=vk.VK_SUBPASS_EXTERNAL, dstSubpass=0, srcStageMask=vk.VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, srcAccessMask=vk.VK_ACCESS_SHADER_READ_BIT, dstStageMask=vk.VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, dstAccessMask=vk.VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, ), vk.VkSubpassDependency( srcSubpass=0, dstSubpass=vk.VK_SUBPASS_EXTERNAL, srcStageMask=vk.VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, srcAccessMask=vk.VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, dstStageMask=vk.VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, dstAccessMask=vk.VK_ACCESS_SHADER_READ_BIT, ), ] create_info = vk.VkRenderPassCreateInfo( attachmentCount=1, pAttachments=[depth_attachment], subpassCount=1, pSubpasses=[subpass], dependencyCount=2, pDependencies=dependencies, ) render_pass = vk.vkCreateRenderPass(device, create_info, None) log.debug("Shadow render pass created") return render_pass
[docs] def create_pick_pass(device: Any, depth_format: int = 0) -> Any: """Create the picking render pass (R32_UINT colour + optional depth).""" attachments = [ vk.VkAttachmentDescription( format=vk.VK_FORMAT_R32_UINT, samples=vk.VK_SAMPLE_COUNT_1_BIT, loadOp=vk.VK_ATTACHMENT_LOAD_OP_CLEAR, storeOp=vk.VK_ATTACHMENT_STORE_OP_STORE, stencilLoadOp=vk.VK_ATTACHMENT_LOAD_OP_DONT_CARE, stencilStoreOp=vk.VK_ATTACHMENT_STORE_OP_DONT_CARE, initialLayout=vk.VK_IMAGE_LAYOUT_UNDEFINED, finalLayout=vk.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, ), ] color_ref = vk.VkAttachmentReference( attachment=0, layout=vk.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, ) depth_ref = None if depth_format: attachments.append(vk.VkAttachmentDescription( format=depth_format, samples=vk.VK_SAMPLE_COUNT_1_BIT, loadOp=vk.VK_ATTACHMENT_LOAD_OP_CLEAR, storeOp=vk.VK_ATTACHMENT_STORE_OP_DONT_CARE, stencilLoadOp=vk.VK_ATTACHMENT_LOAD_OP_DONT_CARE, stencilStoreOp=vk.VK_ATTACHMENT_STORE_OP_DONT_CARE, initialLayout=vk.VK_IMAGE_LAYOUT_UNDEFINED, finalLayout=vk.VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, )) depth_ref = vk.VkAttachmentReference( attachment=1, layout=vk.VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, ) subpass = vk.VkSubpassDescription( pipelineBindPoint=vk.VK_PIPELINE_BIND_POINT_GRAPHICS, colorAttachmentCount=1, pColorAttachments=[color_ref], pDepthStencilAttachment=depth_ref, ) src_stages = vk.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT dst_stages = vk.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT dst_access = vk.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT if depth_format: src_stages |= vk.VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT dst_stages |= vk.VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT dst_access |= vk.VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT dependency = vk.VkSubpassDependency( srcSubpass=vk.VK_SUBPASS_EXTERNAL, dstSubpass=0, srcStageMask=src_stages, srcAccessMask=0, dstStageMask=dst_stages, dstAccessMask=dst_access, ) create_info = vk.VkRenderPassCreateInfo( attachmentCount=len(attachments), pAttachments=attachments, subpassCount=1, pSubpasses=[subpass], dependencyCount=1, pDependencies=[dependency], ) render_pass = vk.vkCreateRenderPass(device, create_info, None) log.debug("Pick render pass created (depth=%s)", bool(depth_format)) return render_pass