MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_stencil.cpp
Go to the documentation of this file.
2
5
6#include <array>
7#include <vector>
8
9namespace mxvk {
11
12 void VK_Stencil::initialize(const VulkanContext &context, VkExtent2D extent, VkFormat color_format, VkFormat depth_format, VkPipelineCache pipeline_cache, const std::string &mask_vertex_shader, const std::string &mask_fragment_shader, const std::string &content_vertex_shader, const std::string &content_fragment_shader) {
13 destroy();
14 vk_context = context;
15 stencil_extent = extent;
16 render_color_format = color_format;
17 render_depth_format = depth_format;
18 cache = pipeline_cache;
19 mask_vertex_path = mask_vertex_shader;
20 mask_fragment_path = mask_fragment_shader;
21 content_vertex_path = content_vertex_shader;
22 content_fragment_path = content_fragment_shader;
23 stencil_image_format = choose_stencil_format();
24 create_resources();
25 create_pipelines();
26 }
27
29 destroy_pipelines();
30 destroy_image();
31 vk_context = {};
32 stencil_extent = {};
33 render_color_format = VK_FORMAT_UNDEFINED;
34 render_depth_format = VK_FORMAT_UNDEFINED;
35 stencil_image_format = VK_FORMAT_UNDEFINED;
36 cache = VK_NULL_HANDLE;
37 mask_vertex_path.clear();
38 mask_fragment_path.clear();
39 content_vertex_path.clear();
40 content_fragment_path.clear();
41 }
42
43 void VK_Stencil::resize(VkExtent2D extent) {
44 if (extent.width == stencil_extent.width && extent.height == stencil_extent.height) {
45 return;
46 }
47 stencil_extent = extent;
48 destroy_image();
49 create_resources();
50 }
51
52 void VK_Stencil::prepare_for_rendering(VkCommandBuffer cmd) {
53 if (!valid()) {
54 return;
55 }
56
57 VkImageMemoryBarrier2 barrier{};
58 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
59 barrier.srcStageMask = image_initialized ? VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT : VK_PIPELINE_STAGE_2_NONE;
60 barrier.srcAccessMask = image_initialized ? VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT : VK_ACCESS_2_NONE;
61 barrier.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT;
62 barrier.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
63 barrier.oldLayout = image_initialized ? VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
64 barrier.newLayout = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL;
65 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
66 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
67 barrier.image = image;
68 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
69 barrier.subresourceRange.levelCount = 1;
70 barrier.subresourceRange.layerCount = 1;
71
72 VkDependencyInfo dependency{};
73 dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
74 dependency.imageMemoryBarrierCount = 1;
75 dependency.pImageMemoryBarriers = &barrier;
76 vkCmdPipelineBarrier2(cmd, &dependency);
77 image_initialized = true;
78 }
79
80 void VK_Stencil::configure_attachment(VkRenderingAttachmentInfo &attachment) const {
81 if (!valid()) {
82 return;
83 }
84 attachment.imageView = view;
85 attachment.imageLayout = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL;
86 attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
87 attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
88 attachment.clearValue.depthStencil = {1.0f, 0U};
89 }
90
91 void VK_Stencil::draw_mask(VkCommandBuffer cmd, const PushConstants &push_constants) const {
92 if (mask_pipeline == VK_NULL_HANDLE || mask_layout == VK_NULL_HANDLE) {
93 return;
94 }
95 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, mask_pipeline);
96 vkCmdPushConstants(cmd, mask_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants), &push_constants);
97 vkCmdDraw(cmd, 3, 1, 0, 0);
98 }
99
100 void VK_Stencil::draw_content(VkCommandBuffer cmd, const PushConstants &push_constants) const {
101 if (content_pipeline == VK_NULL_HANDLE || content_layout == VK_NULL_HANDLE) {
102 return;
103 }
104 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, content_pipeline);
105 vkCmdPushConstants(cmd, content_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants), &push_constants);
106 vkCmdDraw(cmd, 3, 1, 0, 0);
107 }
108
109 bool VK_Stencil::valid() const noexcept { return vk_context.device != VK_NULL_HANDLE && image != VK_NULL_HANDLE && view != VK_NULL_HANDLE && stencil_extent.width > 0U && stencil_extent.height > 0U; }
110
111 void VK_Stencil::create_resources() {
112 if (vk_context.device == VK_NULL_HANDLE || stencil_extent.width == 0U || stencil_extent.height == 0U) {
113 return;
114 }
115 create_image(vk_context, stencil_extent.width, stencil_extent.height, stencil_image_format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, image, memory);
116 view = create_image_view(vk_context.device, image, stencil_image_format, VK_IMAGE_ASPECT_STENCIL_BIT);
117 image_initialized = false;
118 }
119
120 void VK_Stencil::create_pipelines() {
121 VkPushConstantRange push_constant{};
122 push_constant.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
123 push_constant.offset = 0;
124 push_constant.size = sizeof(PushConstants);
125
126 VkPipelineLayoutCreateInfo layout_info{};
127 layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
128 layout_info.pushConstantRangeCount = 1;
129 layout_info.pPushConstantRanges = &push_constant;
130 if (vkCreatePipelineLayout(vk_context.device, &layout_info, nullptr, &mask_layout) != VK_SUCCESS || vkCreatePipelineLayout(vk_context.device, &layout_info, nullptr, &content_layout) != VK_SUCCESS) {
131 throw mxvk::Exception("failed to create stencil pipeline layout");
132 }
133
134 mask_pipeline = create_pipeline(mask_vertex_path, mask_fragment_path, mask_layout, true);
135 content_pipeline = create_pipeline(content_vertex_path, content_fragment_path, content_layout, false);
136 }
137
138 void VK_Stencil::destroy_pipelines() {
139 if (vk_context.device == VK_NULL_HANDLE) {
140 mask_layout = VK_NULL_HANDLE;
141 content_layout = VK_NULL_HANDLE;
142 mask_pipeline = VK_NULL_HANDLE;
143 content_pipeline = VK_NULL_HANDLE;
144 return;
145 }
146 if (mask_pipeline != VK_NULL_HANDLE) {
147 vkDestroyPipeline(vk_context.device, mask_pipeline, nullptr);
148 }
149 if (content_pipeline != VK_NULL_HANDLE) {
150 vkDestroyPipeline(vk_context.device, content_pipeline, nullptr);
151 }
152 if (mask_layout != VK_NULL_HANDLE) {
153 vkDestroyPipelineLayout(vk_context.device, mask_layout, nullptr);
154 }
155 if (content_layout != VK_NULL_HANDLE) {
156 vkDestroyPipelineLayout(vk_context.device, content_layout, nullptr);
157 }
158 mask_layout = VK_NULL_HANDLE;
159 content_layout = VK_NULL_HANDLE;
160 mask_pipeline = VK_NULL_HANDLE;
161 content_pipeline = VK_NULL_HANDLE;
162 }
163
164 void VK_Stencil::destroy_image() {
165 if (vk_context.device == VK_NULL_HANDLE) {
166 image = VK_NULL_HANDLE;
167 memory = VK_NULL_HANDLE;
168 view = VK_NULL_HANDLE;
169 image_initialized = false;
170 return;
171 }
172 if (view != VK_NULL_HANDLE) {
173 vkDestroyImageView(vk_context.device, view, nullptr);
174 }
175 if (image != VK_NULL_HANDLE) {
176 vkDestroyImage(vk_context.device, image, nullptr);
177 }
178 if (memory != VK_NULL_HANDLE) {
179 vkFreeMemory(vk_context.device, memory, nullptr);
180 }
181 image = VK_NULL_HANDLE;
182 memory = VK_NULL_HANDLE;
183 view = VK_NULL_HANDLE;
184 image_initialized = false;
185 }
186
187 VkFormat VK_Stencil::choose_stencil_format() const {
188 const std::array<VkFormat, 3> candidates{
189 VK_FORMAT_S8_UINT,
190 VK_FORMAT_D24_UNORM_S8_UINT,
191 VK_FORMAT_D32_SFLOAT_S8_UINT,
192 };
193
194 for (const VkFormat format : candidates) {
195 VkFormatProperties properties{};
196 vkGetPhysicalDeviceFormatProperties(vk_context.physical_device, format, &properties);
197 if ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0U) {
198 return format;
199 }
200 }
201 throw mxvk::Exception("failed to find a supported Vulkan stencil format");
202 }
203
204 VkPipeline VK_Stencil::create_pipeline(const std::string &vertex_shader, const std::string &fragment_shader, VkPipelineLayout layout, bool writes_stencil) const {
205 const std::vector<char> vert_bytes = load_spv(vertex_shader);
206 const std::vector<char> frag_bytes = load_spv(fragment_shader);
207 const VkShaderModule vert_module = create_shader_module(vk_context.device, vert_bytes);
208 VkShaderModule frag_module = VK_NULL_HANDLE;
209 VkPipeline pipeline = VK_NULL_HANDLE;
210 try {
211 frag_module = create_shader_module(vk_context.device, frag_bytes);
212
213 std::array<VkPipelineShaderStageCreateInfo, 2> stages{};
214 stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
215 stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
216 stages[0].module = vert_module;
217 stages[0].pName = "main";
218 stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
219 stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
220 stages[1].module = frag_module;
221 stages[1].pName = "main";
222
223 VkPipelineVertexInputStateCreateInfo vertex_input{};
224 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
225
226 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
227 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
228 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
229
230 VkPipelineViewportStateCreateInfo viewport_state{};
231 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
232 viewport_state.viewportCount = 1;
233 viewport_state.scissorCount = 1;
234
235 const VkDynamicState dynamic_states[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
236 VkPipelineDynamicStateCreateInfo dynamic_state{};
237 dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
238 dynamic_state.dynamicStateCount = 2;
239 dynamic_state.pDynamicStates = dynamic_states;
240
241 VkPipelineRasterizationStateCreateInfo rasterizer{};
242 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
243 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
244 rasterizer.cullMode = VK_CULL_MODE_NONE;
245 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
246 rasterizer.lineWidth = 1.0f;
247
248 VkPipelineMultisampleStateCreateInfo multisample{};
249 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
250 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
251
252 VkStencilOpState stencil_op{};
253 stencil_op.failOp = VK_STENCIL_OP_KEEP;
254 stencil_op.passOp = writes_stencil ? VK_STENCIL_OP_REPLACE : VK_STENCIL_OP_KEEP;
255 stencil_op.depthFailOp = VK_STENCIL_OP_KEEP;
256 stencil_op.compareOp = writes_stencil ? VK_COMPARE_OP_ALWAYS : VK_COMPARE_OP_EQUAL;
257 stencil_op.compareMask = 0xffU;
258 stencil_op.writeMask = writes_stencil ? 0xffU : 0U;
259 stencil_op.reference = 1U;
260
261 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
262 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
263 depth_stencil.depthTestEnable = VK_FALSE;
264 depth_stencil.depthWriteEnable = VK_FALSE;
265 depth_stencil.stencilTestEnable = VK_TRUE;
266 depth_stencil.front = stencil_op;
267 depth_stencil.back = stencil_op;
268
269 VkPipelineColorBlendAttachmentState blend_attachment{};
270 blend_attachment.colorWriteMask = writes_stencil ? 0U : (VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT);
271
272 VkPipelineColorBlendStateCreateInfo color_blend{};
273 color_blend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
274 color_blend.attachmentCount = 1;
275 color_blend.pAttachments = &blend_attachment;
276
277 VkPipelineRenderingCreateInfo rendering{};
278 rendering.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
279 rendering.colorAttachmentCount = 1;
280 rendering.pColorAttachmentFormats = &render_color_format;
281 rendering.depthAttachmentFormat = render_depth_format;
282 rendering.stencilAttachmentFormat = stencil_image_format;
283
284 VkGraphicsPipelineCreateInfo pipeline_info{};
285 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
286 pipeline_info.pNext = &rendering;
287 pipeline_info.stageCount = static_cast<uint32_t>(stages.size());
288 pipeline_info.pStages = stages.data();
289 pipeline_info.pVertexInputState = &vertex_input;
290 pipeline_info.pInputAssemblyState = &input_assembly;
291 pipeline_info.pViewportState = &viewport_state;
292 pipeline_info.pRasterizationState = &rasterizer;
293 pipeline_info.pMultisampleState = &multisample;
294 pipeline_info.pDepthStencilState = &depth_stencil;
295 pipeline_info.pColorBlendState = &color_blend;
296 pipeline_info.pDynamicState = &dynamic_state;
297 pipeline_info.layout = layout;
298
299 if (vkCreateGraphicsPipelines(vk_context.device, cache, 1, &pipeline_info, nullptr, &pipeline) != VK_SUCCESS) {
300 throw mxvk::Exception("failed to create stencil graphics pipeline");
301 }
302 } catch (...) {
303 if (pipeline != VK_NULL_HANDLE) {
304 vkDestroyPipeline(vk_context.device, pipeline, nullptr);
305 }
306 if (frag_module != VK_NULL_HANDLE) {
307 vkDestroyShaderModule(vk_context.device, frag_module, nullptr);
308 }
309 vkDestroyShaderModule(vk_context.device, vert_module, nullptr);
310 throw;
311 }
312
313 vkDestroyShaderModule(vk_context.device, frag_module, nullptr);
314 vkDestroyShaderModule(vk_context.device, vert_module, nullptr);
315 return pipeline;
316 }
317
318} // namespace mxvk
void initialize(const VulkanContext &context, VkExtent2D extent, VkFormat color_format, VkFormat depth_format, VkPipelineCache pipeline_cache, const std::string &mask_vertex_shader, const std::string &mask_fragment_shader, const std::string &content_vertex_shader, const std::string &content_fragment_shader)
Create the stencil attachment and fullscreen mask/content pipelines.
~VK_Stencil()
Destroy any live Vulkan resources owned by the helper.
void draw_mask(VkCommandBuffer cmd, const PushConstants &push_constants) const
Draw the fullscreen stencil-writing mask pass.
VkExtent2D extent() const noexcept
Return the current stencil attachment extent.
void draw_content(VkCommandBuffer cmd, const PushConstants &push_constants) const
Draw the fullscreen content pass clipped by stencil reference value 1.
void prepare_for_rendering(VkCommandBuffer cmd)
Transition the stencil image for use as a dynamic-rendering stencil attachment.
void destroy()
Release all Vulkan resources and reset the helper to an empty state.
void resize(VkExtent2D extent)
Recreate the stencil image for a new render extent.
void configure_attachment(VkRenderingAttachmentInfo &attachment) const
Fill a VkRenderingAttachmentInfo for VK_Window dynamic rendering.
bool valid() const noexcept
Check whether image resources are ready for rendering.
Reusable dynamic-rendering stencil image and fullscreen stencil pipelines.
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
VkShaderModule create_shader_module(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
void create_image(const VulkanContext &context, uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &memory)
Create a 2D VkImage and allocate/bind its memory.
VkImageView create_image_view(VkDevice device, VkImage image, VkFormat format, VkImageAspectFlags aspect)
Create a 2D image view for an image.
std::vector< char > load_spv(const std::string &path)
Load a SPIR-V file from disk.
Push-constant payload shared by the mask and content shaders.
Minimal Vulkan handles required by MXVK resource helpers.
VkDevice device
Logical device used to create and destroy resources.