diff --git a/code/graphics/shader_types.cpp b/code/graphics/shader_types.cpp index 80a733b7c2a..f8ab24eccf4 100644 --- a/code/graphics/shader_types.cpp +++ b/code/graphics/shader_types.cpp @@ -162,6 +162,19 @@ static ShaderVariantInfo SHADER_VARIANTS[] = { {SDR_TYPE_SHADOW_MAP_GEN, true, SDR_FLAG_SHADOW_FALLBACK, "GEOMETRY_FALLBACK", {}, "Use a geometry shader to select the cascade layer instead of writing gl_Layer from the vertex shader"} }; +static_assert(VATTRIB_MODEL_MATRIX + 4 <= 32, "vertex input mask is 32 bits wide"); + +uint32_t shader_vertex_input_mask(const ShaderTypeInfo& info) +{ + uint32_t mask = 0; + for (auto attr : info.attributes) { + // The Vulkan pipeline drops every location the mask omits, so a mat4 needs all four of its bits. + const uint32_t locations = (attr == VATTRIB_MODEL_MATRIX) ? 4u : 1u; + mask |= ((1u << locations) - 1u) << attr; + } + return mask; +} + const ShaderTypeInfo* shader_get_type_info(shader_type type) { for (auto & i : SHADER_TYPES) { diff --git a/code/graphics/shader_types.h b/code/graphics/shader_types.h index 09860a78d50..d8846487347 100644 --- a/code/graphics/shader_types.h +++ b/code/graphics/shader_types.h @@ -55,6 +55,14 @@ const ShaderTypeInfo* shader_get_type_info(shader_type type); // Lookup helper: find variant info by (type, flag). Returns nullptr if not found. const ShaderVariantInfo* shader_get_variant_info(shader_type type, int flag); +/** + * @brief Bitmask of the vertex attribute locations a shader type reads. + * + * One bit per location; VATTRIB_MODEL_MATRIX contributes bits 8-11 because a + * mat4 input occupies four consecutive locations. + */ +uint32_t shader_vertex_input_mask(const ShaderTypeInfo& info); + // Iterate all variants matching type whose flag bit is set in flags. void shader_for_each_active_variant(shader_type type, unsigned int flags, const std::function& fn); diff --git a/code/graphics/vulkan/VulkanDeferred.cpp b/code/graphics/vulkan/VulkanDeferred.cpp index d4a48744347..f2dd15a1a46 100644 --- a/code/graphics/vulkan/VulkanDeferred.cpp +++ b/code/graphics/vulkan/VulkanDeferred.cpp @@ -959,6 +959,7 @@ void vulkan_render_decals(decal_material* material_info, // Build pipeline config for decal rendering PipelineConfig config; config.shaderType = SDR_TYPE_DECAL; + config.shaderFlags = material_info->get_shader_flags(); config.primitiveType = prim_type; config.depthMode = material_info->get_depth_mode(); config.depthWriteEnabled = false; diff --git a/code/graphics/vulkan/VulkanShader.cpp b/code/graphics/vulkan/VulkanShader.cpp index da9a29bc5dc..1986e90a30d 100644 --- a/code/graphics/vulkan/VulkanShader.cpp +++ b/code/graphics/vulkan/VulkanShader.cpp @@ -127,10 +127,7 @@ void VulkanShaderManager::recompileAllShaders(const std::functionattributes) { - shader.vertexInputMask |= (1u << attr); - } + shader.vertexInputMask = shader_vertex_input_mask(*typeInfo); SCP_string fragFile = typeInfo->frag; auto fragSpirv = m_compiler->compile(fragFile, vk::ShaderStageFlagBits::eFragment, type, flags, requiresRaytracing); @@ -216,10 +213,7 @@ int VulkanShaderManager::loadShader(shader_type type, unsigned int flags) vertFile.c_str(), e.what())); } } - shader.vertexInputMask = 0; - for (auto attr : typeInfo->attributes) { - shader.vertexInputMask |= (1u << attr); - } + shader.vertexInputMask = shader_vertex_input_mask(*typeInfo); // Compile fragment shader SCP_string fragFile = typeInfo->frag; diff --git a/test/src/graphics/test_material.cpp b/test/src/graphics/test_material.cpp new file mode 100644 index 00000000000..eec20b2cc5c --- /dev/null +++ b/test/src/graphics/test_material.cpp @@ -0,0 +1,16 @@ +#include +#include + +// USE_NORMAL_MAP means "read the scene normal buffer": set when the decal brings no normal map of its own. +TEST(DecalMaterial, SceneNormalFlagSetOnlyWithoutDecalNormalMap) +{ + decal_material without_normal; + without_normal.set_texture_map(TM_BASE_TYPE, 0); + without_normal.set_texture_map(TM_NORMAL_TYPE, -1); + EXPECT_EQ(without_normal.get_shader_flags(), static_cast(SDR_FLAG_DECAL_USE_NORMAL_MAP)); + + decal_material with_normal; + with_normal.set_texture_map(TM_BASE_TYPE, 0); + with_normal.set_texture_map(TM_NORMAL_TYPE, 1); + EXPECT_EQ(with_normal.get_shader_flags(), 0u); +} diff --git a/test/src/graphics/test_shader_types.cpp b/test/src/graphics/test_shader_types.cpp new file mode 100644 index 00000000000..f2efc1a2096 --- /dev/null +++ b/test/src/graphics/test_shader_types.cpp @@ -0,0 +1,37 @@ +#include +#include + +// A mat4 input spans locations 8-11; the Vulkan pipeline drops any location the mask omits. +TEST(ShaderTypes, ModelMatrixInputMaskCoversFourLocations) +{ + const ShaderTypeInfo* decal = shader_get_type_info(SDR_TYPE_DECAL); + ASSERT_NE(decal, nullptr); + + const uint32_t mask = shader_vertex_input_mask(*decal); + for (uint32_t loc = VATTRIB_MODEL_MATRIX; loc < VATTRIB_MODEL_MATRIX + 4; ++loc) { + EXPECT_TRUE(mask & (1u << loc)) << "location " << loc << " missing from mask " << mask; + } + EXPECT_TRUE(mask & (1u << VATTRIB_POSITION)); +} + +TEST(ShaderTypes, VertexInputMaskIsExactPerType) +{ + struct Case { + shader_type type; + uint32_t expected; + }; + const Case cases[] = { + {SDR_TYPE_DECAL, (1u << VATTRIB_POSITION) | (0xFu << VATTRIB_MODEL_MATRIX)}, + {SDR_TYPE_NANOVG, (1u << VATTRIB_POSITION) | (1u << VATTRIB_TEXCOORD)}, + {SDR_TYPE_MODEL, + (1u << VATTRIB_POSITION) | (1u << VATTRIB_TEXCOORD) | (1u << VATTRIB_NORMAL) | (1u << VATTRIB_TANGENT) | + (1u << VATTRIB_MODELID)}, + {SDR_TYPE_EFFECT_PARTICLE, + (1u << VATTRIB_POSITION) | (1u << VATTRIB_TEXCOORD) | (1u << VATTRIB_RADIUS) | (1u << VATTRIB_COLOR)}, + }; + for (const auto& c : cases) { + const ShaderTypeInfo* info = shader_get_type_info(c.type); + ASSERT_NE(info, nullptr) << "type " << static_cast(c.type); + EXPECT_EQ(shader_vertex_input_mask(*info), c.expected) << "type " << static_cast(c.type); + } +} diff --git a/test/src/source_groups.cmake b/test/src/source_groups.cmake index 1c268dda86e..103f0399349 100644 --- a/test/src/source_groups.cmake +++ b/test/src/source_groups.cmake @@ -25,6 +25,8 @@ add_file_folder("Globalincs" add_file_folder("Graphics" graphics/test_font.cpp + graphics/test_material.cpp + graphics/test_shader_types.cpp ) if (FSO_BUILD_WITH_VULKAN)