From c50210f23071d0952d5858411421d63ef9d92557 Mon Sep 17 00:00:00 2001 From: Elizabeth Figura Date: Sat, 9 Aug 2025 15:33:46 -0500 Subject: [PATCH] vkd3d-shader: Implement shader model 1.0-1.3 texture projection. --- Makefile.am | 1 + include/vkd3d_shader.h | 19 ++++++ libs/vkd3d-shader/ir.c | 96 +++++++++++++++++++++++++-- tests/hlsl/bump.shader_test | 10 +++ tests/hlsl/ps1-projection.shader_test | 62 +++++++++++++++++ tests/shader_runner.c | 18 ++++- tests/shader_runner.h | 1 + tests/shader_runner_d3d9.c | 4 ++ tests/shader_runner_vulkan.c | 7 +- 9 files changed, 209 insertions(+), 9 deletions(-) create mode 100644 tests/hlsl/ps1-projection.shader_test diff --git a/Makefile.am b/Makefile.am index 1f3cb9ecb..1f53f25d8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -221,6 +221,7 @@ vkd3d_shader_tests = \ tests/hlsl/pow.shader_test \ tests/hlsl/precise-modifier.shader_test \ tests/hlsl/primitive-id.shader_test \ + tests/hlsl/ps1-projection.shader_test \ tests/hlsl/ps1-sampler.shader_test \ tests/hlsl/rasteriser-ordered-views.shader_test \ tests/hlsl/rcp.shader_test \ diff --git a/include/vkd3d_shader.h b/include/vkd3d_shader.h index 352c222f2..cc6cf4001 100644 --- a/include/vkd3d_shader.h +++ b/include/vkd3d_shader.h @@ -1039,6 +1039,25 @@ enum vkd3d_shader_parameter_name VKD3D_SHADER_PARAMETER_NAME_BUMP_LUMINANCE_OFFSET_3, VKD3D_SHADER_PARAMETER_NAME_BUMP_LUMINANCE_OFFSET_4, VKD3D_SHADER_PARAMETER_NAME_BUMP_LUMINANCE_OFFSET_5, + /** + * A mask of projected textures. + * + * When this parameter is provided to a shader model 1.0-1.3 pixel shader, + * for each nonzero bit of this mask, the corresponding texture will be + * projected. That is, it will have its coordinates divided by their W + * component before sampling. + * + * The default value is zero, i.e. no textures are projected. + * + * The data type for this parameter must be + * VKD3D_SHADER_PARAMETER_DATA_TYPE_UINT32. + * + * Only VKD3D_SHADER_PARAMETER_TYPE_IMMEDIATE_CONSTANT is supported in this + * version of vkd3d-shader. + * + * \since 1.19 + */ + VKD3D_SHADER_PARAMETER_NAME_PROJECTED_TEXTURE_MASK, VKD3D_FORCE_32_BIT_ENUM(VKD3D_SHADER_PARAMETER_NAME), }; diff --git a/libs/vkd3d-shader/ir.c b/libs/vkd3d-shader/ir.c index 6a1c5303e..241006e32 100644 --- a/libs/vkd3d-shader/ir.c +++ b/libs/vkd3d-shader/ir.c @@ -1936,8 +1936,36 @@ static enum vkd3d_result vsir_program_lower_texldl(struct vsir_program *program, return VKD3D_OK; } -static enum vkd3d_result vsir_program_lower_tex(struct vsir_program *program, struct vkd3d_shader_instruction *ins) +static bool is_texture_projected(const struct vsir_program *program, + struct vkd3d_shader_message_context *message_context, unsigned int index) { + const struct vkd3d_shader_parameter1 *parameter; + + if (!(parameter = vsir_program_get_parameter(program, VKD3D_SHADER_PARAMETER_NAME_PROJECTED_TEXTURE_MASK))) + return false; + + if (parameter->type != VKD3D_SHADER_PARAMETER_TYPE_IMMEDIATE_CONSTANT) + { + vkd3d_shader_error(message_context, NULL, VKD3D_SHADER_ERROR_VSIR_NOT_IMPLEMENTED, + "Unsupported projected texture mask parameter type %#x.", parameter->type); + return false; + } + + if (parameter->data_type != VKD3D_SHADER_PARAMETER_DATA_TYPE_UINT32) + { + vkd3d_shader_error(message_context, NULL, VKD3D_SHADER_ERROR_VSIR_INVALID_DATA_TYPE, + "Invalid projected texture mask parameter data type %#x.", parameter->data_type); + return false; + } + + return parameter->u.immediate_constant.u.u32 & (1u << index); +} + +static enum vkd3d_result vsir_program_lower_tex(struct vsir_program *program, + struct vsir_program_iterator *it, struct vkd3d_shader_message_context *message_context) +{ + struct vkd3d_shader_instruction *ins = vsir_program_iterator_current(it); + const struct vkd3d_shader_location location = ins->location; const struct vkd3d_shader_descriptor_info1 *sampler; unsigned int idx = ins->dst[0].reg.idx[0].offset; struct vkd3d_shader_src_param *srcs; @@ -1951,10 +1979,40 @@ static enum vkd3d_result vsir_program_lower_tex(struct vsir_program *program, st if (!(srcs = vsir_program_get_src_params(program, 4))) return VKD3D_ERROR_OUT_OF_MEMORY; - vsir_src_param_init(&srcs[0], VKD3DSPR_TEXTURE, VSIR_DATA_F32, 1); - srcs[0].reg.idx[0].offset = idx; - srcs[0].reg.dimension = VSIR_DIMENSION_VEC4; - srcs[0].swizzle = VKD3D_SHADER_NO_SWIZZLE; + if (is_texture_projected(program, message_context, idx)) + { + struct vkd3d_shader_dst_param *dst = ins->dst; + uint32_t coords = program->ssa_count++; + + /* div sr0, t#, t#.w */ + + if (!vsir_program_iterator_insert_after(it, 1)) + return VKD3D_ERROR_OUT_OF_MEMORY; + + ins = vsir_program_iterator_current(it); + if (!vsir_instruction_init_with_params(program, ins, &location, VSIR_OP_DIV, 1, 2)) + return VKD3D_ERROR_OUT_OF_MEMORY; + dst_param_init_ssa_float4(&ins->dst[0], coords); + vsir_src_param_init(&ins->src[0], VKD3DSPR_TEXTURE, VSIR_DATA_F32, 1); + ins->src[0].reg.idx[0].offset = idx; + ins->src[0].reg.dimension = VSIR_DIMENSION_VEC4; + ins->src[0].swizzle = VKD3D_SHADER_NO_SWIZZLE; + ins->src[1] = ins->src[0]; + ins->src[1].swizzle = VKD3D_SHADER_SWIZZLE(W, W, W, W); + + ins = vsir_program_iterator_next(it); + vsir_instruction_init(ins, &location, VSIR_OP_SAMPLE); + ins->dst_count = 1; + ins->dst = dst; + src_param_init_ssa_float4(&srcs[0], coords); + } + else + { + vsir_src_param_init(&srcs[0], VKD3DSPR_TEXTURE, VSIR_DATA_F32, 1); + srcs[0].reg.idx[0].offset = idx; + srcs[0].reg.dimension = VSIR_DIMENSION_VEC4; + srcs[0].swizzle = VKD3D_SHADER_NO_SWIZZLE; + } vsir_src_param_init_resource(&srcs[1], idx, idx); vsir_src_param_init_sampler(&srcs[2], idx, idx); @@ -2082,6 +2140,7 @@ static enum vkd3d_result vsir_program_lower_texbem(struct vsir_program *program, unsigned int idx = ins->dst[0].reg.idx[0].offset; uint32_t ssa_coords, ssa_luminance, ssa_sample; struct vkd3d_shader_src_param orig_coords; + bool projected; /* texbem t#, SRC * -> @@ -2097,6 +2156,11 @@ static enum vkd3d_result vsir_program_lower_texbem(struct vsir_program *program, * mad srLUM.x, SRC.z, BUMP_LUMINANCE_SCALE#, BUMP_LUMINANCE_OFFSET# * mul t#, t#, srLUM.xxxx * + * If projecting, we replace srCOORDS calculation with + * + * div srPROJ, t#, t#.w + * bem srCOORDS.xy, srPROJ.xy, SRC + * * Note that the t# destination will subsequently be turned into a temp. */ descriptor = vkd3d_shader_find_descriptor(&program->descriptors, VKD3D_SHADER_DESCRIPTOR_TYPE_SAMPLER, idx); @@ -2115,7 +2179,8 @@ static enum vkd3d_result vsir_program_lower_texbem(struct vsir_program *program, return VKD3D_ERROR_NOT_IMPLEMENTED; } - if (!vsir_program_iterator_insert_after(it, is_texbeml ? 4 : 2)) + projected = is_texture_projected(program, message_context, idx); + if (!vsir_program_iterator_insert_after(it, 2 + (is_texbeml ? 2 : 0) + (projected ? 1 : 0))) return VKD3D_ERROR_OUT_OF_MEMORY; vsir_src_param_init(&orig_coords, VKD3DSPR_TEXTURE, VSIR_DATA_F32, 1); @@ -2123,6 +2188,23 @@ static enum vkd3d_result vsir_program_lower_texbem(struct vsir_program *program, orig_coords.reg.dimension = VSIR_DIMENSION_VEC4; orig_coords.swizzle = VKD3D_SHADER_NO_SWIZZLE; + if (projected) + { + uint32_t ssa_proj = program->ssa_count++; + + ins = vsir_program_iterator_current(it); + if (!vsir_instruction_init_with_params(program, ins, &location, VSIR_OP_DIV, 1, 2)) + return VKD3D_ERROR_OUT_OF_MEMORY; + dst_param_init_ssa_float4(&ins->dst[0], ssa_proj); + ins->src[0] = orig_coords; + ins->src[1] = ins->src[0]; + ins->src[1].swizzle = VKD3D_SHADER_SWIZZLE(W, W, W, W); + + src_param_init_ssa_float4(&orig_coords, ssa_proj); + + vsir_program_iterator_next(it); + } + if (!(ins = generate_bump_coords(program, it, idx, &orig_coords, &src[0], &location))) return VKD3D_ERROR_OUT_OF_MEMORY; ssa_coords = ins->dst[0].reg.idx[0].offset; @@ -2290,7 +2372,7 @@ static enum vkd3d_result vsir_program_lower_d3dbc_instructions(struct vsir_progr break; case VSIR_OP_TEX: - ret = vsir_program_lower_tex(program, ins); + ret = vsir_program_lower_tex(program, &it, message_context); break; case VSIR_OP_TEXLD: diff --git a/tests/hlsl/bump.shader_test b/tests/hlsl/bump.shader_test index dc4891476..56187fa8b 100644 --- a/tests/hlsl/bump.shader_test +++ b/tests/hlsl/bump.shader_test @@ -67,10 +67,15 @@ ffff0101 % ps_1_1 [test] bump 1 f32(0.1, 0.2, 0.3, 0.5) 2.0 0.3 +projected 1 disable draw quad % WARP transposes the matrix for TEXBEM/TEXBEML, but not for BEM. if(!warp) probe (250, 500) f32(0.58, 0.27, 0.73, 0.42) 32 if(!warp) probe (750, 500) f32(0.47, 0.33, 0.67, 0.53) 16 +projected 1 enable +draw quad +if(!warp) probe (250, 500) f32(0.28, 0.47, 0.53, 0.72) 32 +if(!warp) probe (750, 500) f32(0.17, 0.53, 0.47, 0.83) 16 [pixel shader d3dbc-hex] % TODO: Convert to assembly. @@ -82,11 +87,16 @@ ffff0101 % ps_1_1 [test] bump 1 f32(0.1, 0.2, 0.3, 0.5) 2.0 0.3 +projected 1 disable draw quad % Besides transposing the matrix, WARP also uses the texcoord t1.z (0.4) % as a luminance factor, instead of the textured value t0.z (0.3 or 0.2). if(!warp) probe (250, 500) f32(0.522, 0.243, 0.657, 0.378) 32 if(!warp) probe (750, 500) f32(0.329, 0.231, 0.469, 0.371) 16 +projected 1 enable +draw quad +if(!warp) probe (250, 500) f32(0.252, 0.423, 0.477, 0.648) 32 +if(!warp) probe (750, 500) f32(0.119, 0.371, 0.329, 0.581) 32 [pixel shader d3dbc-hex] % TODO: Convert to assembly. diff --git a/tests/hlsl/ps1-projection.shader_test b/tests/hlsl/ps1-projection.shader_test new file mode 100644 index 000000000..0a1643211 --- /dev/null +++ b/tests/hlsl/ps1-projection.shader_test @@ -0,0 +1,62 @@ +% Test for the D3DTTFF_* projected texture flags. + +[require] +shader model < 3.0 + +[sampler 0] +filter point point point +address clamp clamp clamp + +[srv 0] +size (2d, 5, 5) +.0 .0 .0 .1 .1 .0 .0 .1 .2 .0 .0 .1 .3 .0 .0 .1 .4 .0 .0 .1 +.0 .1 .0 .1 .1 .1 .0 .1 .2 .1 .0 .1 .3 .1 .0 .1 .4 .1 .0 .1 +.0 .2 .0 .1 .1 .2 .0 .1 .2 .2 .0 .1 .3 .2 .0 .1 .4 .2 .0 .1 +.0 .3 .0 .1 .1 .3 .0 .1 .2 .3 .0 .1 .3 .3 .0 .1 .4 .3 .0 .1 +.0 .4 .0 .1 .1 .4 .0 .1 .2 .4 .0 .1 .3 .4 .0 .1 .4 .4 .0 .1 + +[vertex shader] +void main(inout float4 pos : position, out float4 tex : texcoord) +{ + tex = float4(1.2, 0.5, 4.0, 2.0); +} + +[pixel shader d3dbc-hex] +% TODO: Convert to assembly or HLSL. +ffff0101 % ps_1_1 +00000042 b00f0000 % tex t0 +00000001 800f0000 b0e40000 % mov r0, t0 +0000ffff % end + +[test] +projected 0 disable +draw quad +probe (0, 0) f32(0.4, 0.2, 0.0, 0.1) +projected 0 enable +draw quad +probe (0, 0) f32(0.3, 0.1, 0.0, 0.1) + +% Doesn't affect 1.4 or 2.0. +[pixel shader d3dbc-hex] +% TODO: Convert to assembly or HLSL. +ffff0104 % ps_1_4 +00000042 800f0000 b0e40000 % texld r0, t0 +0000ffff % end + +[test] +projected 0 enable +draw quad +probe (0, 0) f32(0.4, 0.2, 0.0, 0.1) + +[pixel shader] +sampler sam; + +float4 main(float2 tex : texcoord) : sv_target +{ + return tex2D(sam, tex); +} + +[test] +projected 0 enable +draw quad +probe (0, 0) f32(0.4, 0.2, 0.0, 0.1) diff --git a/tests/shader_runner.c b/tests/shader_runner.c index 8b01ccb1a..535a413b3 100644 --- a/tests/shader_runner.c +++ b/tests/shader_runner.c @@ -765,8 +765,10 @@ static void parse_resource_directive(struct resource_params *resource, const cha for (;;) { + while (isspace(*line)) + ++line; u.u = strtoul(line, &rest, 0); - if (rest && *rest == '.') + if (*line == '.' || (rest && *rest == '.')) u.f = strtof(line, &rest); if (rest == line) @@ -1764,6 +1766,20 @@ static void parse_test_directive(struct shader_runner *runner, const char *line) else fatal_error("Invalid denorm mode '%s'.\n", line); } + else if (match_string(line, "projected", &line)) + { + unsigned int index; + + index = strtoul(line, (char **)&rest, 10); + if (rest == line || index >= 6) + fatal_error("Malformed projection directive '%s'.\n", line); + line = rest; + + if (match_string(line, "enable", &line)) + runner->projected_texture_mask |= (1u << index); + else + runner->projected_texture_mask &= ~(1u << index); + } else { fatal_error("Unknown test directive '%s'.\n", line); diff --git a/tests/shader_runner.h b/tests/shader_runner.h index 72bbefd56..5a6a7e67e 100644 --- a/tests/shader_runner.h +++ b/tests/shader_runner.h @@ -304,6 +304,7 @@ struct shader_runner float matrix[2][2]; float luminance_scale, luminance_offset; } bump[8]; + uint8_t projected_texture_mask; enum denorm_mode denorm_mode; diff --git a/tests/shader_runner_d3d9.c b/tests/shader_runner_d3d9.c index 90c322a6d..ae2859ca1 100644 --- a/tests/shader_runner_d3d9.c +++ b/tests/shader_runner_d3d9.c @@ -651,6 +651,10 @@ static bool d3d9_runner_draw(struct shader_runner *r, hr = IDirect3DDevice9_SetTextureStageState(device, i, D3DTSS_BUMPENVLOFFSET, float_to_int(runner->r.bump[i].luminance_offset)); ok(hr == D3D_OK, "Failed to set texture state, hr %#lx.\n", hr); + + hr = IDirect3DDevice9_SetTextureStageState(device, i, D3DTSS_TEXTURETRANSFORMFLAGS, + (runner->r.projected_texture_mask & (1u << i)) ? D3DTTFF_PROJECTED : 0); + ok(hr == D3D_OK, "Failed to set texture state, hr %#lx.\n", hr); } hr = IDirect3DDevice9_CreateVertexDeclaration(device, decl_elements, &vertex_declaration); diff --git a/tests/shader_runner_vulkan.c b/tests/shader_runner_vulkan.c index f76b0a73d..b86d6b4bc 100644 --- a/tests/shader_runner_vulkan.c +++ b/tests/shader_runner_vulkan.c @@ -366,7 +366,7 @@ static bool compile_d3d_code(struct vulkan_shader_runner *runner, struct vkd3d_shader_varying_map varying_map[12]; struct vkd3d_shader_resource_binding *binding; struct vkd3d_shader_compile_option options[2]; - struct vkd3d_shader_parameter1 parameters[40]; + struct vkd3d_shader_parameter1 parameters[41]; unsigned int i; char *messages; int ret; @@ -609,6 +609,11 @@ static bool compile_d3d_code(struct vulkan_shader_runner *runner, parameters[34 + i].u.immediate_constant.u.f32 = runner->r.bump[i].luminance_offset; } + parameters[40].name = VKD3D_SHADER_PARAMETER_NAME_PROJECTED_TEXTURE_MASK; + parameters[40].type = VKD3D_SHADER_PARAMETER_TYPE_IMMEDIATE_CONSTANT; + parameters[40].data_type = VKD3D_SHADER_PARAMETER_DATA_TYPE_UINT32; + parameters[40].u.immediate_constant.u.u32 = runner->r.projected_texture_mask; + parameter_info.parameter_count = ARRAY_SIZE(parameters); parameter_info.parameters = parameters;