vkd3d: Implement TIER_1 variable rate shading

Signed-off-by: Joshua Ashton <joshua@froggi.es>
This commit is contained in:
Joshua Ashton 2021-02-04 05:17:02 +00:00 committed by Hans-Kristian Arntzen
parent fdf3d30792
commit c0d4ead8ca
5 changed files with 213 additions and 25 deletions

View File

@ -3820,6 +3820,10 @@ static void d3d12_command_list_reset_state(struct d3d12_command_list *list,
list->dynamic_state.primitive_topology = D3D_PRIMITIVE_TOPOLOGY_POINTLIST;
list->dynamic_state.vk_primitive_topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
list->dynamic_state.fragment_shading_rate.fragment_size = (VkExtent2D) { 1u, 1u };
list->dynamic_state.fragment_shading_rate.combiner_ops[0] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR;
list->dynamic_state.fragment_shading_rate.combiner_ops[1] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR;
memset(list->pipeline_bindings, 0, sizeof(list->pipeline_bindings));
memset(list->descriptor_heaps, 0, sizeof(list->descriptor_heaps));
@ -4622,6 +4626,13 @@ static void d3d12_command_list_update_dynamic_state(struct d3d12_command_list *l
}
}
if (dyn_state->dirty_flags & VKD3D_DYNAMIC_STATE_FRAGMENT_SHADING_RATE)
{
VK_CALL(vkCmdSetFragmentShadingRateKHR(list->vk_command_buffer,
&dyn_state->fragment_shading_rate.fragment_size,
dyn_state->fragment_shading_rate.combiner_ops));
}
dyn_state->dirty_flags = 0;
}
@ -8126,10 +8137,60 @@ static void STDMETHODCALLTYPE d3d12_command_list_DispatchRays(d3d12_command_list
FIXME("iface %p, desc %p stub!\n", iface, desc);
}
static VkFragmentShadingRateCombinerOpKHR vk_shading_rate_combiner_from_d3d12(D3D12_SHADING_RATE_COMBINER combiner)
{
switch (combiner)
{
case D3D12_SHADING_RATE_COMBINER_PASSTHROUGH:
return VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR;
case D3D12_SHADING_RATE_COMBINER_OVERRIDE:
return VK_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_KHR;
case D3D12_SHADING_RATE_COMBINER_MAX:
return VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR;
case D3D12_SHADING_RATE_COMBINER_MIN:
return VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN_KHR;
case D3D12_SHADING_RATE_COMBINER_SUM:
/* Undocumented log space */
return VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_KHR;
default:
ERR("Unhandled shading rate combiner %u.\n", combiner);
/* Default to passthrough for unknown */
return VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR;
}
}
static uint32_t vk_fragment_size_from_d3d12(D3D12_AXIS_SHADING_RATE axis_rate)
{
switch (axis_rate)
{
case D3D12_AXIS_SHADING_RATE_1X: return 1;
case D3D12_AXIS_SHADING_RATE_2X: return 2;
case D3D12_AXIS_SHADING_RATE_4X: return 4;
default:
ERR("Unhandled axis shading rate %u.\n", axis_rate);
return 1;
}
}
static void STDMETHODCALLTYPE d3d12_command_list_RSSetShadingRate(d3d12_command_list_iface *iface,
D3D12_SHADING_RATE base, const D3D12_SHADING_RATE_COMBINER *combiners)
{
FIXME("iface %p, base %#x, combiners %p stub!\n", iface, base, combiners);
struct d3d12_command_list *list = impl_from_ID3D12GraphicsCommandList(iface);
struct vkd3d_dynamic_state *dyn_state = &list->dynamic_state;
TRACE("iface %p, base %#x, combiners %p\n", iface, base, combiners);
dyn_state->fragment_shading_rate.fragment_size = (VkExtent2D) {
vk_fragment_size_from_d3d12(D3D12_GET_COARSE_SHADING_RATE_X_AXIS(base)),
vk_fragment_size_from_d3d12(D3D12_GET_COARSE_SHADING_RATE_Y_AXIS(base))
};
for (uint32_t i = 0; i < D3D12_RS_SET_SHADING_RATE_COMBINER_COUNT; i++)
dyn_state->fragment_shading_rate.combiner_ops[i] = combiners
? vk_shading_rate_combiner_from_d3d12(combiners[i])
: VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR;
dyn_state->dirty_flags |= VKD3D_DYNAMIC_STATE_FRAGMENT_SHADING_RATE;
}
static void STDMETHODCALLTYPE d3d12_command_list_RSSetShadingRateImage(d3d12_command_list_iface *iface,

View File

@ -89,6 +89,7 @@ static const struct vkd3d_optional_extension_info optional_device_extensions[] =
VK_EXTENSION(KHR_DEFERRED_HOST_OPERATIONS, KHR_deferred_host_operations),
VK_EXTENSION(KHR_SPIRV_1_4, KHR_spirv_1_4),
VK_EXTENSION(KHR_SHADER_FLOAT_CONTROLS, KHR_shader_float_controls),
VK_EXTENSION(KHR_FRAGMENT_SHADING_RATE, KHR_fragment_shading_rate),
/* EXT extensions */
VK_EXTENSION(EXT_CALIBRATED_TIMESTAMPS, EXT_calibrated_timestamps),
VK_EXTENSION(EXT_CONDITIONAL_RENDERING, EXT_conditional_rendering),
@ -774,17 +775,101 @@ static uint32_t vkd3d_physical_device_get_time_domains(struct d3d12_device *devi
return result;
}
bool d3d12_device_supports_variable_shading_rate_tier_1(struct d3d12_device *device)
{
const struct vkd3d_physical_device_info *info = &device->device_info;
return info->fragment_shading_rate_features.pipelineFragmentShadingRate &&
(device->vk_info.device_limits.framebufferColorSampleCounts & VK_SAMPLE_COUNT_2_BIT);
}
static D3D12_VARIABLE_SHADING_RATE_TIER d3d12_device_determine_variable_shading_rate_tier(struct d3d12_device *device)
{
if (!d3d12_device_supports_variable_shading_rate_tier_1(device))
return D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED;
/* TODO: TIER_2
- impl RSSetShadingRateImage
- prop fragmentShadingRateNonTrivialCombinerOp
- feat primitiveFragmentShadingRate
- DXIL bringup for PrimitiveShadingRateKHR built-in
- feat attachmentFragmentShadingRate
*/
return D3D12_VARIABLE_SHADING_RATE_TIER_1;
}
static const struct
{
VkExtent2D fragment_size;
VkSampleCountFlags min_sample_counts;
} additional_shading_rates[] =
{
{ { 2, 4 }, VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_2_BIT },
{ { 4, 2 }, VK_SAMPLE_COUNT_1_BIT },
{ { 4, 4 }, VK_SAMPLE_COUNT_1_BIT },
};
static bool d3d12_device_determine_additional_shading_rates_supported(struct d3d12_device *device)
{
const struct vkd3d_vk_instance_procs *vk_procs = &device->vkd3d_instance->vk_procs;
VkPhysicalDeviceFragmentShadingRateKHR *fragment_shading_rates;
VkPhysicalDevice physical_device = device->vk_physical_device;
uint32_t additional_shading_rates_supported = 0;
uint32_t fragment_shading_rate_count;
VkResult vr;
/* Early out if we don't support at least variable shading rate TIER1 */
if (d3d12_device_determine_variable_shading_rate_tier(device) == D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED)
return false;
if ((vr = VK_CALL(vkGetPhysicalDeviceFragmentShadingRatesKHR(physical_device, &fragment_shading_rate_count, NULL))) < 0)
{
ERR("Failed to enumerate additional shading rates, vr %d.\n", vr);
return false;
}
if (!(fragment_shading_rates = vkd3d_calloc(fragment_shading_rate_count, sizeof(*fragment_shading_rates))))
return false;
if ((vr = VK_CALL(vkGetPhysicalDeviceFragmentShadingRatesKHR(physical_device, &fragment_shading_rate_count, fragment_shading_rates))) < 0)
{
ERR("Failed to enumerate additional shading rates, vr %d.\n", vr);
vkd3d_free(fragment_shading_rates);
return false;
}
for (uint32_t i = 0; i < fragment_shading_rate_count; i++)
{
for (uint32_t j = 0; j < ARRAY_SIZE(additional_shading_rates); j++)
{
if (fragment_shading_rates[i].fragmentSize.width == additional_shading_rates[j].fragment_size.width &&
fragment_shading_rates[i].fragmentSize.height == additional_shading_rates[j].fragment_size.height &&
(fragment_shading_rates[i].sampleCounts & additional_shading_rates[j].min_sample_counts) == additional_shading_rates[j].min_sample_counts)
{
additional_shading_rates_supported++;
break;
}
}
}
vkd3d_free(fragment_shading_rates);
return additional_shading_rates_supported == ARRAY_SIZE(additional_shading_rates);
}
static void vkd3d_physical_device_info_init(struct vkd3d_physical_device_info *info, struct d3d12_device *device)
{
VkPhysicalDeviceShaderSubgroupExtendedTypesFeaturesKHR *shader_subgroup_extended_types_features;
VkPhysicalDeviceAccelerationStructurePropertiesKHR *acceleration_structure_properties;
const struct vkd3d_vk_instance_procs *vk_procs = &device->vkd3d_instance->vk_procs;
VkPhysicalDeviceSubgroupSizeControlPropertiesEXT *subgroup_size_control_properties;
VkPhysicalDeviceFragmentShadingRatePropertiesKHR *fragment_shading_rate_properties;
VkPhysicalDeviceAccelerationStructureFeaturesKHR *acceleration_structure_features;
VkPhysicalDeviceRayTracingPipelinePropertiesKHR *ray_tracing_pipeline_properties;
VkPhysicalDeviceInlineUniformBlockPropertiesEXT *inline_uniform_block_properties;
VkPhysicalDeviceExtendedDynamicStateFeaturesEXT *extended_dynamic_state_features;
VkPhysicalDeviceExternalMemoryHostPropertiesEXT *external_memory_host_properties;
VkPhysicalDeviceFragmentShadingRateFeaturesKHR *fragment_shading_rate_features;
VkPhysicalDeviceBufferDeviceAddressFeaturesKHR *buffer_device_address_features;
VkPhysicalDeviceCustomBorderColorPropertiesEXT *custom_border_color_properties;
VkPhysicalDeviceConditionalRenderingFeaturesEXT *conditional_rendering_features;
@ -859,6 +944,8 @@ static void vkd3d_physical_device_info_init(struct vkd3d_physical_device_info *i
ray_tracing_pipeline_features = &info->ray_tracing_pipeline_features;
float_control_properties = &info->float_control_properties;
ext_4444_formats_features = &info->ext_4444_formats_features;
fragment_shading_rate_features = &info->fragment_shading_rate_features;
fragment_shading_rate_properties = &info->fragment_shading_rate_properties;
info->features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
info->properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
@ -1052,6 +1139,14 @@ static void vkd3d_physical_device_info_init(struct vkd3d_physical_device_info *i
vk_prepend_struct(&info->properties2, float_control_properties);
}
if (vulkan_info->KHR_fragment_shading_rate)
{
fragment_shading_rate_properties->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR;
fragment_shading_rate_features->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR;
vk_prepend_struct(&info->properties2, fragment_shading_rate_properties);
vk_prepend_struct(&info->features2, fragment_shading_rate_features);
}
VK_CALL(vkGetPhysicalDeviceFeatures2(physical_device, &info->features2));
VK_CALL(vkGetPhysicalDeviceProperties2(physical_device, &info->properties2));
}
@ -1627,6 +1722,9 @@ static HRESULT vkd3d_init_device_caps(struct d3d12_device *device,
return E_INVALIDARG;
}
if (vulkan_info->KHR_fragment_shading_rate)
physical_device_info->additional_shading_rates_supported = d3d12_device_determine_additional_shading_rates_supported(device);
return S_OK;
}
@ -4781,10 +4879,11 @@ static void d3d12_device_caps_init_feature_options6(struct d3d12_device *device)
{
D3D12_FEATURE_DATA_D3D12_OPTIONS6 *options6 = &device->d3d12_caps.options6;
/* Currently not supported */
options6->AdditionalShadingRatesSupported = FALSE;
options6->AdditionalShadingRatesSupported = device->device_info.additional_shading_rates_supported;
options6->VariableShadingRateTier = d3d12_device_determine_variable_shading_rate_tier(device);
/* Currently not supported, requires TIER_2 shading rate */
options6->PerPrimitiveShadingRateSupportedWithViewportIndexing = FALSE;
options6->VariableShadingRateTier = D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED;
options6->ShadingRateImageTileSize = 0;
/* Not implemented */
options6->BackgroundProcessingSupported = FALSE;

View File

@ -2432,10 +2432,11 @@ static bool vk_blend_attachment_needs_blend_constants(const VkPipelineColorBlend
vk_blend_factor_needs_blend_constants(attachment->dstAlphaBlendFactor));
}
static uint32_t d3d12_graphics_pipeline_state_init_dynamic_state(const struct d3d12_graphics_pipeline_state *graphics,
static uint32_t d3d12_graphics_pipeline_state_init_dynamic_state(struct d3d12_pipeline_state *state,
VkPipelineDynamicStateCreateInfo *dynamic_desc, VkDynamicState *dynamic_state_buffer,
const struct vkd3d_pipeline_key *key)
{
struct d3d12_graphics_pipeline_state *graphics = &state->graphics;
uint32_t dynamic_state_flags;
unsigned int i, count;
@ -2446,15 +2447,16 @@ static uint32_t d3d12_graphics_pipeline_state_init_dynamic_state(const struct d3
}
dynamic_state_list[] =
{
{ VKD3D_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_VIEWPORT },
{ VKD3D_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_SCISSOR },
{ VKD3D_DYNAMIC_STATE_VIEWPORT_COUNT, VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT_EXT },
{ VKD3D_DYNAMIC_STATE_SCISSOR_COUNT, VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT_EXT },
{ VKD3D_DYNAMIC_STATE_BLEND_CONSTANTS, VK_DYNAMIC_STATE_BLEND_CONSTANTS },
{ VKD3D_DYNAMIC_STATE_STENCIL_REFERENCE, VK_DYNAMIC_STATE_STENCIL_REFERENCE },
{ VKD3D_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_DEPTH_BOUNDS },
{ VKD3D_DYNAMIC_STATE_TOPOLOGY, VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT },
{ VKD3D_DYNAMIC_STATE_VERTEX_BUFFER_STRIDE, VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT },
{ VKD3D_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_VIEWPORT },
{ VKD3D_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_SCISSOR },
{ VKD3D_DYNAMIC_STATE_VIEWPORT_COUNT, VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT_EXT },
{ VKD3D_DYNAMIC_STATE_SCISSOR_COUNT, VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT_EXT },
{ VKD3D_DYNAMIC_STATE_BLEND_CONSTANTS, VK_DYNAMIC_STATE_BLEND_CONSTANTS },
{ VKD3D_DYNAMIC_STATE_STENCIL_REFERENCE, VK_DYNAMIC_STATE_STENCIL_REFERENCE },
{ VKD3D_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_DEPTH_BOUNDS },
{ VKD3D_DYNAMIC_STATE_TOPOLOGY, VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT },
{ VKD3D_DYNAMIC_STATE_VERTEX_BUFFER_STRIDE, VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT },
{ VKD3D_DYNAMIC_STATE_FRAGMENT_SHADING_RATE, VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR },
};
dynamic_state_flags = 0;
@ -2494,6 +2496,14 @@ static uint32_t d3d12_graphics_pipeline_state_init_dynamic_state(const struct d3
}
}
/* We always need to enable fragment shading rate dynamic state when rasterizing.
* D3D12 has no information about this ahead of time for a pipeline
* unlike Vulkan.
* Target Independent Rasterization (ForcedSampleCount) is not supported when this is used
* so we don't need to worry about side effects when there are no render targets. */
if (d3d12_device_supports_variable_shading_rate_tier_1(state->device) && graphics->rt_count)
dynamic_state_flags |= VKD3D_DYNAMIC_STATE_FRAGMENT_SHADING_RATE;
/* Build dynamic state create info */
for (i = 0, count = 0; i < ARRAY_SIZE(dynamic_state_list); i++)
{
@ -3359,7 +3369,7 @@ VkPipeline d3d12_pipeline_state_create_pipeline_variant(struct d3d12_pipeline_st
HRESULT hr;
memcpy(bindings, graphics->attribute_bindings, graphics->attribute_binding_count * sizeof(*bindings));
*dynamic_state_flags = d3d12_graphics_pipeline_state_init_dynamic_state(graphics, &dynamic_create_info,
*dynamic_state_flags = d3d12_graphics_pipeline_state_init_dynamic_state(state, &dynamic_create_info,
dynamic_state_buffer, key);
if (key && !key->dynamic_stride)

View File

@ -118,6 +118,7 @@ struct vkd3d_vulkan_info
bool KHR_deferred_host_operations;
bool KHR_spirv_1_4;
bool KHR_shader_float_controls;
bool KHR_fragment_shading_rate;
/* EXT device extensions */
bool EXT_calibrated_timestamps;
bool EXT_conditional_rendering;
@ -867,16 +868,17 @@ int vkd3d_parse_root_signature_v_1_0(const struct vkd3d_shader_code *dxbc,
enum vkd3d_dynamic_state_flag
{
VKD3D_DYNAMIC_STATE_VIEWPORT = (1 << 0),
VKD3D_DYNAMIC_STATE_SCISSOR = (1 << 1),
VKD3D_DYNAMIC_STATE_BLEND_CONSTANTS = (1 << 2),
VKD3D_DYNAMIC_STATE_STENCIL_REFERENCE = (1 << 3),
VKD3D_DYNAMIC_STATE_DEPTH_BOUNDS = (1 << 4),
VKD3D_DYNAMIC_STATE_TOPOLOGY = (1 << 5),
VKD3D_DYNAMIC_STATE_VERTEX_BUFFER = (1 << 6),
VKD3D_DYNAMIC_STATE_VIEWPORT_COUNT = (1 << 7),
VKD3D_DYNAMIC_STATE_SCISSOR_COUNT = (1 << 8),
VKD3D_DYNAMIC_STATE_VERTEX_BUFFER_STRIDE = (1 << 9),
VKD3D_DYNAMIC_STATE_VIEWPORT = (1 << 0),
VKD3D_DYNAMIC_STATE_SCISSOR = (1 << 1),
VKD3D_DYNAMIC_STATE_BLEND_CONSTANTS = (1 << 2),
VKD3D_DYNAMIC_STATE_STENCIL_REFERENCE = (1 << 3),
VKD3D_DYNAMIC_STATE_DEPTH_BOUNDS = (1 << 4),
VKD3D_DYNAMIC_STATE_TOPOLOGY = (1 << 5),
VKD3D_DYNAMIC_STATE_VERTEX_BUFFER = (1 << 6),
VKD3D_DYNAMIC_STATE_VIEWPORT_COUNT = (1 << 7),
VKD3D_DYNAMIC_STATE_SCISSOR_COUNT = (1 << 8),
VKD3D_DYNAMIC_STATE_VERTEX_BUFFER_STRIDE = (1 << 9),
VKD3D_DYNAMIC_STATE_FRAGMENT_SHADING_RATE = (1 << 10),
};
struct vkd3d_shader_debug_ring_spec_constants
@ -1245,6 +1247,12 @@ struct vkd3d_dynamic_state
D3D12_PRIMITIVE_TOPOLOGY primitive_topology;
VkPrimitiveTopology vk_primitive_topology;
struct
{
VkExtent2D fragment_size;
VkFragmentShadingRateCombinerOpKHR combiner_ops[D3D12_RS_SET_SHADING_RATE_COMBINER_COUNT];
} fragment_shading_rate;
};
/* ID3D12CommandList */
@ -2031,6 +2039,7 @@ struct vkd3d_physical_device_info
VkPhysicalDeviceRayTracingPipelinePropertiesKHR ray_tracing_pipeline_properties;
VkPhysicalDeviceAccelerationStructurePropertiesKHR acceleration_structure_properties;
VkPhysicalDeviceFloatControlsPropertiesKHR float_control_properties;
VkPhysicalDeviceFragmentShadingRatePropertiesKHR fragment_shading_rate_properties;
VkPhysicalDeviceProperties2KHR properties2;
@ -2054,11 +2063,14 @@ struct vkd3d_physical_device_info
VkPhysicalDeviceMutableDescriptorTypeFeaturesVALVE mutable_descriptor_features;
VkPhysicalDeviceRayTracingPipelineFeaturesKHR ray_tracing_pipeline_features;
VkPhysicalDeviceAccelerationStructureFeaturesKHR acceleration_structure_features;
VkPhysicalDeviceFragmentShadingRateFeaturesKHR fragment_shading_rate_features;
VkPhysicalDeviceFeatures2 features2;
/* others, for extensions that have no feature bits */
uint32_t time_domains; /* vkd3d_time_domain_flag */
bool additional_shading_rates_supported; /* d3d12 additional fragment shading rates cap */
};
struct d3d12_caps
@ -2197,6 +2209,8 @@ static inline bool d3d12_device_use_ssbo_root_descriptors(struct d3d12_device *d
d3d12_device_get_ssbo_alignment(device) <= 4;
}
bool d3d12_device_supports_variable_shading_rate_tier_1(struct d3d12_device *device);
/* ID3DBlob */
struct d3d_blob
{

View File

@ -205,6 +205,10 @@ VK_DEVICE_EXT_PFN(vkCmdSetRayTracingPipelineStackSizeKHR)
VK_DEVICE_EXT_PFN(vkCmdTraceRaysKHR)
VK_DEVICE_EXT_PFN(vkCmdTraceRaysIndirectKHR)
/* VK_KHR_fragment_shading_rate */
VK_INSTANCE_PFN(vkGetPhysicalDeviceFragmentShadingRatesKHR)
VK_DEVICE_EXT_PFN(vkCmdSetFragmentShadingRateKHR)
/* VK_EXT_calibrated_timestamps */
VK_DEVICE_EXT_PFN(vkGetCalibratedTimestampsEXT)
VK_INSTANCE_EXT_PFN(vkGetPhysicalDeviceCalibrateableTimeDomainsEXT)