Compare commits

...

1 Commits

Author SHA1 Message Date
wildcard 0d39d21843 [vulkan] eds overhaul
added:
1. VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT
2. VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT 
3. VK_DYNAMIC_STATE_LINE_STIPPLE_EXT
VIDS changes: fixed attribute update which was off by one in UpdateVertexInput() (VIDS has to be tested)
Added cached tracking for current primitive topology and patch control points in state tracker.
2026-03-11 13:36:37 +01:00
8 changed files with 169 additions and 28 deletions
@@ -830,7 +830,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
.pAttachments = cb_attachments.data(), .pAttachments = cb_attachments.data(),
.blendConstants = {} .blendConstants = {}
}; };
static_vector<VkDynamicState, 34> dynamic_states{ static_vector<VkDynamicState, 40> dynamic_states{
VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR,
VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS, VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS,
VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
@@ -849,6 +849,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
VK_DYNAMIC_STATE_STENCIL_OP_EXT, VK_DYNAMIC_STATE_STENCIL_OP_EXT,
}; };
dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end()); dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end());
dynamic_states.push_back(VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT);
// VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT is part of EDS1 // VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT is part of EDS1
// Only use it if VIDS is not active (VIDS replaces it with full vertex input control) // Only use it if VIDS is not active (VIDS replaces it with full vertex input control)
@@ -863,7 +864,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT); dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT);
} }
// EDS2 - Core (3 states) // EDS2 - Core states
if (key.state.extended_dynamic_state_2) { if (key.state.extended_dynamic_state_2) {
static constexpr std::array extended2{ static constexpr std::array extended2{
VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE_EXT, VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE_EXT,
@@ -871,6 +872,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT, VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT,
}; };
dynamic_states.insert(dynamic_states.end(), extended2.begin(), extended2.end()); dynamic_states.insert(dynamic_states.end(), extended2.begin(), extended2.end());
if (device.IsExtExtendedDynamicState2PatchControlPointsSupported()) {
dynamic_states.push_back(VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT);
}
} }
// EDS2 - LogicOp (granular) // EDS2 - LogicOp (granular)
@@ -913,6 +917,10 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
} }
} }
if (device.IsExtLineRasterizationSupported() && device.SupportsStippledRectangularLines()) {
dynamic_states.push_back(VK_DYNAMIC_STATE_LINE_STIPPLE_EXT);
}
const VkPipelineDynamicStateCreateInfo dynamic_state_ci{ const VkPipelineDynamicStateCreateInfo dynamic_state_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
@@ -90,6 +90,10 @@ public:
return fragment_has_color0_output; return fragment_has_color0_output;
} }
bool HasTessellationStages() const noexcept {
return spv_modules[1] || spv_modules[2];
}
bool UsesExtendedDynamicState() const noexcept { bool UsesExtendedDynamicState() const noexcept {
return key.state.extended_dynamic_state != 0; return key.state.extended_dynamic_state != 0;
} }
@@ -479,7 +479,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
device.IsExtExtendedDynamicState2Supported(); device.IsExtExtendedDynamicState2Supported();
dynamic_features.has_extended_dynamic_state_2_logic_op = dynamic_features.has_extended_dynamic_state_2_logic_op =
device.IsExtExtendedDynamicState2ExtrasSupported(); device.IsExtExtendedDynamicState2ExtrasSupported();
dynamic_features.has_extended_dynamic_state_2_patch_control_points = false; dynamic_features.has_extended_dynamic_state_2_patch_control_points =
device.IsExtExtendedDynamicState2PatchControlPointsSupported();
dynamic_features.has_extended_dynamic_state_3_blend = dynamic_features.has_extended_dynamic_state_3_blend =
device.IsExtExtendedDynamicState3BlendingSupported(); device.IsExtExtendedDynamicState3BlendingSupported();
+126 -17
View File
@@ -61,6 +61,33 @@ struct DrawParams {
bool is_indexed; bool is_indexed;
}; };
bool SupportsPrimitiveRestart(VkPrimitiveTopology topology) {
static constexpr std::array unsupported_topologies{
VK_PRIMITIVE_TOPOLOGY_POINT_LIST,
VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY,
VK_PRIMITIVE_TOPOLOGY_PATCH_LIST,
// VK_PRIMITIVE_TOPOLOGY_QUAD_LIST_EXT,
};
return std::ranges::find(unsupported_topologies, topology) == unsupported_topologies.end();
}
VkPrimitiveTopology DynamicInputAssemblyTopology(const Device& device,
const MaxwellDrawState& draw_state,
const GraphicsPipeline& pipeline) {
auto topology = MaxwellToVK::PrimitiveTopology(device, draw_state.topology);
if (topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) {
if (!pipeline.HasTessellationStages()) {
topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
}
} else if (pipeline.HasTessellationStages()) {
topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
}
return topology;
}
VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t index, float scale) { VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t index, float scale) {
const auto& src = regs.viewport_transform[index]; const auto& src = regs.viewport_transform[index];
const auto conv = [scale](float value) { const auto conv = [scale](float value) {
@@ -1017,9 +1044,12 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateDepthBounds(regs); UpdateDepthBounds(regs);
UpdateStencilFaces(regs); UpdateStencilFaces(regs);
UpdateLineWidth(regs); UpdateLineWidth(regs);
UpdateLineStipple(regs);
// EDS1: CullMode, DepthCompare, FrontFace, StencilOp, DepthBoundsTest, DepthTest, DepthWrite, StencilTest // EDS1: PrimitiveTopology, CullMode, DepthCompare, FrontFace, StencilOp, DepthBoundsTest,
// DepthTest, DepthWrite, StencilTest
if (device.IsExtExtendedDynamicStateSupported()) { if (device.IsExtExtendedDynamicStateSupported()) {
UpdatePrimitiveTopology();
UpdateCullMode(regs); UpdateCullMode(regs);
UpdateDepthCompareOp(regs); UpdateDepthCompareOp(regs);
UpdateFrontFace(regs); UpdateFrontFace(regs);
@@ -1032,11 +1062,12 @@ void RasterizerVulkan::UpdateDynamicStates() {
} }
} }
// EDS2: PrimitiveRestart, RasterizerDiscard, DepthBias enable/disable // EDS2: PrimitiveRestart, RasterizerDiscard, DepthBias enable/disable, PatchControlPoints
if (device.IsExtExtendedDynamicState2Supported()) { if (device.IsExtExtendedDynamicState2Supported()) {
UpdatePrimitiveRestartEnable(regs); UpdatePrimitiveRestartEnable(regs);
UpdateRasterizerDiscardEnable(regs); UpdateRasterizerDiscardEnable(regs);
UpdateDepthBiasEnable(regs); UpdateDepthBiasEnable(regs);
UpdatePatchControlPoints(regs);
} }
// EDS2 Extras: LogicOp operation selection // EDS2 Extras: LogicOp operation selection
@@ -1384,6 +1415,28 @@ void RasterizerVulkan::UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs) {
}); });
} }
void RasterizerVulkan::UpdatePrimitiveTopology() {
GraphicsPipeline* const pipeline = pipeline_cache.CurrentGraphicsPipeline();
if (pipeline == nullptr) {
return;
}
const MaxwellDrawState& draw_state = maxwell3d->draw_manager->GetDrawState();
const VkPrimitiveTopology topology = DynamicInputAssemblyTopology(device, draw_state, *pipeline);
if (!state_tracker.ChangePrimitiveTopology(static_cast<u32>(topology))) {
return;
}
// Primitive restart support depends on topology, so force re-evaluation on topology changes
if (device.IsExtExtendedDynamicState2Supported()) {
maxwell3d->dirty.flags[Dirty::PrimitiveRestartEnable] = true;
}
scheduler.Record([topology](vk::CommandBuffer cmdbuf) {
cmdbuf.SetPrimitiveTopologyEXT(topology);
});
}
void RasterizerVulkan::UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) { void RasterizerVulkan::UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchDepthBoundsTestEnable()) { if (!state_tracker.TouchDepthBoundsTestEnable()) {
return; return;
@@ -1420,11 +1473,49 @@ void RasterizerVulkan::UpdatePrimitiveRestartEnable(Tegra::Engines::Maxwell3D::R
if (!state_tracker.TouchPrimitiveRestartEnable()) { if (!state_tracker.TouchPrimitiveRestartEnable()) {
return; return;
} }
scheduler.Record([enable = regs.primitive_restart.enabled](vk::CommandBuffer cmdbuf) { GraphicsPipeline* const pipeline = pipeline_cache.CurrentGraphicsPipeline();
if (pipeline == nullptr) {
return;
}
const MaxwellDrawState& draw_state = maxwell3d->draw_manager->GetDrawState();
const VkPrimitiveTopology topology = DynamicInputAssemblyTopology(device, draw_state, *pipeline);
bool enable = regs.primitive_restart.enabled != 0;
if (device.IsMoltenVK()) {
// MoltenVK/Metal
enable = true;
} else if (enable) {
enable =
((topology != VK_PRIMITIVE_TOPOLOGY_PATCH_LIST &&
device.IsTopologyListPrimitiveRestartSupported()) ||
SupportsPrimitiveRestart(topology) ||
(topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST &&
device.IsPatchListPrimitiveRestartSupported()));
}
scheduler.Record([enable](vk::CommandBuffer cmdbuf) {
cmdbuf.SetPrimitiveRestartEnableEXT(enable); cmdbuf.SetPrimitiveRestartEnableEXT(enable);
}); });
} }
void RasterizerVulkan::UpdatePatchControlPoints(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!device.IsExtExtendedDynamicState2PatchControlPointsSupported()) {
return;
}
GraphicsPipeline* const pipeline = pipeline_cache.CurrentGraphicsPipeline();
if (pipeline == nullptr || !pipeline->HasTessellationStages()) {
return;
}
const u32 patch_control_points = (std::max)(regs.patch_vertices, 1u);
if (!state_tracker.ChangePatchControlPoints(patch_control_points)) {
return;
}
scheduler.Record([patch_control_points](vk::CommandBuffer cmdbuf) {
cmdbuf.SetPatchControlPointsEXT(patch_control_points);
});
}
void RasterizerVulkan::UpdateRasterizerDiscardEnable(Tegra::Engines::Maxwell3D::Regs& regs) { void RasterizerVulkan::UpdateRasterizerDiscardEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchRasterizerDiscardEnable()) { if (!state_tracker.TouchRasterizerDiscardEnable()) {
return; return;
@@ -1464,6 +1555,20 @@ void RasterizerVulkan::UpdateLineStippleEnable(Tegra::Engines::Maxwell3D::Regs&
}); });
} }
void RasterizerVulkan::UpdateLineStipple(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchLineStipple()) {
return;
}
if (!device.IsExtLineRasterizationSupported() || !device.SupportsStippledRectangularLines()) {
return;
}
scheduler.Record([factor = regs.line_stipple_params.factor,
pattern = static_cast<u16>(regs.line_stipple_params.pattern)](
vk::CommandBuffer cmdbuf) {
cmdbuf.SetLineStippleEXT(factor, pattern);
});
}
void RasterizerVulkan::UpdateLineRasterizationMode(Tegra::Engines::Maxwell3D::Regs& regs) { void RasterizerVulkan::UpdateLineRasterizationMode(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!device.IsExtLineRasterizationSupported()) { if (!device.IsExtLineRasterizationSupported()) {
return; return;
@@ -1771,25 +1876,29 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
// generating dirty state. Track the highest dirty attribute and update all attributes until // generating dirty state. Track the highest dirty attribute and update all attributes until
// that one. // that one.
size_t highest_dirty_attr{}; size_t highest_dirty_attr{};
bool has_dirty_attr = false;
for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
if (dirty[Dirty::VertexAttribute0 + index]) { if (dirty[Dirty::VertexAttribute0 + index]) {
has_dirty_attr = true;
highest_dirty_attr = index; highest_dirty_attr = index;
} }
} }
for (size_t index = 0; index < highest_dirty_attr; ++index) { if (has_dirty_attr) {
const Maxwell::VertexAttribute attribute{regs.vertex_attrib_format[index]}; for (size_t index = 0; index <= highest_dirty_attr; ++index) {
const u32 binding{attribute.buffer}; const Maxwell::VertexAttribute attribute{regs.vertex_attrib_format[index]};
dirty[Dirty::VertexAttribute0 + index] = false; const u32 binding{attribute.buffer};
dirty[Dirty::VertexBinding0 + static_cast<size_t>(binding)] = true; dirty[Dirty::VertexAttribute0 + index] = false;
if (!attribute.constant) { dirty[Dirty::VertexBinding0 + static_cast<size_t>(binding)] = true;
attributes.push_back({ if (!attribute.constant) {
.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, attributes.push_back({
.pNext = nullptr, .sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT,
.location = static_cast<u32>(index), .pNext = nullptr,
.binding = binding, .location = static_cast<u32>(index),
.format = MaxwellToVK::VertexFormat(device, attribute.type, attribute.size), .binding = binding,
.offset = attribute.offset, .format = MaxwellToVK::VertexFormat(device, attribute.type, attribute.size),
}); .offset = attribute.offset,
});
}
} }
} }
for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
@@ -170,11 +170,13 @@ private:
void UpdateLineWidth(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateLineWidth(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdatePrimitiveTopology();
void UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdatePrimitiveRestartEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdatePrimitiveRestartEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdatePatchControlPoints(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateRasterizerDiscardEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateRasterizerDiscardEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateConservativeRasterizationMode(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateConservativeRasterizationMode(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateLineStippleEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateLineStippleEnable(Tegra::Engines::Maxwell3D::Regs& regs);
@@ -216,12 +216,15 @@ void SetupDirtyVertexAttributes(Tables& tables) {
} }
void SetupDirtyVertexBindings(Tables& tables) { void SetupDirtyVertexBindings(Tables& tables) {
// Do NOT include stride here, it's implicit in VertexBuffer // Dynamic vertex input needs binding state updates when stride/divisor/instancing changes
static constexpr size_t stride_offset = 0;
static constexpr size_t divisor_offset = 3; static constexpr size_t divisor_offset = 3;
for (size_t i = 0; i < Regs::NumVertexArrays; ++i) { for (size_t i = 0; i < Regs::NumVertexArrays; ++i) {
const u8 flag = static_cast<u8>(VertexBinding0 + i); const u8 flag = static_cast<u8>(VertexBinding0 + i);
tables[0][OFF(vertex_stream_instances) + i] = VertexInput; tables[0][OFF(vertex_stream_instances) + i] = VertexInput;
tables[1][OFF(vertex_stream_instances) + i] = flag; tables[1][OFF(vertex_stream_instances) + i] = flag;
tables[0][OFF(vertex_streams) + i * NUM(vertex_streams[0]) + stride_offset] = VertexInput;
tables[1][OFF(vertex_streams) + i * NUM(vertex_streams[0]) + stride_offset] = flag;
tables[0][OFF(vertex_streams) + i * NUM(vertex_streams[0]) + divisor_offset] = VertexInput; tables[0][OFF(vertex_streams) + i * NUM(vertex_streams[0]) + divisor_offset] = VertexInput;
tables[1][OFF(vertex_streams) + i * NUM(vertex_streams[0]) + divisor_offset] = flag; tables[1][OFF(vertex_streams) + i * NUM(vertex_streams[0]) + divisor_offset] = flag;
} }
@@ -265,7 +268,8 @@ void StateTracker::ChangeChannel(Tegra::Control::ChannelState& channel_state) {
void StateTracker::InvalidateState() { void StateTracker::InvalidateState() {
flags->set(); flags->set();
current_topology = INVALID_TOPOLOGY; current_primitive_topology = INVALID_PRIMITIVE_TOPOLOGY;
current_patch_control_points = INVALID_PATCH_CONTROL_POINTS;
stencil_reset = true; stencil_reset = true;
} }
@@ -85,7 +85,8 @@ public:
void InvalidateCommandBufferState() { void InvalidateCommandBufferState() {
(*flags) |= invalidation_flags; (*flags) |= invalidation_flags;
current_topology = INVALID_TOPOLOGY; current_primitive_topology = INVALID_PRIMITIVE_TOPOLOGY;
current_patch_control_points = INVALID_PATCH_CONTROL_POINTS;
stencil_reset = true; stencil_reset = true;
} }
@@ -280,9 +281,15 @@ public:
return Exchange(Dirty::LineRasterizationMode, false); return Exchange(Dirty::LineRasterizationMode, false);
} }
bool ChangePrimitiveTopology(Maxwell::PrimitiveTopology new_topology) { bool ChangePrimitiveTopology(u32 new_topology) {
const bool has_changed = current_topology != new_topology; const bool has_changed = current_primitive_topology != new_topology;
current_topology = new_topology; current_primitive_topology = new_topology;
return has_changed;
}
bool ChangePatchControlPoints(u32 new_patch_control_points) {
const bool has_changed = current_patch_control_points != new_patch_control_points;
current_patch_control_points = new_patch_control_points;
return has_changed; return has_changed;
} }
@@ -293,7 +300,8 @@ public:
void InvalidateState(); void InvalidateState();
private: private:
static constexpr auto INVALID_TOPOLOGY = static_cast<Maxwell::PrimitiveTopology>(~0u); static constexpr u32 INVALID_PRIMITIVE_TOPOLOGY = ~0u;
static constexpr u32 INVALID_PATCH_CONTROL_POINTS = ~0u;
bool Exchange(std::size_t id, bool new_value) const noexcept { bool Exchange(std::size_t id, bool new_value) const noexcept {
const bool is_dirty = (*flags)[id]; const bool is_dirty = (*flags)[id];
@@ -310,7 +318,8 @@ private:
Tegra::Engines::Maxwell3D::DirtyState::Flags* flags; Tegra::Engines::Maxwell3D::DirtyState::Flags* flags;
Tegra::Engines::Maxwell3D::DirtyState::Flags default_flags; Tegra::Engines::Maxwell3D::DirtyState::Flags default_flags;
Tegra::Engines::Maxwell3D::DirtyState::Flags invalidation_flags; Tegra::Engines::Maxwell3D::DirtyState::Flags invalidation_flags;
Maxwell::PrimitiveTopology current_topology = INVALID_TOPOLOGY; u32 current_primitive_topology = INVALID_PRIMITIVE_TOPOLOGY;
u32 current_patch_control_points = INVALID_PATCH_CONTROL_POINTS;
bool two_sided_stencil = false; bool two_sided_stencil = false;
StencilProperties front{}; StencilProperties front{};
StencilProperties back{}; StencilProperties back{};
@@ -625,6 +625,10 @@ public:
return features.extended_dynamic_state2.extendedDynamicState2LogicOp; return features.extended_dynamic_state2.extendedDynamicState2LogicOp;
} }
bool IsExtExtendedDynamicState2PatchControlPointsSupported() const {
return features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints;
}
/// Returns true if the device supports VK_EXT_extended_dynamic_state3. /// Returns true if the device supports VK_EXT_extended_dynamic_state3.
bool IsExtExtendedDynamicState3Supported() const { bool IsExtExtendedDynamicState3Supported() const {
return extensions.extended_dynamic_state3; return extensions.extended_dynamic_state3;