layers: Implement `VkViewport` parameter_validation
diff --git a/layers/parameter_validation_utils.cpp b/layers/parameter_validation_utils.cpp
index 65df05d..bf90ce1 100644
--- a/layers/parameter_validation_utils.cpp
+++ b/layers/parameter_validation_utils.cpp
@@ -1011,6 +1011,169 @@
return skip;
}
+bool pv_VkViewport(const layer_data *device_data, const VkViewport &viewport, const char *fn_name, const char *param_name,
+ VkDebugReportObjectTypeEXT object_type, uint64_t object = 0) {
+ bool skip = false;
+ debug_report_data *report_data = device_data->report_data;
+
+ // Note: for numerical correctness
+ // - float comparisons should expect NaN (comparison always false).
+ // - VkPhysicalDeviceLimits::maxViewportDimensions is uint32_t, not float -> careful.
+
+ const auto f_lte_u32_exact = [](const float v1_f, const uint32_t v2_u32) {
+ if (isnan(v1_f)) return false;
+ if (v1_f <= 0.0f) return true;
+
+ float intpart;
+ const float fract = modff(v1_f, &intpart);
+
+ assert(std::numeric_limits<float>::radix == 2);
+ const float u32_max_plus1 = ldexpf(1.0f, 32); // hopefully exact
+ if (intpart >= u32_max_plus1) return false;
+
+ uint32_t v1_u32 = static_cast<uint32_t>(intpart);
+ if (v1_u32 < v2_u32)
+ return true;
+ else if (v1_u32 == v2_u32 && fract == 0.0f)
+ return true;
+ else
+ return false;
+ };
+
+ const auto f_lte_u32_direct = [](const float v1_f, const uint32_t v2_u32) {
+ const float v2_f = static_cast<float>(v2_u32); // not accurate for > radix^digits; and undefined rounding mode
+ return (v1_f <= v2_f);
+ };
+
+ // width
+ bool width_healthy = true;
+ const auto max_w = device_data->device_limits.maxViewportDimensions[0];
+
+ if (!(viewport.width > 0.0f)) {
+ width_healthy = false;
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, object_type, object, __LINE__, VALIDATION_ERROR_15000dd4,
+ LayerName, "%s: %s.width (=%f) is not greater than 0.0. %s", fn_name, param_name, viewport.width,
+ validation_error_map[VALIDATION_ERROR_15000dd4]);
+ } else if (!(f_lte_u32_exact(viewport.width, max_w) || f_lte_u32_direct(viewport.width, max_w))) {
+ width_healthy = false;
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, object_type, object, __LINE__, VALIDATION_ERROR_15000dd6,
+ LayerName, "%s: %s.width (=%f) exceeds VkPhysicalDeviceLimits::maxViewportDimensions[0] (=%" PRIu32 "). %s",
+ fn_name, param_name, viewport.width, max_w, validation_error_map[VALIDATION_ERROR_15000dd6]);
+ } else if (!f_lte_u32_exact(viewport.width, max_w) && f_lte_u32_direct(viewport.width, max_w)) {
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_WARNING_BIT_EXT, object_type, object, __LINE__, NONE, LayerName,
+ "%s: %s.width (=%f) technically exceeds VkPhysicalDeviceLimits::maxViewportDimensions[0] (=%" PRIu32
+ "), but it is within the static_cast<float>(maxViewportDimensions[0]) limit. %s",
+ fn_name, param_name, viewport.width, max_w, validation_error_map[VALIDATION_ERROR_15000dd6]);
+ }
+
+ // height
+ bool height_healthy = true;
+ const bool negative_height_enabled =
+ device_data->extensions.vk_khr_maintenance1 || device_data->extensions.vk_amd_negative_viewport_height;
+ const auto max_h = device_data->device_limits.maxViewportDimensions[1];
+
+ if (!negative_height_enabled && !(viewport.height > 0.0f)) {
+ height_healthy = false;
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, object_type, object, __LINE__, VALIDATION_ERROR_15000dd8,
+ LayerName, "%s: %s.height (=%f) is not greater 0.0. %s", fn_name, param_name, viewport.height,
+ validation_error_map[VALIDATION_ERROR_15000dd8]);
+ } else if (!(f_lte_u32_exact(fabsf(viewport.height), max_h) || f_lte_u32_direct(fabsf(viewport.height), max_h))) {
+ height_healthy = false;
+
+ skip |= log_msg(
+ report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, object_type, object, __LINE__, VALIDATION_ERROR_15000dda, LayerName,
+ "%s: Absolute value of %s.height (=%f) exceeds VkPhysicalDeviceLimits::maxViewportDimensions[1] (=%" PRIu32 "). %s",
+ fn_name, param_name, viewport.height, max_h, validation_error_map[VALIDATION_ERROR_15000dda]);
+ } else if (!f_lte_u32_exact(fabsf(viewport.height), max_h) && f_lte_u32_direct(fabsf(viewport.height), max_h)) {
+ height_healthy = false;
+
+ skip |= log_msg(
+ report_data, VK_DEBUG_REPORT_WARNING_BIT_EXT, object_type, object, __LINE__, NONE, LayerName,
+ "%s: Absolute value of %s.height (=%f) technically exceeds VkPhysicalDeviceLimits::maxViewportDimensions[1] (=%" PRIu32
+ "), but it is within the static_cast<float>(maxViewportDimensions[1]) limit. %s",
+ fn_name, param_name, viewport.height, max_h, validation_error_map[VALIDATION_ERROR_15000dda]);
+ }
+
+ // x
+ bool x_healthy = true;
+ if (!(viewport.x >= device_data->device_limits.viewportBoundsRange[0])) {
+ x_healthy = false;
+ skip |=
+ log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, object_type, object, __LINE__, VALIDATION_ERROR_15000ddc, LayerName,
+ "%s: %s.x (=%f) is less than VkPhysicalDeviceLimits::viewportBoundsRange[0] (=%f). %s", fn_name, param_name,
+ viewport.x, device_data->device_limits.viewportBoundsRange[0], validation_error_map[VALIDATION_ERROR_15000ddc]);
+ }
+
+ // x + width
+ if (x_healthy && width_healthy) {
+ const float right_bound = viewport.x + viewport.width;
+ if (!(right_bound <= device_data->device_limits.viewportBoundsRange[1])) {
+ skip |= log_msg(
+ report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, object_type, object, __LINE__, VALIDATION_ERROR_150009a0, LayerName,
+ "%s: %s.x + %s.width (=%f + %f = %f) is greater than VkPhysicalDeviceLimits::viewportBoundsRange[1] (=%f). %s",
+ fn_name, param_name, param_name, viewport.x, viewport.width, right_bound,
+ device_data->device_limits.viewportBoundsRange[1], validation_error_map[VALIDATION_ERROR_150009a0]);
+ }
+ }
+
+ // y
+ bool y_healthy = true;
+ if (!(viewport.y >= device_data->device_limits.viewportBoundsRange[0])) {
+ y_healthy = false;
+ skip |=
+ log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, object_type, object, __LINE__, VALIDATION_ERROR_15000dde, LayerName,
+ "%s: %s.y (=%f) is less than VkPhysicalDeviceLimits::viewportBoundsRange[0] (=%f). %s", fn_name, param_name,
+ viewport.y, device_data->device_limits.viewportBoundsRange[0], validation_error_map[VALIDATION_ERROR_15000dde]);
+ } else if (negative_height_enabled && !(viewport.y <= device_data->device_limits.viewportBoundsRange[1])) {
+ y_healthy = false;
+ skip |=
+ log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, object_type, object, __LINE__, VALIDATION_ERROR_15000de0, LayerName,
+ "%s: %s.y (=%f) exceeds VkPhysicalDeviceLimits::viewportBoundsRange[1] (=%f). %s", fn_name, param_name,
+ viewport.y, device_data->device_limits.viewportBoundsRange[1], validation_error_map[VALIDATION_ERROR_15000de0]);
+ }
+
+ // y + height
+ if (y_healthy && height_healthy) {
+ const float boundary = viewport.y + viewport.height;
+
+ if (!(boundary <= device_data->device_limits.viewportBoundsRange[1])) {
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, object_type, object, __LINE__, VALIDATION_ERROR_150009a2,
+ LayerName,
+ "%s: %s.y + %s.height (=%f + %f = %f) exceeds VkPhysicalDeviceLimits::viewportBoundsRange[1] (=%f). %s",
+ fn_name, param_name, param_name, viewport.y, viewport.height, boundary,
+ device_data->device_limits.viewportBoundsRange[1], validation_error_map[VALIDATION_ERROR_150009a2]);
+ } else if (negative_height_enabled && !(boundary >= device_data->device_limits.viewportBoundsRange[0])) {
+ skip |= log_msg(
+ report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, object_type, object, __LINE__, VALIDATION_ERROR_15000de2, LayerName,
+ "%s: %s.y + %s.height (=%f + %f = %f) is less than VkPhysicalDeviceLimits::viewportBoundsRange[0] (=%f). %s",
+ fn_name, param_name, param_name, viewport.y, viewport.height, boundary,
+ device_data->device_limits.viewportBoundsRange[0], validation_error_map[VALIDATION_ERROR_15000de2]);
+ }
+ }
+
+ if (!device_data->extensions.vk_ext_depth_range_unrestricted) {
+ // minDepth
+ if (!(viewport.minDepth >= 0.0) || !(viewport.minDepth <= 1.0)) {
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, object_type, object, __LINE__, VALIDATION_ERROR_150009a4,
+ LayerName,
+ "%s: VK_EXT_depth_range_unrestricted extension is not enabled and %s.minDepth (=%f) is not within the "
+ "[0.0, 1.0] range. %s",
+ fn_name, param_name, viewport.minDepth, validation_error_map[VALIDATION_ERROR_150009a4]);
+ }
+
+ // maxDepth
+ if (!(viewport.maxDepth >= 0.0) || !(viewport.maxDepth <= 1.0)) {
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, object_type, object, __LINE__, VALIDATION_ERROR_150009a6,
+ LayerName,
+ "%s: VK_EXT_depth_range_unrestricted extension is not enabled and %s.maxDepth (=%f) is not within the "
+ "[0.0, 1.0] range. %s",
+ fn_name, param_name, viewport.maxDepth, validation_error_map[VALIDATION_ERROR_150009a6]);
+ }
+ }
+
+ return skip;
+}
+
bool pv_vkCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount,
const VkGraphicsPipelineCreateInfo *pCreateInfos, const VkAllocationCallbacks *pAllocator,
VkPipeline *pPipelines) {
@@ -1274,7 +1437,17 @@
i, i, validation_error_map[VALIDATION_ERROR_096005d8]);
}
- // TODO: validate the VkViewports in pViewports here
+ // validate the VkViewports
+ if (!has_dynamic_viewport && viewport_state.pViewports) {
+ for (uint32_t viewport_i = 0; viewport_i < viewport_state.viewportCount; ++viewport_i) {
+ const auto &viewport = viewport_state.pViewports[viewport_i]; // will crash on invalid ptr
+ const char fn_name[] = "vkCreateGraphicsPipelines";
+ const std::string param_name = "pCreateInfos[" + std::to_string(i) + "].pViewportState->pViewports[" +
+ std::to_string(viewport_i) + "]";
+ skip |= pv_VkViewport(device_data, viewport, fn_name, param_name.c_str(),
+ VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT);
+ }
+ }
if (has_dynamic_viewport_w_scaling_nv && !device_data->extensions.vk_nv_clip_space_w_scaling) {
skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT,
@@ -2069,67 +2242,19 @@
firstViewport, validation_error_map[VALIDATION_ERROR_1e000990]);
}
}
-
- if (viewport.width <= 0 || viewport.width > limits.maxViewportDimensions[0]) {
- skip |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, 0,
- __LINE__, VALIDATION_ERROR_15000996, LayerName,
- "vkCmdSetViewport %d: width (%f) exceeds permitted bounds (0,%u). %s", viewportIndex,
- viewport.width, limits.maxViewportDimensions[0], validation_error_map[VALIDATION_ERROR_15000996]);
- }
-
- if (device_data->extensions.vk_amd_negative_viewport_height || device_data->extensions.vk_khr_maintenance1) {
- // Check lower bound against negative viewport height instead of zero
- if (viewport.height <= -(static_cast<int32_t>(limits.maxViewportDimensions[1])) ||
- (viewport.height > limits.maxViewportDimensions[1])) {
- skip |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT,
- VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, 0, __LINE__, VALIDATION_ERROR_1500099a, LayerName,
- "vkCmdSetViewport %d: height (%f) exceeds permitted bounds (-%u,%u). %s", viewportIndex,
- viewport.height, limits.maxViewportDimensions[1], limits.maxViewportDimensions[1],
- validation_error_map[VALIDATION_ERROR_1500099a]);
- }
- } else {
- if ((viewport.height <= 0) || (viewport.height > limits.maxViewportDimensions[1])) {
- skip |=
- log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, 0,
- __LINE__, VALIDATION_ERROR_15000998, LayerName,
- "vkCmdSetViewport %d: height (%f) exceeds permitted bounds (0,%u). %s", viewportIndex,
- viewport.height, limits.maxViewportDimensions[1], validation_error_map[VALIDATION_ERROR_15000998]);
- }
- }
-
- if (viewport.x < limits.viewportBoundsRange[0] || viewport.x > limits.viewportBoundsRange[1]) {
- skip |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, 0,
- __LINE__, VALIDATION_ERROR_1500099e, LayerName,
- "vkCmdSetViewport %d: x (%f) exceeds permitted bounds (%f,%f). %s", viewportIndex, viewport.x,
- limits.viewportBoundsRange[0], limits.viewportBoundsRange[1],
- validation_error_map[VALIDATION_ERROR_1500099e]);
- }
-
- if (viewport.y < limits.viewportBoundsRange[0] || viewport.y > limits.viewportBoundsRange[1]) {
- skip |= log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, 0,
- __LINE__, VALIDATION_ERROR_1500099e, LayerName,
- "vkCmdSetViewport %d: y (%f) exceeds permitted bounds (%f,%f). %s", viewportIndex, viewport.y,
- limits.viewportBoundsRange[0], limits.viewportBoundsRange[1],
- validation_error_map[VALIDATION_ERROR_1500099e]);
- }
-
- if (viewport.x + viewport.width > limits.viewportBoundsRange[1]) {
- skip |=
- log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, 0,
- __LINE__, VALIDATION_ERROR_150009a0, LayerName,
- "vkCmdSetViewport %d: x (%f) + width (%f) exceeds permitted bound (%f). %s", viewportIndex, viewport.x,
- viewport.width, limits.viewportBoundsRange[1], validation_error_map[VALIDATION_ERROR_150009a0]);
- }
-
- if (viewport.y + viewport.height > limits.viewportBoundsRange[1]) {
- skip |=
- log_msg(device_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, 0,
- __LINE__, VALIDATION_ERROR_150009a2, LayerName,
- "vkCmdSetViewport %d: y (%f) + height (%f) exceeds permitted bound (%f). %s", viewportIndex, viewport.y,
- viewport.height, limits.viewportBoundsRange[1], validation_error_map[VALIDATION_ERROR_150009a2]);
- }
}
}
+
+ if (pViewports) {
+ for (uint32_t viewport_i = 0; viewport_i < viewportCount; ++viewport_i) {
+ const auto &viewport = pViewports[viewport_i]; // will crash on invalid ptr
+ const char fn_name[] = "vkCmdSetViewport";
+ const std::string param_name = "pViewports[" + std::to_string(viewport_i) + "]";
+ skip |= pv_VkViewport(device_data, viewport, fn_name, param_name.c_str(),
+ VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, HandleToUint64(commandBuffer));
+ }
+ }
+
return skip;
}