This is page 3 of 7. Use http://codebase.md/mehmetoguzderin/shaderc-vkrunner-mcp?page={x} to view the full context. # Directory Structure ``` ├── .devcontainer │ ├── devcontainer.json │ ├── docker-compose.yml │ └── Dockerfile ├── .gitattributes ├── .github │ └── workflows │ └── build-push-image.yml ├── .gitignore ├── .vscode │ └── mcp.json ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.adoc ├── shaderc-vkrunner-mcp.jpg ├── src │ └── main.rs └── vkrunner ├── .editorconfig ├── .gitignore ├── .gitlab-ci.yml ├── build.rs ├── Cargo.toml ├── COPYING ├── examples │ ├── compute-shader.shader_test │ ├── cooperative-matrix.shader_test │ ├── depth-buffer.shader_test │ ├── desc_set_and_binding.shader_test │ ├── entrypoint.shader_test │ ├── float-framebuffer.shader_test │ ├── frexp.shader_test │ ├── geometry.shader_test │ ├── indices.shader_test │ ├── layouts.shader_test │ ├── properties.shader_test │ ├── push-constants.shader_test │ ├── require-subgroup-size.shader_test │ ├── row-major.shader_test │ ├── spirv.shader_test │ ├── ssbo.shader_test │ ├── tolerance.shader_test │ ├── tricolore.shader_test │ ├── ubo.shader_test │ ├── vertex-data-piglit.shader_test │ └── vertex-data.shader_test ├── include │ ├── vk_video │ │ ├── vulkan_video_codec_av1std_decode.h │ │ ├── vulkan_video_codec_av1std_encode.h │ │ ├── vulkan_video_codec_av1std.h │ │ ├── vulkan_video_codec_h264std_decode.h │ │ ├── vulkan_video_codec_h264std_encode.h │ │ ├── vulkan_video_codec_h264std.h │ │ ├── vulkan_video_codec_h265std_decode.h │ │ ├── vulkan_video_codec_h265std_encode.h │ │ ├── vulkan_video_codec_h265std.h │ │ └── vulkan_video_codecs_common.h │ └── vulkan │ ├── vk_platform.h │ ├── vulkan_core.h │ └── vulkan.h ├── precompile-script.py ├── README.md ├── scripts │ └── update-vulkan.sh ├── src │ └── main.rs ├── test-build.sh └── vkrunner ├── allocate_store.rs ├── buffer.rs ├── compiler │ └── fake_process.rs ├── compiler.rs ├── config.rs ├── context.rs ├── enum_table.rs ├── env_var_test.rs ├── executor.rs ├── fake_vulkan.rs ├── features.rs ├── flush_memory.rs ├── format_table.rs ├── format.rs ├── half_float.rs ├── hex.rs ├── inspect.rs ├── lib.rs ├── logger.rs ├── make-enums.py ├── make-features.py ├── make-formats.py ├── make-pipeline-key-data.py ├── make-vulkan-funcs-data.py ├── parse_num.rs ├── pipeline_key_data.rs ├── pipeline_key.rs ├── pipeline_set.rs ├── requirements.rs ├── result.rs ├── script.rs ├── shader_stage.rs ├── slot.rs ├── small_float.rs ├── source.rs ├── stream.rs ├── temp_file.rs ├── tester.rs ├── tolerance.rs ├── util.rs ├── vbo.rs ├── vk.rs ├── vulkan_funcs_data.rs ├── vulkan_funcs.rs ├── window_format.rs └── window.rs ``` # Files -------------------------------------------------------------------------------- /vkrunner/include/vk_video/vulkan_video_codec_h265std.h: -------------------------------------------------------------------------------- ``` #ifndef VULKAN_VIDEO_CODEC_H265STD_H_ #define VULKAN_VIDEO_CODEC_H265STD_H_ 1 /* ** Copyright 2015-2025 The Khronos Group Inc. ** ** SPDX-License-Identifier: Apache-2.0 */ /* ** This header is generated from the Khronos Vulkan XML API Registry. ** */ #ifdef __cplusplus extern "C" { #endif // vulkan_video_codec_h265std is a preprocessor guard. Do not pass it to API calls. #define vulkan_video_codec_h265std 1 #include "vulkan_video_codecs_common.h" #define STD_VIDEO_H265_CPB_CNT_LIST_SIZE 32 #define STD_VIDEO_H265_SUBLAYERS_LIST_SIZE 7 #define STD_VIDEO_H265_SCALING_LIST_4X4_NUM_LISTS 6 #define STD_VIDEO_H265_SCALING_LIST_4X4_NUM_ELEMENTS 16 #define STD_VIDEO_H265_SCALING_LIST_8X8_NUM_LISTS 6 #define STD_VIDEO_H265_SCALING_LIST_8X8_NUM_ELEMENTS 64 #define STD_VIDEO_H265_SCALING_LIST_16X16_NUM_LISTS 6 #define STD_VIDEO_H265_SCALING_LIST_16X16_NUM_ELEMENTS 64 #define STD_VIDEO_H265_SCALING_LIST_32X32_NUM_LISTS 2 #define STD_VIDEO_H265_SCALING_LIST_32X32_NUM_ELEMENTS 64 #define STD_VIDEO_H265_CHROMA_QP_OFFSET_LIST_SIZE 6 #define STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_COLS_LIST_SIZE 19 #define STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_ROWS_LIST_SIZE 21 #define STD_VIDEO_H265_PREDICTOR_PALETTE_COMPONENTS_LIST_SIZE 3 #define STD_VIDEO_H265_PREDICTOR_PALETTE_COMP_ENTRIES_LIST_SIZE 128 #define STD_VIDEO_H265_MAX_NUM_LIST_REF 15 #define STD_VIDEO_H265_MAX_CHROMA_PLANES 2 #define STD_VIDEO_H265_MAX_SHORT_TERM_REF_PIC_SETS 64 #define STD_VIDEO_H265_MAX_DPB_SIZE 16 #define STD_VIDEO_H265_MAX_LONG_TERM_REF_PICS_SPS 32 #define STD_VIDEO_H265_MAX_LONG_TERM_PICS 16 #define STD_VIDEO_H265_MAX_DELTA_POC 48 #define STD_VIDEO_H265_NO_REFERENCE_PICTURE 0xFF typedef enum StdVideoH265ChromaFormatIdc { STD_VIDEO_H265_CHROMA_FORMAT_IDC_MONOCHROME = 0, STD_VIDEO_H265_CHROMA_FORMAT_IDC_420 = 1, STD_VIDEO_H265_CHROMA_FORMAT_IDC_422 = 2, STD_VIDEO_H265_CHROMA_FORMAT_IDC_444 = 3, STD_VIDEO_H265_CHROMA_FORMAT_IDC_INVALID = 0x7FFFFFFF, STD_VIDEO_H265_CHROMA_FORMAT_IDC_MAX_ENUM = 0x7FFFFFFF } StdVideoH265ChromaFormatIdc; typedef enum StdVideoH265ProfileIdc { STD_VIDEO_H265_PROFILE_IDC_MAIN = 1, STD_VIDEO_H265_PROFILE_IDC_MAIN_10 = 2, STD_VIDEO_H265_PROFILE_IDC_MAIN_STILL_PICTURE = 3, STD_VIDEO_H265_PROFILE_IDC_FORMAT_RANGE_EXTENSIONS = 4, STD_VIDEO_H265_PROFILE_IDC_SCC_EXTENSIONS = 9, STD_VIDEO_H265_PROFILE_IDC_INVALID = 0x7FFFFFFF, STD_VIDEO_H265_PROFILE_IDC_MAX_ENUM = 0x7FFFFFFF } StdVideoH265ProfileIdc; typedef enum StdVideoH265LevelIdc { STD_VIDEO_H265_LEVEL_IDC_1_0 = 0, STD_VIDEO_H265_LEVEL_IDC_2_0 = 1, STD_VIDEO_H265_LEVEL_IDC_2_1 = 2, STD_VIDEO_H265_LEVEL_IDC_3_0 = 3, STD_VIDEO_H265_LEVEL_IDC_3_1 = 4, STD_VIDEO_H265_LEVEL_IDC_4_0 = 5, STD_VIDEO_H265_LEVEL_IDC_4_1 = 6, STD_VIDEO_H265_LEVEL_IDC_5_0 = 7, STD_VIDEO_H265_LEVEL_IDC_5_1 = 8, STD_VIDEO_H265_LEVEL_IDC_5_2 = 9, STD_VIDEO_H265_LEVEL_IDC_6_0 = 10, STD_VIDEO_H265_LEVEL_IDC_6_1 = 11, STD_VIDEO_H265_LEVEL_IDC_6_2 = 12, STD_VIDEO_H265_LEVEL_IDC_INVALID = 0x7FFFFFFF, STD_VIDEO_H265_LEVEL_IDC_MAX_ENUM = 0x7FFFFFFF } StdVideoH265LevelIdc; typedef enum StdVideoH265SliceType { STD_VIDEO_H265_SLICE_TYPE_B = 0, STD_VIDEO_H265_SLICE_TYPE_P = 1, STD_VIDEO_H265_SLICE_TYPE_I = 2, STD_VIDEO_H265_SLICE_TYPE_INVALID = 0x7FFFFFFF, STD_VIDEO_H265_SLICE_TYPE_MAX_ENUM = 0x7FFFFFFF } StdVideoH265SliceType; typedef enum StdVideoH265PictureType { STD_VIDEO_H265_PICTURE_TYPE_P = 0, STD_VIDEO_H265_PICTURE_TYPE_B = 1, STD_VIDEO_H265_PICTURE_TYPE_I = 2, STD_VIDEO_H265_PICTURE_TYPE_IDR = 3, STD_VIDEO_H265_PICTURE_TYPE_INVALID = 0x7FFFFFFF, STD_VIDEO_H265_PICTURE_TYPE_MAX_ENUM = 0x7FFFFFFF } StdVideoH265PictureType; typedef enum StdVideoH265AspectRatioIdc { STD_VIDEO_H265_ASPECT_RATIO_IDC_UNSPECIFIED = 0, STD_VIDEO_H265_ASPECT_RATIO_IDC_SQUARE = 1, STD_VIDEO_H265_ASPECT_RATIO_IDC_12_11 = 2, STD_VIDEO_H265_ASPECT_RATIO_IDC_10_11 = 3, STD_VIDEO_H265_ASPECT_RATIO_IDC_16_11 = 4, STD_VIDEO_H265_ASPECT_RATIO_IDC_40_33 = 5, STD_VIDEO_H265_ASPECT_RATIO_IDC_24_11 = 6, STD_VIDEO_H265_ASPECT_RATIO_IDC_20_11 = 7, STD_VIDEO_H265_ASPECT_RATIO_IDC_32_11 = 8, STD_VIDEO_H265_ASPECT_RATIO_IDC_80_33 = 9, STD_VIDEO_H265_ASPECT_RATIO_IDC_18_11 = 10, STD_VIDEO_H265_ASPECT_RATIO_IDC_15_11 = 11, STD_VIDEO_H265_ASPECT_RATIO_IDC_64_33 = 12, STD_VIDEO_H265_ASPECT_RATIO_IDC_160_99 = 13, STD_VIDEO_H265_ASPECT_RATIO_IDC_4_3 = 14, STD_VIDEO_H265_ASPECT_RATIO_IDC_3_2 = 15, STD_VIDEO_H265_ASPECT_RATIO_IDC_2_1 = 16, STD_VIDEO_H265_ASPECT_RATIO_IDC_EXTENDED_SAR = 255, STD_VIDEO_H265_ASPECT_RATIO_IDC_INVALID = 0x7FFFFFFF, STD_VIDEO_H265_ASPECT_RATIO_IDC_MAX_ENUM = 0x7FFFFFFF } StdVideoH265AspectRatioIdc; typedef struct StdVideoH265DecPicBufMgr { uint32_t max_latency_increase_plus1[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE]; uint8_t max_dec_pic_buffering_minus1[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE]; uint8_t max_num_reorder_pics[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE]; } StdVideoH265DecPicBufMgr; typedef struct StdVideoH265SubLayerHrdParameters { uint32_t bit_rate_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE]; uint32_t cpb_size_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE]; uint32_t cpb_size_du_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE]; uint32_t bit_rate_du_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE]; uint32_t cbr_flag; } StdVideoH265SubLayerHrdParameters; typedef struct StdVideoH265HrdFlags { uint32_t nal_hrd_parameters_present_flag : 1; uint32_t vcl_hrd_parameters_present_flag : 1; uint32_t sub_pic_hrd_params_present_flag : 1; uint32_t sub_pic_cpb_params_in_pic_timing_sei_flag : 1; uint32_t fixed_pic_rate_general_flag : 8; uint32_t fixed_pic_rate_within_cvs_flag : 8; uint32_t low_delay_hrd_flag : 8; } StdVideoH265HrdFlags; typedef struct StdVideoH265HrdParameters { StdVideoH265HrdFlags flags; uint8_t tick_divisor_minus2; uint8_t du_cpb_removal_delay_increment_length_minus1; uint8_t dpb_output_delay_du_length_minus1; uint8_t bit_rate_scale; uint8_t cpb_size_scale; uint8_t cpb_size_du_scale; uint8_t initial_cpb_removal_delay_length_minus1; uint8_t au_cpb_removal_delay_length_minus1; uint8_t dpb_output_delay_length_minus1; uint8_t cpb_cnt_minus1[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE]; uint16_t elemental_duration_in_tc_minus1[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE]; uint16_t reserved[3]; const StdVideoH265SubLayerHrdParameters* pSubLayerHrdParametersNal; const StdVideoH265SubLayerHrdParameters* pSubLayerHrdParametersVcl; } StdVideoH265HrdParameters; typedef struct StdVideoH265VpsFlags { uint32_t vps_temporal_id_nesting_flag : 1; uint32_t vps_sub_layer_ordering_info_present_flag : 1; uint32_t vps_timing_info_present_flag : 1; uint32_t vps_poc_proportional_to_timing_flag : 1; } StdVideoH265VpsFlags; typedef struct StdVideoH265ProfileTierLevelFlags { uint32_t general_tier_flag : 1; uint32_t general_progressive_source_flag : 1; uint32_t general_interlaced_source_flag : 1; uint32_t general_non_packed_constraint_flag : 1; uint32_t general_frame_only_constraint_flag : 1; } StdVideoH265ProfileTierLevelFlags; typedef struct StdVideoH265ProfileTierLevel { StdVideoH265ProfileTierLevelFlags flags; StdVideoH265ProfileIdc general_profile_idc; StdVideoH265LevelIdc general_level_idc; } StdVideoH265ProfileTierLevel; typedef struct StdVideoH265VideoParameterSet { StdVideoH265VpsFlags flags; uint8_t vps_video_parameter_set_id; uint8_t vps_max_sub_layers_minus1; uint8_t reserved1; uint8_t reserved2; uint32_t vps_num_units_in_tick; uint32_t vps_time_scale; uint32_t vps_num_ticks_poc_diff_one_minus1; uint32_t reserved3; const StdVideoH265DecPicBufMgr* pDecPicBufMgr; const StdVideoH265HrdParameters* pHrdParameters; const StdVideoH265ProfileTierLevel* pProfileTierLevel; } StdVideoH265VideoParameterSet; typedef struct StdVideoH265ScalingLists { uint8_t ScalingList4x4[STD_VIDEO_H265_SCALING_LIST_4X4_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_4X4_NUM_ELEMENTS]; uint8_t ScalingList8x8[STD_VIDEO_H265_SCALING_LIST_8X8_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_8X8_NUM_ELEMENTS]; uint8_t ScalingList16x16[STD_VIDEO_H265_SCALING_LIST_16X16_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_16X16_NUM_ELEMENTS]; uint8_t ScalingList32x32[STD_VIDEO_H265_SCALING_LIST_32X32_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_32X32_NUM_ELEMENTS]; uint8_t ScalingListDCCoef16x16[STD_VIDEO_H265_SCALING_LIST_16X16_NUM_LISTS]; uint8_t ScalingListDCCoef32x32[STD_VIDEO_H265_SCALING_LIST_32X32_NUM_LISTS]; } StdVideoH265ScalingLists; typedef struct StdVideoH265SpsVuiFlags { uint32_t aspect_ratio_info_present_flag : 1; uint32_t overscan_info_present_flag : 1; uint32_t overscan_appropriate_flag : 1; uint32_t video_signal_type_present_flag : 1; uint32_t video_full_range_flag : 1; uint32_t colour_description_present_flag : 1; uint32_t chroma_loc_info_present_flag : 1; uint32_t neutral_chroma_indication_flag : 1; uint32_t field_seq_flag : 1; uint32_t frame_field_info_present_flag : 1; uint32_t default_display_window_flag : 1; uint32_t vui_timing_info_present_flag : 1; uint32_t vui_poc_proportional_to_timing_flag : 1; uint32_t vui_hrd_parameters_present_flag : 1; uint32_t bitstream_restriction_flag : 1; uint32_t tiles_fixed_structure_flag : 1; uint32_t motion_vectors_over_pic_boundaries_flag : 1; uint32_t restricted_ref_pic_lists_flag : 1; } StdVideoH265SpsVuiFlags; typedef struct StdVideoH265SequenceParameterSetVui { StdVideoH265SpsVuiFlags flags; StdVideoH265AspectRatioIdc aspect_ratio_idc; uint16_t sar_width; uint16_t sar_height; uint8_t video_format; uint8_t colour_primaries; uint8_t transfer_characteristics; uint8_t matrix_coeffs; uint8_t chroma_sample_loc_type_top_field; uint8_t chroma_sample_loc_type_bottom_field; uint8_t reserved1; uint8_t reserved2; uint16_t def_disp_win_left_offset; uint16_t def_disp_win_right_offset; uint16_t def_disp_win_top_offset; uint16_t def_disp_win_bottom_offset; uint32_t vui_num_units_in_tick; uint32_t vui_time_scale; uint32_t vui_num_ticks_poc_diff_one_minus1; uint16_t min_spatial_segmentation_idc; uint16_t reserved3; uint8_t max_bytes_per_pic_denom; uint8_t max_bits_per_min_cu_denom; uint8_t log2_max_mv_length_horizontal; uint8_t log2_max_mv_length_vertical; const StdVideoH265HrdParameters* pHrdParameters; } StdVideoH265SequenceParameterSetVui; typedef struct StdVideoH265PredictorPaletteEntries { uint16_t PredictorPaletteEntries[STD_VIDEO_H265_PREDICTOR_PALETTE_COMPONENTS_LIST_SIZE][STD_VIDEO_H265_PREDICTOR_PALETTE_COMP_ENTRIES_LIST_SIZE]; } StdVideoH265PredictorPaletteEntries; typedef struct StdVideoH265SpsFlags { uint32_t sps_temporal_id_nesting_flag : 1; uint32_t separate_colour_plane_flag : 1; uint32_t conformance_window_flag : 1; uint32_t sps_sub_layer_ordering_info_present_flag : 1; uint32_t scaling_list_enabled_flag : 1; uint32_t sps_scaling_list_data_present_flag : 1; uint32_t amp_enabled_flag : 1; uint32_t sample_adaptive_offset_enabled_flag : 1; uint32_t pcm_enabled_flag : 1; uint32_t pcm_loop_filter_disabled_flag : 1; uint32_t long_term_ref_pics_present_flag : 1; uint32_t sps_temporal_mvp_enabled_flag : 1; uint32_t strong_intra_smoothing_enabled_flag : 1; uint32_t vui_parameters_present_flag : 1; uint32_t sps_extension_present_flag : 1; uint32_t sps_range_extension_flag : 1; uint32_t transform_skip_rotation_enabled_flag : 1; uint32_t transform_skip_context_enabled_flag : 1; uint32_t implicit_rdpcm_enabled_flag : 1; uint32_t explicit_rdpcm_enabled_flag : 1; uint32_t extended_precision_processing_flag : 1; uint32_t intra_smoothing_disabled_flag : 1; uint32_t high_precision_offsets_enabled_flag : 1; uint32_t persistent_rice_adaptation_enabled_flag : 1; uint32_t cabac_bypass_alignment_enabled_flag : 1; uint32_t sps_scc_extension_flag : 1; uint32_t sps_curr_pic_ref_enabled_flag : 1; uint32_t palette_mode_enabled_flag : 1; uint32_t sps_palette_predictor_initializers_present_flag : 1; uint32_t intra_boundary_filtering_disabled_flag : 1; } StdVideoH265SpsFlags; typedef struct StdVideoH265ShortTermRefPicSetFlags { uint32_t inter_ref_pic_set_prediction_flag : 1; uint32_t delta_rps_sign : 1; } StdVideoH265ShortTermRefPicSetFlags; typedef struct StdVideoH265ShortTermRefPicSet { StdVideoH265ShortTermRefPicSetFlags flags; uint32_t delta_idx_minus1; uint16_t use_delta_flag; uint16_t abs_delta_rps_minus1; uint16_t used_by_curr_pic_flag; uint16_t used_by_curr_pic_s0_flag; uint16_t used_by_curr_pic_s1_flag; uint16_t reserved1; uint8_t reserved2; uint8_t reserved3; uint8_t num_negative_pics; uint8_t num_positive_pics; uint16_t delta_poc_s0_minus1[STD_VIDEO_H265_MAX_DPB_SIZE]; uint16_t delta_poc_s1_minus1[STD_VIDEO_H265_MAX_DPB_SIZE]; } StdVideoH265ShortTermRefPicSet; typedef struct StdVideoH265LongTermRefPicsSps { uint32_t used_by_curr_pic_lt_sps_flag; uint32_t lt_ref_pic_poc_lsb_sps[STD_VIDEO_H265_MAX_LONG_TERM_REF_PICS_SPS]; } StdVideoH265LongTermRefPicsSps; typedef struct StdVideoH265SequenceParameterSet { StdVideoH265SpsFlags flags; StdVideoH265ChromaFormatIdc chroma_format_idc; uint32_t pic_width_in_luma_samples; uint32_t pic_height_in_luma_samples; uint8_t sps_video_parameter_set_id; uint8_t sps_max_sub_layers_minus1; uint8_t sps_seq_parameter_set_id; uint8_t bit_depth_luma_minus8; uint8_t bit_depth_chroma_minus8; uint8_t log2_max_pic_order_cnt_lsb_minus4; uint8_t log2_min_luma_coding_block_size_minus3; uint8_t log2_diff_max_min_luma_coding_block_size; uint8_t log2_min_luma_transform_block_size_minus2; uint8_t log2_diff_max_min_luma_transform_block_size; uint8_t max_transform_hierarchy_depth_inter; uint8_t max_transform_hierarchy_depth_intra; uint8_t num_short_term_ref_pic_sets; uint8_t num_long_term_ref_pics_sps; uint8_t pcm_sample_bit_depth_luma_minus1; uint8_t pcm_sample_bit_depth_chroma_minus1; uint8_t log2_min_pcm_luma_coding_block_size_minus3; uint8_t log2_diff_max_min_pcm_luma_coding_block_size; uint8_t reserved1; uint8_t reserved2; uint8_t palette_max_size; uint8_t delta_palette_max_predictor_size; uint8_t motion_vector_resolution_control_idc; uint8_t sps_num_palette_predictor_initializers_minus1; uint32_t conf_win_left_offset; uint32_t conf_win_right_offset; uint32_t conf_win_top_offset; uint32_t conf_win_bottom_offset; const StdVideoH265ProfileTierLevel* pProfileTierLevel; const StdVideoH265DecPicBufMgr* pDecPicBufMgr; const StdVideoH265ScalingLists* pScalingLists; const StdVideoH265ShortTermRefPicSet* pShortTermRefPicSet; const StdVideoH265LongTermRefPicsSps* pLongTermRefPicsSps; const StdVideoH265SequenceParameterSetVui* pSequenceParameterSetVui; const StdVideoH265PredictorPaletteEntries* pPredictorPaletteEntries; } StdVideoH265SequenceParameterSet; typedef struct StdVideoH265PpsFlags { uint32_t dependent_slice_segments_enabled_flag : 1; uint32_t output_flag_present_flag : 1; uint32_t sign_data_hiding_enabled_flag : 1; uint32_t cabac_init_present_flag : 1; uint32_t constrained_intra_pred_flag : 1; uint32_t transform_skip_enabled_flag : 1; uint32_t cu_qp_delta_enabled_flag : 1; uint32_t pps_slice_chroma_qp_offsets_present_flag : 1; uint32_t weighted_pred_flag : 1; uint32_t weighted_bipred_flag : 1; uint32_t transquant_bypass_enabled_flag : 1; uint32_t tiles_enabled_flag : 1; uint32_t entropy_coding_sync_enabled_flag : 1; uint32_t uniform_spacing_flag : 1; uint32_t loop_filter_across_tiles_enabled_flag : 1; uint32_t pps_loop_filter_across_slices_enabled_flag : 1; uint32_t deblocking_filter_control_present_flag : 1; uint32_t deblocking_filter_override_enabled_flag : 1; uint32_t pps_deblocking_filter_disabled_flag : 1; uint32_t pps_scaling_list_data_present_flag : 1; uint32_t lists_modification_present_flag : 1; uint32_t slice_segment_header_extension_present_flag : 1; uint32_t pps_extension_present_flag : 1; uint32_t cross_component_prediction_enabled_flag : 1; uint32_t chroma_qp_offset_list_enabled_flag : 1; uint32_t pps_curr_pic_ref_enabled_flag : 1; uint32_t residual_adaptive_colour_transform_enabled_flag : 1; uint32_t pps_slice_act_qp_offsets_present_flag : 1; uint32_t pps_palette_predictor_initializers_present_flag : 1; uint32_t monochrome_palette_flag : 1; uint32_t pps_range_extension_flag : 1; } StdVideoH265PpsFlags; typedef struct StdVideoH265PictureParameterSet { StdVideoH265PpsFlags flags; uint8_t pps_pic_parameter_set_id; uint8_t pps_seq_parameter_set_id; uint8_t sps_video_parameter_set_id; uint8_t num_extra_slice_header_bits; uint8_t num_ref_idx_l0_default_active_minus1; uint8_t num_ref_idx_l1_default_active_minus1; int8_t init_qp_minus26; uint8_t diff_cu_qp_delta_depth; int8_t pps_cb_qp_offset; int8_t pps_cr_qp_offset; int8_t pps_beta_offset_div2; int8_t pps_tc_offset_div2; uint8_t log2_parallel_merge_level_minus2; uint8_t log2_max_transform_skip_block_size_minus2; uint8_t diff_cu_chroma_qp_offset_depth; uint8_t chroma_qp_offset_list_len_minus1; int8_t cb_qp_offset_list[STD_VIDEO_H265_CHROMA_QP_OFFSET_LIST_SIZE]; int8_t cr_qp_offset_list[STD_VIDEO_H265_CHROMA_QP_OFFSET_LIST_SIZE]; uint8_t log2_sao_offset_scale_luma; uint8_t log2_sao_offset_scale_chroma; int8_t pps_act_y_qp_offset_plus5; int8_t pps_act_cb_qp_offset_plus5; int8_t pps_act_cr_qp_offset_plus3; uint8_t pps_num_palette_predictor_initializers; uint8_t luma_bit_depth_entry_minus8; uint8_t chroma_bit_depth_entry_minus8; uint8_t num_tile_columns_minus1; uint8_t num_tile_rows_minus1; uint8_t reserved1; uint8_t reserved2; uint16_t column_width_minus1[STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_COLS_LIST_SIZE]; uint16_t row_height_minus1[STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_ROWS_LIST_SIZE]; uint32_t reserved3; const StdVideoH265ScalingLists* pScalingLists; const StdVideoH265PredictorPaletteEntries* pPredictorPaletteEntries; } StdVideoH265PictureParameterSet; #ifdef __cplusplus } #endif #endif ``` -------------------------------------------------------------------------------- /vkrunner/src/main.rs: -------------------------------------------------------------------------------- ```rust // vkrunner // // Copyright (C) 2018 Intel Corporation // Copyright 2023 Neil Roberts // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice (including the next // paragraph) shall be included in all copies or substantial portions of the // Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. use std::ffi::{OsString, OsStr}; use std::collections::HashMap; use std::fmt; use std::process::ExitCode; use std::ptr; use std::rc::Rc; use std::cell::RefCell; use std::ffi::c_void; use std::io::{self, BufWriter}; use std::fs::File; extern crate vkrunner; use vkrunner::{Config, Executor, Source, inspect, result}; #[derive(Debug)] struct Opt { short: Option<char>, long: &'static str, help: &'static str, argument_name: Option<&'static str>, argument_type: ArgumentType, } #[derive(Debug)] enum OptError { UnknownOption(String), MissingArgument(&'static Opt), InvalidUtf8(&'static Opt), InvalidInteger(String), ArgumentForFlag(&'static Opt), } #[derive(Debug)] enum Error { OptError(OptError), InvalidTokenReplacement(String), ShowHelp, TestFailed, IoError(io::Error), BufferNotFound(u32), NoBuffers, ZeroDeviceId, } #[derive(Debug)] enum ArgumentType { Flag, Filename, StringArray, Integer, } #[derive(Debug)] enum ArgumentValue { Flag, Filename(OsString), StringArray(Vec<String>), Integer(u32), } type OptValues = HashMap<&'static str, ArgumentValue>; #[derive(Debug)] struct Options { values: OptValues, scripts: Vec<OsString>, } #[derive(Debug)] struct TokenReplacement<'a> { token: &'a str, replacement: &'a str, } impl fmt::Display for OptError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { OptError::UnknownOption(s) => write!(f, "Unknown option: {}", s), OptError::MissingArgument(o) => { write!(f, "Option --{} requires an argument", o.long) }, OptError::InvalidUtf8(o) => { write!(f, "Invalid UTF-8 in argument to --{}", o.long) }, OptError::InvalidInteger(s) => { write!(f, "Invalid integer: {}", s) }, OptError::ArgumentForFlag(o) => { write!( f, "Option --{} is a flag and can not take an argument", o.long, ) }, } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::OptError(e) => e.fmt(f), Error::InvalidTokenReplacement(s) => { write!(f, "Invalid token replacement: {}", s) }, Error::ShowHelp => format_help(f), Error::TestFailed => { write!(f, "{}", format_result(result::Result::Fail)) }, Error::IoError(e) => e.fmt(f), Error::BufferNotFound(binding) => { write!(f, "No buffer with binding {} was found", binding) }, Error::NoBuffers => { write!( f, "Buffer dump requested but the script has no buffers" ) }, Error::ZeroDeviceId => { write!( f, "Device IDs start from 1 but 0 was specified", ) }, } } } impl From<OptError> for Error { fn from(e: OptError) -> Error { Error::OptError(e) } } impl From<io::Error> for Error { fn from(e: io::Error) -> Error { Error::IoError(e) } } static HELP_OPTION: &'static str = "help"; static IMAGE_OPTION: &'static str = "image"; static BUFFER_OPTION: &'static str = "buffer"; static BINDING_OPTION: &'static str = "binding"; static DISASM_OPTION: &'static str = "disasm"; static REPLACE_OPTION: &'static str = "replace"; static QUIET_OPTION: &'static str = "quiet"; static DEVICE_ID_OPTION: &'static str = "device-id"; static OPTIONS: [Opt; 8] = [ Opt { short: Some('h'), long: HELP_OPTION, help: "Show this help message", argument_name: None, argument_type: ArgumentType::Flag, }, Opt { short: Some('i'), long: IMAGE_OPTION, help: "Write the final rendering to IMG as a PPM image", argument_name: Some("IMG"), argument_type: ArgumentType::Filename, }, Opt { short: Some('b'), long: BUFFER_OPTION, help: "Dump contents of a UBO or SSBO to BUF", argument_name: Some("BUF"), argument_type: ArgumentType::Filename, }, Opt { short: Some('B'), long: BINDING_OPTION, help: "Select which buffer to dump using the -b option. Defaults to \ first buffer", argument_name: Some("BINDING"), argument_type: ArgumentType::Integer, }, Opt { short: Some('d'), long: DISASM_OPTION, help: "Show the SPIR-V disassembly", argument_name: None, argument_type: ArgumentType::Flag, }, Opt { short: Some('D'), long: REPLACE_OPTION, help: "Replace occurences of TOK with REPL in the scripts", argument_name: Some("TOK=REPL"), argument_type: ArgumentType::StringArray, }, Opt { short: Some('q'), long: QUIET_OPTION, help: "Don’t print any non-error information to stdout", argument_name: None, argument_type: ArgumentType::Flag, }, Opt { short: None, long: DEVICE_ID_OPTION, help: "Select the Vulkan device", argument_name: Some("DEVID"), argument_type: ArgumentType::Integer, }, ]; fn format_help(f: &mut fmt::Formatter) -> fmt::Result { writeln!( f, "usage: vkrunner [OPTION]… SCRIPT…\n\ Runs the shader test script SCRIPT\n\ \n\ Options:" )?; let longest_long = OPTIONS .iter() .map(|o| o.long.chars().count()) .max() .unwrap(); let longest_arg = OPTIONS .iter() .map(|o| o.argument_name.map(|n| n.chars().count()).unwrap_or(0)) .max() .unwrap(); for (i, option) in OPTIONS.iter().enumerate() { if i > 0 { writeln!(f)?; } write!(f, " ")?; match option.short { Some(c) => write!(f, "-{},", c)?, None => write!(f, " ")?, } write!( f, "--{:long_width$} {:arg_width$} {}", option.long, option.argument_name.unwrap_or(""), option.help, long_width = longest_long, arg_width = longest_arg, )?; } Ok(()) } fn next_arg<I>( opt: &'static Opt, args: &mut I, opt_arg: Option<&str> ) -> Result<OsString, OptError> where I: Iterator<Item = OsString> { // Use opt_arg if it’s available or else get the next arg from the iterator opt_arg .map(|arg| arg.into()) .or_else(|| args.next()) .ok_or_else(|| OptError::MissingArgument(opt)) } fn process_argument<I>( values: &mut OptValues, args: &mut I, opt: &'static Opt, opt_arg: Option<&str>, ) -> Result<(), OptError> where I: Iterator<Item = OsString> { match opt.argument_type { ArgumentType::Flag => { if opt_arg.is_some() { return Err(OptError::ArgumentForFlag(opt)); } values.insert(opt.long, ArgumentValue::Flag); }, ArgumentType::Filename => { values.insert( opt.long, ArgumentValue::Filename(next_arg(opt, args, opt_arg)?), ); }, ArgumentType::StringArray => { let arg = next_arg(opt, args, opt_arg)?; match arg.to_str() { Some(s) => { values.entry(opt.long) .and_modify(|value| match value { ArgumentValue::StringArray(values) => values.push(s.to_string()), _ => unreachable!(), }) .or_insert_with(|| ArgumentValue::StringArray( vec![s.to_string()] )); }, None => return Err(OptError::InvalidUtf8(opt)), } }, ArgumentType::Integer => { let arg = next_arg(opt, args, opt_arg)?; match arg.to_str() { Some(s) => match s.parse::<u32>() { Ok(value) => { values.insert( opt.long, ArgumentValue::Integer(value), ); }, Err(_) => { return Err(OptError::InvalidInteger( s.to_owned() )); }, }, None => return Err(OptError::InvalidUtf8(opt)), } }, } Ok(()) } fn process_long_arg<I>( values: &mut OptValues, args: &mut I, arg: &str ) -> Result<(), OptError> where I: Iterator<Item = OsString> { let (arg, opt_arg) = match arg.split_once('=') { Some((arg, opt_arg)) => (arg, Some(opt_arg)), None => (arg, None), }; for opt in OPTIONS.iter() { if opt.long.eq(arg) { return process_argument(values, args, opt, opt_arg); } } Err(OptError::UnknownOption(format!("--{}", arg))) } fn process_short_args<I>( values: &mut OptValues, args: &mut I, arg: &str ) -> Result<(), OptError> where I: Iterator<Item = OsString> { if arg.len() == 0 { return Err(OptError::UnknownOption("-".to_string())); } 'arg_loop: for ch in arg.chars() { for opt in OPTIONS.iter() { if let Some(opt_ch) = opt.short { if opt_ch == ch { process_argument(values, args, opt, None)?; continue 'arg_loop; } } } return Err(OptError::UnknownOption(format!("{}", ch))); } Ok(()) } fn parse_options<I>( mut args: I ) -> Result<Options, OptError> where I: Iterator<Item = OsString> { let mut values = HashMap::new(); let mut scripts = Vec::new(); // Skip the first arg if args.next().is_none() { return Ok(Options { values, scripts }); } while let Some(arg) = args.next() { match arg.to_str() { Some(arg_str) => { if arg_str == "--" { scripts.extend(args); break; } else if arg_str.starts_with("--") { process_long_arg(&mut values, &mut args, &arg_str[2..])?; } else if arg_str.starts_with("-") { process_short_args(&mut values, &mut args, &arg_str[1..])?; } else { scripts.push(arg); } }, None => scripts.push(arg), } } Ok(Options { values, scripts }) } impl<'a> InspectData<'a> { fn new(options: &'a Options) -> InspectData<'a> { InspectData { image_filename: match options.values.get(IMAGE_OPTION) { Some(ArgumentValue::Filename(filename)) => { Some(filename) }, _ => None, }, buffer_filename: match options.values.get(BUFFER_OPTION) { Some(ArgumentValue::Filename(filename)) => { Some(filename) }, _ => None, }, buffer_binding: match options.values.get(BINDING_OPTION) { Some(&ArgumentValue::Integer(binding)) => { Some(binding) }, _ => None, }, failed: false, } } } fn get_token_replacements<'a>( values: &'a OptValues ) -> Result<Vec<TokenReplacement<'a>>, Error> { let mut res = Vec::new(); if let Some(ArgumentValue::StringArray(replacements)) = values.get(REPLACE_OPTION) { for replacement in replacements { match replacement.split_once('=') { None => { return Err(Error::InvalidTokenReplacement( replacement.to_owned() )); }, Some((token, replacement)) => res.push(TokenReplacement { token, replacement, }), } } } Ok(res) } struct InspectData<'a> { image_filename: Option<&'a OsStr>, buffer_filename: Option<&'a OsStr>, buffer_binding: Option<u32>, failed: bool, } fn write_ppm( image: &inspect::Image, filename: &OsStr, ) -> Result<(), Error> { let mut file = BufWriter::new(File::create(filename)?); use std::io::Write; write!( &mut file, "P6\n\ {} {}\n\ 255\n", image.width, image.height, )?; let format_size = image.format.size(); for y in 0..image.height { let mut line = unsafe { std::slice::from_raw_parts( (image.data as *const u8).add(y as usize * image.stride), image.width as usize * format_size, ) }; for _ in 0..image.width { let pixel = image.format.load_pixel(&line[0..format_size]); let mut bytes = [0u8; 3]; for (i, component) in pixel[0..3].iter().enumerate() { bytes[i] = (component.clamp(0.0, 1.0) * 255.0).round() as u8; } file.write_all(&bytes)?; line = &line[format_size..]; } } Ok(()) } fn write_buffer( data: &inspect::Data, filename: &OsStr, binding: Option<u32>, ) -> Result<(), Error> { let buffers = unsafe { std::slice::from_raw_parts(data.buffers, data.n_buffers) }; let buffer = match binding { None => match buffers.get(0) { Some(buffer) => buffer, None => return Err(Error::NoBuffers), }, Some(binding) => match buffers.iter().find( |b| b.binding as u32 == binding ) { Some(buffer) => buffer, None => return Err(Error::BufferNotFound(binding)), }, }; let mut file = File::create(filename)?; let data = unsafe { std::slice::from_raw_parts( buffer.data as *const u8, buffer.size, ) }; use std::io::Write; file.write_all(data)?; Ok(()) } extern "C" fn inspect_cb(data: &inspect::Data, user_data: *mut c_void) { let inspect_data = unsafe { &mut *(user_data as *mut InspectData) }; if let Some(filename) = inspect_data.image_filename { match write_ppm(&data.color_buffer, filename) { Err(e) => { eprintln!("{}", e); inspect_data.failed = true; }, Ok(()) => (), } } if let Some(filename) = inspect_data.buffer_filename { match write_buffer(&data, filename, inspect_data.buffer_binding) { Err(e) => { eprintln!("{}", e); inspect_data.failed = true; }, Ok(()) => (), } } } fn set_up_config( config: &Rc<RefCell<Config>>, options: &Options, inspect_data: &mut InspectData, ) -> Result<(), Error> { let mut config = config.borrow_mut(); config.set_inspect_cb(Some(inspect_cb)); config.set_user_data(ptr::addr_of_mut!(*inspect_data).cast()); if let Some(&ArgumentValue::Integer(device_id)) = options.values.get(DEVICE_ID_OPTION) { match device_id.checked_sub(1) { None => return Err(Error::ZeroDeviceId), Some(device_id) => config.set_device_id(Some(device_id as usize)), } } if let Some(ArgumentValue::Flag) = options.values.get(DISASM_OPTION) { config.set_show_disassembly(true); } Ok(()) } fn format_result(result: result::Result) -> String { format!("PIGLIT: {{\"result\": \"{}\" }}", result.name()) } fn add_token_replacements( token_replacements: &[TokenReplacement<'_>], source: &mut Source, ) { for token_replacement in token_replacements.iter() { source.add_token_replacement( token_replacement.token.to_owned(), token_replacement.replacement.to_owned(), ); } } fn run() -> Result<(), Error> { let options = parse_options(std::env::args_os())?; if options.values.contains_key(HELP_OPTION) || options.scripts.is_empty() { return Err(Error::ShowHelp); } let config = Rc::new(RefCell::new(Config::new())); let mut inspect_data = InspectData::new(&options); set_up_config(&config, &options, &mut inspect_data)?; let token_replacements = get_token_replacements(&options.values)?; let mut executor = Executor::new(Rc::clone(&config)); let mut overall_result = result::Result::Skip; for script_filename in options.scripts.iter() { if options.scripts.len() > 1 && !options.values.contains_key(QUIET_OPTION) { println!("{}", script_filename.to_string_lossy()); } let mut source = Source::from_file(script_filename.into()); add_token_replacements(&token_replacements, &mut source); let result = executor.execute(&source); overall_result = overall_result.merge(result); } if inspect_data.failed { overall_result = overall_result.merge(result::Result::Fail); } match overall_result { result::Result::Fail => Err(Error::TestFailed), result::Result::Pass if options.values.contains_key(QUIET_OPTION) => { Ok(()) }, _ => { println!("{}", format_result(overall_result)); Ok(()) }, } } fn main() -> ExitCode { match run() { Ok(()) => ExitCode::SUCCESS, Err(e) => { eprintln!("{}", e); ExitCode::FAILURE }, } } #[cfg(test)] mod test { use super::*; #[test] fn all_arg_types() { let args: Vec<OsString> = vec![ "vkrunner".into(), "-dB".into(), "12".into(), "--image".into(), "screenshot.png".into(), "--replace".into(), "bad=aĉa".into(), "--replace=good=bona".into(), "script.shader_test".into(), ]; let options = parse_options(args.into_iter()).unwrap(); assert!(matches!(&options.values[DISASM_OPTION], ArgumentValue::Flag)); let ArgumentValue::Integer(binding) = &options.values[BINDING_OPTION] else { unreachable!(); }; assert_eq!(*binding, 12); let ArgumentValue::Filename(image) = &options.values[IMAGE_OPTION] else { unreachable!(); }; assert_eq!(image.to_str().unwrap(), "screenshot.png"); let ArgumentValue::StringArray(values) = &options.values[REPLACE_OPTION] else { unreachable!(); }; assert_eq!( values, &vec!["bad=aĉa".to_string(), "good=bona".to_string()] ); assert_eq!(options.scripts.len(), 1); assert_eq!(options.scripts[0].to_str().unwrap(), "script.shader_test"); } #[test] fn multi_arguments() { let args = vec![ "vkrunner".into(), "-ib".into(), "image.png".into(), "buffer.raw".into(), ]; let options = parse_options(args.into_iter()).unwrap(); let ArgumentValue::Filename(image) = &options.values[IMAGE_OPTION] else { unreachable!(); }; assert_eq!(image.to_str().unwrap(), "image.png"); let ArgumentValue::Filename(image) = &options.values[BUFFER_OPTION] else { unreachable!(); }; assert_eq!(image.to_str().unwrap(), "buffer.raw"); } #[test] fn unknown_option() { let args = vec!["vkrunner".into(), "--bad-option".into()].into_iter(); let error = parse_options(args).unwrap_err(); assert_eq!(&error.to_string(), "Unknown option: --bad-option"); let args = vec!["vkrunner".into(), "-d?".into()].into_iter(); let error = parse_options(args).unwrap_err(); assert_eq!(&error.to_string(), "Unknown option: ?"); let args = vec!["vkrunner".into(), "-".into()].into_iter(); let error = parse_options(args).unwrap_err(); assert_eq!(&error.to_string(), "Unknown option: -"); } #[test] fn trailing_arguments() { let args = vec![ "vkrunner".into(), "-i".into(), "image.png".into(), "--".into(), "-i.shader_test".into(), ]; let options = parse_options(args.into_iter()).unwrap(); assert_eq!(options.scripts.len(), 1); assert_eq!(options.scripts[0].to_str().unwrap(), "-i.shader_test"); } #[test] fn missing_argument() { let args = vec!["vkrunner".into(), "--buffer".into()].into_iter(); let error = parse_options(args).unwrap_err(); assert_eq!(&error.to_string(), "Option --buffer requires an argument"); let args = vec!["vkrunner".into(), "-D".into()].into_iter(); let error = parse_options(args).unwrap_err(); assert_eq!(&error.to_string(), "Option --replace requires an argument"); let args = vec!["vkrunner".into(), "-B".into()].into_iter(); let error = parse_options(args).unwrap_err(); assert_eq!(&error.to_string(), "Option --binding requires an argument"); } #[test] #[cfg(unix)] fn invalid_utf8() { use std::os::unix::ffi::OsStrExt; use std::ffi::OsStr; let args = vec![ "vkrunner".into(), "-D".into(), OsStr::from_bytes(b"buffer-\x80.raw").into(), ].into_iter(); let error = parse_options(args).unwrap_err(); assert_eq!( &error.to_string(), "Invalid UTF-8 in argument to --replace", ); let args = vec![ "vkrunner".into(), "-B".into(), OsStr::from_bytes(b"TWELVE-\x80").into(), ].into_iter(); let error = parse_options(args).unwrap_err(); assert_eq!( &error.to_string(), "Invalid UTF-8 in argument to --binding", ); } #[test] fn invalid_integer() { let args = vec![ "vkrunner".into(), "-B".into(), "twelve".into(), ].into_iter(); let error = parse_options(args).unwrap_err(); assert_eq!( &error.to_string(), "Invalid integer: twelve", ); } #[test] fn argument_for_flag() { let args = vec!["vkrunner".into(), "--quiet=yes".into()].into_iter(); let error = parse_options(args).unwrap_err(); assert_eq!( &error.to_string(), "Option --quiet is a flag and can not take an argument", ); } } ``` -------------------------------------------------------------------------------- /vkrunner/vkrunner/vulkan_funcs_data.rs: -------------------------------------------------------------------------------- ```rust // Automatically generated by make-vulkan-funcs-data.py #[derive(Debug, Clone)] #[allow(non_snake_case)] pub struct Library { lib_vulkan: *const c_void, lib_vulkan_is_fake: bool, pub vkGetInstanceProcAddr: vk::PFN_vkGetInstanceProcAddr, pub vkCreateInstance: vk::PFN_vkCreateInstance, pub vkEnumerateInstanceExtensionProperties: vk::PFN_vkEnumerateInstanceExtensionProperties, } #[derive(Debug, Clone)] #[allow(non_snake_case)] pub struct Instance { pub vkCreateDevice: vk::PFN_vkCreateDevice, pub vkDestroyInstance: vk::PFN_vkDestroyInstance, pub vkEnumerateDeviceExtensionProperties: vk::PFN_vkEnumerateDeviceExtensionProperties, pub vkEnumeratePhysicalDevices: vk::PFN_vkEnumeratePhysicalDevices, pub vkGetDeviceProcAddr: vk::PFN_vkGetDeviceProcAddr, pub vkGetPhysicalDeviceFeatures: vk::PFN_vkGetPhysicalDeviceFeatures, pub vkGetPhysicalDeviceFeatures2KHR: vk::PFN_vkGetPhysicalDeviceFeatures2KHR, pub vkGetPhysicalDeviceFormatProperties: vk::PFN_vkGetPhysicalDeviceFormatProperties, pub vkGetPhysicalDeviceMemoryProperties: vk::PFN_vkGetPhysicalDeviceMemoryProperties, pub vkGetPhysicalDeviceProperties: vk::PFN_vkGetPhysicalDeviceProperties, pub vkGetPhysicalDeviceProperties2: vk::PFN_vkGetPhysicalDeviceProperties2, pub vkGetPhysicalDeviceQueueFamilyProperties: vk::PFN_vkGetPhysicalDeviceQueueFamilyProperties, pub vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR: vk::PFN_vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR, } #[derive(Debug, Clone)] #[allow(non_snake_case)] #[allow(dead_code)] pub struct Device { pub vkAllocateCommandBuffers: vk::PFN_vkAllocateCommandBuffers, pub vkAllocateDescriptorSets: vk::PFN_vkAllocateDescriptorSets, pub vkAllocateMemory: vk::PFN_vkAllocateMemory, pub vkBeginCommandBuffer: vk::PFN_vkBeginCommandBuffer, pub vkBindBufferMemory: vk::PFN_vkBindBufferMemory, pub vkBindImageMemory: vk::PFN_vkBindImageMemory, pub vkCmdBeginRenderPass: vk::PFN_vkCmdBeginRenderPass, pub vkCmdBindDescriptorSets: vk::PFN_vkCmdBindDescriptorSets, pub vkCmdBindIndexBuffer: vk::PFN_vkCmdBindIndexBuffer, pub vkCmdBindPipeline: vk::PFN_vkCmdBindPipeline, pub vkCmdBindVertexBuffers: vk::PFN_vkCmdBindVertexBuffers, pub vkCmdClearAttachments: vk::PFN_vkCmdClearAttachments, pub vkCmdCopyBufferToImage: vk::PFN_vkCmdCopyBufferToImage, pub vkCmdCopyImageToBuffer: vk::PFN_vkCmdCopyImageToBuffer, pub vkCmdDispatch: vk::PFN_vkCmdDispatch, pub vkCmdDraw: vk::PFN_vkCmdDraw, pub vkCmdDrawIndexed: vk::PFN_vkCmdDrawIndexed, pub vkCmdDrawIndexedIndirect: vk::PFN_vkCmdDrawIndexedIndirect, pub vkCmdEndRenderPass: vk::PFN_vkCmdEndRenderPass, pub vkCmdPipelineBarrier: vk::PFN_vkCmdPipelineBarrier, pub vkCmdPushConstants: vk::PFN_vkCmdPushConstants, pub vkCmdSetScissor: vk::PFN_vkCmdSetScissor, pub vkCmdSetViewport: vk::PFN_vkCmdSetViewport, pub vkCreateBuffer: vk::PFN_vkCreateBuffer, pub vkCreateCommandPool: vk::PFN_vkCreateCommandPool, pub vkCreateComputePipelines: vk::PFN_vkCreateComputePipelines, pub vkCreateDescriptorPool: vk::PFN_vkCreateDescriptorPool, pub vkCreateDescriptorSetLayout: vk::PFN_vkCreateDescriptorSetLayout, pub vkCreateFence: vk::PFN_vkCreateFence, pub vkCreateFramebuffer: vk::PFN_vkCreateFramebuffer, pub vkCreateGraphicsPipelines: vk::PFN_vkCreateGraphicsPipelines, pub vkCreateImage: vk::PFN_vkCreateImage, pub vkCreateImageView: vk::PFN_vkCreateImageView, pub vkCreatePipelineCache: vk::PFN_vkCreatePipelineCache, pub vkCreatePipelineLayout: vk::PFN_vkCreatePipelineLayout, pub vkCreateRenderPass: vk::PFN_vkCreateRenderPass, pub vkCreateSampler: vk::PFN_vkCreateSampler, pub vkCreateSemaphore: vk::PFN_vkCreateSemaphore, pub vkCreateShaderModule: vk::PFN_vkCreateShaderModule, pub vkDestroyBuffer: vk::PFN_vkDestroyBuffer, pub vkDestroyCommandPool: vk::PFN_vkDestroyCommandPool, pub vkDestroyDescriptorPool: vk::PFN_vkDestroyDescriptorPool, pub vkDestroyDescriptorSetLayout: vk::PFN_vkDestroyDescriptorSetLayout, pub vkDestroyDevice: vk::PFN_vkDestroyDevice, pub vkDestroyFence: vk::PFN_vkDestroyFence, pub vkDestroyFramebuffer: vk::PFN_vkDestroyFramebuffer, pub vkDestroyImage: vk::PFN_vkDestroyImage, pub vkDestroyImageView: vk::PFN_vkDestroyImageView, pub vkDestroyPipeline: vk::PFN_vkDestroyPipeline, pub vkDestroyPipelineCache: vk::PFN_vkDestroyPipelineCache, pub vkDestroyPipelineLayout: vk::PFN_vkDestroyPipelineLayout, pub vkDestroyRenderPass: vk::PFN_vkDestroyRenderPass, pub vkDestroySampler: vk::PFN_vkDestroySampler, pub vkDestroySemaphore: vk::PFN_vkDestroySemaphore, pub vkDestroyShaderModule: vk::PFN_vkDestroyShaderModule, pub vkEndCommandBuffer: vk::PFN_vkEndCommandBuffer, pub vkFlushMappedMemoryRanges: vk::PFN_vkFlushMappedMemoryRanges, pub vkFreeCommandBuffers: vk::PFN_vkFreeCommandBuffers, pub vkFreeDescriptorSets: vk::PFN_vkFreeDescriptorSets, pub vkFreeMemory: vk::PFN_vkFreeMemory, pub vkGetBufferMemoryRequirements: vk::PFN_vkGetBufferMemoryRequirements, pub vkGetDeviceQueue: vk::PFN_vkGetDeviceQueue, pub vkGetImageMemoryRequirements: vk::PFN_vkGetImageMemoryRequirements, pub vkGetImageSubresourceLayout: vk::PFN_vkGetImageSubresourceLayout, pub vkInvalidateMappedMemoryRanges: vk::PFN_vkInvalidateMappedMemoryRanges, pub vkMapMemory: vk::PFN_vkMapMemory, pub vkQueueSubmit: vk::PFN_vkQueueSubmit, pub vkQueueWaitIdle: vk::PFN_vkQueueWaitIdle, pub vkResetFences: vk::PFN_vkResetFences, pub vkUnmapMemory: vk::PFN_vkUnmapMemory, pub vkUpdateDescriptorSets: vk::PFN_vkUpdateDescriptorSets, pub vkWaitForFences: vk::PFN_vkWaitForFences, } impl Instance { pub unsafe fn new( get_instance_proc_cb: GetInstanceProcFunc, user_data: *const c_void, ) -> Instance { Instance { vkCreateDevice: std::mem::transmute(get_instance_proc_cb( "vkCreateDevice\0".as_ptr().cast(), user_data, )), vkDestroyInstance: std::mem::transmute(get_instance_proc_cb( "vkDestroyInstance\0".as_ptr().cast(), user_data, )), vkEnumerateDeviceExtensionProperties: std::mem::transmute(get_instance_proc_cb( "vkEnumerateDeviceExtensionProperties\0".as_ptr().cast(), user_data, )), vkEnumeratePhysicalDevices: std::mem::transmute(get_instance_proc_cb( "vkEnumeratePhysicalDevices\0".as_ptr().cast(), user_data, )), vkGetDeviceProcAddr: std::mem::transmute(get_instance_proc_cb( "vkGetDeviceProcAddr\0".as_ptr().cast(), user_data, )), vkGetPhysicalDeviceFeatures: std::mem::transmute(get_instance_proc_cb( "vkGetPhysicalDeviceFeatures\0".as_ptr().cast(), user_data, )), vkGetPhysicalDeviceFeatures2KHR: std::mem::transmute(get_instance_proc_cb( "vkGetPhysicalDeviceFeatures2KHR\0".as_ptr().cast(), user_data, )), vkGetPhysicalDeviceFormatProperties: std::mem::transmute(get_instance_proc_cb( "vkGetPhysicalDeviceFormatProperties\0".as_ptr().cast(), user_data, )), vkGetPhysicalDeviceMemoryProperties: std::mem::transmute(get_instance_proc_cb( "vkGetPhysicalDeviceMemoryProperties\0".as_ptr().cast(), user_data, )), vkGetPhysicalDeviceProperties: std::mem::transmute(get_instance_proc_cb( "vkGetPhysicalDeviceProperties\0".as_ptr().cast(), user_data, )), vkGetPhysicalDeviceProperties2: std::mem::transmute(get_instance_proc_cb( "vkGetPhysicalDeviceProperties2\0".as_ptr().cast(), user_data, )), vkGetPhysicalDeviceQueueFamilyProperties: std::mem::transmute(get_instance_proc_cb( "vkGetPhysicalDeviceQueueFamilyProperties\0".as_ptr().cast(), user_data, )), vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR: std::mem::transmute(get_instance_proc_cb( "vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR\0".as_ptr().cast(), user_data, )), } } } #[allow(dead_code)] impl Device { pub fn new(instance: &Instance, device: vk::VkDevice) -> Device { Device { vkAllocateCommandBuffers: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkAllocateCommandBuffers\0".as_ptr().cast(), )) }, vkAllocateDescriptorSets: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkAllocateDescriptorSets\0".as_ptr().cast(), )) }, vkAllocateMemory: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkAllocateMemory\0".as_ptr().cast(), )) }, vkBeginCommandBuffer: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkBeginCommandBuffer\0".as_ptr().cast(), )) }, vkBindBufferMemory: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkBindBufferMemory\0".as_ptr().cast(), )) }, vkBindImageMemory: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkBindImageMemory\0".as_ptr().cast(), )) }, vkCmdBeginRenderPass: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdBeginRenderPass\0".as_ptr().cast(), )) }, vkCmdBindDescriptorSets: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdBindDescriptorSets\0".as_ptr().cast(), )) }, vkCmdBindIndexBuffer: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdBindIndexBuffer\0".as_ptr().cast(), )) }, vkCmdBindPipeline: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdBindPipeline\0".as_ptr().cast(), )) }, vkCmdBindVertexBuffers: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdBindVertexBuffers\0".as_ptr().cast(), )) }, vkCmdClearAttachments: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdClearAttachments\0".as_ptr().cast(), )) }, vkCmdCopyBufferToImage: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdCopyBufferToImage\0".as_ptr().cast(), )) }, vkCmdCopyImageToBuffer: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdCopyImageToBuffer\0".as_ptr().cast(), )) }, vkCmdDispatch: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdDispatch\0".as_ptr().cast(), )) }, vkCmdDraw: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdDraw\0".as_ptr().cast(), )) }, vkCmdDrawIndexed: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdDrawIndexed\0".as_ptr().cast(), )) }, vkCmdDrawIndexedIndirect: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdDrawIndexedIndirect\0".as_ptr().cast(), )) }, vkCmdEndRenderPass: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdEndRenderPass\0".as_ptr().cast(), )) }, vkCmdPipelineBarrier: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdPipelineBarrier\0".as_ptr().cast(), )) }, vkCmdPushConstants: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdPushConstants\0".as_ptr().cast(), )) }, vkCmdSetScissor: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdSetScissor\0".as_ptr().cast(), )) }, vkCmdSetViewport: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCmdSetViewport\0".as_ptr().cast(), )) }, vkCreateBuffer: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateBuffer\0".as_ptr().cast(), )) }, vkCreateCommandPool: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateCommandPool\0".as_ptr().cast(), )) }, vkCreateComputePipelines: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateComputePipelines\0".as_ptr().cast(), )) }, vkCreateDescriptorPool: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateDescriptorPool\0".as_ptr().cast(), )) }, vkCreateDescriptorSetLayout: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateDescriptorSetLayout\0".as_ptr().cast(), )) }, vkCreateFence: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateFence\0".as_ptr().cast(), )) }, vkCreateFramebuffer: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateFramebuffer\0".as_ptr().cast(), )) }, vkCreateGraphicsPipelines: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateGraphicsPipelines\0".as_ptr().cast(), )) }, vkCreateImage: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateImage\0".as_ptr().cast(), )) }, vkCreateImageView: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateImageView\0".as_ptr().cast(), )) }, vkCreatePipelineCache: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreatePipelineCache\0".as_ptr().cast(), )) }, vkCreatePipelineLayout: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreatePipelineLayout\0".as_ptr().cast(), )) }, vkCreateRenderPass: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateRenderPass\0".as_ptr().cast(), )) }, vkCreateSampler: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateSampler\0".as_ptr().cast(), )) }, vkCreateSemaphore: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateSemaphore\0".as_ptr().cast(), )) }, vkCreateShaderModule: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkCreateShaderModule\0".as_ptr().cast(), )) }, vkDestroyBuffer: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyBuffer\0".as_ptr().cast(), )) }, vkDestroyCommandPool: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyCommandPool\0".as_ptr().cast(), )) }, vkDestroyDescriptorPool: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyDescriptorPool\0".as_ptr().cast(), )) }, vkDestroyDescriptorSetLayout: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyDescriptorSetLayout\0".as_ptr().cast(), )) }, vkDestroyDevice: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyDevice\0".as_ptr().cast(), )) }, vkDestroyFence: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyFence\0".as_ptr().cast(), )) }, vkDestroyFramebuffer: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyFramebuffer\0".as_ptr().cast(), )) }, vkDestroyImage: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyImage\0".as_ptr().cast(), )) }, vkDestroyImageView: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyImageView\0".as_ptr().cast(), )) }, vkDestroyPipeline: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyPipeline\0".as_ptr().cast(), )) }, vkDestroyPipelineCache: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyPipelineCache\0".as_ptr().cast(), )) }, vkDestroyPipelineLayout: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyPipelineLayout\0".as_ptr().cast(), )) }, vkDestroyRenderPass: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyRenderPass\0".as_ptr().cast(), )) }, vkDestroySampler: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroySampler\0".as_ptr().cast(), )) }, vkDestroySemaphore: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroySemaphore\0".as_ptr().cast(), )) }, vkDestroyShaderModule: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkDestroyShaderModule\0".as_ptr().cast(), )) }, vkEndCommandBuffer: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkEndCommandBuffer\0".as_ptr().cast(), )) }, vkFlushMappedMemoryRanges: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkFlushMappedMemoryRanges\0".as_ptr().cast(), )) }, vkFreeCommandBuffers: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkFreeCommandBuffers\0".as_ptr().cast(), )) }, vkFreeDescriptorSets: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkFreeDescriptorSets\0".as_ptr().cast(), )) }, vkFreeMemory: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkFreeMemory\0".as_ptr().cast(), )) }, vkGetBufferMemoryRequirements: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkGetBufferMemoryRequirements\0".as_ptr().cast(), )) }, vkGetDeviceQueue: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkGetDeviceQueue\0".as_ptr().cast(), )) }, vkGetImageMemoryRequirements: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkGetImageMemoryRequirements\0".as_ptr().cast(), )) }, vkGetImageSubresourceLayout: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkGetImageSubresourceLayout\0".as_ptr().cast(), )) }, vkInvalidateMappedMemoryRanges: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkInvalidateMappedMemoryRanges\0".as_ptr().cast(), )) }, vkMapMemory: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkMapMemory\0".as_ptr().cast(), )) }, vkQueueSubmit: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkQueueSubmit\0".as_ptr().cast(), )) }, vkQueueWaitIdle: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkQueueWaitIdle\0".as_ptr().cast(), )) }, vkResetFences: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkResetFences\0".as_ptr().cast(), )) }, vkUnmapMemory: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkUnmapMemory\0".as_ptr().cast(), )) }, vkUpdateDescriptorSets: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkUpdateDescriptorSets\0".as_ptr().cast(), )) }, vkWaitForFences: unsafe { std::mem::transmute(instance.vkGetDeviceProcAddr.unwrap()( device, "vkWaitForFences\0".as_ptr().cast(), )) }, } } } ``` -------------------------------------------------------------------------------- /vkrunner/vkrunner/pipeline_key.rs: -------------------------------------------------------------------------------- ```rust // vkrunner // // Copyright (C) 2018 Intel Corporation // Copyright 2023 Neil Roberts // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice (including the next // paragraph) shall be included in all copies or substantial portions of the // Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. use crate::shader_stage; use crate::vk; use crate::parse_num; use crate::hex; use crate::util; use std::fmt; use std::mem; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Type { Graphics, Compute, } /// Notes whether the pipeline will be used to draw a rectangle or /// whether it will use the data in the `[vertex data]` section of the /// script. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Source { Rectangle, VertexData, } /// The failure code returned by [set](Key::set) #[derive(Copy, Clone, Debug)] pub enum SetPropertyError<'a> { /// The property was not recognised by VkRunner NotFound { property: &'a str }, /// The property was recognised but the value string was not in a /// valid format InvalidValue { value: &'a str }, } impl<'a> fmt::Display for SetPropertyError<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { SetPropertyError::NotFound { property } => { write!(f, "Unknown property: {}", property) }, SetPropertyError::InvalidValue { value } => { write!(f, "Invalid value: {}", value) }, } } } enum PropertyType { Bool, Int, Float, } struct Property { prop_type: PropertyType, num: usize, name: &'static str, } include!{"pipeline_key_data.rs"} struct EnumValue { name: &'static str, value: i32, } include!{"enum_table.rs"} /// A set of properties that can be used to create a VkPipeline. The /// intention is that this will work as a key so that the program can /// detect if the same state is used multiple times so it can reuse /// the same key. #[derive(Clone)] pub struct Key { pipeline_type: Type, source: Source, entrypoints: [Option<String>; shader_stage::N_STAGES], bool_properties: [bool; N_BOOL_PROPERTIES], int_properties: [i32; N_INT_PROPERTIES], float_properties: [f32; N_FLOAT_PROPERTIES], } impl Key { pub fn set_pipeline_type(&mut self, pipeline_type: Type) { self.pipeline_type = pipeline_type; } pub fn pipeline_type(&self) -> Type { self.pipeline_type } pub fn set_source(&mut self, source: Source) { self.source = source; } pub fn source(&self) -> Source { self.source } pub fn set_topology(&mut self, topology: vk::VkPrimitiveTopology) { self.int_properties[TOPOLOGY_PROP_NUM] = topology as i32; } pub fn set_patch_control_points(&mut self, patch_control_points: u32) { self.int_properties[PATCH_CONTROL_POINTS_PROP_NUM] = patch_control_points as i32; } pub fn set_entrypoint( &mut self, stage: shader_stage::Stage, entrypoint: String, ) { self.entrypoints[stage as usize] = Some(entrypoint); } pub fn entrypoint(&self, stage: shader_stage::Stage) -> &str { match &self.entrypoints[stage as usize] { Some(s) => &s[..], None => "main", } } fn find_prop( property: &str ) -> Result<&'static Property, SetPropertyError> { PROPERTIES.binary_search_by(|p| p.name.cmp(property)) .and_then(|pos| Ok(&PROPERTIES[pos])) .or_else(|_| Err(SetPropertyError::NotFound { property })) } fn set_bool<'a>( &mut self, prop: &Property, value: &'a str, ) -> Result<(), SetPropertyError<'a>> { let value = if value == "true" { true } else if value == "false" { false } else { match parse_num::parse_i32(value) { Ok((v, tail)) if tail.is_empty() => v != 0, _ => return Err(SetPropertyError::InvalidValue { value }), } }; self.bool_properties[prop.num] = value; Ok(()) } // Looks up the given enum name in ENUM_VALUES using a binary // search. Any trailing characters that can’t be part of an enum // name are cut. If successful it returns the enum value and a // slice containing the part of the name that was cut. Otherwise // returns None. fn lookup_enum(name: &str) -> Option<(i32, &str)> { let length = name .chars() .take_while(|&c| c.is_alphanumeric() || c == '_') .count(); let part = &name[0..length]; ENUM_VALUES.binary_search_by(|enum_value| enum_value.name.cmp(part)) .ok() .and_then(|pos| Some((ENUM_VALUES[pos].value, &name[length..]))) .or_else(|| None) } fn set_int<'a>( &mut self, prop: &Property, value: &'a str, ) -> Result<(), SetPropertyError<'a>> { let mut value_part = value; let mut num_value = 0i32; loop { value_part = value_part.trim_start(); if let Ok((v, tail)) = parse_num::parse_i32(value_part) { num_value |= v; value_part = tail; } else if let Some((v, tail)) = Key::lookup_enum(value_part) { num_value |= v; value_part = tail; } else { break Err(SetPropertyError::InvalidValue { value }); } value_part = value_part.trim_start(); if value_part.is_empty() { self.int_properties[prop.num] = num_value; break Ok(()); } // If there’s anything left after the number it must be // the ‘|’ operator if !value_part.starts_with('|') { break Err(SetPropertyError::InvalidValue { value }); } // Skip the ‘|’ character value_part = &value_part[1..]; } } fn set_float<'a>( &mut self, prop: &Property, value: &'a str, ) -> Result<(), SetPropertyError<'a>> { match hex::parse_f32(value) { Ok((v, tail)) if tail.is_empty() => { self.float_properties[prop.num] = v; Ok(()) }, _ => Err(SetPropertyError::InvalidValue { value }), } } /// Set a property on the pipeline key. The property name is one /// of the members of any of the structs pointed to by /// `VkGraphicsPipelineCreateInfo`. For example, if `prop_name` is /// `"polygonMode"` then it will set the `polygonMode` field of /// the `VkPipelineRasterizationStateCreateInfo` struct pointed to /// by the `VkGraphicsPipelineCreateInfo` struct. /// /// The `value` will be interpreted depending on the type of the /// property. It will be one of the following three basic types: /// /// bool: The value can either be `true`, `false` or an integer /// value. If it’s an integer the bool will be true if the value /// is non-zero. /// /// int: The value can either be an integer value or one of the /// enum names used by the properties. You can also use the `|` /// operator to bitwise or values. This is useful for setting /// flags. For example `VK_COLOR_COMPONENT_R_BIT | /// VK_COLOR_COMPONENT_G_BIT` can be used as a value to set the /// `colorWriteMask` property. /// /// float: The value will be interperted as a floating-point value /// using [hex::parse_f32]. pub fn set<'a>( &mut self, prop_name: &'a str, value: &'a str ) -> Result<(), SetPropertyError<'a>> { let prop = Key::find_prop(prop_name)?; let value = value.trim(); match prop.prop_type { PropertyType::Bool => self.set_bool(prop, value), PropertyType::Int => self.set_int(prop, value), PropertyType::Float => self.set_float(prop, value), } } fn alloc_struct_size( buf: &mut Vec<u8>, struct_type: vk::VkStructureType, size: usize, align: usize ) -> usize { let offset = util::align(buf.len(), align); buf.resize(offset + size, 0); buf[offset..offset + mem::size_of::<vk::VkStructureType>()] .copy_from_slice(&struct_type.to_ne_bytes()); offset } fn alloc_struct<T>( buf: &mut Vec<u8>, struct_type: vk::VkStructureType, ) -> usize { Key::alloc_struct_size( buf, struct_type, mem::size_of::<T>(), mem::align_of::<T>(), ) } fn alloc_create_info() -> Box<[u8]> { let mut buf = Vec::new(); // Allocate all of the structures before setting the pointers // because the addresses will change when the vec grows let base_offset = Key::alloc_struct::<vk::VkGraphicsPipelineCreateInfo>( &mut buf, vk::VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, ); assert_eq!(base_offset, 0); let input_assembly = Key::alloc_struct::<vk::VkPipelineInputAssemblyStateCreateInfo>( &mut buf, vk::VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, ); let tessellation = Key::alloc_struct::<vk::VkPipelineTessellationStateCreateInfo>( &mut buf, vk::VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO, ); let rasterization = Key::alloc_struct::<vk::VkPipelineRasterizationStateCreateInfo>( &mut buf, vk::VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, ); let color_blend = Key::alloc_struct::<vk::VkPipelineColorBlendStateCreateInfo>( &mut buf, vk::VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, ); let color_blend_attachment = Key::alloc_struct::<vk::VkPipelineColorBlendAttachmentState>( &mut buf, 0, // no struture type ); let depth_stencil = Key::alloc_struct::<vk::VkPipelineDepthStencilStateCreateInfo>( &mut buf, vk::VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, ); let mut buf = buf.into_boxed_slice(); let create_info = unsafe { &mut *(buf.as_mut_ptr() as *mut vk::VkGraphicsPipelineCreateInfo) }; let base_ptr = buf.as_ptr() as *const u8; // SAFETY: The pointer adds should all be within the single // allocated buf unsafe { create_info.pInputAssemblyState = base_ptr.add(input_assembly).cast(); create_info.pTessellationState = base_ptr.add(tessellation).cast(); create_info.pRasterizationState = base_ptr.add(rasterization).cast(); create_info.pColorBlendState = base_ptr.add(color_blend).cast(); create_info.pDepthStencilState = base_ptr.add(depth_stencil).cast(); // We need to transmute to get rid of the const let color_blend: &mut vk::VkPipelineColorBlendStateCreateInfo = mem::transmute(create_info.pColorBlendState); color_blend.pAttachments = base_ptr.add(color_blend_attachment).cast(); } buf } /// Allocates a `VkGraphicsPipelineCreateInfo` struct inside a /// boxed u8 slice along with the /// `VkPipelineInputAssemblyStateCreateInfo`, /// `VkPipelineTessellationStateCreateInfo`, /// `VkPipelineRasterizationStateCreateInfo`, /// `VkPipelineColorBlendStateCreateInfo`, /// `VkPipelineColorBlendAttachmentState` and /// `VkPipelineDepthStencilStateCreateInfo` structs that it points /// to. The properties from the pipeline key are filled in and the /// `sType` fields are given the appropriate values. All other /// fields are initialised to zero. The structs need to be in a /// box because they contain pointers to each other which means /// the structs won’t work correctly if the array is moved to a /// different address. The `VkGraphicsPipelineCreateInfo` is at /// the start of the array and the other structs can be found by /// following the internal pointers. pub fn to_create_info(&self) -> Box<[u8]> { let mut buf = Key::alloc_create_info(); let create_info = unsafe { &mut *(buf.as_mut_ptr() as *mut vk::VkGraphicsPipelineCreateInfo) }; unsafe { mem::transmute::<_, &mut vk::VkPipelineColorBlendStateCreateInfo>( create_info.pColorBlendState ).attachmentCount = 1; } copy_properties_to_create_info(self, create_info); buf } } impl PartialEq for Key { fn eq(&self, other: &Key) -> bool { if self.pipeline_type != other.pipeline_type { return false; } match self.pipeline_type { Type::Graphics => { if self.source != other.source || self.bool_properties != other.bool_properties || self.int_properties != other.int_properties || self.float_properties != other.float_properties { return false; } // Check the entrypoints for all of the stages except // the compute stage because that doesn’t affect // pipelines used for graphics. for i in 0..shader_stage::N_STAGES { if i == shader_stage::Stage::Compute as usize { continue; } if self.entrypoints[i].ne(&other.entrypoints[i]) { return false; } } true }, Type::Compute => { // All of the properties only have an effect when the // pipeline is used for graphics so the only thing we // care about is the compute entrypoint. self.entrypoints[shader_stage::Stage::Compute as usize].eq( &other.entrypoints[shader_stage::Stage::Compute as usize] ) }, } } } // Custom debug implementation for Key that reports the properties // using the property names instead of confusing arrays. impl fmt::Debug for Key { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "Key {{ pipeline_type: {:?}, \ source: {:?}, \ entrypoints: {:?}", self.pipeline_type, self.source, &self.entrypoints, )?; for prop in PROPERTIES.iter() { write!(f, ", {}: ", prop.name)?; match prop.prop_type { PropertyType::Bool => { write!(f, "{}", self.bool_properties[prop.num])?; }, PropertyType::Int => { write!(f, "{}", self.int_properties[prop.num])?; }, PropertyType::Float => { write!(f, "{}", self.float_properties[prop.num])?; }, } } write!(f, " }}") } } #[cfg(test)] mod test { use super::*; use shader_stage::Stage; fn get_create_info_topology(key: &Key) -> vk::VkPrimitiveTopology { let s = key.to_create_info(); let create_info = unsafe { mem::transmute::<_, &vk::VkGraphicsPipelineCreateInfo>( s.as_ptr() ) }; unsafe { (*create_info.pInputAssemblyState).topology } } fn get_create_info_pcp(key: &Key) -> u32 { let s = key.to_create_info(); let create_info = unsafe { mem::transmute::<_, &vk::VkGraphicsPipelineCreateInfo>( s.as_ptr() ) }; unsafe { (*create_info.pTessellationState).patchControlPoints } } #[test] fn test_key_base() { let mut key = Key::default(); key.set_pipeline_type(Type::Graphics); assert_eq!(key.pipeline_type(), Type::Graphics); key.set_pipeline_type(Type::Compute); assert_eq!(key.pipeline_type(), Type::Compute); key.set_source(Source::Rectangle); assert_eq!(key.source(), Source::Rectangle); key.set_source(Source::VertexData); assert_eq!(key.source(), Source::VertexData); key.set_topology(vk::VK_PRIMITIVE_TOPOLOGY_POINT_LIST); assert_eq!( get_create_info_topology(&key), vk::VK_PRIMITIVE_TOPOLOGY_POINT_LIST, ); key.set_topology(vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP); assert_eq!( get_create_info_topology(&key), vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, ); key.set_patch_control_points(5); assert_eq!(get_create_info_pcp(&key), 5); key.set_patch_control_points(u32::MAX); assert_eq!(get_create_info_pcp(&key), u32::MAX); key.set_entrypoint(Stage::Vertex, "mystery_vortex".to_owned()); assert_eq!(key.entrypoint(Stage::Vertex), "mystery_vortex"); key.set_entrypoint(Stage::Fragment, "fraggle_rock".to_owned()); assert_eq!(key.entrypoint(Stage::Fragment), "fraggle_rock"); assert_eq!(key.entrypoint(Stage::Geometry), "main"); } #[test] fn test_all_props() { let mut key = Key::default(); // Check that setting all of the props works without returning // an error for prop in PROPERTIES.iter() { assert!(key.set(prop.name, "1").is_ok()); } } fn check_bool_prop(value: &str) -> vk::VkBool32 { let mut key = Key::default(); assert!(key.set("depthTestEnable", value).is_ok()); let s = key.to_create_info(); let create_info = unsafe { mem::transmute::<_, &vk::VkGraphicsPipelineCreateInfo>( s.as_ptr() ) }; unsafe { (*create_info.pDepthStencilState).depthTestEnable } } #[test] fn test_bool_props() { assert_eq!(check_bool_prop("true"), 1); assert_eq!(check_bool_prop("false"), 0); assert_eq!(check_bool_prop(" true "), 1); assert_eq!(check_bool_prop(" false "), 0); assert_eq!(check_bool_prop("1"), 1); assert_eq!(check_bool_prop("42"), 1); assert_eq!(check_bool_prop(" 0x42 "), 1); assert_eq!(check_bool_prop("0"), 0); assert_eq!(check_bool_prop(" -0 "), 0); let e = Key::default().set("depthTestEnable", "foo").unwrap_err(); assert_eq!(e.to_string(), "Invalid value: foo"); let e = Key::default().set("stencilTestEnable", "9 foo").unwrap_err(); assert_eq!(e.to_string(), "Invalid value: 9 foo"); let e = Key::default().set("stencilTestEnable", "true fo").unwrap_err(); assert_eq!(e.to_string(), "Invalid value: true fo"); } fn check_int_prop(value: &str) -> u32 { let mut key = Key::default(); assert!(key.set("patchControlPoints", value).is_ok()); get_create_info_pcp(&key) } #[test] fn test_int_props() { assert_eq!(check_int_prop("0"), 0); assert_eq!(check_int_prop("1"), 1); assert_eq!(check_int_prop("-1"), u32::MAX); assert_eq!(check_int_prop(" 42 "), 42); assert_eq!(check_int_prop(" 8 | 1 "), 9); assert_eq!(check_int_prop("6|16|1"), 23); assert_eq!(check_int_prop("010"), 8); assert_eq!(check_int_prop("0x80|010"), 0x88); assert_eq!( check_int_prop("VK_COLOR_COMPONENT_R_BIT"), vk::VK_COLOR_COMPONENT_R_BIT, ); assert_eq!( check_int_prop( "VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT" ), vk::VK_COLOR_COMPONENT_R_BIT | vk::VK_COLOR_COMPONENT_G_BIT, ); let e = Key::default().set("patchControlPoints", "").unwrap_err(); assert_eq!(e.to_string(), "Invalid value: "); let e = Key::default().set("patchControlPoints", "9 |").unwrap_err(); assert_eq!(e.to_string(), "Invalid value: 9 |"); let e = Key::default().set("patchControlPoints", "|9").unwrap_err(); assert_eq!(e.to_string(), "Invalid value: |9"); let e = Key::default().set("patchControlPoints", "9|foo").unwrap_err(); assert_eq!(e.to_string(), "Invalid value: 9|foo"); let e = Key::default().set("patchControlPoints", "9foo").unwrap_err(); assert_eq!(e.to_string(), "Invalid value: 9foo"); let mut key = Key::default(); // Check that all enum values can be set without error for e in ENUM_VALUES.iter() { assert!(key.set("srcColorBlendFactor", e.name).is_ok()); } } fn check_float_prop(value: &str) -> f32 { let mut key = Key::default(); assert!(key.set("depthBiasClamp", value).is_ok()); let s = key.to_create_info(); let create_info = unsafe { mem::transmute::<_, &vk::VkGraphicsPipelineCreateInfo>( s.as_ptr() ) }; unsafe { (*create_info.pRasterizationState).depthBiasClamp } } #[test] fn test_float_props() { assert_eq!(check_float_prop("1"), 1.0); assert_eq!(check_float_prop("-1"), -1.0); assert_eq!(check_float_prop("1.0e1"), 10.0); assert_eq!(check_float_prop(" 0x3F800000 "), 1.0); let e = Key::default().set("lineWidth", "0.3 foo").unwrap_err(); assert_eq!(e.to_string(), "Invalid value: 0.3 foo"); let e = Key::default().set("lineWidth", "foo").unwrap_err(); assert_eq!(e.to_string(), "Invalid value: foo"); } #[test] fn test_unknown_property() { let e = Key::default().set("unicornCount", "2").unwrap_err(); assert_eq!(e.to_string(), "Unknown property: unicornCount"); } #[test] fn test_struct_types() { let s = Key::default().to_create_info(); let create_info = unsafe { mem::transmute::<_, &vk::VkGraphicsPipelineCreateInfo>( s.as_ptr() ) }; unsafe { assert_eq!( (*create_info.pDepthStencilState).sType, vk::VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, ); assert_eq!( (*create_info.pColorBlendState).sType, vk::VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, ); } } #[test] fn test_eq() { let mut key_a = Key::default(); let mut key_b = key_a.clone(); assert!(key_a.eq(&key_b)); key_a.set_source(Source::VertexData); assert!(!key_a.eq(&key_b)); key_b.set_source(Source::VertexData); assert!(key_a.eq(&key_b)); assert!(key_a.set("depthClampEnable", "true").is_ok()); assert!(!key_a.eq(&key_b)); assert!(key_b.set("depthClampEnable", "true").is_ok()); assert!(key_a.eq(&key_b)); assert!(key_a.set("colorWriteMask", "1").is_ok()); assert!(!key_a.eq(&key_b)); assert!(key_b.set("colorWriteMask", "1").is_ok()); assert!(key_a.eq(&key_b)); assert!(key_a.set("lineWidth", "3.0").is_ok()); assert!(!key_a.eq(&key_b)); assert!(key_b.set("lineWidth", "3.0").is_ok()); assert!(key_a.eq(&key_b)); key_a.set_entrypoint(Stage::TessEval, "durberville".to_owned()); assert!(!key_a.eq(&key_b)); key_b.set_entrypoint(Stage::TessEval, "durberville".to_owned()); assert!(key_a.eq(&key_b)); // Setting the compute entry point shouldn’t affect the // equality for graphics pipelines key_a.set_entrypoint(Stage::Compute, "saysno".to_owned()); assert!(key_a.eq(&key_b)); key_b.set_entrypoint(Stage::Compute, "saysno".to_owned()); assert!(key_a.eq(&key_b)); key_a.set_pipeline_type(Type::Compute); assert!(!key_a.eq(&key_b)); key_b.set_pipeline_type(Type::Compute); assert!(key_a.eq(&key_b)); // Setting properties shouldn’t affect the equality for compute shaders assert!(key_a.set("lineWidth", "5.0").is_ok()); assert!(key_a.eq(&key_b)); key_a.set_source(Source::Rectangle); assert!(key_a.eq(&key_b)); key_a.set_entrypoint(Stage::TessCtrl, "yes".to_owned()); assert!(key_a.eq(&key_b)); // Setting the compute entrypoint however should affect the equality key_a.set_entrypoint(Stage::Compute, "rclub".to_owned()); assert!(!key_a.eq(&key_b)); key_b.set_entrypoint(Stage::Compute, "rclub".to_owned()); assert!(key_a.eq(&key_b)); } #[test] fn test_debug() { let mut key = Key::default(); assert!(key.set("depthWriteEnable", "true").is_ok()); assert!(key.set("colorWriteMask", "1").is_ok()); assert!(key.set("lineWidth", "42.0").is_ok()); assert_eq!( format!("{:?}", key), "Key { \ pipeline_type: Graphics, \ source: Rectangle, \ entrypoints: [None, None, None, None, None, None], \ alphaBlendOp: 0, \ back.compareMask: -1, \ back.compareOp: 7, \ back.depthFailOp: 0, \ back.failOp: 0, \ back.passOp: 0, \ back.reference: 0, \ back.writeMask: -1, \ blendEnable: false, \ colorBlendOp: 0, \ colorWriteMask: 1, \ cullMode: 0, \ depthBiasClamp: 0, \ depthBiasConstantFactor: 0, \ depthBiasEnable: false, \ depthBiasSlopeFactor: 0, \ depthBoundsTestEnable: false, \ depthClampEnable: false, \ depthCompareOp: 1, \ depthTestEnable: false, \ depthWriteEnable: true, \ dstAlphaBlendFactor: 7, \ dstColorBlendFactor: 7, \ front.compareMask: -1, \ front.compareOp: 7, \ front.depthFailOp: 0, \ front.failOp: 0, \ front.passOp: 0, \ front.reference: 0, \ front.writeMask: -1, \ frontFace: 0, \ lineWidth: 42, \ logicOp: 15, \ logicOpEnable: false, \ maxDepthBounds: 0, \ minDepthBounds: 0, \ patchControlPoints: 0, \ polygonMode: 0, \ primitiveRestartEnable: false, \ rasterizerDiscardEnable: false, \ srcAlphaBlendFactor: 6, \ srcColorBlendFactor: 6, \ stencilTestEnable: false, \ topology: 4 \ }", ); } } ``` -------------------------------------------------------------------------------- /vkrunner/vkrunner/vbo.rs: -------------------------------------------------------------------------------- ```rust // Copyright © 2011, 2016, 2018 Intel Corporation // Copyright 2023 Neil Roberts // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice (including the next // paragraph) shall be included in all copies or substantial portions of the // Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // Based on piglit-vbo.cpp //! This module adds the facility for specifying vertex data to //! VkRunner tests using a columnar text format, for example: //! //! ```text //! 0/r32g32b32_sfloat 1/r32_uint 3/int/int 4/int/int //! 0.0 0.0 0.0 10 0 0 # comment //! 0.0 1.0 0.0 5 1 1 //! 1.0 1.0 0.0 0 0 1 //! ``` //! //! The format consists of a row of column headers followed by any //! number of rows of data. Each column header has the form //! `ATTRLOC/FORMAT` where `ATTRLOC` is the location of the vertex //! attribute to be bound to this column and FORMAT is the name of a //! VkFormat minus the `VK_FORMAT` prefix. //! //! Alternatively the column header can use something closer to the //! Piglit format like `ATTRLOC/GL_TYPE/GLSL_TYPE`. `GL_TYPE` is the //! GL type of data that follows (“`half`”, “`float`”, “`double`”, //! “`byte`”, “`ubyte`”, “`short`”, “`ushort`”, “`int`” or “`uint`”), //! `GLSL_TYPE` is the GLSL type of the data (“`int`”, “`uint`”, //! “`float`”, “`double`”, “`ivec*`”, “`uvec*`”, “`vec*`”, “`dvec*`”). //! //! The data follows the column headers in space-separated form. `#` //! can be used for comments, as in shell scripts. //! //! The text can be parsed either by using the [`str::parse::<Vbo>`] //! method to parse an entire string, or by constructing a [Parser] //! object to parse the data line-by-line. use crate::format::{Format, Mode}; use crate::{util, parse_num, hex}; use std::fmt; use std::cell::RefCell; #[derive(Debug, Clone)] pub enum Error { InvalidHeader(String), InvalidData(String), } /// Struct representing a blob of structured data that can be used as /// vertex inputs to Vulkan. The Vbo can be constructed either by /// parsing an entire string slice with the [str::parse] method or by /// parsing the source line-by-line by constructing a [Parser] object. #[derive(Debug)] pub struct Vbo { // Description of each attribute attribs: Box<[Attrib]>, // Raw data buffer containing parsed numbers raw_data: Box<[u8]>, // Number of bytes in each row of raw_data stride: usize, } #[derive(Debug)] pub struct Attrib { format: &'static Format, // Vertex location location: u32, // Byte offset into the vertex data of this attribute offset: usize, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match &self { Error::InvalidHeader(s) => write!(f, "{}", s), Error::InvalidData(s) => write!(f, "{}", s), } } } impl Vbo { #[inline] pub fn attribs(&self) -> &[Attrib] { self.attribs.as_ref() } #[inline] pub fn raw_data(&self) -> &[u8] { self.raw_data.as_ref() } #[inline] pub fn stride(&self) -> usize { self.stride } } impl Attrib { #[inline] pub fn format(&self) -> &'static Format { self.format } #[inline] pub fn location(&self) -> u32 { self.location } #[inline] pub fn offset(&self) -> usize { self.offset } } /// Helper struct to construct a [Vbo] by parsing data line-by-line. /// Construct the parser with [new](Parser::new) and then add each /// line with [parse_line](Parser::parse_line). When the vbo data is /// complete call [into_vbo](Parser::into_vbo) to finish the parsing /// and convert the parser into the final Vbo. #[derive(Debug)] pub struct Parser { // None if we haven’t parsed the header line yet, otherwise an // array of attribs attribs: Option<Box<[Attrib]>>, raw_data: RefCell<Vec<u8>>, stride: usize, } macro_rules! invalid_header { ($data:expr, $($message:expr),+) => { return Err(Error::InvalidHeader(format!($($message),+))) }; } macro_rules! invalid_data { ($data:expr, $($message:expr),+) => { return Err(Error::InvalidData(format!($($message),+))) }; } impl std::str::FromStr for Vbo { type Err = Error; fn from_str(s: &str) -> Result<Vbo, Error> { let mut data = Parser::new(); for line in s.lines() { data.parse_line(line)?; } data.into_vbo() } } impl Parser { pub fn new() -> Parser { Parser { raw_data: RefCell::new(Vec::new()), stride: 0, attribs: None, } } fn trim_line(line: &str) -> &str { let line = line.trim_start(); // Remove comments at the end of the line let line = match line.trim_start().find('#') { Some(end) => &line[0..end], None => line, }; line.trim_end() } fn lookup_gl_type(&self, gl_type: &str) -> Result<(Mode, usize), Error> { struct GlType { name: &'static str, mode: Mode, bit_size: usize, } static GL_TYPES: [GlType; 9] = [ GlType { name: "byte", mode: Mode::SINT, bit_size: 8 }, GlType { name: "ubyte", mode: Mode::UINT, bit_size: 8 }, GlType { name: "short", mode: Mode::SINT, bit_size: 16 }, GlType { name: "ushort", mode: Mode::UINT, bit_size: 16 }, GlType { name: "int", mode: Mode::SINT, bit_size: 32 }, GlType { name: "uint", mode: Mode::UINT, bit_size: 32 }, GlType { name: "half", mode: Mode::SFLOAT, bit_size: 16 }, GlType { name: "float", mode: Mode::SFLOAT, bit_size: 32 }, GlType { name: "double", mode: Mode::SFLOAT, bit_size: 64 }, ]; match GL_TYPES.iter().find(|t| t.name == gl_type) { Some(t) => Ok((t.mode, t.bit_size)), None => invalid_header!(self, "Unknown GL type: {}", gl_type), } } fn components_for_glsl_type( &self, glsl_type: &str ) -> Result<usize, Error> { if ["int", "uint", "float", "double"].contains(&glsl_type) { return Ok(1); } let vec_part = match glsl_type.chars().next() { Some('i' | 'u' | 'd') => &glsl_type[1..], _ => glsl_type, }; if !vec_part.starts_with("vec") { invalid_header!(self, "Unknown GLSL type: {}", glsl_type); } match vec_part[3..].parse::<usize>() { Ok(n) if n >= 2 && n <= 4 => Ok(n), _ => invalid_header!(self, "Invalid vec size: {}", glsl_type), } } fn decode_type( &self, gl_type: &str, glsl_type: &str ) -> Result<&'static Format, Error> { let (mode, bit_size) = self.lookup_gl_type(gl_type)?; let n_components = self.components_for_glsl_type(glsl_type)?; match Format::lookup_by_details(bit_size, mode, n_components) { Some(f) => Ok(f), None => { invalid_header!( self, "Invalid type combo: {}/{}", gl_type, glsl_type ); }, } } fn parse_attrib( &mut self, s: &str, offset: usize ) -> Result<Attrib, Error> { let mut parts = s.split('/'); let location = match parts.next().unwrap().parse::<u32>() { Ok(n) => n, Err(_) => invalid_header!(self, "Invalid attrib location in {}", s), }; let format_name = match parts.next() { Some(n) => n, None => { invalid_header!( self, "Column headers must be in the form \ location/format. Got: {}", s ); }, }; let format = match parts.next() { None => match Format::lookup_by_name(format_name) { None => { invalid_header!( self, "Unknown format: {}", format_name ); }, Some(f) => f, }, Some(glsl_type) => { if let Some(_) = parts.next() { invalid_header!( self, "Extra data at end of column header: {}", s ); } self.decode_type(format_name, glsl_type)? }, }; Ok(Attrib { format, location, offset: util::align(offset, format.alignment()), }) } fn parse_header_line(&mut self, line: &str) -> Result<(), Error> { let mut attribs = Vec::new(); let mut stride = 0; let mut max_alignment = 1; for attrib in line.split_whitespace() { let attrib = self.parse_attrib(attrib, stride)?; stride = attrib.offset + attrib.format.size(); let alignment = attrib.format.alignment(); if alignment > max_alignment { max_alignment = alignment; } attribs.push(attrib); } self.attribs = Some(attribs.into_boxed_slice()); self.stride = util::align(stride, max_alignment); Ok(()) } #[inline] fn write_bytes(data: &mut [u8], bytes: &[u8]) { data[0..bytes.len()].copy_from_slice(bytes); } fn parse_unsigned_datum<'a>( &self, bit_size: usize, text: &'a str, data: &mut [u8], ) -> Result<&'a str, Error> { match bit_size { 8 => match parse_num::parse_u8(text) { Err(_) => { invalid_data!(self, "Couldn’t parse as unsigned byte") }, Ok((v, tail)) => { Parser::write_bytes(data, &v.to_ne_bytes()); Ok(tail) }, }, 16 => match parse_num::parse_u16(text) { Err(_) => { invalid_data!(self, "Couldn’t parse as unsigned short") }, Ok((v, tail)) => { Parser::write_bytes(data, &v.to_ne_bytes()); Ok(tail) }, }, 32 => match parse_num::parse_u32(text) { Err(_) => { invalid_data!(self, "Couldn’t parse as unsigned int") }, Ok((v, tail)) => { Parser::write_bytes(data, &v.to_ne_bytes()); Ok(tail) }, }, 64 => match parse_num::parse_u64(text) { Err(_) => { invalid_data!(self, "Couldn’t parse as unsigned long") }, Ok((v, tail)) => { Parser::write_bytes(data, &v.to_ne_bytes()); Ok(tail) }, }, _ => unreachable!("unexpected bit size {}", bit_size), } } fn parse_signed_datum<'a>( &self, bit_size: usize, text: &'a str, data: &mut [u8], ) -> Result<&'a str, Error> { match bit_size { 8 => match parse_num::parse_i8(text) { Err(_) => { invalid_data!(self, "Couldn’t parse as signed byte") }, Ok((v, tail)) => { Parser::write_bytes(data, &v.to_ne_bytes()); Ok(tail) }, }, 16 => match parse_num::parse_i16(text) { Err(_) => { invalid_data!(self, "Couldn’t parse as signed short") }, Ok((v, tail)) => { Parser::write_bytes(data, &v.to_ne_bytes()); Ok(tail) }, }, 32 => match parse_num::parse_i32(text) { Err(_) => { invalid_data!(self, "Couldn’t parse as signed int") }, Ok((v, tail)) => { Parser::write_bytes(data, &v.to_ne_bytes()); Ok(tail) }, }, 64 => match parse_num::parse_i64(text) { Err(_) => { invalid_data!(self, "Couldn’t parse as signed long") }, Ok((v, tail)) => { Parser::write_bytes(data, &v.to_ne_bytes()); Ok(tail) }, }, _ => unreachable!("unexpected bit size {}", bit_size), } } fn parse_float_datum<'a>( &self, bit_size: usize, text: &'a str, data: &mut [u8], ) -> Result<&'a str, Error> { match bit_size { 16 => match hex::parse_half_float(text) { Err(_) => { invalid_data!(self, "Couldn’t parse as half float") }, Ok((v, tail)) => { Parser::write_bytes(data, &v.to_ne_bytes()); Ok(tail) }, }, 32 => match hex::parse_f32(text) { Err(_) => { invalid_data!(self, "Couldn’t parse as float") }, Ok((v, tail)) => { Parser::write_bytes(data, &v.to_ne_bytes()); Ok(tail) }, }, 64 => match hex::parse_f64(text) { Err(_) => { invalid_data!(self, "Couldn’t parse as double") }, Ok((v, tail)) => { Parser::write_bytes(data, &v.to_ne_bytes()); Ok(tail) }, }, _ => unreachable!("unexpected bit size {}", bit_size), } } // Parse a single number (floating point or integral) from one of // the data rows and store it at the start of the `data` slice. If // successful it returns the slice in `text` after the number. // Otherwise it returns an error. fn parse_datum<'a>( &self, mode: Mode, bit_size: usize, text: &'a str, data: &mut [u8], ) -> Result<&'a str, Error> { match mode { Mode::SFLOAT => self.parse_float_datum(bit_size, text, data), Mode::UNORM | Mode::USCALED | Mode::UINT | Mode::SRGB => { self.parse_unsigned_datum(bit_size, text, data) }, Mode::SNORM | Mode::SSCALED | Mode::SINT => { self.parse_signed_datum(bit_size, text, data) }, Mode::UFLOAT => { // This shouldn’t happen because all of the UFLOAT // formats are packed so the data will be integers. unreachable!("unexpected UFLOAT component"); }, } } // Parse each component of an unpacked data format (floating point // or integral) from one of the data rows and store it at the // start of the `data` slice. If successful it returns the slice // in `text` after the values. Otherwise it returns an error. fn parse_unpacked_data<'a>( &self, format: &'static Format, mut text: &'a str, mut data: &mut [u8] ) -> Result<&'a str, Error> { for part in format.parts() { text = self.parse_datum(part.mode, part.bits, text, data)?; data = &mut data[part.bits / 8..]; } Ok(text) } fn parse_data_line(&mut self, mut line: &str) -> Result<(), Error> { // Allocate space in raw_data for this line let old_length = self.raw_data.borrow().len(); self.raw_data.borrow_mut().resize(old_length + self.stride, 0); for attrib in self.attribs.as_ref().unwrap().iter() { let data_ptr = &mut self.raw_data.borrow_mut()[old_length + attrib.offset..]; match attrib.format.packed_size() { Some(packed_size) => { line = self.parse_unsigned_datum( packed_size, line, data_ptr )?; }, None => { line = self.parse_unpacked_data( attrib.format, line, data_ptr )?; } } } if !line.trim_end().is_empty() { invalid_data!(self, "Extra data at end of line"); } Ok(()) } /// Add one line of data to the vbo. Returns an error if the line /// is invalid. pub fn parse_line(&mut self, line: &str) -> Result<(), Error> { let line = Parser::trim_line(line); // Ignore blank or comment-only lines */ if line.len() <= 0 { return Ok(()); } if self.attribs.is_none() { self.parse_header_line(line)?; } else { self.parse_data_line(line)?; } Ok(()) } /// Call this at the end of parsing to convert the parser into the /// final Vbo. This can fail if the parser didn’t have enough data /// to complete the vbo. pub fn into_vbo(mut self) -> Result<Vbo, Error> { let attribs = match self.attribs.take() { None => invalid_header!(data, "Missing header line"), Some(a) => a, }; Ok(Vbo { attribs, raw_data: self.raw_data.into_inner().into_boxed_slice(), stride: self.stride, }) } } #[cfg(test)] mod test { use super::*; use crate::vk; #[test] fn test_general() { let source = "# position color \n\ 0/R32G32_SFLOAT 1/A8B8G8R8_UNORM_PACK32 \n\ \n\ # Top-left red \n\ -1 -1 0xff0000ff \n\ 0 -1 0xff1200ff"; let vbo = source.parse::<Vbo>().unwrap(); assert_eq!(vbo.attribs().len(), 2); assert_eq!(vbo.stride(), 4 * 3); assert_eq!( vbo.attribs()[0].format(), Format::lookup_by_vk_format(vk::VK_FORMAT_R32G32_SFLOAT), ); assert_eq!(vbo.attribs()[0].location(), 0); assert_eq!(vbo.attribs()[0].offset(), 0); assert_eq!( vbo.attribs()[1].format(), Format::lookup_by_vk_format(vk::VK_FORMAT_A8B8G8R8_UNORM_PACK32), ); assert_eq!(vbo.attribs()[1].location(), 1); assert_eq!(vbo.attribs()[1].offset(), 8); assert_eq!(vbo.raw_data().len() % vbo.stride(), 0); let mut expected_data = Vec::<u8>::new(); expected_data.extend(&(-1.0f32).to_ne_bytes()); expected_data.extend(&(-1.0f32).to_ne_bytes()); expected_data.extend(0xff0000ffu32.to_ne_bytes()); expected_data.extend(0.0f32.to_ne_bytes()); expected_data.extend(&(-1.0f32).to_ne_bytes()); expected_data.extend(0xff1200ffu32.to_ne_bytes()); assert_eq!(vbo.raw_data(), &expected_data); } #[test] fn test_no_header() { let err = "".parse::<Vbo>().unwrap_err(); assert_eq!(err.to_string(), "Missing header line"); assert!(matches!(err, Error::InvalidHeader(_))); } #[test] fn test_line_comment() { let source = "0/R32_SFLOAT\n\ 42.0 # the next number is ignored 32.0"; let vbo = source.parse::<Vbo>().unwrap(); assert_eq!(vbo.raw_data(), &42.0f32.to_ne_bytes()); } fn test_gl_type(name: &str, values: &str, expected_bytes: &[u8]) { let source = format!("0/{}/int\n{}", name, values); let vbo = source.parse::<Vbo>().unwrap(); assert_eq!(vbo.raw_data(), expected_bytes); assert_eq!(vbo.attribs().len(), 1); assert_eq!(vbo.attribs()[0].location, 0); assert_eq!(vbo.attribs()[0].format.parts().len(), 1); assert_eq!( vbo.attribs()[0].format.parts()[0].bits, expected_bytes.len() * 8 ); } fn test_glsl_type(name: &str, values: &str, expected_floats: &[f32]) { let source = format!("1/float/{}\n{}", name, values); let vbo = source.parse::<Vbo>().unwrap(); let expected_bytes = expected_floats .iter() .map(|v| v.to_ne_bytes()) .flatten() .collect::<Vec<u8>>(); assert_eq!(vbo.raw_data(), &expected_bytes); assert_eq!(vbo.attribs().len(), 1); assert_eq!(vbo.attribs()[0].location, 1); assert_eq!( vbo.attribs()[0].format.parts().len(), expected_floats.len() ); for part in vbo.attribs()[0].format.parts() { assert_eq!(part.bits, 32); } } #[test] fn test_piglit_style_format() { test_gl_type("byte", "-42", &(-42i8).to_ne_bytes()); test_gl_type("ubyte", "42", &[42u8]); test_gl_type("short", "-30000", &(-30000i16).to_ne_bytes()); test_gl_type("ushort", "65534", &65534u16.to_ne_bytes()); test_gl_type("int", "-70000", &(-70000i32).to_ne_bytes()); test_gl_type("uint", "0xffffffff", &u32::MAX.to_ne_bytes()); test_gl_type("half", "-2", &0xc000u16.to_ne_bytes()); test_gl_type("float", "1.0000", &1.0f32.to_ne_bytes()); test_gl_type("double", "32.0000", &32.0f64.to_ne_bytes()); let err = "1/uverylong/int".parse::<Vbo>().unwrap_err(); assert_eq!(&err.to_string(), "Unknown GL type: uverylong"); assert!(matches!(err, Error::InvalidHeader(_))); test_glsl_type("int", "1.0", &[1.0]); test_glsl_type("uint", "2.0", &[2.0]); test_glsl_type("float", "3.0", &[3.0]); test_glsl_type("double", "4.0", &[4.0]); test_glsl_type("vec2", "1.0 2.0", &[1.0, 2.0]); test_glsl_type("vec3", "1.0 2.0 3.0", &[1.0, 2.0, 3.0]); test_glsl_type("vec4", "1.0 2.0 3.0 4.0", &[1.0, 2.0, 3.0, 4.0]); test_glsl_type("ivec2", "1.0 2.0", &[1.0, 2.0]); test_glsl_type("uvec2", "1.0 2.0", &[1.0, 2.0]); test_glsl_type("dvec2", "1.0 2.0", &[1.0, 2.0]); let err = "1/int/ituple2".parse::<Vbo>().unwrap_err(); assert_eq!(&err.to_string(), "Unknown GLSL type: ituple2"); assert!(matches!(err, Error::InvalidHeader(_))); let err = "1/int/ivecfoo".parse::<Vbo>().unwrap_err(); assert_eq!(&err.to_string(), "Invalid vec size: ivecfoo"); assert!(matches!(err, Error::InvalidHeader(_))); let err = "1/int/vec1".parse::<Vbo>().unwrap_err(); assert_eq!(&err.to_string(), "Invalid vec size: vec1"); assert!(matches!(err, Error::InvalidHeader(_))); let err = "1/int/dvec5".parse::<Vbo>().unwrap_err(); assert_eq!(&err.to_string(), "Invalid vec size: dvec5"); assert!(matches!(err, Error::InvalidHeader(_))); } #[test] fn test_bad_attrib() { let err = "foo/int/int".parse::<Vbo>().unwrap_err(); assert_eq!(&err.to_string(), "Invalid attrib location in foo/int/int"); assert!(matches!(err, Error::InvalidHeader(_))); assert_eq!( "12".parse::<Vbo>().unwrap_err().to_string(), "Column headers must be in the form location/format. \ Got: 12", ); assert_eq!( "1/R76_SFLOAT".parse::<Vbo>().unwrap_err().to_string(), "Unknown format: R76_SFLOAT", ); assert_eq!( "1/int/int/more_int".parse::<Vbo>().unwrap_err().to_string(), "Extra data at end of column header: 1/int/int/more_int", ); } #[test] fn test_alignment() { let source = "1/R8_UNORM 2/R64_SFLOAT 3/R8_UNORM\n \ 1 12.0 24"; let vbo = source.parse::<Vbo>().unwrap(); assert_eq!(vbo.attribs().len(), 3); assert_eq!(vbo.attribs()[0].offset, 0); assert_eq!(vbo.attribs()[0].format.parts()[0].bits, 8); assert_eq!(vbo.attribs()[1].offset, 8); assert_eq!(vbo.attribs()[1].format.parts()[0].bits, 64); assert_eq!(vbo.attribs()[2].offset, 16); assert_eq!(vbo.attribs()[2].format.parts()[0].bits, 8); assert_eq!(vbo.stride, 24); } fn test_type(format: &str, values: &str, expected_bytes: &[u8]) { // Add an extra attribute so we can test it got the right offset let source = format!("8/{} 9/R8_UNORM\n{} 42", format, values); let vbo = source.parse::<Vbo>().unwrap(); let mut full_expected_bytes = expected_bytes.to_owned(); full_expected_bytes.push(42); assert!(vbo.stride() >= expected_bytes.len() + 1); full_expected_bytes.resize(vbo.stride(), 0); assert_eq!(vbo.raw_data(), full_expected_bytes); assert_eq!(vbo.attribs().len(), 2); assert_eq!( vbo.attribs()[0].format.parts()[0].bits, expected_bytes.len() * 8 ); } fn test_value_error(format: &str, error_text: &str) { let source = format!("0/{}\nfoo", format); let err = source.parse::<Vbo>().unwrap_err(); assert_eq!(&err.to_string(), error_text); } #[test] fn test_parse_datum() { test_type("R8_UNORM", "12", &[12u8]); test_value_error("R8_UNORM", "Couldn’t parse as unsigned byte"); test_type("R16_UNORM", "65000", &65000u16.to_ne_bytes()); test_value_error("R16_USCALED", "Couldn’t parse as unsigned short"); test_type("R32_UINT", "66000", &66000u32.to_ne_bytes()); test_value_error("R32_UINT", "Couldn’t parse as unsigned int"); test_type("R64_UINT", "0xffffffffffffffff", &u64::MAX.to_ne_bytes()); test_value_error("R64_UINT", "Couldn’t parse as unsigned long"); test_type("R8_SNORM", "-12", &(-12i8).to_ne_bytes()); test_value_error("R8_SNORM", "Couldn’t parse as signed byte"); test_type("R16_SNORM", "-32768", &(-32768i16).to_ne_bytes()); test_value_error("R16_SNORM", "Couldn’t parse as signed short"); test_type("R32_SINT", "-66000", &(-66000i32).to_ne_bytes()); test_value_error("R32_SINT", "Couldn’t parse as signed int"); test_type("R64_SINT", "0x7fffffffffffffff", &i64::MAX.to_ne_bytes()); test_value_error("R64_SINT", "Couldn’t parse as signed long"); test_type("R16_SFLOAT", "0xc000", &0xc000u16.to_ne_bytes()); test_type("R16_SFLOAT", "-2", &0xc000u16.to_ne_bytes()); test_value_error("R16_SFLOAT", "Couldn’t parse as half float"); test_type("R32_SFLOAT", "-2", &(-2.0f32).to_ne_bytes()); test_value_error("R32_SFLOAT", "Couldn’t parse as float"); test_type("R64_SFLOAT", "-4", &(-4.0f64).to_ne_bytes()); test_value_error("R64_SFLOAT", "Couldn’t parse as double"); } #[test] fn test_packed_data() { let source = "1/B10G11R11_UFLOAT_PACK32\n\ 0xfedcba98"; let vbo = source.parse::<Vbo>().unwrap(); assert_eq!(vbo.raw_data(), &0xfedcba98u32.to_ne_bytes()); } #[test] fn test_trailing_data() { let source = "1/R8_UNORM\n\ 23 25 "; let err = source.parse::<Vbo>().unwrap_err(); assert_eq!(err.to_string(), "Extra data at end of line"); } } ``` -------------------------------------------------------------------------------- /vkrunner/vkrunner/window.rs: -------------------------------------------------------------------------------- ```rust // vkrunner // // Copyright (C) 2013, 2014, 2015, 2017, 2023 Neil Roberts // Copyright (C) 2019 Google LLC // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice (including the next // paragraph) shall be included in all copies or substantial portions of the // Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. use crate::context::Context; use crate::window_format::WindowFormat; use crate::format::{Format, Component}; use crate::vk; use crate::result; use crate::vulkan_funcs; use crate::buffer::{self, MappedMemory, DeviceMemory, Buffer}; use std::rc::Rc; use std::fmt; use std::ffi::c_void; use std::ptr; /// Struct containing the framebuffer and all of the objects on top of /// the [Context] needed to construct it. It also keeps a reference to /// the Context which can be retrieved publically so this works as a /// central object to share the essential Vulkan resources used for /// running tests. #[derive(Debug)] pub struct Window { format: WindowFormat, // These are listed in the reverse order that they are created so // that they will be destroyed in the right order too need_linear_memory_invalidate: bool, linear_memory_stride: usize, linear_memory_map: MappedMemory, linear_memory: DeviceMemory, linear_buffer: Buffer, framebuffer: Framebuffer, _depth_stencil_resources: Option<DepthStencilResources>, _color_image_view: ImageView, _memory: DeviceMemory, color_image: Image, // The first render pass is used for the first render and has a // loadOp of DONT_CARE. The second is used for subsequent renders // and loads the framebuffer contents. render_pass: [RenderPass; 2], context: Rc<Context>, } #[derive(Debug)] struct DepthStencilResources { // These are listed in the reverse order that they are created so // that they will be destroyed in the right order too image_view: ImageView, _memory: DeviceMemory, _image: Image, } #[derive(Debug)] pub enum WindowError { IncompatibleFormat(String), RenderPassError, ImageError, ImageViewError, BufferError(buffer::Error), FramebufferError, } impl fmt::Display for WindowError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { WindowError::IncompatibleFormat(s) => write!(f, "{}", s), WindowError::BufferError(e) => e.fmt(f), WindowError::RenderPassError => write!( f, "Error creating render pass", ), WindowError::ImageError => write!( f, "Error creating vkImage", ), WindowError::ImageViewError => write!( f, "Error creating vkImageView", ), WindowError::FramebufferError => write!( f, "Error creating vkFramebuffer", ), } } } impl WindowError { pub fn result(&self) -> result::Result { match self { WindowError::IncompatibleFormat(_) => result::Result::Skip, WindowError::RenderPassError => result::Result::Fail, WindowError::ImageError => result::Result::Fail, WindowError::ImageViewError => result::Result::Fail, WindowError::FramebufferError => result::Result::Fail, WindowError::BufferError(_) => result::Result::Fail, } } } impl From<buffer::Error> for WindowError { fn from(e: buffer::Error) -> WindowError { WindowError::BufferError(e) } } fn check_format( context: &Context, format: &Format, flags: vk::VkFormatFeatureFlags, ) -> bool { let mut format_properties: vk::VkFormatProperties = Default::default(); unsafe { context.instance().vkGetPhysicalDeviceFormatProperties.unwrap()( context.physical_device(), format.vk_format, &mut format_properties as *mut vk::VkFormatProperties, ); } format_properties.optimalTilingFeatures & flags == flags } fn check_window_format( context: &Context, window_format: &WindowFormat, ) -> Result<(), WindowError> { if !check_format( context, window_format.color_format, vk::VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | vk::VK_FORMAT_FEATURE_BLIT_SRC_BIT, ) { return Err(WindowError::IncompatibleFormat(format!( "Format {} is not supported as a color attachment and blit source", window_format.color_format.name, ))); } if let Some(depth_stencil_format) = window_format.depth_stencil_format { if !check_format( context, depth_stencil_format, vk::VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, ) { return Err(WindowError::IncompatibleFormat(format!( "Format {} is not supported as a depth/stencil attachment", depth_stencil_format.name, ))); } } Ok(()) } #[derive(Debug)] struct RenderPass { render_pass: vk::VkRenderPass, // Needed for the destructor context: Rc<Context>, } impl RenderPass { fn new( context: Rc<Context>, window_format: &WindowFormat, first_render: bool, ) -> Result<RenderPass, WindowError> { let has_stencil = match window_format.depth_stencil_format { None => false, Some(format) => { format .parts() .into_iter() .find(|p| p.component == Component::S) .is_some() }, }; let attachment_descriptions = [ vk::VkAttachmentDescription { flags: 0, format: window_format.color_format.vk_format, samples: vk::VK_SAMPLE_COUNT_1_BIT, loadOp: if first_render { vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE } else { vk::VK_ATTACHMENT_LOAD_OP_LOAD }, storeOp: vk::VK_ATTACHMENT_STORE_OP_STORE, stencilLoadOp: vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE, stencilStoreOp: vk::VK_ATTACHMENT_STORE_OP_DONT_CARE, initialLayout: if first_render { vk::VK_IMAGE_LAYOUT_UNDEFINED } else { vk::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }, finalLayout: vk::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }, vk::VkAttachmentDescription { flags: 0, format: match window_format.depth_stencil_format { Some(f) => f.vk_format, None => 0, }, samples: vk::VK_SAMPLE_COUNT_1_BIT, loadOp: if first_render { vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE } else { vk::VK_ATTACHMENT_LOAD_OP_LOAD }, storeOp: vk::VK_ATTACHMENT_STORE_OP_STORE, stencilLoadOp: if first_render || !has_stencil { vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE } else { vk::VK_ATTACHMENT_LOAD_OP_LOAD }, stencilStoreOp: if has_stencil { vk::VK_ATTACHMENT_STORE_OP_STORE } else { vk::VK_ATTACHMENT_STORE_OP_DONT_CARE }, initialLayout: if first_render { vk::VK_IMAGE_LAYOUT_UNDEFINED } else { vk::VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }, finalLayout: vk::VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }, ]; let color_attachment_reference = vk::VkAttachmentReference { attachment: 0, layout: vk::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; let depth_stencil_attachment_reference = vk::VkAttachmentReference { attachment: 1, layout: vk::VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }; let subpass_descriptions = [ vk::VkSubpassDescription { flags: 0, pipelineBindPoint: vk::VK_PIPELINE_BIND_POINT_GRAPHICS, inputAttachmentCount: 0, pInputAttachments: ptr::null(), colorAttachmentCount: 1, pColorAttachments: ptr::addr_of!(color_attachment_reference), pResolveAttachments: ptr::null(), pDepthStencilAttachment: if window_format.depth_stencil_format.is_some() { ptr::addr_of!(depth_stencil_attachment_reference) } else { ptr::null() }, preserveAttachmentCount: 0, pPreserveAttachments: ptr::null(), }, ]; let render_pass_create_info = vk::VkRenderPassCreateInfo { sType: vk::VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, pNext: ptr::null(), flags: 0, attachmentCount: attachment_descriptions.len() as u32 - window_format.depth_stencil_format.is_none() as u32, pAttachments: ptr::addr_of!(attachment_descriptions[0]), subpassCount: subpass_descriptions.len() as u32, pSubpasses: ptr::addr_of!(subpass_descriptions[0]), dependencyCount: 0, pDependencies: ptr::null(), }; let mut render_pass: vk::VkRenderPass = vk::null_handle(); let res = unsafe { context.device().vkCreateRenderPass.unwrap()( context.vk_device(), ptr::addr_of!(render_pass_create_info), ptr::null(), // allocator ptr::addr_of_mut!(render_pass) ) }; if res == vk::VK_SUCCESS { Ok(RenderPass { render_pass, context }) } else { Err(WindowError::RenderPassError) } } } impl Drop for RenderPass { fn drop(&mut self) { unsafe { self.context.device().vkDestroyRenderPass.unwrap()( self.context.vk_device(), self.render_pass, ptr::null(), // allocator ); } } } #[derive(Debug)] struct Image { image: vk::VkImage, // Needed for the destructor context: Rc<Context>, } impl Drop for Image { fn drop(&mut self) { unsafe { self.context.device().vkDestroyImage.unwrap()( self.context.vk_device(), self.image, ptr::null(), // allocator ); } } } impl Image { fn new_from_create_info( context: Rc<Context>, image_create_info: &vk::VkImageCreateInfo, ) -> Result<Image, WindowError> { let mut image: vk::VkImage = vk::null_handle(); let res = unsafe { context.device().vkCreateImage.unwrap()( context.vk_device(), image_create_info as *const vk::VkImageCreateInfo, ptr::null(), // allocator ptr::addr_of_mut!(image), ) }; if res == vk::VK_SUCCESS { Ok(Image { image, context }) } else { Err(WindowError::ImageError) } } fn new_color( context: Rc<Context>, window_format: &WindowFormat, ) -> Result<Image, WindowError> { let image_create_info = vk::VkImageCreateInfo { sType: vk::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, pNext: ptr::null(), flags: 0, imageType: vk::VK_IMAGE_TYPE_2D, format: window_format.color_format.vk_format, extent: vk::VkExtent3D { width: window_format.width as u32, height: window_format.height as u32, depth: 1, }, mipLevels: 1, arrayLayers: 1, samples: vk::VK_SAMPLE_COUNT_1_BIT, tiling: vk::VK_IMAGE_TILING_OPTIMAL, usage: vk::VK_IMAGE_USAGE_TRANSFER_SRC_BIT | vk::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, sharingMode: vk::VK_SHARING_MODE_EXCLUSIVE, queueFamilyIndexCount: 0, pQueueFamilyIndices: ptr::null(), initialLayout: vk::VK_IMAGE_LAYOUT_UNDEFINED, }; Image::new_from_create_info(context, &image_create_info) } fn new_depth_stencil( context: Rc<Context>, format: &Format, width: usize, height: usize, ) -> Result<Image, WindowError> { let image_create_info = vk::VkImageCreateInfo { sType: vk::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, pNext: ptr::null(), flags: 0, imageType: vk::VK_IMAGE_TYPE_2D, format: format.vk_format, extent: vk::VkExtent3D { width: width as u32, height: height as u32, depth: 1, }, mipLevels: 1, arrayLayers: 1, samples: vk::VK_SAMPLE_COUNT_1_BIT, tiling: vk::VK_IMAGE_TILING_OPTIMAL, usage: vk::VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, sharingMode: vk::VK_SHARING_MODE_EXCLUSIVE, queueFamilyIndexCount: 0, pQueueFamilyIndices: ptr::null(), initialLayout: vk::VK_IMAGE_LAYOUT_UNDEFINED, }; Image::new_from_create_info(context, &image_create_info) } } #[derive(Debug)] struct ImageView { image_view: vk::VkImageView, // Needed for the destructor context: Rc<Context>, } impl Drop for ImageView { fn drop(&mut self) { unsafe { self.context.device().vkDestroyImageView.unwrap()( self.context.vk_device(), self.image_view, ptr::null(), // allocator ); } } } impl ImageView { fn new( context: Rc<Context>, format: &Format, image: vk::VkImage, aspect_mask: vk::VkImageAspectFlags, ) -> Result<ImageView, WindowError> { let image_view_create_info = vk::VkImageViewCreateInfo { sType: vk::VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, pNext: ptr::null(), flags: 0, image, viewType: vk::VK_IMAGE_VIEW_TYPE_2D, format: format.vk_format, components: vk::VkComponentMapping { r: vk::VK_COMPONENT_SWIZZLE_R, g: vk::VK_COMPONENT_SWIZZLE_G, b: vk::VK_COMPONENT_SWIZZLE_B, a: vk::VK_COMPONENT_SWIZZLE_A, }, subresourceRange: vk::VkImageSubresourceRange { aspectMask: aspect_mask, baseMipLevel: 0, levelCount: 1, baseArrayLayer: 0, layerCount: 1 }, }; let mut image_view: vk::VkImageView = vk::null_handle(); let res = unsafe { context.device().vkCreateImageView.unwrap()( context.vk_device(), ptr::addr_of!(image_view_create_info), ptr::null(), // allocator ptr::addr_of_mut!(image_view), ) }; if res == vk::VK_SUCCESS { Ok(ImageView { image_view, context }) } else { Err(WindowError::ImageViewError) } } } impl DepthStencilResources { fn new( context: Rc<Context>, format: &Format, width: usize, height: usize, ) -> Result<DepthStencilResources, WindowError> { let image = Image::new_depth_stencil( Rc::clone(&context), format, width, height, )?; let memory = DeviceMemory::new_image( Rc::clone(&context), 0, // memory_type_flags image.image, )?; let image_view = ImageView::new( context, format, image.image, format.depth_stencil_aspect_flags(), )?; Ok(DepthStencilResources { _image: image, _memory: memory, image_view }) } } #[derive(Debug)] struct Framebuffer { framebuffer: vk::VkFramebuffer, // Needed for the destructor context: Rc<Context>, } impl Drop for Framebuffer { fn drop(&mut self) { unsafe { self.context.device().vkDestroyFramebuffer.unwrap()( self.context.vk_device(), self.framebuffer, ptr::null(), // allocator ); } } } impl Framebuffer { fn new( context: Rc<Context>, window_format: &WindowFormat, render_pass: vk::VkRenderPass, color_image_view: vk::VkImageView, depth_stencil_image_view: Option<vk::VkImageView>, ) -> Result<Framebuffer, WindowError> { let mut attachments = vec![color_image_view]; if let Some(image_view) = depth_stencil_image_view { attachments.push(image_view); } let framebuffer_create_info = vk::VkFramebufferCreateInfo { sType: vk::VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, pNext: ptr::null(), flags: 0, renderPass: render_pass, attachmentCount: attachments.len() as u32, pAttachments: attachments.as_ptr(), width: window_format.width as u32, height: window_format.height as u32, layers: 1, }; let mut framebuffer: vk::VkFramebuffer = vk::null_handle(); let res = unsafe { context.device().vkCreateFramebuffer.unwrap()( context.vk_device(), ptr::addr_of!(framebuffer_create_info), ptr::null(), // allocator ptr::addr_of_mut!(framebuffer), ) }; if res == vk::VK_SUCCESS { Ok(Framebuffer { framebuffer, context }) } else { Err(WindowError::FramebufferError) } } } fn need_linear_memory_invalidate( context: &Context, linear_memory_type: u32, ) -> bool { context .memory_properties() .memoryTypes[linear_memory_type as usize] .propertyFlags & vk::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT == 0 } impl Window { pub fn new( context: Rc<Context>, format: &WindowFormat, ) -> Result<Window, WindowError> { check_window_format(&context, format)?; let render_pass = [ RenderPass::new(Rc::clone(&context), format, true)?, RenderPass::new(Rc::clone(&context), format, false)?, ]; let color_image = Image::new_color(Rc::clone(&context), format)?; let memory = DeviceMemory::new_image( Rc::clone(&context), 0, // memory_type_flags color_image.image, )?; let color_image_view = ImageView::new( Rc::clone(&context), format.color_format, color_image.image, vk::VK_IMAGE_ASPECT_COLOR_BIT, )?; let depth_stencil_resources = match &format.depth_stencil_format { Some(depth_stencil_format) => Some(DepthStencilResources::new( Rc::clone(&context), depth_stencil_format, format.width, format.height, )?), None => None, }; let framebuffer = Framebuffer::new( Rc::clone(&context), format, render_pass[0].render_pass, color_image_view.image_view, depth_stencil_resources.as_ref().map(|r| r.image_view.image_view), )?; let linear_memory_stride = format.color_format.size() * format.width; let linear_buffer = Buffer::new( Rc::clone(&context), linear_memory_stride * format.height, vk::VK_BUFFER_USAGE_TRANSFER_DST_BIT, )?; let linear_memory = DeviceMemory::new_buffer( Rc::clone(&context), vk::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, linear_buffer.buffer, )?; let linear_memory_map = MappedMemory::new( Rc::clone(&context), linear_memory.memory, )?; Ok(Window { format: format.clone(), need_linear_memory_invalidate: need_linear_memory_invalidate( &context, linear_memory.memory_type_index, ), linear_memory_stride, linear_memory_map, linear_memory, linear_buffer, framebuffer, _depth_stencil_resources: depth_stencil_resources, _color_image_view: color_image_view, _memory: memory, color_image, render_pass, context, }) } /// Retrieve the [Context] that the Window was created with. pub fn context(&self) -> &Rc<Context> { &self.context } /// Retrieve the [WindowFormat] that the Window was created for. pub fn format(&self) -> &WindowFormat { &self.format } /// Retrieve the [Device](vulkan_funcs::Device) that the /// Window was created from. This is just a convenience function /// for getting the device from the [Context]. pub fn device(&self) -> &vulkan_funcs::Device { self.context.device() } /// Retrieve the [VkDevice](vk::VkDevice) that the window was /// created from. This is just a convenience function for getting /// the device from the [Context]. pub fn vk_device(&self) -> vk::VkDevice { self.context.vk_device() } /// Get the two [VkRenderPasses](vk::VkRenderPass) that were /// created for the window. The first render pass should be used /// for the first render and the second one should be used for all /// subsequent renders. pub fn render_passes(&self) -> [vk::VkRenderPass; 2] { [ self.render_pass[0].render_pass, self.render_pass[1].render_pass, ] } /// Get the vulkan handle to the linear memory that can be used to /// copy framebuffer results into in order to inspect it. pub fn linear_memory(&self) -> vk::VkDeviceMemory { self.linear_memory.memory } /// Get the `VkBuffer` that represents the linear memory buffer pub fn linear_buffer(&self) -> vk::VkBuffer { self.linear_buffer.buffer } /// Get the pointer to the mapping that the Window holds to /// examine the linear memory buffer. pub fn linear_memory_map(&self) -> *const c_void { self.linear_memory_map.pointer } /// Get the stride of the linear memory buffer pub fn linear_memory_stride(&self) -> usize { self.linear_memory_stride } /// Return whether the mapping for the linear memory buffer owned /// by the Window needs to be invalidated with /// `vkInvalidateMappedMemoryRanges` before it can be read after /// it has been modified. This is will be true if the memory type /// used for the linear memory buffer doesn’t have the /// `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` flag set. pub fn need_linear_memory_invalidate(&self) -> bool { self.need_linear_memory_invalidate } /// Return the `VkFramebuffer` that was created for the window. pub fn framebuffer(&self) -> vk::VkFramebuffer { self.framebuffer.framebuffer } /// Return the `VkImage` that was created for the color buffer of /// the window. pub fn color_image(&self) -> vk::VkImage { self.color_image.image } } #[cfg(test)] mod test { use super::*; use crate::fake_vulkan::{FakeVulkan, HandleType}; use crate::requirements::Requirements; fn base_fake_vulkan() -> Box<FakeVulkan> { let mut fake_vulkan = FakeVulkan::new(); fake_vulkan.physical_devices.push(Default::default()); fake_vulkan.physical_devices[0].format_properties.insert( vk::VK_FORMAT_B8G8R8A8_UNORM, vk::VkFormatProperties { linearTilingFeatures: 0, optimalTilingFeatures: vk::VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | vk::VK_FORMAT_FEATURE_BLIT_SRC_BIT, bufferFeatures: 0, }, ); fake_vulkan.physical_devices[0].format_properties.insert( vk::VK_FORMAT_D24_UNORM_S8_UINT, vk::VkFormatProperties { linearTilingFeatures: 0, optimalTilingFeatures: vk::VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, bufferFeatures: 0, }, ); let memory_properties = &mut fake_vulkan.physical_devices[0].memory_properties; memory_properties.memoryTypes[0].propertyFlags = vk::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; memory_properties.memoryTypeCount = 1; fake_vulkan.memory_requirements.memoryTypeBits = 1; fake_vulkan } fn get_render_pass_attachments( fake_vulkan: &mut FakeVulkan, render_pass: vk::VkRenderPass, ) -> &[vk::VkAttachmentDescription] { match &fake_vulkan.get_handle(render_pass).data { HandleType::RenderPass { attachments } => attachments.as_slice(), _ => unreachable!("mismatched handle type"), } } struct TestResources { // These two need to be dropped in this order context: Rc<Context>, fake_vulkan: Box<FakeVulkan>, } fn test_simple_function_error( function_name: &str, expected_error_string: &str, ) -> TestResources { let mut fake_vulkan = base_fake_vulkan(); fake_vulkan.queue_result( function_name.to_string(), vk::VK_ERROR_UNKNOWN ); fake_vulkan.set_override(); let context = Rc::new(Context::new( &Requirements::new(), None ).unwrap()); let err = Window::new( Rc::clone(&context), &Default::default(), // format ).unwrap_err(); assert_eq!(&err.to_string(), expected_error_string); assert_eq!(err.result(), result::Result::Fail); TestResources { context, fake_vulkan } } #[test] fn basic() { let mut fake_vulkan = base_fake_vulkan(); fake_vulkan.set_override(); let context = Rc::new(Context::new( &Requirements::new(), None ).unwrap()); let window = Window::new( Rc::clone(&context), &Default::default() // format ).unwrap(); assert!(Rc::ptr_eq(window.context(), &context)); assert_eq!( window.format().color_format.vk_format, vk::VK_FORMAT_B8G8R8A8_UNORM ); assert_eq!( window.device() as *const vulkan_funcs::Device, context.device() as *const vulkan_funcs::Device, ); assert_eq!(window.vk_device(), context.vk_device()); assert_ne!(window.render_passes()[0], window.render_passes()[1]); assert!(window.linear_memory() != vk::null_handle()); assert!(window.linear_buffer() != vk::null_handle()); assert!(!window.linear_memory_map().is_null()); assert_eq!( window.linear_memory_stride(), window.format().color_format.size() * window.format().width ); assert!(window.need_linear_memory_invalidate()); assert!(window.framebuffer() != vk::null_handle()); assert!(window.color_image() != vk::null_handle()); let rp = get_render_pass_attachments( fake_vulkan.as_mut(), window.render_passes()[0] ); assert_eq!(rp.len(), 1); assert_eq!(rp[0].loadOp, vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE); assert_eq!(rp[0].initialLayout, vk::VK_IMAGE_LAYOUT_UNDEFINED); assert_eq!(rp[0].stencilLoadOp, vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE); let rp = get_render_pass_attachments( fake_vulkan.as_mut(), window.render_passes()[1] ); assert_eq!(rp.len(), 1); assert_eq!(rp[0].loadOp, vk::VK_ATTACHMENT_LOAD_OP_LOAD); assert_eq!( rp[0].initialLayout, vk::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ); assert_eq!(rp[0].stencilLoadOp, vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE); } #[test] fn depth_stencil() { let mut fake_vulkan = base_fake_vulkan(); let mut depth_format = WindowFormat::default(); depth_format.depth_stencil_format = Some(Format::lookup_by_vk_format( vk::VK_FORMAT_D24_UNORM_S8_UINT, )); fake_vulkan.set_override(); let context = Rc::new(Context::new( &Requirements::new(), None ).unwrap()); let window = Window::new( Rc::clone(&context), &depth_format, ).unwrap(); let rp = get_render_pass_attachments( fake_vulkan.as_mut(), window.render_passes()[0] ); assert_eq!(rp.len(), 2); assert_eq!(rp[0].loadOp, vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE); assert_eq!(rp[0].initialLayout, vk::VK_IMAGE_LAYOUT_UNDEFINED); assert_eq!(rp[0].stencilLoadOp, vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE); assert_eq!(rp[1].loadOp, vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE); assert_eq!(rp[1].initialLayout, vk::VK_IMAGE_LAYOUT_UNDEFINED); assert_eq!(rp[1].stencilLoadOp, vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE); assert_eq!(rp[1].stencilStoreOp, vk::VK_ATTACHMENT_STORE_OP_STORE); let rp = get_render_pass_attachments( fake_vulkan.as_mut(), window.render_passes()[1] ); assert_eq!(rp.len(), 2); assert_eq!(rp[0].loadOp, vk::VK_ATTACHMENT_LOAD_OP_LOAD); assert_eq!( rp[0].initialLayout, vk::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ); assert_eq!(rp[0].stencilLoadOp, vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE); assert_eq!(rp[1].loadOp, vk::VK_ATTACHMENT_LOAD_OP_LOAD); assert_eq!( rp[1].initialLayout, vk::VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL ); assert_eq!(rp[1].stencilLoadOp, vk::VK_ATTACHMENT_LOAD_OP_LOAD); assert_eq!(rp[1].stencilStoreOp, vk::VK_ATTACHMENT_STORE_OP_STORE); } #[test] fn incompatible_format() { let mut fake_vulkan = base_fake_vulkan(); fake_vulkan .physical_devices[0] .format_properties .insert( vk::VK_FORMAT_R8_UNORM, vk::VkFormatProperties { linearTilingFeatures: 0, optimalTilingFeatures: 0, bufferFeatures: 0, }, ); let mut depth_format = WindowFormat::default(); depth_format.depth_stencil_format = Some(Format::lookup_by_vk_format( vk::VK_FORMAT_R8_UNORM, )); fake_vulkan.set_override(); let context = Rc::new(Context::new( &Requirements::new(), None ).unwrap()); let err = Window::new( Rc::clone(&context), &depth_format, ).unwrap_err(); assert_eq!( &err.to_string(), "Format R8_UNORM is not supported as a depth/stencil attachment", ); assert_eq!(err.result(), result::Result::Skip); fake_vulkan .physical_devices[0] .format_properties .get_mut(&vk::VK_FORMAT_B8G8R8A8_UNORM) .unwrap() .optimalTilingFeatures &= !vk::VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT; fake_vulkan.set_override(); let context = Rc::new(Context::new( &Requirements::new(), None ).unwrap()); let err = Window::new( Rc::clone(&context), &Default::default(), // format ).unwrap_err(); assert_eq!( &err.to_string(), "Format B8G8R8A8_UNORM is not supported as a color attachment \ and blit source", ); assert_eq!(err.result(), result::Result::Skip); } #[test] fn render_pass_error() { let mut res = test_simple_function_error( "vkCreateRenderPass", "Error creating render pass" ); // Try making the second render pass fail too res.fake_vulkan.queue_result( "vkCreateRenderPass".to_string(), vk::VK_SUCCESS ); res.fake_vulkan.queue_result( "vkCreateRenderPass".to_string(), vk::VK_ERROR_UNKNOWN ); let err = Window::new( Rc::clone(&res.context), &Default::default(), // format ).unwrap_err(); assert_eq!(&err.to_string(), "Error creating render pass"); } #[test] fn image_error() { let mut res = test_simple_function_error( "vkCreateImage", "Error creating vkImage" ); // Also try the depth/stencil image res.fake_vulkan.queue_result( "vkCreateImage".to_string(), vk::VK_SUCCESS ); res.fake_vulkan.queue_result( "vkCreateImage".to_string(), vk::VK_ERROR_UNKNOWN ); let mut depth_format = WindowFormat::default(); depth_format.depth_stencil_format = Some(Format::lookup_by_vk_format( vk::VK_FORMAT_D24_UNORM_S8_UINT, )); let err = Window::new( Rc::clone(&res.context), &depth_format, ).unwrap_err(); assert_eq!(&err.to_string(), "Error creating vkImage"); } #[test] fn image_view_error() { let mut res = test_simple_function_error( "vkCreateImageView", "Error creating vkImageView" ); // Also try the depth/stencil image res.fake_vulkan.queue_result( "vkCreateImageView".to_string(), vk::VK_SUCCESS ); res.fake_vulkan.queue_result( "vkCreateImageView".to_string(), vk::VK_ERROR_UNKNOWN ); let mut depth_format = WindowFormat::default(); depth_format.depth_stencil_format = Some(Format::lookup_by_vk_format( vk::VK_FORMAT_D24_UNORM_S8_UINT, )); let err = Window::new( Rc::clone(&res.context), &depth_format, ).unwrap_err(); assert_eq!(&err.to_string(), "Error creating vkImageView"); } #[test] fn allocate_store_error() { let mut res = test_simple_function_error( "vkAllocateMemory", "vkAllocateMemory failed" ); // Make the second allocate fail to so that the depth/stencil // image will fail res.fake_vulkan.queue_result( "vkAllocateMemory".to_string(), vk::VK_SUCCESS, ); res.fake_vulkan.queue_result( "vkAllocateMemory".to_string(), vk::VK_ERROR_UNKNOWN, ); let mut depth_format = WindowFormat::default(); depth_format.depth_stencil_format = Some(Format::lookup_by_vk_format( vk::VK_FORMAT_D24_UNORM_S8_UINT, )); let err = Window::new( Rc::clone(&res.context), &depth_format, ).unwrap_err(); assert_eq!(&err.to_string(), "vkAllocateMemory failed"); assert_eq!(err.result(), result::Result::Fail); // Remove the host visible bit so the linear memory won’t allocate let memory_properties = &mut res.fake_vulkan.physical_devices[0].memory_properties; memory_properties.memoryTypes[0].propertyFlags &= !vk::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; res.fake_vulkan.set_override(); let context = Rc::new(Context::new( &Requirements::new(), None ).unwrap()); let err = Window::new( Rc::clone(&context), &Default::default(), // format ).unwrap_err(); assert_eq!( &err.to_string(), "Couldn’t find suitable memory type to allocate buffer", ); assert_eq!(err.result(), result::Result::Fail); } #[test] fn map_memory_error() { test_simple_function_error("vkMapMemory", "vkMapMemory failed"); } #[test] fn buffer_error() { test_simple_function_error("vkCreateBuffer", "Error creating vkBuffer"); } #[test] fn framebuffer_error() { test_simple_function_error( "vkCreateFramebuffer", "Error creating vkFramebuffer", ); } #[test] fn rectangular_framebuffer() { let fake_vulkan = base_fake_vulkan(); fake_vulkan.set_override(); let context = Rc::new(Context::new( &Requirements::new(), None ).unwrap()); let mut format = WindowFormat::default(); format.width = 200; format.height = 100; let window = Window::new( Rc::clone(&context), &format, ).unwrap(); assert_eq!( window.linear_memory_stride(), 200 * format.color_format.size() ); let HandleType::Memory { ref contents, .. } = fake_vulkan.get_handle(window.linear_memory()).data else { unreachable!("Mismatched handle"); }; assert_eq!( contents.len(), window.linear_memory_stride() * 100, ); } } ``` -------------------------------------------------------------------------------- /vkrunner/vkrunner/context.rs: -------------------------------------------------------------------------------- ```rust // vkrunner // // Copyright (C) 2013, 2014, 2015, 2017, 2023 Neil Roberts // Copyright (C) 2018 Intel Corporation // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice (including the next // paragraph) shall be included in all copies or substantial portions of the // Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. use crate::vk; use crate::requirements::{self, Requirements}; use crate::vulkan_funcs; use crate::util::env_var_as_boolean; use crate::result; use std::ffi::{c_char, c_void, CStr}; use std::fmt; use std::ptr; /// Struct containing the VkDevice and accessories such as a /// VkCommandPool, VkQueue and the function pointers from /// [vulkan_funcs]. #[derive(Debug)] pub struct Context { // These three need to be in this order so that they will be // dropped in the correct order. device_pair: DevicePair, instance_pair: InstancePair, // This isn’t read anywhere but we want to keep it alive for the // duration of the Context so that the library won’t be unloaded. _vklib: Option<Box<vulkan_funcs::Library>>, physical_device: vk::VkPhysicalDevice, memory_properties: vk::VkPhysicalDeviceMemoryProperties, command_pool: vk::VkCommandPool, command_buffer: vk::VkCommandBuffer, fence: vk::VkFence, queue: vk::VkQueue, always_flush_memory: bool, } /// Error returned by [Context::new] #[derive(Debug)] pub enum Error { FuncsError(vulkan_funcs::Error), RequirementsError(requirements::Error), EnumerateInstanceExtensionPropertiesFailed, MissingInstanceExtension(String), IncompatibleDriver, CreateInstanceFailed, CreateDeviceFailed, CreateCommandPoolFailed, CommandBufferAllocateFailed, CreateFenceFailed, EnumeratePhysicalDevicesFailed, NoDevices, /// None of the drivers succeeded and the vector is an error for /// each possible driver. DeviceErrors(Vec<Error>), NoGraphicsQueueFamily, InvalidDeviceId { device_id: usize, n_devices: u32 }, } impl From<vulkan_funcs::Error> for Error { fn from(error: vulkan_funcs::Error) -> Error { Error::FuncsError(error) } } impl From<requirements::Error> for Error { fn from(error: requirements::Error) -> Error { Error::RequirementsError(error) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::FuncsError(e) => e.fmt(f), Error::RequirementsError(e) => e.fmt(f), Error::EnumerateInstanceExtensionPropertiesFailed => { write!(f, "vkEnumerateInstanceExtensionProperties failed") }, Error::MissingInstanceExtension(s) => { write!(f, "Missing instance extension: {}", s) }, Error::IncompatibleDriver => { write!( f, "vkCreateInstance reported VK_ERROR_INCOMPATIBLE_DRIVER", ) }, Error::CreateInstanceFailed => write!(f, "vkCreateInstance failed"), Error::CreateDeviceFailed => write!(f, "vkCreateDevice failed"), Error::CreateCommandPoolFailed => { write!(f, "vkCreateCommandPool failed") }, Error::CommandBufferAllocateFailed => { write!(f, "vkCommandBufferAllocate failed") }, Error::CreateFenceFailed => { write!(f, "vkCreateFence failed") }, Error::EnumeratePhysicalDevicesFailed => { write!(f, "vkEnumeratePhysicalDevices failed") }, Error::NoDevices => { write!(f, "The Vulkan instance reported zero drivers") }, Error::DeviceErrors(errors) => { for (i, error) in errors.iter().enumerate() { if i > 0 { writeln!(f)?; } write!(f, "{}: {}", i, error)?; } Ok(()) }, Error::NoGraphicsQueueFamily => { write!( f, "Device has no graphics queue family" ) }, &Error::InvalidDeviceId { device_id, n_devices } => { write!( f, "Device {} was selected but the Vulkan instance only \ reported {} device{}.", device_id.saturating_add(1), n_devices, if n_devices == 1 { "" } else { "s" }, ) }, } } } impl Error { pub fn result(&self) -> result::Result { match self { Error::FuncsError(_) => result::Result::Fail, Error::RequirementsError(e) => e.result(), Error::EnumerateInstanceExtensionPropertiesFailed => { result::Result::Fail }, Error::MissingInstanceExtension(_) => result::Result::Skip, Error::IncompatibleDriver => result::Result::Skip, Error::CreateInstanceFailed => result::Result::Fail, Error::CreateDeviceFailed => result::Result::Fail, Error::CreateCommandPoolFailed => result::Result::Fail, Error::CommandBufferAllocateFailed => result::Result::Fail, Error::CreateFenceFailed => result::Result::Fail, Error::EnumeratePhysicalDevicesFailed => result::Result::Fail, Error::NoDevices => result::Result::Skip, Error::DeviceErrors(errors) => { // If all of the errors were Fail then we’ll return // failure overall, otherwise we’ll return Skip. if errors.iter().all(|e| e.result() == result::Result::Fail) { result::Result::Fail } else { result::Result::Skip } }, Error::NoGraphicsQueueFamily => result::Result::Skip, Error::InvalidDeviceId { .. } => result::Result::Fail, } } } struct GetInstanceProcClosure<'a> { vklib: &'a vulkan_funcs::Library, vk_instance: vk::VkInstance, } extern "C" fn get_instance_proc( func_name: *const c_char, user_data: *const c_void, ) -> *const c_void { unsafe { let data: &GetInstanceProcClosure = &*user_data.cast(); let vklib = data.vklib; std::mem::transmute( vklib.vkGetInstanceProcAddr.unwrap()( data.vk_instance, func_name.cast() ) ) } } // ext is a zero-terminated byte array, as one of the constants in // vulkan_bindings. fn check_instance_extension( vklib: &vulkan_funcs::Library, ext: &[u8], ) -> Result<(), Error> { let mut count: u32 = 0; let res = unsafe { vklib.vkEnumerateInstanceExtensionProperties.unwrap()( ptr::null(), // layerName ptr::addr_of_mut!(count), ptr::null_mut(), // props ) }; if res != vk::VK_SUCCESS { return Err(Error::EnumerateInstanceExtensionPropertiesFailed); } let mut props = Vec::<vk::VkExtensionProperties>::new(); props.resize_with(count as usize, Default::default); let res = unsafe { vklib.vkEnumerateInstanceExtensionProperties.unwrap()( ptr::null(), // layerName ptr::addr_of_mut!(count), props.as_mut_ptr(), ) }; if res != vk::VK_SUCCESS { return Err(Error::EnumerateInstanceExtensionPropertiesFailed); } 'prop_loop: for prop in props.iter() { assert!(ext.len() <= prop.extensionName.len()); // Can’t just compare the slices because one is i8 and the // other is u8. ext should include the null terminator so this // will check for null too. for (i, &c) in ext.iter().enumerate() { if c as c_char != prop.extensionName[i] { continue 'prop_loop; } } // If we made it here then it’s the same extension name return Ok(()); } let ext_name = CStr::from_bytes_with_nul(ext).unwrap(); Err(Error::MissingInstanceExtension(ext_name.to_string_lossy().to_string())) } // Struct that contains a VkInstance and its function pointers from // vulkan_funcs. This is needed so that it can have a drop // implementation that uses the function pointers to free the // instance. #[derive(Debug)] struct InstancePair { is_external: bool, vk_instance: vk::VkInstance, vkinst: Box<vulkan_funcs::Instance>, } impl InstancePair { fn new( vklib: &vulkan_funcs::Library, requirements: &Requirements ) -> Result<InstancePair, Error> { let application_info = vk::VkApplicationInfo { sType: vk::VK_STRUCTURE_TYPE_APPLICATION_INFO, pNext: ptr::null(), pApplicationName: "vkrunner\0".as_ptr().cast(), applicationVersion: 0, pEngineName: ptr::null(), engineVersion: 0, apiVersion: requirements.version(), }; let mut instance_create_info = vk::VkInstanceCreateInfo { sType: vk::VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, pNext: ptr::null(), flags: 0, pApplicationInfo: ptr::addr_of!(application_info), enabledLayerCount: 0, ppEnabledLayerNames: ptr::null(), enabledExtensionCount: 0, ppEnabledExtensionNames: ptr::null(), }; let ext = vk::VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; let structures = requirements.c_structures(); let mut enabled_extensions = Vec::<*const c_char>::new(); if structures.is_some() { check_instance_extension(vklib, ext)?; enabled_extensions.push(ext.as_ptr().cast()); } instance_create_info.enabledExtensionCount = enabled_extensions.len() as u32; instance_create_info.ppEnabledExtensionNames = enabled_extensions.as_ptr(); // VkInstance is a dispatchable handle so it is always a // pointer and we can’t use vk::null_handle. let mut vk_instance = ptr::null_mut(); let res = unsafe { vklib.vkCreateInstance.unwrap()( ptr::addr_of!(instance_create_info), ptr::null(), // allocator ptr::addr_of_mut!(vk_instance), ) }; match res { vk::VK_ERROR_INCOMPATIBLE_DRIVER => Err(Error::IncompatibleDriver), vk::VK_SUCCESS => { let vkinst = unsafe { let closure = GetInstanceProcClosure { vklib, vk_instance, }; Box::new(vulkan_funcs::Instance::new( get_instance_proc, ptr::addr_of!(closure).cast(), )) }; Ok(InstancePair { is_external: false, vk_instance, vkinst, }) }, _ => Err(Error::CreateInstanceFailed), } } fn new_external( get_instance_proc_cb: vulkan_funcs::GetInstanceProcFunc, user_data: *const c_void, ) -> InstancePair { InstancePair { is_external: true, vk_instance: ptr::null_mut(), vkinst: unsafe { Box::new(vulkan_funcs::Instance::new( get_instance_proc_cb, user_data, )) }, } } } impl Drop for InstancePair { fn drop(&mut self) { if !self.is_external { unsafe { self.vkinst.vkDestroyInstance.unwrap()( self.vk_instance, ptr::null(), // allocator ); } } } } // Struct that contains a VkDevice and its function pointers from // vulkan_funcs. This is needed so that it can have a drop // implementation that uses the function pointers to free the device. #[derive(Debug)] struct DevicePair { is_external: bool, device: vk::VkDevice, vkdev: Box<vulkan_funcs::Device>, } impl DevicePair { fn new( instance_pair: &InstancePair, requirements: &Requirements, physical_device: vk::VkPhysicalDevice, queue_family: u32, ) -> Result<DevicePair, Error> { let structures = requirements.c_structures(); let base_features = requirements.c_base_features(); let extensions = requirements.c_extensions(); let queue_priorities = [1.0f32]; let queue_create_info = vk::VkDeviceQueueCreateInfo { sType: vk::VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, pNext: ptr::null(), flags: 0, queueFamilyIndex: queue_family, queueCount: 1, pQueuePriorities: queue_priorities.as_ptr(), }; let device_create_info = vk::VkDeviceCreateInfo { sType: vk::VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, pNext: structures .and_then(|s| Some(s.as_ptr().cast())) .unwrap_or(ptr::null()), flags: 0, queueCreateInfoCount: 1, pQueueCreateInfos: ptr::addr_of!(queue_create_info), enabledLayerCount: 0, ppEnabledLayerNames: ptr::null(), enabledExtensionCount: extensions.len() as u32, ppEnabledExtensionNames: if extensions.len() > 0 { extensions.as_ptr().cast() } else { ptr::null() }, pEnabledFeatures: base_features, }; // VkDevice is a dispatchable handle so it is always a pointer // and we can’t use vk::null_handle. let mut device = ptr::null_mut(); let res = unsafe { instance_pair.vkinst.vkCreateDevice.unwrap()( physical_device, ptr::addr_of!(device_create_info), ptr::null(), // allocator ptr::addr_of_mut!(device), ) }; if res == vk::VK_SUCCESS { Ok(DevicePair { is_external: false, device, vkdev: Box::new(vulkan_funcs::Device::new( &instance_pair.vkinst, device, )), }) } else { Err(Error::CreateDeviceFailed) } } fn new_external( instance_pair: &InstancePair, device: vk::VkDevice, ) -> DevicePair { DevicePair { is_external: true, device, vkdev: Box::new(vulkan_funcs::Device::new( &instance_pair.vkinst, device, )), } } } impl Drop for DevicePair { fn drop(&mut self) { if !self.is_external { unsafe { self.vkdev.vkDestroyDevice.unwrap()( self.device, ptr::null(), // allocator ); } } } } fn free_resources( device_pair: &DevicePair, mut command_pool: Option<vk::VkCommandPool>, mut command_buffer: Option<vk::VkCommandBuffer>, mut fence: Option<vk::VkFence>, ) { unsafe { if let Some(fence) = fence.take() { device_pair.vkdev.vkDestroyFence.unwrap()( device_pair.device, fence, ptr::null() // allocator ); } if let Some(mut command_buffer) = command_buffer.take() { device_pair.vkdev.vkFreeCommandBuffers.unwrap()( device_pair.device, command_pool.unwrap(), 1, // commandBufferCount ptr::addr_of_mut!(command_buffer), ); } if let Some(command_pool) = command_pool.take() { device_pair.vkdev.vkDestroyCommandPool.unwrap()( device_pair.device, command_pool, ptr::null() // allocator ); } } } // This is a helper struct for creating the rest of the context // resources. Its members are optional so that it can handle // destruction if an error occurs between creating one of the // resources. struct DeviceResources<'a> { device_pair: &'a DevicePair, command_pool: Option<vk::VkCommandPool>, command_buffer: Option<vk::VkCommandBuffer>, fence: Option<vk::VkFence>, } impl<'a> DeviceResources<'a> { fn create_command_pool( &mut self, queue_family: u32, ) -> Result<(), Error> { let command_pool_create_info = vk::VkCommandPoolCreateInfo { sType: vk::VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, pNext: ptr::null(), flags: vk::VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, queueFamilyIndex: queue_family, }; let mut command_pool = vk::null_handle(); let res = unsafe { self.device_pair.vkdev.vkCreateCommandPool.unwrap()( self.device_pair.device, ptr::addr_of!(command_pool_create_info), ptr::null(), // allocator ptr::addr_of_mut!(command_pool), ) }; if res != vk::VK_SUCCESS { return Err(Error::CreateCommandPoolFailed); } self.command_pool = Some(command_pool); Ok(()) } fn allocate_command_buffer(&mut self) -> Result<(), Error> { let command_buffer_allocate_info = vk::VkCommandBufferAllocateInfo { sType: vk::VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, pNext: ptr::null(), commandPool: self.command_pool.unwrap(), level: vk::VK_COMMAND_BUFFER_LEVEL_PRIMARY, commandBufferCount: 1, }; // VkCommandBuffer is a dispatchable handle so it is always a // pointer and we can’t use vk::null_handle. let mut command_buffer = ptr::null_mut(); let res = unsafe { self.device_pair.vkdev.vkAllocateCommandBuffers.unwrap()( self.device_pair.device, ptr::addr_of!(command_buffer_allocate_info), ptr::addr_of_mut!(command_buffer), ) }; if res != vk::VK_SUCCESS { return Err(Error::CommandBufferAllocateFailed); } self.command_buffer = Some(command_buffer); Ok(()) } fn create_fence(&mut self) -> Result<(), Error> { let fence_create_info = vk::VkFenceCreateInfo { sType: vk::VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, pNext: ptr::null(), flags: 0, }; let mut fence = vk::null_handle(); let res = unsafe { self.device_pair.vkdev.vkCreateFence.unwrap()( self.device_pair.device, ptr::addr_of!(fence_create_info), ptr::null(), // allocator ptr::addr_of_mut!(fence), ) }; if res != vk::VK_SUCCESS { return Err(Error::CreateFenceFailed); } self.fence = Some(fence); Ok(()) } fn new( device_pair: &'a DevicePair, queue_family: u32, ) -> Result<DeviceResources<'a>, Error> { let mut device_resources = DeviceResources { device_pair, command_pool: None, command_buffer: None, fence: None, }; device_resources.create_command_pool(queue_family)?; device_resources.allocate_command_buffer()?; device_resources.create_fence()?; Ok(device_resources) } } impl<'a> Drop for DeviceResources<'a> { fn drop(&mut self) { free_resources( self.device_pair, self.command_pool.take(), self.command_buffer.take(), self.fence.take(), ); } } fn find_queue_family( instance_pair: &InstancePair, physical_device: vk::VkPhysicalDevice, ) -> Result<u32, Error> { let vkinst = instance_pair.vkinst.as_ref(); let mut count = 0u32; unsafe { vkinst.vkGetPhysicalDeviceQueueFamilyProperties.unwrap()( physical_device, ptr::addr_of_mut!(count), ptr::null_mut(), // queues ); } let mut queues = Vec::<vk::VkQueueFamilyProperties>::new(); queues.resize_with(count as usize, Default::default); unsafe { vkinst.vkGetPhysicalDeviceQueueFamilyProperties.unwrap()( physical_device, ptr::addr_of_mut!(count), queues.as_mut_ptr(), ); } for (i, queue) in queues.into_iter().enumerate() { if queue.queueFlags & vk::VK_QUEUE_GRAPHICS_BIT != 0 && queue.queueCount >= 1 { return Ok(i as u32); } } Err(Error::NoGraphicsQueueFamily) } // Checks whether the chosen physical device can be used and has an // appropriate queue family. If so, it returns the index of the queue // family, otherwise an error. fn check_physical_device( instance_pair: &InstancePair, requirements: &Requirements, physical_device: vk::VkPhysicalDevice, ) -> Result<u32, Error> { requirements.check( instance_pair.vkinst.as_ref(), physical_device )?; find_queue_family(instance_pair, physical_device) } // Checks all of the physical devices advertised by the instance. If // it can found one matching the requirements then it returns it along // with a queue family index of the first graphics queue. Otherwise // returns an error. fn find_physical_device( instance_pair: &InstancePair, requirements: &Requirements, device_id: Option<usize>, ) -> Result<(vk::VkPhysicalDevice, u32), Error> { let vkinst = instance_pair.vkinst.as_ref(); let mut count = 0u32; let res = unsafe { vkinst.vkEnumeratePhysicalDevices.unwrap()( instance_pair.vk_instance, ptr::addr_of_mut!(count), ptr::null_mut(), // devices ) }; if res != vk::VK_SUCCESS { return Err(Error::EnumeratePhysicalDevicesFailed); } let mut devices = Vec::<vk::VkPhysicalDevice>::new(); devices.resize(count as usize, ptr::null_mut()); let res = unsafe { vkinst.vkEnumeratePhysicalDevices.unwrap()( instance_pair.vk_instance, ptr::addr_of_mut!(count), devices.as_mut_ptr(), ) }; if res != vk::VK_SUCCESS { return Err(Error::EnumeratePhysicalDevicesFailed); } if let Some(device_id) = device_id { if device_id >= count as usize { return Err(Error::InvalidDeviceId { device_id, n_devices: count }); } else { return match check_physical_device( instance_pair, requirements, devices[device_id] ) { Ok(queue_family) => Ok((devices[device_id], queue_family)), Err(e) => Err(e), }; } } // Collect all of the errors into an array so we can report all of // them in a combined error message if we can’t find a good // device. let mut errors = Vec::<Error>::new(); for device in devices { match check_physical_device( instance_pair, requirements, device ) { Ok(queue_family) => return Ok((device, queue_family)), Err(e) => { errors.push(e); }, } } match errors.len() { 0 => Err(Error::NoDevices), 1 => Err(errors.pop().unwrap()), _ => Err(Error::DeviceErrors(errors)), } } impl Context { fn new_internal( vklib: Option<Box<vulkan_funcs::Library>>, instance_pair: InstancePair, physical_device: vk::VkPhysicalDevice, queue_family: u32, device_pair: DevicePair, ) -> Result<Context, Error> { let mut memory_properties = vk::VkPhysicalDeviceMemoryProperties::default(); unsafe { instance_pair.vkinst.vkGetPhysicalDeviceMemoryProperties.unwrap()( physical_device, ptr::addr_of_mut!(memory_properties), ); } // VkQueue is a dispatchable handle so it is always a pointer // and we can’t use vk::null_handle. let mut queue = ptr::null_mut(); unsafe { device_pair.vkdev.vkGetDeviceQueue.unwrap()( device_pair.device, queue_family, 0, // queueIndex ptr::addr_of_mut!(queue) ); } let mut resources = DeviceResources::new( &device_pair, queue_family, )?; let command_pool = resources.command_pool.take().unwrap(); let command_buffer = resources.command_buffer.take().unwrap(); let fence = resources.fence.take().unwrap(); drop(resources); Ok(Context { _vklib: vklib, instance_pair, device_pair, physical_device, memory_properties, command_pool, command_buffer, fence, queue, always_flush_memory: env_var_as_boolean( "VKRUNNER_ALWAYS_FLUSH_MEMORY", false ) }) } /// Constructs a Context or returns [Error::Failure] if an /// error occurred while constructing it or /// [Error::Incompatible] if the requirements couldn’t be /// met. `device_id` can optionally be set to limit the device /// selection to an index in the list returned by /// `vkEnumeratePhysicalDevices`. pub fn new( requirements: &Requirements, device_id: Option<usize>, ) -> Result<Context, Error> { let vklib = Box::new(vulkan_funcs::Library::new()?); let instance_pair = InstancePair::new(vklib.as_ref(), requirements)?; let (physical_device, queue_family) = find_physical_device( &instance_pair, requirements, device_id, )?; let device_pair = DevicePair::new( &instance_pair, requirements, physical_device, queue_family, )?; Context::new_internal( Some(vklib), instance_pair, physical_device, queue_family, device_pair ) } /// Constructs a Context from a VkDevice created externally. The /// VkDevice won’t be freed when the context is dropped. It is the /// caller’s responsibility to keep the device alive during the /// lifetime of the Context. It also needs to ensure that any /// features and extensions that might be used during script /// execution were enabled when the device was created. /// /// `get_instance_proc_cb` will be called to get the /// instance-level functions that the context needs. The rest of /// the functions will be retrieved using the /// `vkGetDeviceProcAddr` function that that returns. `user_data` /// will be passed to the function. pub fn new_with_device( get_instance_proc_cb: vulkan_funcs::GetInstanceProcFunc, user_data: *const c_void, physical_device: vk::VkPhysicalDevice, queue_family: u32, device: vk::VkDevice ) -> Result<Context, Error> { let instance_pair = InstancePair::new_external( get_instance_proc_cb, user_data, ); let device_pair = DevicePair::new_external( &instance_pair, device, ); Context::new_internal( None, // vklib instance_pair, physical_device, queue_family, device_pair, ) } /// Get the instance function pointers #[inline] pub fn instance(&self) -> &vulkan_funcs::Instance { &self.instance_pair.vkinst } /// Get the VkDevice handle #[inline] pub fn vk_device(&self) -> vk::VkDevice { self.device_pair.device } /// Get the device function pointers #[inline] pub fn device(&self) -> &vulkan_funcs::Device { &self.device_pair.vkdev } /// Get the physical device that was used to create this context. #[inline] pub fn physical_device(&self) -> vk::VkPhysicalDevice { self.physical_device } /// Get the memory properties struct for the physical device. This /// is queried from the physical device once when the context is /// constructed and cached for later use so this method is very /// cheap. #[inline] pub fn memory_properties(&self) -> &vk::VkPhysicalDeviceMemoryProperties { &self.memory_properties } /// Get the single shared command buffer that is associated with /// the context. #[inline] pub fn command_buffer(&self) -> vk::VkCommandBuffer { self.command_buffer } /// Get the single shared fence that is associated with the /// context. #[inline] pub fn fence(&self) -> vk::VkFence { self.fence } /// Get the queue chosen for this context. #[inline] pub fn queue(&self) -> vk::VkQueue { self.queue } /// Returns whether the memory should always be flushed regardless /// of whether the `VK_MEMORY_PROPERTY_HOST_COHERENT` bit is set /// in the [memory_properties](Context::memory_properties). This /// is mainly for testing purposes and can be enabled by setting /// the `VKRUNNER_ALWAYS_FLUSH_MEMORY` environment variable to /// `true`. #[inline] pub fn always_flush_memory(&self) -> bool { self.always_flush_memory } } impl Drop for Context { fn drop(&mut self) { free_resources( &self.device_pair, Some(self.command_pool), Some(self.command_buffer), Some(self.fence), ); } } #[cfg(test)] mod test { use super::*; use crate::fake_vulkan::{FakeVulkan, HandleType}; use crate::env_var_test::EnvVarLock; #[test] fn base() { let mut fake_vulkan = FakeVulkan::new(); fake_vulkan.physical_devices.push(Default::default()); fake_vulkan.physical_devices[0].memory_properties.memoryTypeCount = 3; fake_vulkan.set_override(); let context = Context::new(&Requirements::new(), None).unwrap(); assert!(!context.instance_pair.vk_instance.is_null()); assert!(context.instance().vkCreateDevice.is_some()); assert!(!context.vk_device().is_null()); assert!(context.device().vkCreateCommandPool.is_some()); assert_eq!( context.physical_device(), fake_vulkan.index_to_physical_device(0) ); assert_eq!(context.memory_properties().memoryTypeCount, 3); assert!(!context.command_buffer().is_null()); assert!(context.fence() != vk::null_handle()); assert_eq!(FakeVulkan::unmake_queue(context.queue()), (0, 0)); } #[test] fn no_devices() { let fake_vulkan = FakeVulkan::new(); fake_vulkan.set_override(); let err = Context::new(&Requirements::new(), None).unwrap_err(); assert_eq!( &err.to_string(), "The Vulkan instance reported zero drivers" ); } #[test] fn check_instance_extension() { let mut fake_vulkan = FakeVulkan::new(); fake_vulkan.physical_devices.push(Default::default()); let mut reqs = Requirements::new(); // Add a requirement so that c_structures won’t be NULL reqs.add("shaderInt8"); // Make vkEnumerateInstanceExtensionProperties fail fake_vulkan.queue_result( "vkEnumerateInstanceExtensionProperties".to_string(), vk::VK_ERROR_UNKNOWN, ); fake_vulkan.set_override(); let err = Context::new(&mut reqs, None).unwrap_err(); assert_eq!( err.to_string(), "vkEnumerateInstanceExtensionProperties failed", ); // Make it fail the second time too fake_vulkan.queue_result( "vkEnumerateInstanceExtensionProperties".to_string(), vk::VK_SUCCESS, ); fake_vulkan.queue_result( "vkEnumerateInstanceExtensionProperties".to_string(), vk::VK_ERROR_UNKNOWN, ); fake_vulkan.set_override(); let err = Context::new(&mut reqs, None).unwrap_err(); assert_eq!( err.to_string(), "vkEnumerateInstanceExtensionProperties failed", ); // Add an extension name with the same length but different characters fake_vulkan.add_instance_extension( "VK_KHR_get_physical_device_properties3" ); // Add a shorter extension name fake_vulkan.add_instance_extension("VK_KHR"); // And a longer one fake_vulkan.add_instance_extension( "VK_KHR_get_physical_device_properties23" ); fake_vulkan.set_override(); let err = Context::new(&mut reqs, None).unwrap_err(); assert_eq!( err.to_string(), "Missing instance extension: \ VK_KHR_get_physical_device_properties2", ); } #[test] fn extension_feature() { let mut fake_vulkan = FakeVulkan::new(); fake_vulkan.physical_devices.push(Default::default()); fake_vulkan.add_instance_extension( "VK_KHR_get_physical_device_properties2" ); let mut reqs = Requirements::new(); reqs.add("multiview"); fake_vulkan.set_override(); assert_eq!( Context::new(&mut reqs, None).unwrap_err().to_string(), "Missing required extension: VK_KHR_multiview", ); fake_vulkan.physical_devices[0].add_extension("VK_KHR_multiview"); fake_vulkan.physical_devices[0].multiview.multiview = vk::VK_TRUE; fake_vulkan.set_override(); Context::new(&mut reqs, None).unwrap(); } #[test] fn multiple_mismatches() { let mut fake_vulkan = FakeVulkan::new(); let mut reqs = Requirements::new(); reqs.add("madeup_extension"); fake_vulkan.physical_devices.push(Default::default()); fake_vulkan.physical_devices[0].properties.apiVersion = crate::requirements::make_version(0, 1, 0); fake_vulkan.physical_devices[0].add_extension("madeup_extension"); fake_vulkan.physical_devices.push(Default::default()); fake_vulkan.physical_devices[1].queue_families.clear(); fake_vulkan.physical_devices[1].add_extension("madeup_extension"); fake_vulkan.physical_devices.push(Default::default()); fake_vulkan.set_override(); let err = Context::new(&mut reqs, None).unwrap_err(); assert_eq!( err.to_string(), "0: Vulkan API version 1.0.0 required but the driver \ reported 0.1.0\n\ 1: Device has no graphics queue family\n\ 2: Missing required extension: madeup_extension", ); assert_eq!(err.result(), result::Result::Skip); // Try making them one of them fail fake_vulkan.queue_result( "vkEnumerateDeviceExtensionProperties".to_string(), vk::VK_ERROR_UNKNOWN, ); fake_vulkan.set_override(); let err = Context::new(&mut reqs, None).unwrap_err(); assert_eq!( err.to_string(), "0: vkEnumerateDeviceExtensionProperties failed\n\ 1: Device has no graphics queue family\n\ 2: Missing required extension: madeup_extension", ); assert_eq!(err.result(), result::Result::Skip); // Try making all of them fail fake_vulkan.physical_devices[0] = Default::default(); fake_vulkan.physical_devices[1] = Default::default(); fake_vulkan.queue_result( "vkEnumerateDeviceExtensionProperties".to_string(), vk::VK_ERROR_UNKNOWN, ); fake_vulkan.queue_result( "vkEnumerateDeviceExtensionProperties".to_string(), vk::VK_ERROR_UNKNOWN, ); fake_vulkan.queue_result( "vkEnumerateDeviceExtensionProperties".to_string(), vk::VK_ERROR_UNKNOWN, ); fake_vulkan.set_override(); let err = Context::new(&mut reqs, None).unwrap_err(); assert_eq!( err.to_string(), "0: vkEnumerateDeviceExtensionProperties failed\n\ 1: vkEnumerateDeviceExtensionProperties failed\n\ 2: vkEnumerateDeviceExtensionProperties failed", ); assert_eq!(err.result(), result::Result::Fail); // Finally add a physical device that will succeed fake_vulkan.physical_devices.push(Default::default()); fake_vulkan.physical_devices[3].add_extension("madeup_extension"); fake_vulkan.set_override(); let context = Context::new(&mut reqs, None).unwrap(); assert_eq!( fake_vulkan.physical_device_to_index(context.physical_device), 3, ); } #[test] fn device_id() { let mut fake_vulkan = FakeVulkan::new(); for _ in 0..3 { fake_vulkan.physical_devices.push(Default::default()); } let mut reqs = Requirements::new(); // Try selecting each device for i in 0..3 { fake_vulkan.set_override(); let context = Context::new(&mut reqs, Some(i)).unwrap(); assert_eq!( fake_vulkan.physical_device_to_index(context.physical_device), i, ); } // Try selecting a non-existant device fake_vulkan.set_override(); let err = Context::new(&mut reqs, Some(3)).unwrap_err(); assert_eq!( err.to_string(), "Device 4 was selected but the Vulkan instance only reported 3 \ devices." ); fake_vulkan.physical_devices.truncate(1); fake_vulkan.set_override(); let err = Context::new(&mut reqs, Some(3)).unwrap_err(); assert_eq!( err.to_string(), "Device 4 was selected but the Vulkan instance only reported 1 \ device." ); // Try a failure reqs.add("madeup_extension"); fake_vulkan.physical_devices.push(Default::default()); fake_vulkan.set_override(); let err = Context::new(&mut reqs, Some(0)).unwrap_err(); assert_eq!( err.to_string(), "Missing required extension: madeup_extension", ); } #[test] fn always_flush_memory_false() { let mut fake_vulkan = FakeVulkan::new(); fake_vulkan.physical_devices.push(Default::default()); let mut reqs = Requirements::new(); let _env_var_lock = EnvVarLock::new(&[ ("VKRUNNER_ALWAYS_FLUSH_MEMORY", "false"), ]); fake_vulkan.set_override(); let context = Context::new(&mut reqs, None).unwrap(); assert_eq!(context.always_flush_memory(), false); } #[test] fn always_flush_memory_true() { let mut fake_vulkan = FakeVulkan::new(); fake_vulkan.physical_devices.push(Default::default()); let mut reqs = Requirements::new(); let _env_var_lock = EnvVarLock::new(&[ ("VKRUNNER_ALWAYS_FLUSH_MEMORY", "true"), ]); fake_vulkan.set_override(); let context = Context::new(&mut reqs, None).unwrap(); assert_eq!(context.always_flush_memory(), true); } #[test] fn new_with_device() { let mut fake_vulkan = FakeVulkan::new(); fake_vulkan.physical_devices.push(Default::default()); let device = fake_vulkan.add_dispatchable_handle(HandleType::Device); extern "C" fn no_create_device( _physical_device: vk::VkPhysicalDevice, _create_info: *const vk::VkDeviceCreateInfo, _allocator: *const vk::VkAllocationCallbacks, _device_out: *mut vk::VkDevice, ) -> vk::VkResult { unreachable!( "vkCreateDevice shouldn’t be called for an external \ device" ); } extern "C" fn no_destroy_device( _device: vk::VkDevice, _allocator: *const vk::VkAllocationCallbacks ) { unreachable!( "vkDestroyDevice shouldn’t be called for an external \ device" ); } extern "C" fn no_destroy_instance( _instance: vk::VkInstance, _allocator: *const vk::VkAllocationCallbacks ) { unreachable!( "vkDestroyInstance shouldn’t be called for an external \ device" ); } extern "C" fn get_device_proc_addr( _device: vk::VkDevice, name: *const c_char, ) -> vk::PFN_vkVoidFunction { unsafe { std::mem::transmute(get_instance_proc_cb( name.cast(), (FakeVulkan::current() as *mut FakeVulkan).cast(), )) } } extern "C" fn get_instance_proc_cb( func_name: *const c_char, user_data: *const c_void, ) -> *const c_void { let name = unsafe { CStr::from_ptr(func_name.cast()).to_str().unwrap() }; match name { "vkGetDeviceProcAddr" => unsafe { std::mem::transmute::<vk::PFN_vkGetDeviceProcAddr, _>( Some(get_device_proc_addr) ) }, "vkDestroyInstance" => unsafe { std::mem::transmute::<vk::PFN_vkDestroyInstance, _>( Some(no_destroy_instance) ) }, "vkCreateDevice" => unsafe { std::mem::transmute::<vk::PFN_vkCreateDevice, _>( Some(no_create_device) ) }, "vkDestroyDevice" => unsafe { std::mem::transmute::<vk::PFN_vkDestroyDevice, _>( Some(no_destroy_device) ) }, _ => unsafe { let fake_vulkan = &*(user_data.cast::<FakeVulkan>()); std::mem::transmute( fake_vulkan.get_function(func_name.cast()) ) }, } } let context = Context::new_with_device( get_instance_proc_cb, (fake_vulkan.as_ref() as *const FakeVulkan).cast(), fake_vulkan.index_to_physical_device(0), 3, // queue_family device, ).unwrap(); assert_eq!(context.instance_pair.vk_instance, ptr::null_mut()); assert_eq!( fake_vulkan.physical_device_to_index(context.physical_device()), 0 ); assert_eq!(context.vk_device(), device); assert!(context.device_pair.is_external); assert!(context.instance_pair.is_external); drop(context); fake_vulkan.get_dispatchable_handle_mut(device).freed = true; } } ```