This goes atop !598 and !603.
-- v6: vkd3d-shader/ir: Introduce a simple control flow graph structurizer. vkd3d-shader/ir: Handle PHI nodes when materializing SSA registers. vkd3d-shader/ir: Materialize SSA registers to temporaries. vkd3d-shader/spirv: Support bool TEMP registers. vkd3d-shader/spirv: Move bool casting helpers above register loading helpers. vkd3d-shader/dxil: Set the register before calling src_param_init_scalar().
From: Giovanni Mascellani gmascellani@codeweavers.com
Which uses the register to compute the appropriate swizzle.
Fixes: 1f536238a89dc6bccb411c2db32d7b2010cc8fd7 --- libs/vkd3d-shader/dxil.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/libs/vkd3d-shader/dxil.c b/libs/vkd3d-shader/dxil.c index ba951a6e5..291012d13 100644 --- a/libs/vkd3d-shader/dxil.c +++ b/libs/vkd3d-shader/dxil.c @@ -4725,8 +4725,8 @@ static void sm6_parser_emit_extractval(struct sm6_parser *sm6, const struct dxil vsir_instruction_init(ins, &sm6->p.location, VKD3DSIH_MOV);
src_param = instruction_src_params_alloc(ins, 1, sm6); - src_param_init_scalar(src_param, elem_idx); src_param->reg = src->u.reg; + src_param_init_scalar(src_param, elem_idx);
instruction_dst_param_init_ssa_scalar(ins, sm6); }
From: Giovanni Mascellani gmascellani@codeweavers.com
--- libs/vkd3d-shader/spirv.c | 134 +++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 67 deletions(-)
diff --git a/libs/vkd3d-shader/spirv.c b/libs/vkd3d-shader/spirv.c index d4333e6d0..7877da481 100644 --- a/libs/vkd3d-shader/spirv.c +++ b/libs/vkd3d-shader/spirv.c @@ -3669,6 +3669,73 @@ static uint32_t spirv_compiler_emit_vector_shuffle(struct spirv_compiler *compil type_id, vector1_id, vector2_id, components, component_count); }
+static uint32_t spirv_compiler_emit_int_to_bool(struct spirv_compiler *compiler, + enum vkd3d_shader_conditional_op condition, enum vkd3d_data_type data_type, + unsigned int component_count, uint32_t val_id) +{ + struct vkd3d_spirv_builder *builder = &compiler->spirv_builder; + uint32_t type_id; + SpvOp op; + + assert(!(condition & ~(VKD3D_SHADER_CONDITIONAL_OP_NZ | VKD3D_SHADER_CONDITIONAL_OP_Z))); + + type_id = vkd3d_spirv_get_type_id(builder, VKD3D_SHADER_COMPONENT_BOOL, component_count); + op = condition & VKD3D_SHADER_CONDITIONAL_OP_Z ? SpvOpIEqual : SpvOpINotEqual; + return vkd3d_spirv_build_op_tr2(builder, &builder->function_stream, op, type_id, val_id, + data_type == VKD3D_DATA_UINT64 + ? spirv_compiler_get_constant_uint64_vector(compiler, 0, component_count) + : spirv_compiler_get_constant_uint_vector(compiler, 0, component_count)); +} + +static uint32_t spirv_compiler_emit_bool_to_int(struct spirv_compiler *compiler, + unsigned int component_count, uint32_t val_id, bool signedness) +{ + struct vkd3d_spirv_builder *builder = &compiler->spirv_builder; + uint32_t type_id, true_id, false_id; + + true_id = spirv_compiler_get_constant_uint_vector(compiler, signedness ? 0xffffffff : 1, component_count); + false_id = spirv_compiler_get_constant_uint_vector(compiler, 0, component_count); + type_id = vkd3d_spirv_get_type_id(builder, VKD3D_SHADER_COMPONENT_UINT, component_count); + return vkd3d_spirv_build_op_select(builder, type_id, val_id, true_id, false_id); +} + +static uint32_t spirv_compiler_emit_bool_to_int64(struct spirv_compiler *compiler, + unsigned int component_count, uint32_t val_id, bool signedness) +{ + struct vkd3d_spirv_builder *builder = &compiler->spirv_builder; + uint32_t type_id, true_id, false_id; + + true_id = spirv_compiler_get_constant_uint64_vector(compiler, signedness ? UINT64_MAX : 1, + component_count); + false_id = spirv_compiler_get_constant_uint64_vector(compiler, 0, component_count); + type_id = vkd3d_spirv_get_type_id(builder, VKD3D_SHADER_COMPONENT_UINT64, component_count); + return vkd3d_spirv_build_op_select(builder, type_id, val_id, true_id, false_id); +} + +static uint32_t spirv_compiler_emit_bool_to_float(struct spirv_compiler *compiler, + unsigned int component_count, uint32_t val_id, bool signedness) +{ + struct vkd3d_spirv_builder *builder = &compiler->spirv_builder; + uint32_t type_id, true_id, false_id; + + true_id = spirv_compiler_get_constant_float_vector(compiler, signedness ? -1.0f : 1.0f, component_count); + false_id = spirv_compiler_get_constant_float_vector(compiler, 0.0f, component_count); + type_id = vkd3d_spirv_get_type_id(builder, VKD3D_SHADER_COMPONENT_FLOAT, component_count); + return vkd3d_spirv_build_op_select(builder, type_id, val_id, true_id, false_id); +} + +static uint32_t spirv_compiler_emit_bool_to_double(struct spirv_compiler *compiler, + unsigned int component_count, uint32_t val_id, bool signedness) +{ + struct vkd3d_spirv_builder *builder = &compiler->spirv_builder; + uint32_t type_id, true_id, false_id; + + true_id = spirv_compiler_get_constant_double_vector(compiler, signedness ? -1.0 : 1.0, component_count); + false_id = spirv_compiler_get_constant_double_vector(compiler, 0.0, component_count); + type_id = vkd3d_spirv_get_type_id(builder, VKD3D_SHADER_COMPONENT_DOUBLE, component_count); + return vkd3d_spirv_build_op_select(builder, type_id, val_id, true_id, false_id); +} + static uint32_t spirv_compiler_emit_load_constant(struct spirv_compiler *compiler, const struct vkd3d_shader_register *reg, uint32_t swizzle, uint32_t write_mask) { @@ -4383,73 +4450,6 @@ static void spirv_compiler_emit_interpolation_decorations(struct spirv_compiler } }
-static uint32_t spirv_compiler_emit_int_to_bool(struct spirv_compiler *compiler, - enum vkd3d_shader_conditional_op condition, enum vkd3d_data_type data_type, - unsigned int component_count, uint32_t val_id) -{ - struct vkd3d_spirv_builder *builder = &compiler->spirv_builder; - uint32_t type_id; - SpvOp op; - - assert(!(condition & ~(VKD3D_SHADER_CONDITIONAL_OP_NZ | VKD3D_SHADER_CONDITIONAL_OP_Z))); - - type_id = vkd3d_spirv_get_type_id(builder, VKD3D_SHADER_COMPONENT_BOOL, component_count); - op = condition & VKD3D_SHADER_CONDITIONAL_OP_Z ? SpvOpIEqual : SpvOpINotEqual; - return vkd3d_spirv_build_op_tr2(builder, &builder->function_stream, op, type_id, val_id, - data_type == VKD3D_DATA_UINT64 - ? spirv_compiler_get_constant_uint64_vector(compiler, 0, component_count) - : spirv_compiler_get_constant_uint_vector(compiler, 0, component_count)); -} - -static uint32_t spirv_compiler_emit_bool_to_int(struct spirv_compiler *compiler, - unsigned int component_count, uint32_t val_id, bool signedness) -{ - struct vkd3d_spirv_builder *builder = &compiler->spirv_builder; - uint32_t type_id, true_id, false_id; - - true_id = spirv_compiler_get_constant_uint_vector(compiler, signedness ? 0xffffffff : 1, component_count); - false_id = spirv_compiler_get_constant_uint_vector(compiler, 0, component_count); - type_id = vkd3d_spirv_get_type_id(builder, VKD3D_SHADER_COMPONENT_UINT, component_count); - return vkd3d_spirv_build_op_select(builder, type_id, val_id, true_id, false_id); -} - -static uint32_t spirv_compiler_emit_bool_to_int64(struct spirv_compiler *compiler, - unsigned int component_count, uint32_t val_id, bool signedness) -{ - struct vkd3d_spirv_builder *builder = &compiler->spirv_builder; - uint32_t type_id, true_id, false_id; - - true_id = spirv_compiler_get_constant_uint64_vector(compiler, signedness ? UINT64_MAX : 1, - component_count); - false_id = spirv_compiler_get_constant_uint64_vector(compiler, 0, component_count); - type_id = vkd3d_spirv_get_type_id(builder, VKD3D_SHADER_COMPONENT_UINT64, component_count); - return vkd3d_spirv_build_op_select(builder, type_id, val_id, true_id, false_id); -} - -static uint32_t spirv_compiler_emit_bool_to_float(struct spirv_compiler *compiler, - unsigned int component_count, uint32_t val_id, bool signedness) -{ - struct vkd3d_spirv_builder *builder = &compiler->spirv_builder; - uint32_t type_id, true_id, false_id; - - true_id = spirv_compiler_get_constant_float_vector(compiler, signedness ? -1.0f : 1.0f, component_count); - false_id = spirv_compiler_get_constant_float_vector(compiler, 0.0f, component_count); - type_id = vkd3d_spirv_get_type_id(builder, VKD3D_SHADER_COMPONENT_FLOAT, component_count); - return vkd3d_spirv_build_op_select(builder, type_id, val_id, true_id, false_id); -} - -static uint32_t spirv_compiler_emit_bool_to_double(struct spirv_compiler *compiler, - unsigned int component_count, uint32_t val_id, bool signedness) -{ - struct vkd3d_spirv_builder *builder = &compiler->spirv_builder; - uint32_t type_id, true_id, false_id; - - true_id = spirv_compiler_get_constant_double_vector(compiler, signedness ? -1.0 : 1.0, component_count); - false_id = spirv_compiler_get_constant_double_vector(compiler, 0.0, component_count); - type_id = vkd3d_spirv_get_type_id(builder, VKD3D_SHADER_COMPONENT_DOUBLE, component_count); - return vkd3d_spirv_build_op_select(builder, type_id, val_id, true_id, false_id); -} - typedef uint32_t (*vkd3d_spirv_builtin_fixup_pfn)(struct spirv_compiler *compiler, uint32_t val_id);
From: Giovanni Mascellani gmascellani@codeweavers.com
--- libs/vkd3d-shader/spirv.c | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-)
diff --git a/libs/vkd3d-shader/spirv.c b/libs/vkd3d-shader/spirv.c index 7877da481..100950447 100644 --- a/libs/vkd3d-shader/spirv.c +++ b/libs/vkd3d-shader/spirv.c @@ -3844,8 +3844,21 @@ static uint32_t spirv_compiler_emit_load_scalar(struct spirv_compiler *compiler,
if (component_type != reg_info->component_type) { - type_id = vkd3d_spirv_get_type_id(builder, component_type, 1); - val_id = vkd3d_spirv_build_op_bitcast(builder, type_id, val_id); + if (component_type == VKD3D_SHADER_COMPONENT_BOOL) + { + if (reg_info->component_type != VKD3D_SHADER_COMPONENT_UINT) + { + type_id = vkd3d_spirv_get_type_id(builder, VKD3D_SHADER_COMPONENT_UINT, 1); + val_id = vkd3d_spirv_build_op_bitcast(builder, type_id, val_id); + } + val_id = spirv_compiler_emit_int_to_bool(compiler, VKD3D_SHADER_CONDITIONAL_OP_NZ, + VKD3D_DATA_UINT, 1, val_id); + } + else + { + type_id = vkd3d_spirv_get_type_id(builder, component_type, 1); + val_id = vkd3d_spirv_build_op_bitcast(builder, type_id, val_id); + } }
return val_id; @@ -4032,8 +4045,21 @@ static uint32_t spirv_compiler_emit_load_reg(struct spirv_compiler *compiler,
if (component_type != reg_info.component_type) { - type_id = vkd3d_spirv_get_type_id(builder, component_type, component_count); - val_id = vkd3d_spirv_build_op_bitcast(builder, type_id, val_id); + if (component_type == VKD3D_SHADER_COMPONENT_BOOL) + { + if (reg_info.component_type != VKD3D_SHADER_COMPONENT_UINT) + { + type_id = vkd3d_spirv_get_type_id(builder, VKD3D_SHADER_COMPONENT_UINT, component_count); + val_id = vkd3d_spirv_build_op_bitcast(builder, type_id, val_id); + } + val_id = spirv_compiler_emit_int_to_bool(compiler, VKD3D_SHADER_CONDITIONAL_OP_NZ, + VKD3D_DATA_UINT, component_count, val_id); + } + else + { + type_id = vkd3d_spirv_get_type_id(builder, component_type, component_count); + val_id = vkd3d_spirv_build_op_bitcast(builder, type_id, val_id); + } }
return val_id; @@ -4227,6 +4253,9 @@ static void spirv_compiler_emit_store_reg(struct spirv_compiler *compiler, { if (data_type_is_64_bit(reg->data_type)) src_write_mask = vsir_write_mask_32_from_64(write_mask); + if (component_type == VKD3D_SHADER_COMPONENT_BOOL) + val_id = spirv_compiler_emit_bool_to_int(compiler, + vsir_write_mask_component_count(src_write_mask), val_id, false); type_id = vkd3d_spirv_get_type_id(builder, reg_info.component_type, vsir_write_mask_component_count(src_write_mask)); val_id = vkd3d_spirv_build_op_bitcast(builder, type_id, val_id);
From: Giovanni Mascellani gmascellani@codeweavers.com
For simplicity PHI nodes are not currently handled.
The goal for this pass is to make the CFG structurizer simpler, because it doesn't have to care about the more rigid rules SSA registers have to satisfy than TEMP registers.
It is likely that the generated code will be harder for downstream compilers to optimize and execute efficiently, so once a complete structurizer is in place this pass should be removed, or at least greatly reduced in scope. --- libs/vkd3d-shader/ir.c | 81 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+)
diff --git a/libs/vkd3d-shader/ir.c b/libs/vkd3d-shader/ir.c index a0d6212cb..b92a04b2e 100644 --- a/libs/vkd3d-shader/ir.c +++ b/libs/vkd3d-shader/ir.c @@ -2637,6 +2637,84 @@ fail: return VKD3D_ERROR_OUT_OF_MEMORY; }
+static void materialize_ssas_to_temps_process_src_param(struct vkd3d_shader_parser *parser, struct vkd3d_shader_src_param *src); + +/* This is idempotent: it can be safely applied more than once on the + * same register. */ +static void materialize_ssas_to_temps_process_reg(struct vkd3d_shader_parser *parser, struct vkd3d_shader_register *reg) +{ + unsigned int i; + + if (reg->type == VKD3DSPR_SSA) + { + reg->type = VKD3DSPR_TEMP; + reg->idx[0].offset += parser->program.temp_count; + } + + for (i = 0; i < reg->idx_count; ++i) + if (reg->idx[i].rel_addr) + materialize_ssas_to_temps_process_src_param(parser, reg->idx[i].rel_addr); +} + +static void materialize_ssas_to_temps_process_dst_param(struct vkd3d_shader_parser *parser, struct vkd3d_shader_dst_param *dst) +{ + materialize_ssas_to_temps_process_reg(parser, &dst->reg); +} + +static void materialize_ssas_to_temps_process_src_param(struct vkd3d_shader_parser *parser, struct vkd3d_shader_src_param *src) +{ + materialize_ssas_to_temps_process_reg(parser, &src->reg); +} + +static enum vkd3d_result materialize_ssas_to_temps(struct vkd3d_shader_parser *parser) +{ + struct vkd3d_shader_instruction *instructions = NULL; + size_t ins_capacity = 0, ins_count = 0, i; + + if (!reserve_instructions(&instructions, &ins_capacity, parser->program.instructions.count)) + goto fail; + + for (i = 0; i < parser->program.instructions.count; ++i) + { + struct vkd3d_shader_instruction *ins = &parser->program.instructions.elements[i]; + size_t j; + + if (ins->handler_idx == VKD3DSIH_PHI) + { + WARN("Unhandled PHI when materializing SSA registers.\n"); + vkd3d_shader_parser_error(parser, VKD3D_SHADER_ERROR_VSIR_NOT_IMPLEMENTED, + "Unhandled PHI when materializing SSA registers."); + vkd3d_free(instructions); + return VKD3D_ERROR_NOT_IMPLEMENTED; + } + + for (j = 0; j < ins->dst_count; ++j) + materialize_ssas_to_temps_process_dst_param(parser, &ins->dst[j]); + + for (j = 0; j < ins->src_count; ++j) + materialize_ssas_to_temps_process_src_param(parser, &ins->src[j]); + + if (!reserve_instructions(&instructions, &ins_capacity, ins_count + 1)) + goto fail; + + instructions[ins_count++] = *ins; + } + + vkd3d_free(parser->program.instructions.elements); + parser->program.instructions.elements = instructions; + parser->program.instructions.capacity = ins_capacity; + parser->program.instructions.count = ins_count; + parser->program.temp_count += parser->program.ssa_count; + parser->program.ssa_count = 0; + + return VKD3D_OK; + +fail: + vkd3d_free(instructions); + + return VKD3D_ERROR_OUT_OF_MEMORY; +} + enum vkd3d_result vkd3d_shader_normalise(struct vkd3d_shader_parser *parser, const struct vkd3d_shader_compile_info *compile_info) { @@ -2652,6 +2730,9 @@ enum vkd3d_result vkd3d_shader_normalise(struct vkd3d_shader_parser *parser, { if ((result = lower_switch_to_if_ladder(&parser->program)) < 0) return result; + + if ((result = materialize_ssas_to_temps(parser)) < 0) + return result; } else {
From: Giovanni Mascellani gmascellani@codeweavers.com
PHI nodes cannot be used with TEMP registers, so they have to be converted to MOV/MOVC nodes and moved before the BRANCH node. --- libs/vkd3d-shader/ir.c | 170 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 163 insertions(+), 7 deletions(-)
diff --git a/libs/vkd3d-shader/ir.c b/libs/vkd3d-shader/ir.c index b92a04b2e..1f14f7029 100644 --- a/libs/vkd3d-shader/ir.c +++ b/libs/vkd3d-shader/ir.c @@ -2666,27 +2666,110 @@ static void materialize_ssas_to_temps_process_src_param(struct vkd3d_shader_pars materialize_ssas_to_temps_process_reg(parser, &src->reg); }
+static const struct vkd3d_shader_src_param *materialize_ssas_to_temps_compute_source(struct vkd3d_shader_instruction *ins, + unsigned int label) +{ + unsigned int i; + + assert(ins->handler_idx == VKD3DSIH_PHI); + + for (i = 0; i < ins->src_count; i += 2) + { + if (label_from_src_param(&ins->src[i + 1]) == label) + return &ins->src[i]; + } + + vkd3d_unreachable(); +} + +static bool materialize_ssas_to_temps_synthesize_mov(struct vkd3d_shader_parser *parser, + struct vkd3d_shader_instruction *instruction, const struct vkd3d_shader_location *loc, + const struct vkd3d_shader_dst_param *dest, const struct vkd3d_shader_src_param *cond, + const struct vkd3d_shader_src_param *source, bool invert) +{ + struct vkd3d_shader_src_param *src; + struct vkd3d_shader_dst_param *dst; + + if (!vsir_instruction_init_with_params(&parser->program, instruction, loc, + cond ? VKD3DSIH_MOVC : VKD3DSIH_MOV, 1, cond ? 3 : 1)) + return false; + + dst = instruction->dst; + src = instruction->src; + + dst[0] = *dest; + materialize_ssas_to_temps_process_dst_param(parser, &dst[0]); + + assert(dst[0].write_mask == VKD3DSP_WRITEMASK_0); + assert(dst[0].modifiers == 0); + assert(dst[0].shift == 0); + + if (cond) + { + src[0] = *cond; + src[1 + invert] = *source; + memset(&src[2 - invert], 0, sizeof(src[2 - invert])); + src[2 - invert].reg = dst[0].reg; + materialize_ssas_to_temps_process_src_param(parser, &src[1]); + materialize_ssas_to_temps_process_src_param(parser, &src[2]); + } + else + { + src[0] = *source; + materialize_ssas_to_temps_process_src_param(parser, &src[0]); + } + + return true; +} + static enum vkd3d_result materialize_ssas_to_temps(struct vkd3d_shader_parser *parser) { struct vkd3d_shader_instruction *instructions = NULL; + struct materialize_ssas_to_temps_block_data + { + size_t phi_begin; + size_t phi_count; + } *block_index = NULL; size_t ins_capacity = 0, ins_count = 0, i; + unsigned int current_label = 0;
if (!reserve_instructions(&instructions, &ins_capacity, parser->program.instructions.count)) goto fail;
+ if (!(block_index = vkd3d_calloc(parser->program.block_count, sizeof(*block_index)))) + { + ERR("Failed to allocate block index.\n"); + goto fail; + } + for (i = 0; i < parser->program.instructions.count; ++i) { struct vkd3d_shader_instruction *ins = &parser->program.instructions.elements[i]; - size_t j;
- if (ins->handler_idx == VKD3DSIH_PHI) + switch (ins->handler_idx) { - WARN("Unhandled PHI when materializing SSA registers.\n"); - vkd3d_shader_parser_error(parser, VKD3D_SHADER_ERROR_VSIR_NOT_IMPLEMENTED, - "Unhandled PHI when materializing SSA registers."); - vkd3d_free(instructions); - return VKD3D_ERROR_NOT_IMPLEMENTED; + case VKD3DSIH_LABEL: + current_label = label_from_src_param(&ins->src[0]); + break; + + case VKD3DSIH_PHI: + assert(current_label != 0); + assert(i != 0); + if (block_index[current_label - 1].phi_begin == 0) + block_index[current_label - 1].phi_begin = i; + block_index[current_label - 1].phi_count += 1; + break; + + default: + current_label = 0; + break; } + } + + for (i = 0; i < parser->program.instructions.count; ++i) + { + struct vkd3d_shader_instruction *ins = &parser->program.instructions.elements[i]; + size_t j;
for (j = 0; j < ins->dst_count; ++j) materialize_ssas_to_temps_process_dst_param(parser, &ins->dst[j]); @@ -2694,6 +2777,77 @@ static enum vkd3d_result materialize_ssas_to_temps(struct vkd3d_shader_parser *p for (j = 0; j < ins->src_count; ++j) materialize_ssas_to_temps_process_src_param(parser, &ins->src[j]);
+ switch (ins->handler_idx) + { + case VKD3DSIH_LABEL: + current_label = label_from_src_param(&ins->src[0]); + break; + + case VKD3DSIH_BRANCH: + { + if (vsir_register_is_label(&ins->src[0].reg)) + { + const struct materialize_ssas_to_temps_block_data *data = &block_index[label_from_src_param(&ins->src[0]) - 1]; + + if (!reserve_instructions(&instructions, &ins_capacity, ins_count + data->phi_count)) + goto fail; + + for (j = data->phi_begin; j < data->phi_begin + data->phi_count; ++j) + { + const struct vkd3d_shader_src_param *source; + + source = materialize_ssas_to_temps_compute_source(&parser->program.instructions.elements[j], current_label); + if (!materialize_ssas_to_temps_synthesize_mov(parser, &instructions[ins_count], &ins->location, + &parser->program.instructions.elements[j].dst[0], NULL, source, false)) + goto fail; + + ++ins_count; + } + } + else + { + struct materialize_ssas_to_temps_block_data *data_true = &block_index[label_from_src_param(&ins->src[1]) - 1], + *data_false = &block_index[label_from_src_param(&ins->src[2]) - 1]; + const struct vkd3d_shader_src_param *cond = &ins->src[0]; + + if (!reserve_instructions(&instructions, &ins_capacity, + ins_count + data_true->phi_count + data_false->phi_count)) + goto fail; + + for (j = data_true->phi_begin; j < data_true->phi_begin + data_true->phi_count; ++j) + { + const struct vkd3d_shader_src_param *source; + + source = materialize_ssas_to_temps_compute_source(&parser->program.instructions.elements[j], current_label); + if (!materialize_ssas_to_temps_synthesize_mov(parser, &instructions[ins_count], &ins->location, + &parser->program.instructions.elements[j].dst[0], cond, source, false)) + goto fail; + + ++ins_count; + } + + for (j = data_false->phi_begin; j < data_false->phi_begin + data_false->phi_count; ++j) + { + const struct vkd3d_shader_src_param *source; + + source = materialize_ssas_to_temps_compute_source(&parser->program.instructions.elements[j], current_label); + if (!materialize_ssas_to_temps_synthesize_mov(parser, &instructions[ins_count], &ins->location, + &parser->program.instructions.elements[j].dst[0], cond, source, true)) + goto fail; + + ++ins_count; + } + } + break; + } + + case VKD3DSIH_PHI: + continue; + + default: + break; + } + if (!reserve_instructions(&instructions, &ins_capacity, ins_count + 1)) goto fail;
@@ -2701,6 +2855,7 @@ static enum vkd3d_result materialize_ssas_to_temps(struct vkd3d_shader_parser *p }
vkd3d_free(parser->program.instructions.elements); + vkd3d_free(block_index); parser->program.instructions.elements = instructions; parser->program.instructions.capacity = ins_capacity; parser->program.instructions.count = ins_count; @@ -2711,6 +2866,7 @@ static enum vkd3d_result materialize_ssas_to_temps(struct vkd3d_shader_parser *p
fail: vkd3d_free(instructions); + vkd3d_free(block_index);
return VKD3D_ERROR_OUT_OF_MEMORY; }
From: Giovanni Mascellani gmascellani@codeweavers.com
The structurizer is implemented along the lines of what is usually called the "structured program theorem": the control flow is completely virtualized by mean of an additional TEMP register which stores the block index which is currently running. The whole program is then converted to a huge switch construction enclosed in a loop, executing at each iteration the appropriate block and updating the register depending on block jump instruction.
The algorithm's generality is also its major weakness: it accepts any input program, even if its CFG is not reducible, but the output program lacks any useful convergence information. It satisfies the letter of the SPIR-V requirements, but it is expected that it will be very inefficient to run on a GPU (unless a downstream compiler is able to devirtualize the control flow and do a proper convergence analysis pass). The algorithm is however very simple, and good enough to at least pass tests, enabling further development. A better alternative is expected to be upstreamed incrementally.
Side note: the structured program theorem is often called the Böhm-Jacopini theorem; Böhm and Jacopini did indeed prove a variation of it, but their algorithm is different from what is commontly attributed to them and implemented here, so I opted for not using their name. --- libs/vkd3d-shader/ir.c | 147 ++++++++++++++++++++++++- tests/hlsl/conditional.shader_test | 2 +- tests/hlsl/for.shader_test | 2 +- tests/hlsl/function-return.shader_test | 44 ++++---- tests/hlsl/loop.shader_test | 6 +- tests/hlsl/return.shader_test | 32 +++--- tests/hlsl/sm6-ternary.shader_test | 4 +- tests/hlsl/switch.shader_test | 40 +++---- 8 files changed, 209 insertions(+), 68 deletions(-)
diff --git a/libs/vkd3d-shader/ir.c b/libs/vkd3d-shader/ir.c index 1f14f7029..886344874 100644 --- a/libs/vkd3d-shader/ir.c +++ b/libs/vkd3d-shader/ir.c @@ -470,6 +470,25 @@ static void dst_param_init_ssa_bool(struct vkd3d_shader_dst_param *dst, unsigned dst->reg.idx[0].offset = idx; }
+static void dst_param_init_temp_uint(struct vkd3d_shader_dst_param *dst, unsigned int idx) +{ + vsir_dst_param_init(dst, VKD3DSPR_TEMP, VKD3D_DATA_UINT, 1); + dst->reg.idx[0].offset = idx; + dst->write_mask = VKD3DSP_WRITEMASK_0; +} + +static void src_param_init_temp_uint(struct vkd3d_shader_src_param *src, unsigned int idx) +{ + vsir_src_param_init(src, VKD3DSPR_TEMP, VKD3D_DATA_UINT, 1); + src->reg.idx[0].offset = idx; +} + +static void src_param_init_const_uint(struct vkd3d_shader_src_param *src, uint32_t value) +{ + vsir_src_param_init(src, VKD3DSPR_IMMCONST, VKD3D_DATA_UINT, 0); + src->reg.u.immconst_u32[0] = value; +} + void vsir_instruction_init(struct vkd3d_shader_instruction *ins, const struct vkd3d_shader_location *location, enum vkd3d_shader_opcode handler_idx) { @@ -2871,6 +2890,125 @@ fail: return VKD3D_ERROR_OUT_OF_MEMORY; }
+static enum vkd3d_result simple_structurizer_run(struct vkd3d_shader_parser *parser) +{ + const unsigned int block_temp_idx = parser->program.temp_count; + struct vkd3d_shader_instruction *instructions = NULL; + const struct vkd3d_shader_location no_loc = {0}; + size_t ins_capacity = 0, ins_count = 0, i; + bool first_label_found = false; + + if (!reserve_instructions(&instructions, &ins_capacity, parser->program.instructions.count)) + goto fail; + + for (i = 0; i < parser->program.instructions.count; ++i) + { + struct vkd3d_shader_instruction *ins = &parser->program.instructions.elements[i]; + + switch (ins->handler_idx) + { + case VKD3DSIH_PHI: + case VKD3DSIH_SWITCH_MONOLITHIC: + vkd3d_unreachable(); + + case VKD3DSIH_LABEL: + if (!reserve_instructions(&instructions, &ins_capacity, ins_count + 4)) + goto fail; + + if (!first_label_found) + { + first_label_found = true; + + if (!vsir_instruction_init_with_params(&parser->program, &instructions[ins_count], &no_loc, VKD3DSIH_MOV, 1, 1)) + goto fail; + dst_param_init_temp_uint(&instructions[ins_count].dst[0], block_temp_idx); + src_param_init_const_uint(&instructions[ins_count].src[0], label_from_src_param(&ins->src[0])); + ins_count++; + + if (!vsir_instruction_init_with_params(&parser->program, &instructions[ins_count], &no_loc, VKD3DSIH_LOOP, 0, 0)) + goto fail; + ins_count++; + + if (!vsir_instruction_init_with_params(&parser->program, &instructions[ins_count], &no_loc, VKD3DSIH_SWITCH, 0, 1)) + goto fail; + src_param_init_temp_uint(&instructions[ins_count].src[0], block_temp_idx); + ins_count++; + } + + if (!vsir_instruction_init_with_params(&parser->program, &instructions[ins_count], &no_loc, VKD3DSIH_CASE, 0, 1)) + goto fail; + src_param_init_const_uint(&instructions[ins_count].src[0], label_from_src_param(&ins->src[0])); + ins_count++; + break; + + case VKD3DSIH_BRANCH: + if (!reserve_instructions(&instructions, &ins_capacity, ins_count + 2)) + goto fail; + + if (vsir_register_is_label(&ins->src[0].reg)) + { + if (!vsir_instruction_init_with_params(&parser->program, &instructions[ins_count], &no_loc, VKD3DSIH_MOV, 1, 1)) + goto fail; + dst_param_init_temp_uint(&instructions[ins_count].dst[0], block_temp_idx); + src_param_init_const_uint(&instructions[ins_count].src[0], label_from_src_param(&ins->src[0])); + ins_count++; + } + else + { + if (!vsir_instruction_init_with_params(&parser->program, &instructions[ins_count], &no_loc, VKD3DSIH_MOVC, 1, 3)) + goto fail; + dst_param_init_temp_uint(&instructions[ins_count].dst[0], block_temp_idx); + instructions[ins_count].src[0] = ins->src[0]; + src_param_init_const_uint(&instructions[ins_count].src[1], label_from_src_param(&ins->src[1])); + src_param_init_const_uint(&instructions[ins_count].src[2], label_from_src_param(&ins->src[2])); + ins_count++; + } + + if (!vsir_instruction_init_with_params(&parser->program, &instructions[ins_count], &no_loc, VKD3DSIH_BREAK, 0, 0)) + goto fail; + ins_count++; + break; + + case VKD3DSIH_RET: + default: + if (!reserve_instructions(&instructions, &ins_capacity, ins_count + 1)) + goto fail; + + instructions[ins_count++] = *ins; + break; + } + } + + assert(first_label_found); + + if (!reserve_instructions(&instructions, &ins_capacity, ins_count + 3)) + goto fail; + + if (!vsir_instruction_init_with_params(&parser->program, &instructions[ins_count], &no_loc, VKD3DSIH_ENDSWITCH, 0, 0)) + goto fail; + ins_count++; + + if (!vsir_instruction_init_with_params(&parser->program, &instructions[ins_count], &no_loc, VKD3DSIH_ENDLOOP, 0, 0)) + goto fail; + ins_count++; + + if (!vsir_instruction_init_with_params(&parser->program, &instructions[ins_count], &no_loc, VKD3DSIH_RET, 0, 0)) + goto fail; + ins_count++; + + vkd3d_free(parser->program.instructions.elements); + parser->program.instructions.elements = instructions; + parser->program.instructions.capacity = ins_capacity; + parser->program.instructions.count = ins_count; + parser->program.temp_count += 1; + + return VKD3D_OK; + +fail: + vkd3d_free(instructions); + return VKD3D_ERROR_OUT_OF_MEMORY; +} + enum vkd3d_result vkd3d_shader_normalise(struct vkd3d_shader_parser *parser, const struct vkd3d_shader_compile_info *compile_info) { @@ -2889,6 +3027,9 @@ enum vkd3d_result vkd3d_shader_normalise(struct vkd3d_shader_parser *parser,
if ((result = materialize_ssas_to_temps(parser)) < 0) return result; + + if ((result = simple_structurizer_run(parser)) < 0) + return result; } else { @@ -2916,13 +3057,13 @@ enum vkd3d_result vkd3d_shader_normalise(struct vkd3d_shader_parser *parser,
remove_dead_code(&parser->program);
- if ((result = flatten_control_flow_constructs(parser)) < 0) - return result; - if ((result = normalise_combined_samplers(parser)) < 0) return result; }
+ if ((result = flatten_control_flow_constructs(parser)) < 0) + return result; + if (TRACE_ON()) vkd3d_shader_trace(&parser->program);
diff --git a/tests/hlsl/conditional.shader_test b/tests/hlsl/conditional.shader_test index 5b3038c37..f32b0e86d 100644 --- a/tests/hlsl/conditional.shader_test +++ b/tests/hlsl/conditional.shader_test @@ -88,7 +88,7 @@ float4 main() : sv_target
[test] uniform 0 float4 0.0 0.0 0.0 0.0 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.9, 0.8, 0.7, 0.6)
[pixel shader] diff --git a/tests/hlsl/for.shader_test b/tests/hlsl/for.shader_test index 88654799c..ad7b5e4c4 100644 --- a/tests/hlsl/for.shader_test +++ b/tests/hlsl/for.shader_test @@ -54,7 +54,7 @@ float4 main(float tex : texcoord) : sv_target
[test] uniform 0 uint4 10 0 0 0 -todo(sm>=6) draw quad +draw quad probe ( 0, 0, 159, 480) rgba (10.0, 35.0, 0.0, 0.0) probe (161, 0, 479, 480) rgba (10.0, 38.0, 0.0, 0.0) probe (481, 0, 640, 480) rgba ( 5.0, 10.0, 0.0, 0.0) diff --git a/tests/hlsl/function-return.shader_test b/tests/hlsl/function-return.shader_test index a316baee3..e1477b0aa 100644 --- a/tests/hlsl/function-return.shader_test +++ b/tests/hlsl/function-return.shader_test @@ -79,16 +79,16 @@ float4 main() : sv_target
[test] uniform 0 float 0.1 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.3, 0.2, 0.6, 0.3) 1 uniform 0 float 0.4 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.6, 0.5, 0.6, 0.3) 1 uniform 0 float 0.6 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.6, 0.5, 0.4, 0.5) 1 uniform 0 float 0.8 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.8, 0.7, 0.4, 0.5) 1
[pixel shader todo(sm<4)] @@ -134,13 +134,13 @@ float4 main() : sv_target
[test] uniform 0 float 0.1 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.2, 0.1, 0.2, 0.1) 1 uniform 0 float 0.5 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.5, 0.4, 1.0, 0.9) 1 uniform 0 float 0.9 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (1.0, 0.9, 1.0, 0.6) 1
[pixel shader todo(sm<4)] @@ -235,23 +235,23 @@ float4 main() : sv_target
[test] uniform 0 float 0.0 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.3, 0.2, 0.3, 0.3) 1
uniform 0 float 0.1 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.3, 0.3, 0.3, 0.3) 1
uniform 0 float 0.3 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.3, 0.5, 0.3, 0.3) 1
uniform 0 float 0.7 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.3, 0.9, 0.7, 0.6) 1
uniform 0 float 0.9 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.4, 0.1, 0.7, 0.6) 1
[pixel shader todo(sm<4)] @@ -291,21 +291,21 @@ float4 main() : sv_target uniform 0 float4 0.3 0.0 0.0 0.0 uniform 4 float4 0.0 0.0 0.0 0.0 uniform 8 float4 0.1 0.0 0.0 0.0 -todo(sm<4 | sm>=6) draw quad -todo(sm>=6) probe all rgba (0.3, 0.2, 0.6, 0.6) 1 +todo(sm<4) draw quad +probe all rgba (0.3, 0.2, 0.6, 0.6) 1
uniform 4 float4 0.35 0.0 0.0 0.0 -todo(sm<4 | sm>=6) draw quad -todo(sm>=6) probe all rgba (0.3, 0.3, 0.6, 0.6) 1 +todo(sm<4) draw quad +probe all rgba (0.3, 0.3, 0.6, 0.6) 1
uniform 8 float4 0.5 0.0 0.0 0.0 -todo(sm<4 | sm>=6) draw quad -todo(sm>=6) probe all rgba (0.3, 0.5, 0.6, 0.6) 1 +todo(sm<4) draw quad +probe all rgba (0.3, 0.5, 0.6, 0.6) 1
uniform 0 float4 1.0 0.0 0.0 0.0 -todo(sm<4 | sm>=6) draw quad -todo(sm>=6) probe all rgba (0.3, 0.5, 0.6, 0.6) 1 +todo(sm<4) draw quad +probe all rgba (0.3, 0.5, 0.6, 0.6) 1
uniform 4 float4 2.0 0.0 0.0 0.0 -todo(sm<4 | sm>=6) draw quad -todo(sm>=6) probe all rgba (0.4, 0.1, 0.6, 0.6) 1 +todo(sm<4) draw quad +probe all rgba (0.4, 0.1, 0.6, 0.6) 1 diff --git a/tests/hlsl/loop.shader_test b/tests/hlsl/loop.shader_test index a9431085a..b913167a6 100644 --- a/tests/hlsl/loop.shader_test +++ b/tests/hlsl/loop.shader_test @@ -118,7 +118,7 @@ float4 main() : sv_target }
[test] -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (10.0, 10.0, 10.0, 10.0)
[pixel shader todo(sm<4)] @@ -137,7 +137,7 @@ float4 main() : sv_target }
[test] -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (10.0, 10.0, 10.0, 10.0)
[pixel shader todo(sm<4)] @@ -156,7 +156,7 @@ float4 main() : sv_target }
[test] -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (10.0, 10.0, 10.0, 10.0)
% unroll can't be used with fastopt or loop diff --git a/tests/hlsl/return.shader_test b/tests/hlsl/return.shader_test index e1c32bf87..fa0c454c8 100644 --- a/tests/hlsl/return.shader_test +++ b/tests/hlsl/return.shader_test @@ -89,13 +89,13 @@ void main(out float4 ret : sv_target)
[test] uniform 0 float 0.1 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.1, 0.2, 0.3, 0.4) 1 uniform 0 float 0.5 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.2, 0.3, 0.4, 0.5) 1 uniform 0 float 0.9 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.5, 0.6, 0.7, 0.8) 1
[pixel shader todo(sm<4)] @@ -115,13 +115,13 @@ void main(out float4 ret : sv_target)
[test] uniform 0 float 0.1 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.1, 0.2, 0.3, 0.4) 1 uniform 0 float 0.5 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.5, 0.6, 0.7, 0.8) 1 uniform 0 float 0.9 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.4, 0.5, 0.6, 0.7) 1
[pixel shader todo(sm<4)] @@ -160,23 +160,23 @@ void main(out float4 ret : sv_target)
[test] uniform 0 float 0.0 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.1, 0.1, 0.1, 0.1) 1
uniform 0 float 0.1 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.2, 0.2, 0.2, 0.2) 1
uniform 0 float 0.3 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.4, 0.4, 0.4, 0.4) 1
uniform 0 float 0.7 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.8, 0.8, 0.8, 0.8) 1
uniform 0 float 0.9 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.9, 0.9, 0.9, 0.9) 1
[pixel shader todo(sm<4)] @@ -236,21 +236,21 @@ void main(out float4 ret : sv_target) uniform 0 float4 0.3 0.0 0.0 0.0 uniform 4 float4 0.0 0.0 0.0 0.0 uniform 8 float4 0.1 0.0 0.0 0.0 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.1, 0.1, 0.1, 0.1) 1
uniform 4 float4 0.35 0.0 0.0 0.0 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.2, 0.2, 0.2, 0.2) 1
uniform 8 float4 0.5 0.0 0.0 0.0 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.4, 0.4, 0.4, 0.4) 1
uniform 0 float4 1.0 0.0 0.0 0.0 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.4, 0.4, 0.4, 0.4) 1
uniform 4 float4 2.0 0.0 0.0 0.0 -todo(sm<4 | sm>=6) draw quad +todo(sm<4) draw quad probe all rgba (0.9, 0.9, 0.9, 0.9) 1 diff --git a/tests/hlsl/sm6-ternary.shader_test b/tests/hlsl/sm6-ternary.shader_test index 349d32676..f1c30af9a 100644 --- a/tests/hlsl/sm6-ternary.shader_test +++ b/tests/hlsl/sm6-ternary.shader_test @@ -14,10 +14,10 @@ float4 main() : sv_target
[test] uniform 0 float4 2.0 3.0 4.0 5.0 -todo draw quad +draw quad probe all rgba (2.0, 3.0, 4.0, 5.0) uniform 0 float4 0.0 10.0 11.0 12.0 -todo draw quad +draw quad probe all rgba (-1.0, 9.0, 10.0, 11.0)
diff --git a/tests/hlsl/switch.shader_test b/tests/hlsl/switch.shader_test index 01624f97c..543d44e21 100644 --- a/tests/hlsl/switch.shader_test +++ b/tests/hlsl/switch.shader_test @@ -116,10 +116,10 @@ float4 main() : sv_target
[test] uniform 0 uint4 2 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (1.1, 2.1, 3.1, 4.1) uniform 0 uint4 1 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (1.0, 2.0, 3.0, 4.0)
% floats are accepted @@ -145,10 +145,10 @@ float4 main() : sv_target
[test] uniform 0 uint4 2 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (1.1, 2.1, 3.1, 4.1) uniform 0 uint4 1 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (1.0, 2.0, 3.0, 4.0)
[pixel shader fail(sm>=6)] @@ -173,10 +173,10 @@ float4 main() : sv_target
[test] uniform 0 float4 2.0 0.0 0.0 0.0 -todo(sm>=6) draw quad +draw quad probe all rgba (1.1, 2.1, 3.1, 4.1) uniform 0 float4 1.0 0.0 0.0 0.0 -todo(sm>=6) draw quad +draw quad probe all rgba (1.0, 2.0, 3.0, 4.0)
[pixel shader fail] @@ -374,13 +374,13 @@ float4 main() : sv_target
[test] uniform 0 uint4 2 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (1.1, 2.1, 3.1, 4.1) uniform 0 uint4 1 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (1.2, 2.2, 3.2, 4.2) uniform 0 uint4 0 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (1.0, 2.0, 3.0, 4.0)
% switch breaks within a loop @@ -412,7 +412,7 @@ float4 main() : sv_target
[test] uniform 0 uint4 2 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (5.0, 6.0, 7.0, 8.0)
% default case placement @@ -443,13 +443,13 @@ float4 main() : sv_target
[test] uniform 0 uint4 0 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (4.0, 5.0, 6.0, 7.0) uniform 0 uint4 2 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (2.0, 3.0, 4.0, 5.0) uniform 0 uint4 3 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (4.0, 5.0, 6.0, 7.0)
[pixel shader] @@ -480,13 +480,13 @@ float4 main() : sv_target
[test] uniform 0 uint4 3 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (1.0, 2.0, 3.0, 4.0) uniform 0 uint4 0 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (4.0, 5.0, 6.0, 7.0) uniform 0 uint4 5 0 0 0 -todo(sm>=6) draw quad +draw quad probe all rgba (1.0, 2.0, 3.0, 4.0)
% 'continue' is not supported in switches @@ -546,10 +546,10 @@ float4 main() : sv_target
[test] uniform 0 uint4 0 0 3 1 -todo(sm>=6) draw quad +draw quad probe all rgba (10.0, 11.0, 12.0, 13.0) uniform 0 uint4 1 0 3 1 -todo(sm>=6) draw quad +draw quad probe all rgba (7.0, 8.0, 9.0, 10.0)
% return from a switch nested in a loop @@ -580,8 +580,8 @@ float4 main() : sv_target
[test] uniform 0 uint4 0 0 3 1 -todo(sm>=6) draw quad +draw quad probe all rgba (304.0, 305.0, 306.0, 307.0) uniform 0 uint4 1 0 3 1 -todo(sm>=6) draw quad +draw quad probe all rgba (3.0, 4.0, 5.0, 6.0)