summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.cpp67
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.h73
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp107
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp29
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp56
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.cpp50
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.h8
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h6
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp1
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h8
11 files changed, 291 insertions, 116 deletions
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
index f121fbf0e..16cef8711 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
@@ -50,7 +50,7 @@ void RefreshXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell&
50} // Anonymous namespace 50} // Anonymous namespace
51 51
52void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, 52void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d,
53 bool has_extended_dynamic_state) { 53 bool has_extended_dynamic_state, bool has_dynamic_vertex_input) {
54 const Maxwell& regs = maxwell3d.regs; 54 const Maxwell& regs = maxwell3d.regs;
55 const std::array enabled_lut{ 55 const std::array enabled_lut{
56 regs.polygon_offset_point_enable, 56 regs.polygon_offset_point_enable,
@@ -60,7 +60,8 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d,
60 const u32 topology_index = static_cast<u32>(regs.draw.topology.Value()); 60 const u32 topology_index = static_cast<u32>(regs.draw.topology.Value());
61 61
62 raw1 = 0; 62 raw1 = 0;
63 no_extended_dynamic_state.Assign(has_extended_dynamic_state ? 0 : 1); 63 extended_dynamic_state.Assign(has_extended_dynamic_state ? 1 : 0);
64 dynamic_vertex_input.Assign(has_dynamic_vertex_input ? 1 : 0);
64 xfb_enabled.Assign(regs.tfb_enabled != 0); 65 xfb_enabled.Assign(regs.tfb_enabled != 0);
65 primitive_restart_enable.Assign(regs.primitive_restart.enabled != 0 ? 1 : 0); 66 primitive_restart_enable.Assign(regs.primitive_restart.enabled != 0 ? 1 : 0);
66 depth_bias_enable.Assign(enabled_lut[POLYGON_OFFSET_ENABLE_LUT[topology_index]] != 0 ? 1 : 0); 67 depth_bias_enable.Assign(enabled_lut[POLYGON_OFFSET_ENABLE_LUT[topology_index]] != 0 ? 1 : 0);
@@ -73,11 +74,11 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d,
73 tessellation_clockwise.Assign(regs.tess_mode.cw.Value()); 74 tessellation_clockwise.Assign(regs.tess_mode.cw.Value());
74 logic_op_enable.Assign(regs.logic_op.enable != 0 ? 1 : 0); 75 logic_op_enable.Assign(regs.logic_op.enable != 0 ? 1 : 0);
75 logic_op.Assign(PackLogicOp(regs.logic_op.operation)); 76 logic_op.Assign(PackLogicOp(regs.logic_op.operation));
76 rasterize_enable.Assign(regs.rasterize_enable != 0 ? 1 : 0);
77 topology.Assign(regs.draw.topology); 77 topology.Assign(regs.draw.topology);
78 msaa_mode.Assign(regs.multisample_mode); 78 msaa_mode.Assign(regs.multisample_mode);
79 79
80 raw2 = 0; 80 raw2 = 0;
81 rasterize_enable.Assign(regs.rasterize_enable != 0 ? 1 : 0);
81 const auto test_func = 82 const auto test_func =
82 regs.alpha_test_enabled != 0 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always; 83 regs.alpha_test_enabled != 0 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always;
83 alpha_test_func.Assign(PackComparisonOp(test_func)); 84 alpha_test_func.Assign(PackComparisonOp(test_func));
@@ -93,24 +94,44 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d,
93 alpha_test_ref = Common::BitCast<u32>(regs.alpha_test_ref); 94 alpha_test_ref = Common::BitCast<u32>(regs.alpha_test_ref);
94 point_size = Common::BitCast<u32>(regs.point_size); 95 point_size = Common::BitCast<u32>(regs.point_size);
95 96
96 if (maxwell3d.dirty.flags[Dirty::InstanceDivisors]) { 97 if (maxwell3d.dirty.flags[Dirty::VertexInput]) {
97 maxwell3d.dirty.flags[Dirty::InstanceDivisors] = false; 98 if (has_dynamic_vertex_input) {
98 for (size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { 99 // Dirty flag will be reset by the command buffer update
99 const bool is_enabled = regs.instanced_arrays.IsInstancingEnabled(index); 100 static constexpr std::array LUT{
100 binding_divisors[index] = is_enabled ? regs.vertex_array[index].divisor : 0; 101 0u, // Invalid
101 } 102 1u, // SignedNorm
102 } 103 1u, // UnsignedNorm
103 if (maxwell3d.dirty.flags[Dirty::VertexAttributes]) { 104 2u, // SignedInt
104 maxwell3d.dirty.flags[Dirty::VertexAttributes] = false; 105 3u, // UnsignedInt
105 for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { 106 1u, // UnsignedScaled
106 const auto& input = regs.vertex_attrib_format[index]; 107 1u, // SignedScaled
107 auto& attribute = attributes[index]; 108 1u, // Float
108 attribute.raw = 0; 109 };
109 attribute.enabled.Assign(input.IsConstant() ? 0 : 1); 110 const auto& attrs = regs.vertex_attrib_format;
110 attribute.buffer.Assign(input.buffer); 111 attribute_types = 0;
111 attribute.offset.Assign(input.offset); 112 for (size_t i = 0; i < Maxwell::NumVertexAttributes; ++i) {
112 attribute.type.Assign(static_cast<u32>(input.type.Value())); 113 const u32 mask = attrs[i].constant != 0 ? 0 : 3;
113 attribute.size.Assign(static_cast<u32>(input.size.Value())); 114 const u32 type = LUT[static_cast<size_t>(attrs[i].type.Value())];
115 attribute_types |= static_cast<u64>(type & mask) << (i * 2);
116 }
117 } else {
118 maxwell3d.dirty.flags[Dirty::VertexInput] = false;
119 enabled_divisors = 0;
120 for (size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
121 const bool is_enabled = regs.instanced_arrays.IsInstancingEnabled(index);
122 binding_divisors[index] = is_enabled ? regs.vertex_array[index].divisor : 0;
123 enabled_divisors |= (is_enabled ? u64{1} : 0) << index;
124 }
125 for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
126 const auto& input = regs.vertex_attrib_format[index];
127 auto& attribute = attributes[index];
128 attribute.raw = 0;
129 attribute.enabled.Assign(input.IsConstant() ? 0 : 1);
130 attribute.buffer.Assign(input.buffer);
131 attribute.offset.Assign(input.offset);
132 attribute.type.Assign(static_cast<u32>(input.type.Value()));
133 attribute.size.Assign(static_cast<u32>(input.size.Value()));
134 }
114 } 135 }
115 } 136 }
116 if (maxwell3d.dirty.flags[Dirty::Blending]) { 137 if (maxwell3d.dirty.flags[Dirty::Blending]) {
@@ -126,10 +147,10 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d,
126 return static_cast<u16>(viewport.swizzle.raw); 147 return static_cast<u16>(viewport.swizzle.raw);
127 }); 148 });
128 } 149 }
129 if (no_extended_dynamic_state != 0) { 150 if (!extended_dynamic_state) {
130 dynamic_state.Refresh(regs); 151 dynamic_state.Refresh(regs);
131 } 152 }
132 if (xfb_enabled != 0) { 153 if (xfb_enabled) {
133 RefreshXfbState(xfb_state, regs); 154 RefreshXfbState(xfb_state, regs);
134 } 155 }
135} 156}
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
index 60adae316..04f34eb97 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
@@ -168,44 +168,51 @@ struct FixedPipelineState {
168 168
169 union { 169 union {
170 u32 raw1; 170 u32 raw1;
171 BitField<0, 1, u32> no_extended_dynamic_state; 171 BitField<0, 1, u32> extended_dynamic_state;
172 BitField<1, 1, u32> xfb_enabled; 172 BitField<1, 1, u32> dynamic_vertex_input;
173 BitField<2, 1, u32> primitive_restart_enable; 173 BitField<2, 1, u32> xfb_enabled;
174 BitField<3, 1, u32> depth_bias_enable; 174 BitField<3, 1, u32> primitive_restart_enable;
175 BitField<4, 1, u32> depth_clamp_disabled; 175 BitField<4, 1, u32> depth_bias_enable;
176 BitField<5, 1, u32> ndc_minus_one_to_one; 176 BitField<5, 1, u32> depth_clamp_disabled;
177 BitField<6, 2, u32> polygon_mode; 177 BitField<6, 1, u32> ndc_minus_one_to_one;
178 BitField<8, 5, u32> patch_control_points_minus_one; 178 BitField<7, 2, u32> polygon_mode;
179 BitField<13, 2, u32> tessellation_primitive; 179 BitField<9, 5, u32> patch_control_points_minus_one;
180 BitField<15, 2, u32> tessellation_spacing; 180 BitField<14, 2, u32> tessellation_primitive;
181 BitField<17, 1, u32> tessellation_clockwise; 181 BitField<16, 2, u32> tessellation_spacing;
182 BitField<18, 1, u32> logic_op_enable; 182 BitField<18, 1, u32> tessellation_clockwise;
183 BitField<19, 4, u32> logic_op; 183 BitField<19, 1, u32> logic_op_enable;
184 BitField<23, 1, u32> rasterize_enable; 184 BitField<20, 4, u32> logic_op;
185 BitField<24, 4, Maxwell::PrimitiveTopology> topology; 185 BitField<24, 4, Maxwell::PrimitiveTopology> topology;
186 BitField<28, 4, Tegra::Texture::MsaaMode> msaa_mode; 186 BitField<28, 4, Tegra::Texture::MsaaMode> msaa_mode;
187 }; 187 };
188 union { 188 union {
189 u32 raw2; 189 u32 raw2;
190 BitField<0, 3, u32> alpha_test_func; 190 BitField<0, 1, u32> rasterize_enable;
191 BitField<3, 1, u32> early_z; 191 BitField<1, 3, u32> alpha_test_func;
192 BitField<4, 1, u32> depth_enabled; 192 BitField<4, 1, u32> early_z;
193 BitField<5, 5, u32> depth_format; 193 BitField<5, 1, u32> depth_enabled;
194 BitField<10, 1, u32> y_negate; 194 BitField<6, 5, u32> depth_format;
195 BitField<11, 1, u32> provoking_vertex_last; 195 BitField<11, 1, u32> y_negate;
196 BitField<12, 1, u32> provoking_vertex_last;
196 }; 197 };
197 std::array<u8, Maxwell::NumRenderTargets> color_formats; 198 std::array<u8, Maxwell::NumRenderTargets> color_formats;
198 199
199 u32 alpha_test_ref; 200 u32 alpha_test_ref;
200 u32 point_size; 201 u32 point_size;
201 std::array<u32, Maxwell::NumVertexArrays> binding_divisors;
202 std::array<VertexAttribute, Maxwell::NumVertexAttributes> attributes;
203 std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments; 202 std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments;
204 std::array<u16, Maxwell::NumViewports> viewport_swizzles; 203 std::array<u16, Maxwell::NumViewports> viewport_swizzles;
204 union {
205 u64 attribute_types; // Used with VK_EXT_vertex_input_dynamic_state
206 u64 enabled_divisors;
207 };
208 std::array<VertexAttribute, Maxwell::NumVertexAttributes> attributes;
209 std::array<u32, Maxwell::NumVertexArrays> binding_divisors;
210
205 DynamicState dynamic_state; 211 DynamicState dynamic_state;
206 VideoCommon::TransformFeedbackState xfb_state; 212 VideoCommon::TransformFeedbackState xfb_state;
207 213
208 void Refresh(Tegra::Engines::Maxwell3D& maxwell3d, bool has_extended_dynamic_state); 214 void Refresh(Tegra::Engines::Maxwell3D& maxwell3d, bool has_extended_dynamic_state,
215 bool has_dynamic_vertex_input);
209 216
210 size_t Hash() const noexcept; 217 size_t Hash() const noexcept;
211 218
@@ -216,16 +223,24 @@ struct FixedPipelineState {
216 } 223 }
217 224
218 size_t Size() const noexcept { 225 size_t Size() const noexcept {
219 if (xfb_enabled != 0) { 226 if (xfb_enabled) {
220 // When transform feedback is enabled, use the whole struct 227 // When transform feedback is enabled, use the whole struct
221 return sizeof(*this); 228 return sizeof(*this);
222 } else if (no_extended_dynamic_state != 0) { 229 }
223 // Dynamic state is enabled, we can enable more 230 if (dynamic_vertex_input) {
224 return offsetof(FixedPipelineState, xfb_state); 231 // Exclude dynamic state and attributes
225 } else { 232 return offsetof(FixedPipelineState, attributes);
226 // No XFB, extended dynamic state enabled 233 }
234 if (extended_dynamic_state) {
235 // Exclude dynamic state
227 return offsetof(FixedPipelineState, dynamic_state); 236 return offsetof(FixedPipelineState, dynamic_state);
228 } 237 }
238 // Default
239 return offsetof(FixedPipelineState, xfb_state);
240 }
241
242 u32 DynamicAttributeType(size_t index) const noexcept {
243 return (attribute_types >> (index * 2)) & 0b11;
229 } 244 }
230}; 245};
231static_assert(std::has_unique_object_representations_v<FixedPipelineState>); 246static_assert(std::has_unique_object_representations_v<FixedPipelineState>);
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index 06a80c2ba..ccef71f4c 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -472,39 +472,65 @@ void GraphicsPipeline::ConfigureDraw() {
472 472
473void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { 473void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
474 FixedPipelineState::DynamicState dynamic{}; 474 FixedPipelineState::DynamicState dynamic{};
475 if (!device.IsExtExtendedDynamicStateSupported()) { 475 if (key.state.extended_dynamic_state) {
476 dynamic = key.state.dynamic_state; 476 dynamic = key.state.dynamic_state;
477 } 477 }
478 static_vector<VkVertexInputBindingDescription, 32> vertex_bindings; 478 static_vector<VkVertexInputBindingDescription, 32> vertex_bindings;
479 static_vector<VkVertexInputBindingDivisorDescriptionEXT, 32> vertex_binding_divisors; 479 static_vector<VkVertexInputBindingDivisorDescriptionEXT, 32> vertex_binding_divisors;
480 for (size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { 480 static_vector<VkVertexInputAttributeDescription, 32> vertex_attributes;
481 const bool instanced = key.state.binding_divisors[index] != 0; 481 if (key.state.dynamic_vertex_input) {
482 const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; 482 const auto& input_attributes = stage_infos[0].input_generics;
483 vertex_bindings.push_back({ 483 for (size_t index = 0; index < key.state.attributes.size(); ++index) {
484 .binding = static_cast<u32>(index), 484 const u32 type = key.state.DynamicAttributeType(index);
485 .stride = dynamic.vertex_strides[index], 485 if (!input_attributes[index].used || type == 0) {
486 .inputRate = rate, 486 continue;
487 }); 487 }
488 if (instanced) { 488 vertex_attributes.push_back({
489 vertex_binding_divisors.push_back({ 489 .location = static_cast<u32>(index),
490 .binding = 0,
491 .format = type == 1 ? VK_FORMAT_R32_SFLOAT
492 : type == 2 ? VK_FORMAT_R32_SINT
493 : VK_FORMAT_R32_UINT,
494 .offset = 0,
495 });
496 }
497 if (!vertex_attributes.empty()) {
498 vertex_bindings.push_back({
499 .binding = 0,
500 .stride = 4,
501 .inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
502 });
503 }
504 } else {
505 for (size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
506 const bool instanced = key.state.binding_divisors[index] != 0;
507 const auto rate =
508 instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
509 vertex_bindings.push_back({
490 .binding = static_cast<u32>(index), 510 .binding = static_cast<u32>(index),
491 .divisor = key.state.binding_divisors[index], 511 .stride = dynamic.vertex_strides[index],
512 .inputRate = rate,
492 }); 513 });
514 if (instanced) {
515 vertex_binding_divisors.push_back({
516 .binding = static_cast<u32>(index),
517 .divisor = key.state.binding_divisors[index],
518 });
519 }
493 } 520 }
494 } 521 const auto& input_attributes = stage_infos[0].input_generics;
495 static_vector<VkVertexInputAttributeDescription, 32> vertex_attributes; 522 for (size_t index = 0; index < key.state.attributes.size(); ++index) {
496 const auto& input_attributes = stage_infos[0].input_generics; 523 const auto& attribute = key.state.attributes[index];
497 for (size_t index = 0; index < key.state.attributes.size(); ++index) { 524 if (!attribute.enabled || !input_attributes[index].used) {
498 const auto& attribute = key.state.attributes[index]; 525 continue;
499 if (!attribute.enabled || !input_attributes[index].used) { 526 }
500 continue; 527 vertex_attributes.push_back({
528 .location = static_cast<u32>(index),
529 .binding = attribute.buffer,
530 .format = MaxwellToVK::VertexFormat(attribute.Type(), attribute.Size()),
531 .offset = attribute.offset,
532 });
501 } 533 }
502 vertex_attributes.push_back({
503 .location = static_cast<u32>(index),
504 .binding = attribute.buffer,
505 .format = MaxwellToVK::VertexFormat(attribute.Type(), attribute.Size()),
506 .offset = attribute.offset,
507 });
508 } 534 }
509 VkPipelineVertexInputStateCreateInfo vertex_input_ci{ 535 VkPipelineVertexInputStateCreateInfo vertex_input_ci{
510 .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, 536 .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
@@ -545,27 +571,25 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
545 .flags = 0, 571 .flags = 0,
546 .patchControlPoints = key.state.patch_control_points_minus_one.Value() + 1, 572 .patchControlPoints = key.state.patch_control_points_minus_one.Value() + 1,
547 }; 573 };
548 VkPipelineViewportStateCreateInfo viewport_ci{ 574
549 .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
550 .pNext = nullptr,
551 .flags = 0,
552 .viewportCount = Maxwell::NumViewports,
553 .pViewports = nullptr,
554 .scissorCount = Maxwell::NumViewports,
555 .pScissors = nullptr,
556 };
557 std::array<VkViewportSwizzleNV, Maxwell::NumViewports> swizzles; 575 std::array<VkViewportSwizzleNV, Maxwell::NumViewports> swizzles;
558 std::ranges::transform(key.state.viewport_swizzles, swizzles.begin(), UnpackViewportSwizzle); 576 std::ranges::transform(key.state.viewport_swizzles, swizzles.begin(), UnpackViewportSwizzle);
559 VkPipelineViewportSwizzleStateCreateInfoNV swizzle_ci{ 577 const VkPipelineViewportSwizzleStateCreateInfoNV swizzle_ci{
560 .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_SWIZZLE_STATE_CREATE_INFO_NV, 578 .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_SWIZZLE_STATE_CREATE_INFO_NV,
561 .pNext = nullptr, 579 .pNext = nullptr,
562 .flags = 0, 580 .flags = 0,
563 .viewportCount = Maxwell::NumViewports, 581 .viewportCount = Maxwell::NumViewports,
564 .pViewportSwizzles = swizzles.data(), 582 .pViewportSwizzles = swizzles.data(),
565 }; 583 };
566 if (device.IsNvViewportSwizzleSupported()) { 584 const VkPipelineViewportStateCreateInfo viewport_ci{
567 viewport_ci.pNext = &swizzle_ci; 585 .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
568 } 586 .pNext = device.IsNvViewportSwizzleSupported() ? &swizzle_ci : nullptr,
587 .flags = 0,
588 .viewportCount = Maxwell::NumViewports,
589 .pViewports = nullptr,
590 .scissorCount = Maxwell::NumViewports,
591 .pScissors = nullptr,
592 };
569 593
570 const VkPipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex{ 594 const VkPipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex{
571 .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT, 595 .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT,
@@ -660,13 +684,13 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
660 .pAttachments = cb_attachments.data(), 684 .pAttachments = cb_attachments.data(),
661 .blendConstants = {}, 685 .blendConstants = {},
662 }; 686 };
663 static_vector<VkDynamicState, 17> dynamic_states{ 687 static_vector<VkDynamicState, 18> dynamic_states{
664 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, 688 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR,
665 VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS, 689 VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS,
666 VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, 690 VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
667 VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE, 691 VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE,
668 }; 692 };
669 if (device.IsExtExtendedDynamicStateSupported()) { 693 if (key.state.extended_dynamic_state) {
670 static constexpr std::array extended{ 694 static constexpr std::array extended{
671 VK_DYNAMIC_STATE_CULL_MODE_EXT, 695 VK_DYNAMIC_STATE_CULL_MODE_EXT,
672 VK_DYNAMIC_STATE_FRONT_FACE_EXT, 696 VK_DYNAMIC_STATE_FRONT_FACE_EXT,
@@ -678,6 +702,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
678 VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT, 702 VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT,
679 VK_DYNAMIC_STATE_STENCIL_OP_EXT, 703 VK_DYNAMIC_STATE_STENCIL_OP_EXT,
680 }; 704 };
705 if (key.state.dynamic_vertex_input) {
706 dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT);
707 }
681 dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end()); 708 dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end());
682 } 709 }
683 const VkPipelineDynamicStateCreateInfo dynamic_state_ci{ 710 const VkPipelineDynamicStateCreateInfo dynamic_state_ci{
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 6df4088a7..db7da5555 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -109,6 +109,20 @@ static Shader::AttributeType CastAttributeType(const FixedPipelineState::VertexA
109 return Shader::AttributeType::Float; 109 return Shader::AttributeType::Float;
110} 110}
111 111
112Shader::AttributeType AttributeType(const FixedPipelineState& state, size_t index) {
113 switch (state.DynamicAttributeType(index)) {
114 case 0:
115 return Shader::AttributeType::Disabled;
116 case 1:
117 return Shader::AttributeType::Float;
118 case 2:
119 return Shader::AttributeType::SignedInt;
120 case 3:
121 return Shader::AttributeType::UnsignedInt;
122 }
123 return Shader::AttributeType::Disabled;
124}
125
112Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineCacheKey& key, 126Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineCacheKey& key,
113 const Shader::IR::Program& program) { 127 const Shader::IR::Program& program) {
114 Shader::RuntimeInfo info; 128 Shader::RuntimeInfo info;
@@ -123,13 +137,19 @@ Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineCacheKey& key,
123 if (key.state.topology == Maxwell::PrimitiveTopology::Points) { 137 if (key.state.topology == Maxwell::PrimitiveTopology::Points) {
124 info.fixed_state_point_size = point_size; 138 info.fixed_state_point_size = point_size;
125 } 139 }
126 if (key.state.xfb_enabled != 0) { 140 if (key.state.xfb_enabled) {
127 info.xfb_varyings = VideoCommon::MakeTransformFeedbackVaryings(key.state.xfb_state); 141 info.xfb_varyings = VideoCommon::MakeTransformFeedbackVaryings(key.state.xfb_state);
128 } 142 }
129 info.convert_depth_mode = gl_ndc; 143 info.convert_depth_mode = gl_ndc;
130 } 144 }
131 std::ranges::transform(key.state.attributes, info.generic_input_types.begin(), 145 if (key.state.dynamic_vertex_input) {
132 &CastAttributeType); 146 for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
147 info.generic_input_types[index] = AttributeType(key.state, index);
148 }
149 } else {
150 std::ranges::transform(key.state.attributes, info.generic_input_types.begin(),
151 &CastAttributeType);
152 }
133 break; 153 break;
134 case Shader::Stage::TessellationEval: 154 case Shader::Stage::TessellationEval:
135 // We have to flip tessellation clockwise for some reason... 155 // We have to flip tessellation clockwise for some reason...
@@ -298,7 +318,8 @@ GraphicsPipeline* PipelineCache::CurrentGraphicsPipeline() {
298 current_pipeline = nullptr; 318 current_pipeline = nullptr;
299 return nullptr; 319 return nullptr;
300 } 320 }
301 graphics_key.state.Refresh(maxwell3d, device.IsExtExtendedDynamicStateSupported()); 321 graphics_key.state.Refresh(maxwell3d, device.IsExtExtendedDynamicStateSupported(),
322 device.IsExtVertexInputDynamicStateSupported());
302 323
303 if (current_pipeline) { 324 if (current_pipeline) {
304 GraphicsPipeline* const next{current_pipeline->Next(graphics_key)}; 325 GraphicsPipeline* const next{current_pipeline->Next(graphics_key)};
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index e339e9739..855c17769 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -551,6 +551,9 @@ void RasterizerVulkan::UpdateDynamicStates() {
551 UpdateFrontFace(regs); 551 UpdateFrontFace(regs);
552 UpdateStencilOp(regs); 552 UpdateStencilOp(regs);
553 UpdateStencilTestEnable(regs); 553 UpdateStencilTestEnable(regs);
554 if (device.IsExtVertexInputDynamicStateSupported()) {
555 UpdateVertexInput(regs);
556 }
554 } 557 }
555} 558}
556 559
@@ -780,4 +783,57 @@ void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs&
780 }); 783 });
781} 784}
782 785
786void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs) {
787 auto& dirty{maxwell3d.dirty.flags};
788 if (!dirty[Dirty::VertexInput]) {
789 return;
790 }
791 dirty[Dirty::VertexInput] = false;
792
793 boost::container::static_vector<VkVertexInputBindingDescription2EXT, 32> bindings;
794 boost::container::static_vector<VkVertexInputAttributeDescription2EXT, 32> attributes;
795
796 for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
797 if (!dirty[Dirty::VertexAttribute0 + index]) {
798 continue;
799 }
800 const Maxwell::VertexAttribute attribute{regs.vertex_attrib_format[index]};
801 const u32 binding{attribute.buffer};
802 dirty[Dirty::VertexAttribute0 + index] = false;
803 dirty[Dirty::VertexBinding0 + static_cast<size_t>(binding)] = true;
804
805 attributes.push_back({
806 .sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT,
807 .pNext = nullptr,
808 .location = static_cast<u32>(index),
809 .binding = binding,
810 .format = attribute.IsConstant()
811 ? VK_FORMAT_A8B8G8R8_UNORM_PACK32
812 : MaxwellToVK::VertexFormat(attribute.type, attribute.size),
813 .offset = attribute.offset,
814 });
815 }
816 for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
817 if (!dirty[Dirty::VertexBinding0 + index]) {
818 continue;
819 }
820 dirty[Dirty::VertexBinding0 + index] = false;
821
822 const u32 binding{static_cast<u32>(index)};
823 const auto& input_binding{regs.vertex_array[binding]};
824 const bool is_instanced{regs.instanced_arrays.IsInstancingEnabled(binding)};
825 bindings.push_back({
826 .sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT,
827 .pNext = nullptr,
828 .binding = binding,
829 .stride = input_binding.stride,
830 .inputRate = is_instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX,
831 .divisor = is_instanced ? input_binding.divisor : 1,
832 });
833 }
834 scheduler.Record([bindings, attributes](vk::CommandBuffer cmdbuf) {
835 cmdbuf.SetVertexInputEXT(bindings, attributes);
836 });
837}
838
783} // namespace Vulkan 839} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 1302bed02..c954fa7f8 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -135,6 +135,8 @@ private:
135 void UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs); 135 void UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs);
136 void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs); 136 void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
137 137
138 void UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs);
139
138 Tegra::GPU& gpu; 140 Tegra::GPU& gpu;
139 Tegra::MemoryManager& gpu_memory; 141 Tegra::MemoryManager& gpu_memory;
140 Tegra::Engines::Maxwell3D& maxwell3d; 142 Tegra::Engines::Maxwell3D& maxwell3d;
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
index 956f86845..0ebe0473f 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
@@ -29,9 +29,10 @@ using Flags = Maxwell3D::DirtyState::Flags;
29 29
30Flags MakeInvalidationFlags() { 30Flags MakeInvalidationFlags() {
31 static constexpr int INVALIDATION_FLAGS[]{ 31 static constexpr int INVALIDATION_FLAGS[]{
32 Viewports, Scissors, DepthBias, BlendConstants, DepthBounds, 32 Viewports, Scissors, DepthBias, BlendConstants,
33 StencilProperties, CullMode, DepthBoundsEnable, DepthTestEnable, DepthWriteEnable, 33 DepthBounds, StencilProperties, CullMode, DepthBoundsEnable,
34 DepthCompareOp, FrontFace, StencilOp, StencilTestEnable, VertexBuffers, 34 DepthTestEnable, DepthWriteEnable, DepthCompareOp, FrontFace,
35 StencilOp, StencilTestEnable, VertexBuffers, VertexInput,
35 }; 36 };
36 Flags flags{}; 37 Flags flags{};
37 for (const int flag : INVALIDATION_FLAGS) { 38 for (const int flag : INVALIDATION_FLAGS) {
@@ -40,6 +41,12 @@ Flags MakeInvalidationFlags() {
40 for (int index = VertexBuffer0; index <= VertexBuffer31; ++index) { 41 for (int index = VertexBuffer0; index <= VertexBuffer31; ++index) {
41 flags[index] = true; 42 flags[index] = true;
42 } 43 }
44 for (int index = VertexAttribute0; index <= VertexAttribute31; ++index) {
45 flags[index] = true;
46 }
47 for (int index = VertexBinding0; index <= VertexBinding31; ++index) {
48 flags[index] = true;
49 }
43 return flags; 50 return flags;
44} 51}
45 52
@@ -134,31 +141,38 @@ void SetupDirtyBlending(Tables& tables) {
134 FillBlock(tables[0], OFF(independent_blend), NUM(independent_blend), Blending); 141 FillBlock(tables[0], OFF(independent_blend), NUM(independent_blend), Blending);
135} 142}
136 143
137void SetupDirtyInstanceDivisors(Tables& tables) { 144void SetupDirtyViewportSwizzles(Tables& tables) {
138 static constexpr size_t divisor_offset = 3; 145 static constexpr size_t swizzle_offset = 6;
139 for (size_t index = 0; index < Regs::NumVertexArrays; ++index) { 146 for (size_t index = 0; index < Regs::NumViewports; ++index) {
140 tables[0][OFF(instanced_arrays) + index] = InstanceDivisors; 147 tables[0][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] =
141 tables[0][OFF(vertex_array) + index * NUM(vertex_array[0]) + divisor_offset] = 148 ViewportSwizzles;
142 InstanceDivisors;
143 } 149 }
144} 150}
145 151
146void SetupDirtyVertexAttributes(Tables& tables) { 152void SetupDirtyVertexAttributes(Tables& tables) {
147 FillBlock(tables[0], OFF(vertex_attrib_format), NUM(vertex_attrib_format), VertexAttributes); 153 for (size_t i = 0; i < Regs::NumVertexAttributes; ++i) {
154 const size_t offset = OFF(vertex_attrib_format) + i * NUM(vertex_attrib_format[0]);
155 FillBlock(tables[0], offset, NUM(vertex_attrib_format[0]), VertexAttribute0 + i);
156 }
157 FillBlock(tables[1], OFF(vertex_attrib_format), Regs::NumVertexAttributes, VertexInput);
148} 158}
149 159
150void SetupDirtyViewportSwizzles(Tables& tables) { 160void SetupDirtyVertexBindings(Tables& tables) {
151 static constexpr size_t swizzle_offset = 6; 161 // Do NOT include stride here, it's implicit in VertexBuffer
152 for (size_t index = 0; index < Regs::NumViewports; ++index) { 162 static constexpr size_t divisor_offset = 3;
153 tables[0][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] = 163 for (size_t i = 0; i < Regs::NumVertexArrays; ++i) {
154 ViewportSwizzles; 164 const u8 flag = static_cast<u8>(VertexBinding0 + i);
165 tables[0][OFF(instanced_arrays) + i] = VertexInput;
166 tables[1][OFF(instanced_arrays) + i] = flag;
167 tables[0][OFF(vertex_array) + i * NUM(vertex_array[0]) + divisor_offset] = VertexInput;
168 tables[1][OFF(vertex_array) + i * NUM(vertex_array[0]) + divisor_offset] = flag;
155 } 169 }
156} 170}
157} // Anonymous namespace 171} // Anonymous namespace
158 172
159StateTracker::StateTracker(Tegra::GPU& gpu) 173StateTracker::StateTracker(Tegra::GPU& gpu)
160 : flags{gpu.Maxwell3D().dirty.flags}, invalidation_flags{MakeInvalidationFlags()} { 174 : flags{gpu.Maxwell3D().dirty.flags}, invalidation_flags{MakeInvalidationFlags()} {
161 auto& tables = gpu.Maxwell3D().dirty.tables; 175 auto& tables{gpu.Maxwell3D().dirty.tables};
162 SetupDirtyFlags(tables); 176 SetupDirtyFlags(tables);
163 SetupDirtyViewports(tables); 177 SetupDirtyViewports(tables);
164 SetupDirtyScissors(tables); 178 SetupDirtyScissors(tables);
@@ -175,9 +189,9 @@ StateTracker::StateTracker(Tegra::GPU& gpu)
175 SetupDirtyStencilOp(tables); 189 SetupDirtyStencilOp(tables);
176 SetupDirtyStencilTestEnable(tables); 190 SetupDirtyStencilTestEnable(tables);
177 SetupDirtyBlending(tables); 191 SetupDirtyBlending(tables);
178 SetupDirtyInstanceDivisors(tables);
179 SetupDirtyVertexAttributes(tables);
180 SetupDirtyViewportSwizzles(tables); 192 SetupDirtyViewportSwizzles(tables);
193 SetupDirtyVertexAttributes(tables);
194 SetupDirtyVertexBindings(tables);
181} 195}
182 196
183} // namespace Vulkan 197} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h
index 84e918a71..1976b7e9b 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.h
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.h
@@ -19,6 +19,12 @@ namespace Dirty {
19enum : u8 { 19enum : u8 {
20 First = VideoCommon::Dirty::LastCommonEntry, 20 First = VideoCommon::Dirty::LastCommonEntry,
21 21
22 VertexInput,
23 VertexAttribute0,
24 VertexAttribute31 = VertexAttribute0 + 31,
25 VertexBinding0,
26 VertexBinding31 = VertexBinding0 + 31,
27
22 Viewports, 28 Viewports,
23 Scissors, 29 Scissors,
24 DepthBias, 30 DepthBias,
@@ -36,8 +42,6 @@ enum : u8 {
36 StencilTestEnable, 42 StencilTestEnable,
37 43
38 Blending, 44 Blending,
39 InstanceDivisors,
40 VertexAttributes,
41 ViewportSwizzles, 45 ViewportSwizzles,
42 46
43 Last 47 Last
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 37f589612..4fda472b0 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -239,6 +239,11 @@ public:
239 return ext_extended_dynamic_state; 239 return ext_extended_dynamic_state;
240 } 240 }
241 241
242 /// Returns true if the device supports VK_EXT_vertex_input_dynamic_state.
243 bool IsExtVertexInputDynamicStateSupported() const {
244 return ext_vertex_input_dynamic_state;
245 }
246
242 /// Returns true if the device supports VK_EXT_shader_stencil_export. 247 /// Returns true if the device supports VK_EXT_shader_stencil_export.
243 bool IsExtShaderStencilExportSupported() const { 248 bool IsExtShaderStencilExportSupported() const {
244 return ext_shader_stencil_export; 249 return ext_shader_stencil_export;
@@ -349,6 +354,7 @@ private:
349 bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback. 354 bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback.
350 bool ext_custom_border_color{}; ///< Support for VK_EXT_custom_border_color. 355 bool ext_custom_border_color{}; ///< Support for VK_EXT_custom_border_color.
351 bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state. 356 bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state.
357 bool ext_vertex_input_dynamic_state{}; ///< Support for VK_EXT_vertex_input_dynamic_state.
352 bool ext_shader_stencil_export{}; ///< Support for VK_EXT_shader_stencil_export. 358 bool ext_shader_stencil_export{}; ///< Support for VK_EXT_shader_stencil_export.
353 bool ext_shader_atomic_int64{}; ///< Support for VK_KHR_shader_atomic_int64. 359 bool ext_shader_atomic_int64{}; ///< Support for VK_KHR_shader_atomic_int64.
354 bool ext_provoking_vertex{}; ///< Support for VK_EXT_provoking_vertex. 360 bool ext_provoking_vertex{}; ///< Support for VK_EXT_provoking_vertex.
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index 33fb74bfb..7e13ae8af 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -123,6 +123,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
123 X(vkCmdSetPrimitiveTopologyEXT); 123 X(vkCmdSetPrimitiveTopologyEXT);
124 X(vkCmdSetStencilOpEXT); 124 X(vkCmdSetStencilOpEXT);
125 X(vkCmdSetStencilTestEnableEXT); 125 X(vkCmdSetStencilTestEnableEXT);
126 X(vkCmdSetVertexInputEXT);
126 X(vkCmdResolveImage); 127 X(vkCmdResolveImage);
127 X(vkCreateBuffer); 128 X(vkCreateBuffer);
128 X(vkCreateBufferView); 129 X(vkCreateBufferView);
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 3e36d356a..6e5be1186 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -238,6 +238,7 @@ struct DeviceDispatch : InstanceDispatch {
238 PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT{}; 238 PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT{};
239 PFN_vkCmdSetStencilOpEXT vkCmdSetStencilOpEXT{}; 239 PFN_vkCmdSetStencilOpEXT vkCmdSetStencilOpEXT{};
240 PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT{}; 240 PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT{};
241 PFN_vkCmdSetVertexInputEXT vkCmdSetVertexInputEXT{};
241 PFN_vkCmdResolveImage vkCmdResolveImage{}; 242 PFN_vkCmdResolveImage vkCmdResolveImage{};
242 PFN_vkCreateBuffer vkCreateBuffer{}; 243 PFN_vkCreateBuffer vkCreateBuffer{};
243 PFN_vkCreateBufferView vkCreateBufferView{}; 244 PFN_vkCreateBufferView vkCreateBufferView{};
@@ -1203,6 +1204,13 @@ public:
1203 dld->vkCmdSetStencilTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE); 1204 dld->vkCmdSetStencilTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
1204 } 1205 }
1205 1206
1207 void SetVertexInputEXT(
1208 vk::Span<VkVertexInputBindingDescription2EXT> bindings,
1209 vk::Span<VkVertexInputAttributeDescription2EXT> attributes) const noexcept {
1210 dld->vkCmdSetVertexInputEXT(handle, bindings.size(), bindings.data(), attributes.size(),
1211 attributes.data());
1212 }
1213
1206 void BindTransformFeedbackBuffersEXT(u32 first, u32 count, const VkBuffer* buffers, 1214 void BindTransformFeedbackBuffersEXT(u32 first, u32 count, const VkBuffer* buffers,
1207 const VkDeviceSize* offsets, 1215 const VkDeviceSize* offsets,
1208 const VkDeviceSize* sizes) const noexcept { 1216 const VkDeviceSize* sizes) const noexcept {