tu: Implement VK_EXT_vertex_input_dynamic_state

Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/17554>
This commit is contained in:
Connor Abbott
2022-07-14 14:16:39 +02:00
committed by Marge Bot
parent c82af0c43b
commit 7c7feab4e1
6 changed files with 228 additions and 124 deletions

View File

@@ -578,7 +578,7 @@ Khronos extensions that are not part of any Vulkan version:
VK_EXT_shader_module_identifier DONE (anv, radv, tu)
VK_EXT_transform_feedback DONE (anv, lvp, radv, tu, vn)
VK_EXT_vertex_attribute_divisor DONE (anv, radv, lvp, tu, v3dv)
VK_EXT_vertex_input_dynamic_state DONE (lvp, radv)
VK_EXT_vertex_input_dynamic_state DONE (lvp, radv, tu)
VK_EXT_ycbcr_image_arrays DONE (anv, radv)
VK_ANDROID_external_memory_android_hardware_buffer DONE (anv, radv, vn)
VK_ANDROID_native_buffer DONE (anv, radv, tu, v3dv, vn)

View File

@@ -1867,6 +1867,105 @@ tu_BeginCommandBuffer(VkCommandBuffer commandBuffer,
return VK_SUCCESS;
}
static void
tu6_emit_vertex_strides(struct tu_cmd_buffer *cmd, unsigned num_vbs)
{
struct tu_cs cs;
cmd->state.dynamic_state[TU_DYNAMIC_STATE_VB_STRIDE].iova =
tu_cs_draw_state(&cmd->sub_cs, &cs, 2 * num_vbs).iova;
for (uint32_t i = 0; i < num_vbs; i++)
tu_cs_emit_regs(&cs, A6XX_VFD_FETCH_STRIDE(i, cmd->state.vb[i].stride));
cmd->state.dirty |= TU_CMD_DIRTY_VB_STRIDE;
}
static struct tu_cs
tu_cmd_dynamic_state(struct tu_cmd_buffer *cmd, uint32_t id, uint32_t size)
{
struct tu_cs cs;
assert(id < ARRAY_SIZE(cmd->state.dynamic_state));
cmd->state.dynamic_state[id] = tu_cs_draw_state(&cmd->sub_cs, &cs, size);
/* note: this also avoids emitting draw states before renderpass clears,
* which may use the 3D clear path (for MSAA cases)
*/
if (cmd->state.dirty & TU_CMD_DIRTY_DRAW_STATE)
return cs;
tu_cs_emit_pkt7(&cmd->draw_cs, CP_SET_DRAW_STATE, 3);
tu_cs_emit_draw_state(&cmd->draw_cs, TU_DRAW_STATE_DYNAMIC + id, cmd->state.dynamic_state[id]);
return cs;
}
static void
tu_cmd_end_dynamic_state(struct tu_cmd_buffer *cmd, struct tu_cs *cs,
uint32_t id)
{
assert(id < ARRAY_SIZE(cmd->state.dynamic_state));
cmd->state.dynamic_state[id] = tu_cs_end_draw_state(&cmd->sub_cs, cs);
/* note: this also avoids emitting draw states before renderpass clears,
* which may use the 3D clear path (for MSAA cases)
*/
if (cmd->state.dirty & TU_CMD_DIRTY_DRAW_STATE)
return;
tu_cs_emit_pkt7(&cmd->draw_cs, CP_SET_DRAW_STATE, 3);
tu_cs_emit_draw_state(&cmd->draw_cs, TU_DRAW_STATE_DYNAMIC + id, cmd->state.dynamic_state[id]);
}
static void
tu_update_num_vbs(struct tu_cmd_buffer *cmd, unsigned num_vbs)
{
/* the vertex_buffers draw state always contains all the currently
* bound vertex buffers. update its size to only emit the vbs which
* are actually used by the pipeline
* note there is a HW optimization which makes it so the draw state
* is not re-executed completely when only the size changes
*/
if (cmd->state.vertex_buffers.size != num_vbs * 4) {
cmd->state.vertex_buffers.size = num_vbs * 4;
cmd->state.dirty |= TU_CMD_DIRTY_VERTEX_BUFFERS;
}
if (cmd->state.dynamic_state[TU_DYNAMIC_STATE_VB_STRIDE].size != num_vbs * 2) {
cmd->state.dynamic_state[TU_DYNAMIC_STATE_VB_STRIDE].size = num_vbs * 2;
cmd->state.dirty |= TU_CMD_DIRTY_VB_STRIDE;
}
}
VKAPI_ATTR void VKAPI_CALL
tu_CmdSetVertexInputEXT(VkCommandBuffer commandBuffer,
uint32_t vertexBindingDescriptionCount,
const VkVertexInputBindingDescription2EXT *pVertexBindingDescriptions,
uint32_t vertexAttributeDescriptionCount,
const VkVertexInputAttributeDescription2EXT *pVertexAttributeDescriptions)
{
TU_FROM_HANDLE(tu_cmd_buffer, cmd, commandBuffer);
struct tu_cs cs;
unsigned num_vbs = 0;
for (unsigned i = 0; i < vertexBindingDescriptionCount; i++) {
const VkVertexInputBindingDescription2EXT *binding =
&pVertexBindingDescriptions[i];
num_vbs = MAX2(num_vbs, binding->binding + 1);
cmd->state.vb[binding->binding].stride = binding->stride;
}
tu6_emit_vertex_strides(cmd, num_vbs);
tu_update_num_vbs(cmd, num_vbs);
tu_cs_begin_sub_stream(&cmd->sub_cs, TU6_EMIT_VERTEX_INPUT_MAX_DWORDS, &cs);
tu6_emit_vertex_input(&cs, vertexBindingDescriptionCount,
pVertexBindingDescriptions,
vertexAttributeDescriptionCount,
pVertexAttributeDescriptions);
tu_cmd_end_dynamic_state(cmd, &cs, TU_DYNAMIC_STATE_VERTEX_INPUT);
}
VKAPI_ATTR void VKAPI_CALL
tu_CmdBindVertexBuffers2EXT(VkCommandBuffer commandBuffer,
uint32_t firstBinding,
@@ -1903,15 +2002,8 @@ tu_CmdBindVertexBuffers2EXT(VkCommandBuffer commandBuffer,
cmd->state.dirty |= TU_CMD_DIRTY_VERTEX_BUFFERS;
if (pStrides) {
cmd->state.dynamic_state[TU_DYNAMIC_STATE_VB_STRIDE].iova =
tu_cs_draw_state(&cmd->sub_cs, &cs, 2 * MAX_VBS).iova;
for (uint32_t i = 0; i < MAX_VBS; i++)
tu_cs_emit_regs(&cs, A6XX_VFD_FETCH_STRIDE(i, cmd->state.vb[i].stride));
cmd->state.dirty |= TU_CMD_DIRTY_VB_STRIDE;
}
if (pStrides)
tu6_emit_vertex_strides(cmd, MAX_VBS);
}
VKAPI_ATTR void VKAPI_CALL
@@ -2390,26 +2482,6 @@ tu_EndCommandBuffer(VkCommandBuffer commandBuffer)
return cmd_buffer->record_result;
}
static struct tu_cs
tu_cmd_dynamic_state(struct tu_cmd_buffer *cmd, uint32_t id, uint32_t size)
{
struct tu_cs cs;
assert(id < ARRAY_SIZE(cmd->state.dynamic_state));
cmd->state.dynamic_state[id] = tu_cs_draw_state(&cmd->sub_cs, &cs, size);
/* note: this also avoids emitting draw states before renderpass clears,
* which may use the 3D clear path (for MSAA cases)
*/
if (cmd->state.dirty & TU_CMD_DIRTY_DRAW_STATE)
return cs;
tu_cs_emit_pkt7(&cmd->draw_cs, CP_SET_DRAW_STATE, 3);
tu_cs_emit_draw_state(&cmd->draw_cs, TU_DRAW_STATE_DYNAMIC + id, cmd->state.dynamic_state[id]);
return cs;
}
VKAPI_ATTR void VKAPI_CALL
tu_CmdBindPipeline(VkCommandBuffer commandBuffer,
VkPipelineBindPoint pipelineBindPoint,
@@ -2438,11 +2510,10 @@ tu_CmdBindPipeline(VkCommandBuffer commandBuffer,
if (!(cmd->state.dirty & TU_CMD_DIRTY_DRAW_STATE)) {
uint32_t mask = ~pipeline->dynamic_state_mask & BITFIELD_MASK(TU_DYNAMIC_STATE_COUNT);
tu_cs_emit_pkt7(cs, CP_SET_DRAW_STATE, 3 * (7 + util_bitcount(mask)));
tu_cs_emit_pkt7(cs, CP_SET_DRAW_STATE, 3 * (6 + util_bitcount(mask)));
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_PROGRAM_CONFIG, pipeline->program.config_state);
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_PROGRAM, pipeline->program.state);
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_PROGRAM_BINNING, pipeline->program.binning_state);
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_VI, pipeline->vi.state);
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_RAST, pipeline->rast_state);
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_PRIM_MODE_SYSMEM, pipeline->prim_order_state_sysmem);
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_PRIM_MODE_GMEM, pipeline->prim_order_state_gmem);
@@ -2486,22 +2557,8 @@ tu_CmdBindPipeline(VkCommandBuffer commandBuffer,
cmd->state.dirty |= TU_CMD_DIRTY_VIEWPORTS;
}
/* the vertex_buffers draw state always contains all the currently
* bound vertex buffers. update its size to only emit the vbs which
* are actually used by the pipeline
* note there is a HW optimization which makes it so the draw state
* is not re-executed completely when only the size changes
*/
if (cmd->state.vertex_buffers.size != pipeline->num_vbs * 4) {
cmd->state.vertex_buffers.size = pipeline->num_vbs * 4;
cmd->state.dirty |= TU_CMD_DIRTY_VERTEX_BUFFERS;
}
if ((pipeline->dynamic_state_mask & BIT(TU_DYNAMIC_STATE_VB_STRIDE)) &&
cmd->state.dynamic_state[TU_DYNAMIC_STATE_VB_STRIDE].size != pipeline->num_vbs * 2) {
cmd->state.dynamic_state[TU_DYNAMIC_STATE_VB_STRIDE].size = pipeline->num_vbs * 2;
cmd->state.dirty |= TU_CMD_DIRTY_VB_STRIDE;
}
if (!(pipeline->dynamic_state_mask & BIT(TU_DYNAMIC_STATE_VERTEX_INPUT)))
tu_update_num_vbs(cmd, pipeline->num_vbs);
#define UPDATE_REG(X, Y) { \
/* note: would be better to have pipeline bits already masked */ \
@@ -4478,7 +4535,6 @@ tu6_draw_common(struct tu_cmd_buffer *cmd,
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_PROGRAM_CONFIG, pipeline->program.config_state);
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_PROGRAM, pipeline->program.state);
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_PROGRAM_BINNING, pipeline->program.binning_state);
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_VI, pipeline->vi.state);
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_RAST, pipeline->rast_state);
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_PRIM_MODE_SYSMEM, pipeline->prim_order_state_sysmem);
tu_cs_emit_draw_state(cs, TU_DRAW_STATE_PRIM_MODE_GMEM, pipeline->prim_order_state_gmem);

View File

@@ -25,7 +25,6 @@ enum tu_draw_state_group_id
TU_DRAW_STATE_PROGRAM,
TU_DRAW_STATE_PROGRAM_BINNING,
TU_DRAW_STATE_VB,
TU_DRAW_STATE_VI,
TU_DRAW_STATE_RAST,
TU_DRAW_STATE_CONST,
TU_DRAW_STATE_DESC_SETS,

View File

@@ -212,6 +212,7 @@ get_device_extensions(const struct tu_physical_device *device,
.EXT_image_view_min_lod = true,
.EXT_pipeline_creation_feedback = true,
.EXT_pipeline_creation_cache_control = true,
.EXT_vertex_input_dynamic_state = true,
#ifndef TU_USE_KGSL
.EXT_physical_device_drm = true,
#endif
@@ -900,6 +901,12 @@ tu_GetPhysicalDeviceFeatures2(VkPhysicalDevice physicalDevice,
features->shaderModuleIdentifier = true;
break;
}
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_INPUT_DYNAMIC_STATE_FEATURES_EXT: {
VkPhysicalDeviceVertexInputDynamicStateFeaturesEXT *features =
(VkPhysicalDeviceVertexInputDynamicStateFeaturesEXT *)ext;
features->vertexInputDynamicState = true;
break;
}
default:
break;

View File

@@ -1851,63 +1851,42 @@ tu6_emit_program(struct tu_cs *cs,
}
}
#define TU6_EMIT_VERTEX_INPUT_MAX_DWORDS (MAX_VERTEX_ATTRIBS * 2 + 1)
static void
tu6_emit_vertex_input(struct tu_pipeline *pipeline,
struct tu_draw_state *vi_state,
const VkPipelineVertexInputStateCreateInfo *info)
void
tu6_emit_vertex_input(struct tu_cs *cs,
uint32_t binding_count,
const VkVertexInputBindingDescription2EXT *bindings,
uint32_t unsorted_attr_count,
const VkVertexInputAttributeDescription2EXT *unsorted_attrs)
{
uint32_t binding_instanced = 0; /* bitmask of instanced bindings */
uint32_t step_rate[MAX_VBS];
struct tu_cs cs;
tu_cs_begin_sub_stream(&pipeline->cs,
TU6_EMIT_VERTEX_INPUT_MAX_DWORDS, &cs);
for (uint32_t i = 0; i < info->vertexBindingDescriptionCount; i++) {
const VkVertexInputBindingDescription *binding =
&info->pVertexBindingDescriptions[i];
if (!(pipeline->dynamic_state_mask & BIT(TU_DYNAMIC_STATE_VB_STRIDE))) {
tu_cs_emit_regs(&cs,
A6XX_VFD_FETCH_STRIDE(binding->binding, binding->stride));
}
for (uint32_t i = 0; i < binding_count; i++) {
const VkVertexInputBindingDescription2EXT *binding = &bindings[i];
if (binding->inputRate == VK_VERTEX_INPUT_RATE_INSTANCE)
binding_instanced |= 1 << binding->binding;
binding_instanced |= 1u << binding->binding;
step_rate[binding->binding] = 1;
step_rate[binding->binding] = binding->divisor;
}
const VkPipelineVertexInputDivisorStateCreateInfoEXT *div_state =
vk_find_struct_const(info->pNext, PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT);
if (div_state) {
for (uint32_t i = 0; i < div_state->vertexBindingDivisorCount; i++) {
const VkVertexInputBindingDivisorDescriptionEXT *desc =
&div_state->pVertexBindingDivisors[i];
step_rate[desc->binding] = desc->divisor;
}
}
const VkVertexInputAttributeDescription *attrs[MAX_VERTEX_ATTRIBS] = { };
const VkVertexInputAttributeDescription2EXT *attrs[MAX_VERTEX_ATTRIBS] = { };
unsigned attr_count = 0;
for (uint32_t i = 0; i < info->vertexAttributeDescriptionCount; i++) {
const VkVertexInputAttributeDescription *attr =
&info->pVertexAttributeDescriptions[i];
for (uint32_t i = 0; i < unsorted_attr_count; i++) {
const VkVertexInputAttributeDescription2EXT *attr = &unsorted_attrs[i];
attrs[attr->location] = attr;
attr_count = MAX2(attr_count, attr->location + 1);
}
if (attr_count != 0)
tu_cs_emit_pkt4(&cs, REG_A6XX_VFD_DECODE_INSTR(0), attr_count * 2);
tu_cs_emit_pkt4(cs, REG_A6XX_VFD_DECODE_INSTR(0), attr_count * 2);
for (uint32_t loc = 0; loc < attr_count; loc++) {
const VkVertexInputAttributeDescription *attr = attrs[loc];
const VkVertexInputAttributeDescription2EXT *attr = attrs[loc];
if (attr) {
const struct tu_native_format format = tu6_format_vtx(attr->format);
tu_cs_emit(&cs, A6XX_VFD_DECODE_INSTR(0,
tu_cs_emit(cs, A6XX_VFD_DECODE_INSTR(0,
.idx = attr->binding,
.offset = attr->offset,
.instanced = binding_instanced & (1 << attr->binding),
@@ -1915,14 +1894,12 @@ tu6_emit_vertex_input(struct tu_pipeline *pipeline,
.swap = format.swap,
.unk30 = 1,
._float = !vk_format_is_int(attr->format)).value);
tu_cs_emit(&cs, A6XX_VFD_DECODE_STEP_RATE(0, step_rate[attr->binding]).value);
tu_cs_emit(cs, A6XX_VFD_DECODE_STEP_RATE(0, step_rate[attr->binding]).value);
} else {
tu_cs_emit(&cs, 0);
tu_cs_emit(&cs, 0);
tu_cs_emit(cs, 0);
tu_cs_emit(cs, 0);
}
}
*vi_state = tu_cs_end_draw_state(&pipeline->cs, &cs);
}
void
@@ -3212,6 +3189,10 @@ tu_pipeline_builder_parse_dynamic(struct tu_pipeline_builder *builder,
*/
pipeline->dynamic_state_mask |= BIT(TU_DYNAMIC_STATE_COLOR_WRITE_ENABLE);
break;
case VK_DYNAMIC_STATE_VERTEX_INPUT_EXT:
pipeline->dynamic_state_mask |= BIT(TU_DYNAMIC_STATE_VERTEX_INPUT) |
BIT(TU_DYNAMIC_STATE_VB_STRIDE);
break;
default:
assert(!"unsupported dynamic state");
break;
@@ -3274,33 +3255,6 @@ tu_pipeline_builder_parse_shader_stages(struct tu_pipeline_builder *builder,
}
}
static void
tu_pipeline_builder_parse_vertex_input(struct tu_pipeline_builder *builder,
struct tu_pipeline *pipeline)
{
const VkPipelineVertexInputStateCreateInfo *vi_info =
builder->create_info->pVertexInputState;
/* Bindings may contain holes */
for (unsigned i = 0; i < vi_info->vertexBindingDescriptionCount; i++) {
pipeline->num_vbs =
MAX2(pipeline->num_vbs, vi_info->pVertexBindingDescriptions[i].binding + 1);
}
tu6_emit_vertex_input(pipeline, &pipeline->vi.state, vi_info);
}
static void
tu_pipeline_builder_parse_input_assembly(struct tu_pipeline_builder *builder,
struct tu_pipeline *pipeline)
{
const VkPipelineInputAssemblyStateCreateInfo *ia_info =
builder->create_info->pInputAssemblyState;
pipeline->ia.primtype = tu6_primtype(ia_info->topology);
pipeline->ia.primitive_restart = ia_info->primitiveRestartEnable;
}
static bool
tu_pipeline_static_state(struct tu_pipeline *pipeline, struct tu_cs *cs,
uint32_t id, uint32_t size)
@@ -3314,6 +3268,90 @@ tu_pipeline_static_state(struct tu_pipeline *pipeline, struct tu_cs *cs,
return true;
}
static void
tu_pipeline_builder_parse_vertex_input(struct tu_pipeline_builder *builder,
struct tu_pipeline *pipeline)
{
if (pipeline->dynamic_state_mask & BIT(TU_DYNAMIC_STATE_VERTEX_INPUT))
return;
const VkPipelineVertexInputStateCreateInfo *vi_info =
builder->create_info->pVertexInputState;
struct tu_cs cs;
if (tu_pipeline_static_state(pipeline, &cs, TU_DYNAMIC_STATE_VB_STRIDE,
2 * vi_info->vertexBindingDescriptionCount)) {
for (uint32_t i = 0; i < vi_info->vertexBindingDescriptionCount; i++) {
const VkVertexInputBindingDescription *binding =
&vi_info->pVertexBindingDescriptions[i];
tu_cs_emit_regs(&cs,
A6XX_VFD_FETCH_STRIDE(binding->binding, binding->stride));
}
}
VkVertexInputBindingDescription2EXT bindings[MAX_VBS];
VkVertexInputAttributeDescription2EXT attrs[MAX_VERTEX_ATTRIBS];
for (unsigned i = 0; i < vi_info->vertexBindingDescriptionCount; i++) {
const VkVertexInputBindingDescription *binding =
&vi_info->pVertexBindingDescriptions[i];
bindings[i] = (VkVertexInputBindingDescription2EXT) {
.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT,
.pNext = NULL,
.binding = binding->binding,
.inputRate = binding->inputRate,
.stride = binding->stride,
.divisor = 1,
};
/* Bindings may contain holes */
pipeline->num_vbs = MAX2(pipeline->num_vbs, binding->binding + 1);
}
const VkPipelineVertexInputDivisorStateCreateInfoEXT *div_state =
vk_find_struct_const(vi_info->pNext, PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT);
if (div_state) {
for (uint32_t i = 0; i < div_state->vertexBindingDivisorCount; i++) {
const VkVertexInputBindingDivisorDescriptionEXT *desc =
&div_state->pVertexBindingDivisors[i];
bindings[desc->binding].divisor = desc->divisor;
}
}
for (unsigned i = 0; i < vi_info->vertexAttributeDescriptionCount; i++) {
const VkVertexInputAttributeDescription *attr =
&vi_info->pVertexAttributeDescriptions[i];
attrs[i] = (VkVertexInputAttributeDescription2EXT) {
.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT,
.pNext = NULL,
.binding = attr->binding,
.location = attr->location,
.offset = attr->offset,
.format = attr->format,
};
}
tu_cs_begin_sub_stream(&pipeline->cs,
TU6_EMIT_VERTEX_INPUT_MAX_DWORDS, &cs);
tu6_emit_vertex_input(&cs,
vi_info->vertexBindingDescriptionCount, bindings,
vi_info->vertexAttributeDescriptionCount, attrs);
pipeline->dynamic_state[TU_DYNAMIC_STATE_VERTEX_INPUT] =
tu_cs_end_draw_state(&pipeline->cs, &cs);
}
static void
tu_pipeline_builder_parse_input_assembly(struct tu_pipeline_builder *builder,
struct tu_pipeline *pipeline)
{
const VkPipelineInputAssemblyStateCreateInfo *ia_info =
builder->create_info->pInputAssemblyState;
pipeline->ia.primtype = tu6_primtype(ia_info->topology);
pipeline->ia.primitive_restart = ia_info->primitiveRestartEnable;
}
static void
tu_pipeline_builder_parse_tessellation(struct tu_pipeline_builder *builder,
struct tu_pipeline *pipeline)

View File

@@ -26,6 +26,7 @@ enum tu_dynamic_state
TU_DYNAMIC_STATE_VB_STRIDE,
TU_DYNAMIC_STATE_RASTERIZER_DISCARD,
TU_DYNAMIC_STATE_BLEND,
TU_DYNAMIC_STATE_VERTEX_INPUT,
TU_DYNAMIC_STATE_COUNT,
/* no associated draw state: */
TU_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY = TU_DYNAMIC_STATE_COUNT,
@@ -164,11 +165,6 @@ struct tu_pipeline
struct tu_program_descriptor_linkage link[MESA_SHADER_STAGES];
} program;
struct
{
struct tu_draw_state state;
} vi;
struct
{
enum pc_di_primtype primtype;
@@ -227,6 +223,14 @@ tu6_emit_depth_bias(struct tu_cs *cs,
float clamp,
float slope_factor);
#define TU6_EMIT_VERTEX_INPUT_MAX_DWORDS (MAX_VERTEX_ATTRIBS * 2 + 1)
void tu6_emit_vertex_input(struct tu_cs *cs,
uint32_t binding_count,
const VkVertexInputBindingDescription2EXT *bindings,
uint32_t attr_count,
const VkVertexInputAttributeDescription2EXT *attrs);
uint32_t tu6_rb_mrt_control_rop(VkLogicOp op, bool *rop_reads_dst);
struct tu_pvtmem_config {