[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.
This commit is contained in:
wildcard 2026-03-11 13:36:37 +01:00
parent 8678cb06eb
commit 0d39d21843
8 changed files with 169 additions and 28 deletions

View file

@ -830,7 +830,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
.pAttachments = cb_attachments.data(),
.blendConstants = {}
};
static_vector<VkDynamicState, 34> dynamic_states{
static_vector<VkDynamicState, 40> dynamic_states{
VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR,
VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS,
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,
};
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
// 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);
}
// EDS2 - Core (3 states)
// EDS2 - Core states
if (key.state.extended_dynamic_state_2) {
static constexpr std::array extended2{
VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE_EXT,
@ -871,6 +872,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT,
};
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)
@ -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{
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.pNext = nullptr,

View file

@ -90,6 +90,10 @@ public:
return fragment_has_color0_output;
}
bool HasTessellationStages() const noexcept {
return spv_modules[1] || spv_modules[2];
}
bool UsesExtendedDynamicState() const noexcept {
return key.state.extended_dynamic_state != 0;
}

View file

@ -479,7 +479,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
device.IsExtExtendedDynamicState2Supported();
dynamic_features.has_extended_dynamic_state_2_logic_op =
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 =
device.IsExtExtendedDynamicState3BlendingSupported();

View file

@ -61,6 +61,33 @@ struct DrawParams {
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) {
const auto& src = regs.viewport_transform[index];
const auto conv = [scale](float value) {
@ -1017,9 +1044,12 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateDepthBounds(regs);
UpdateStencilFaces(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()) {
UpdatePrimitiveTopology();
UpdateCullMode(regs);
UpdateDepthCompareOp(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()) {
UpdatePrimitiveRestartEnable(regs);
UpdateRasterizerDiscardEnable(regs);
UpdateDepthBiasEnable(regs);
UpdatePatchControlPoints(regs);
}
// 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) {
if (!state_tracker.TouchDepthBoundsTestEnable()) {
return;
@ -1420,11 +1473,49 @@ void RasterizerVulkan::UpdatePrimitiveRestartEnable(Tegra::Engines::Maxwell3D::R
if (!state_tracker.TouchPrimitiveRestartEnable()) {
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);
});
}
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) {
if (!state_tracker.TouchRasterizerDiscardEnable()) {
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) {
if (!device.IsExtLineRasterizationSupported()) {
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
// that one.
size_t highest_dirty_attr{};
bool has_dirty_attr = false;
for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
if (dirty[Dirty::VertexAttribute0 + index]) {
has_dirty_attr = true;
highest_dirty_attr = index;
}
}
for (size_t index = 0; index < highest_dirty_attr; ++index) {
const Maxwell::VertexAttribute attribute{regs.vertex_attrib_format[index]};
const u32 binding{attribute.buffer};
dirty[Dirty::VertexAttribute0 + index] = false;
dirty[Dirty::VertexBinding0 + static_cast<size_t>(binding)] = true;
if (!attribute.constant) {
attributes.push_back({
.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT,
.pNext = nullptr,
.location = static_cast<u32>(index),
.binding = binding,
.format = MaxwellToVK::VertexFormat(device, attribute.type, attribute.size),
.offset = attribute.offset,
});
if (has_dirty_attr) {
for (size_t index = 0; index <= highest_dirty_attr; ++index) {
const Maxwell::VertexAttribute attribute{regs.vertex_attrib_format[index]};
const u32 binding{attribute.buffer};
dirty[Dirty::VertexAttribute0 + index] = false;
dirty[Dirty::VertexBinding0 + static_cast<size_t>(binding)] = true;
if (!attribute.constant) {
attributes.push_back({
.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT,
.pNext = nullptr,
.location = static_cast<u32>(index),
.binding = binding,
.format = MaxwellToVK::VertexFormat(device, attribute.type, attribute.size),
.offset = attribute.offset,
});
}
}
}
for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {

View file

@ -170,11 +170,13 @@ private:
void UpdateLineWidth(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdatePrimitiveTopology();
void UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateDepthCompareOp(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 UpdateConservativeRasterizationMode(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateLineStippleEnable(Tegra::Engines::Maxwell3D::Regs& regs);

View file

@ -216,12 +216,15 @@ void SetupDirtyVertexAttributes(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;
for (size_t i = 0; i < Regs::NumVertexArrays; ++i) {
const u8 flag = static_cast<u8>(VertexBinding0 + i);
tables[0][OFF(vertex_stream_instances) + i] = VertexInput;
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[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() {
flags->set();
current_topology = INVALID_TOPOLOGY;
current_primitive_topology = INVALID_PRIMITIVE_TOPOLOGY;
current_patch_control_points = INVALID_PATCH_CONTROL_POINTS;
stencil_reset = true;
}

View file

@ -85,7 +85,8 @@ public:
void InvalidateCommandBufferState() {
(*flags) |= invalidation_flags;
current_topology = INVALID_TOPOLOGY;
current_primitive_topology = INVALID_PRIMITIVE_TOPOLOGY;
current_patch_control_points = INVALID_PATCH_CONTROL_POINTS;
stencil_reset = true;
}
@ -280,9 +281,15 @@ public:
return Exchange(Dirty::LineRasterizationMode, false);
}
bool ChangePrimitiveTopology(Maxwell::PrimitiveTopology new_topology) {
const bool has_changed = current_topology != new_topology;
current_topology = new_topology;
bool ChangePrimitiveTopology(u32 new_topology) {
const bool has_changed = current_primitive_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;
}
@ -293,7 +300,8 @@ public:
void InvalidateState();
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 {
const bool is_dirty = (*flags)[id];
@ -310,7 +318,8 @@ private:
Tegra::Engines::Maxwell3D::DirtyState::Flags* flags;
Tegra::Engines::Maxwell3D::DirtyState::Flags default_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;
StencilProperties front{};
StencilProperties back{};

View file

@ -625,6 +625,10 @@ public:
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.
bool IsExtExtendedDynamicState3Supported() const {
return extensions.extended_dynamic_state3;