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;
}
}
```