This is page 5 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/vkrunner/tester.rs:
--------------------------------------------------------------------------------
```rust
// vkrunner
//
// Copyright (C) 2018, 2023 Neil Roberts
// Copyright (C) 2018 Intel Coporation
// 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::window::Window;
use crate::context::Context;
use crate::pipeline_set::{PipelineSet, RectangleVertex};
use crate::pipeline_key;
use crate::script::{Script, BufferType, Operation};
use crate::inspect::Inspector;
use crate::vk;
use crate::buffer::{self, MappedMemory, DeviceMemory, Buffer};
use crate::flush_memory::{self, flush_memory};
use crate::tolerance::Tolerance;
use crate::slot;
use crate::inspect;
use std::fmt;
use std::ptr;
use std::mem;
use std::rc::Rc;
use std::ffi::c_int;
#[derive(Debug)]
pub struct CommandError {
pub line_num: usize,
pub error: Error,
}
#[derive(Debug)]
pub enum Error {
AllocateDescriptorSetsFailed,
BeginCommandBufferFailed,
EndCommandBufferFailed,
ResetFencesFailed,
QueueSubmitFailed,
WaitForFencesFailed,
ProbeFailed(ProbeFailedError),
InvalidateMappedMemoryRangesFailed,
BufferError(buffer::Error),
FlushMemoryError(flush_memory::Error),
CommandErrors(Vec<CommandError>),
InvalidBufferBinding { desc_set: u32, binding: u32 },
InvalidBufferOffset,
SsboProbeFailed {
slot_type: slot::Type,
layout: slot::Layout,
expected: Box<[u8]>,
observed: Box<[u8]>,
},
}
#[derive(Debug)]
pub struct ProbeFailedError {
x: u32,
y: u32,
expected: [f64; 4],
observed: [f64; 4],
n_components: usize,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum State {
/// Any rendering or computing has finished and we can read the
/// buffers.
Idle,
/// The command buffer has begun
CommandBuffer,
/// The render pass has begun
RenderPass,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::AllocateDescriptorSetsFailed => {
write!(f, "vkAllocateDescriptorSets failed")
},
Error::BeginCommandBufferFailed => {
write!(f, "vkBeginCommandBuffer failed")
},
Error::EndCommandBufferFailed => {
write!(f, "vkEndCommandBuffer failed")
},
Error::ResetFencesFailed => {
write!(f, "vkResetFences failed")
},
Error::QueueSubmitFailed => {
write!(f, "vkQueueSubmit failed")
},
Error::WaitForFencesFailed => {
write!(f, "vkWaitForFences failed")
},
Error::InvalidateMappedMemoryRangesFailed => {
write!(f, "vkInvalidateMappedMemeroyRangesFailed failed")
},
Error::ProbeFailed(e) => e.fmt(f),
&Error::SsboProbeFailed {
slot_type,
layout,
ref expected,
ref observed
} => {
write!(
f,
"SSBO probe failed\n\
\x20 Reference:",
)?;
write_slot(f, slot_type, layout, expected)?;
write!(
f,
"\n\
\x20 Observed: ",
)?;
write_slot(f, slot_type, layout, observed)?;
Ok(())
},
Error::BufferError(e) => e.fmt(f),
Error::FlushMemoryError(e) => e.fmt(f),
Error::CommandErrors(errors) => {
for (num, e) in errors.iter().enumerate() {
if num > 0 {
writeln!(f)?;
}
write!(f, "line {}: ", e.line_num)?;
e.error.fmt(f)?;
}
Ok(())
},
Error::InvalidBufferBinding { desc_set, binding } => {
write!(f, "Invalid buffer binding: {}:{}", desc_set, binding)
},
Error::InvalidBufferOffset => {
write!(f, "Invalid buffer offset")
},
}
}
}
fn write_slot(
f: &mut fmt::Formatter,
slot_type: slot::Type,
layout: slot::Layout,
values: &[u8],
) -> fmt::Result {
let base_type = slot_type.base_type();
let base_type_size = base_type.size();
for offset in slot_type.offsets(layout) {
let values = &values[offset..offset + base_type_size];
write!(f, " {}", slot::BaseTypeInSlice::new(base_type, values))?;
}
Ok(())
}
fn format_pixel(f: &mut fmt::Formatter, pixel: &[f64]) -> fmt::Result {
for component in pixel {
write!(f, " {}", component)?;
}
Ok(())
}
impl fmt::Display for ProbeFailedError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Probe color at ({},{})\n\
\x20 Expected:",
self.x,
self.y,
)?;
format_pixel(f, &self.expected[0..self.n_components])?;
write!(
f,
"\n\
\x20 Observed:"
)?;
format_pixel(f, &self.observed[0..self.n_components])
}
}
impl From<buffer::Error> for Error {
fn from(e: buffer::Error) -> Error {
Error::BufferError(e)
}
}
impl From<flush_memory::Error> for Error {
fn from(e: flush_memory::Error) -> Error {
Error::FlushMemoryError(e)
}
}
#[derive(Debug)]
struct DescriptorSetVec<'a> {
handles: Vec<vk::VkDescriptorSet>,
// needed for the destructor
pipeline_set: &'a PipelineSet,
window: &'a Window,
}
impl<'a> DescriptorSetVec<'a> {
fn new(
window: &'a Window,
pipeline_set: &'a PipelineSet,
) -> Result<DescriptorSetVec<'a>, Error> {
let layouts = pipeline_set.descriptor_set_layouts();
let mut handles = Vec::with_capacity(layouts.len());
if !layouts.is_empty() {
let allocate_info = vk::VkDescriptorSetAllocateInfo {
sType: vk::VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
pNext: ptr::null(),
descriptorPool: pipeline_set.descriptor_pool().unwrap(),
descriptorSetCount: layouts.len() as u32,
pSetLayouts: layouts.as_ptr(),
};
let res = unsafe {
window.device().vkAllocateDescriptorSets.unwrap()(
window.vk_device(),
ptr::addr_of!(allocate_info),
handles.as_mut_ptr(),
)
};
if res == vk::VK_SUCCESS {
// SAFETY: We ensured the buffer had the right
// capacity when we constructed it and the call to
// vkAllocateDescriptorSets should have filled it with
// valid values.
unsafe {
handles.set_len(layouts.len());
}
} else {
return Err(Error::AllocateDescriptorSetsFailed);
}
}
Ok(DescriptorSetVec {
handles,
pipeline_set,
window,
})
}
}
impl<'a> Drop for DescriptorSetVec<'a> {
fn drop(&mut self) {
if self.handles.is_empty() {
return;
}
unsafe {
self.window.device().vkFreeDescriptorSets.unwrap()(
self.window.vk_device(),
self.pipeline_set.descriptor_pool().unwrap(),
self.handles.len() as u32,
self.handles.as_ptr(),
);
}
}
}
#[derive(Debug)]
struct TestBuffer {
map: MappedMemory,
memory: DeviceMemory,
buffer: Buffer,
size: usize,
// true if the buffer has been modified through the CPU-mapped
// memory since the last command buffer submission.
pending_write: bool,
}
impl TestBuffer {
fn new(
context: Rc<Context>,
size: usize,
usage: vk::VkBufferUsageFlagBits,
) -> Result<TestBuffer, Error> {
let buffer = Buffer::new(Rc::clone(&context), size, usage)?;
let memory = DeviceMemory::new_buffer(
Rc::clone(&context),
vk::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
buffer.buffer,
)?;
let map = MappedMemory::new(context, memory.memory)?;
Ok(TestBuffer { map, memory, buffer, size, pending_write: false })
}
}
fn allocate_buffer_objects(
window: &Window,
script: &Script,
) -> Result<Vec<TestBuffer>, Error> {
let mut buffers = Vec::with_capacity(script.buffers().len());
for script_buffer in script.buffers().iter() {
let usage = match script_buffer.buffer_type {
BufferType::Ubo => vk::VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
BufferType::Ssbo => vk::VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
};
buffers.push(TestBuffer::new(
Rc::clone(window.context()),
script_buffer.size,
usage,
)?);
}
Ok(buffers)
}
fn write_descriptor_sets(
window: &Window,
script: &Script,
buffers: &[TestBuffer],
descriptor_sets: &[vk::VkDescriptorSet],
) {
let script_buffers = script.buffers();
let buffer_infos = buffers.iter()
.map(|buffer| vk::VkDescriptorBufferInfo {
buffer: buffer.buffer.buffer,
offset: 0,
range: vk::VK_WHOLE_SIZE as vk::VkDeviceSize,
})
.collect::<Vec<_>>();
let writes = script_buffers.iter()
.enumerate()
.map(|(buffer_num, buffer)| vk::VkWriteDescriptorSet {
sType: vk::VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
pNext: ptr::null(),
dstSet: descriptor_sets[buffer.desc_set as usize],
dstBinding: buffer.binding,
dstArrayElement: 0,
descriptorCount: 1,
descriptorType: match buffer.buffer_type {
BufferType::Ubo => vk::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
BufferType::Ssbo => vk::VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
},
pBufferInfo: buffer_infos[buffer_num..].as_ptr(),
pImageInfo: ptr::null(),
pTexelBufferView: ptr::null(),
})
.collect::<Vec<_>>();
unsafe {
window.device().vkUpdateDescriptorSets.unwrap()(
window.vk_device(),
writes.len() as u32,
writes.as_ptr(),
0, // descriptorCopyCount
ptr::null(), // pDescriptorCopies
);
}
}
fn compare_pixel(
pixel_a: &[f64],
pixel_b: &[f64],
tolerance: &Tolerance,
) -> bool {
std::iter::zip(pixel_a, pixel_b)
.enumerate()
.all(|(component, (&a, &b))| tolerance.equal(component, a, b))
}
#[derive(Debug)]
struct Tester<'a> {
window: &'a Window,
pipeline_set: &'a PipelineSet,
script: &'a Script,
buffer_objects: Vec<TestBuffer>,
test_buffers: Vec<TestBuffer>,
descriptor_sets: DescriptorSetVec<'a>,
bound_pipeline: Option<usize>,
bo_descriptor_set_bound: bool,
first_render: bool,
state: State,
vbo_buffer: Option<TestBuffer>,
index_buffer: Option<TestBuffer>,
inspector: Option<Inspector>,
}
impl<'a> Tester<'a> {
fn new(
window: &'a Window,
pipeline_set: &'a PipelineSet,
script: &'a Script,
inspector: Option<Inspector>,
) -> Result<Tester<'a>, Error> {
let buffer_objects = allocate_buffer_objects(window, script)?;
let descriptor_sets = DescriptorSetVec::new(window, pipeline_set)?;
write_descriptor_sets(
window,
script,
&buffer_objects,
&descriptor_sets.handles,
);
Ok(Tester {
window,
pipeline_set,
script,
buffer_objects,
test_buffers: Vec::new(),
descriptor_sets,
bound_pipeline: None,
bo_descriptor_set_bound: false,
first_render: true,
state: State::Idle,
vbo_buffer: None,
index_buffer: None,
inspector,
})
}
fn add_ssbo_barriers(&mut self) {
let barriers = self.buffer_objects.iter().enumerate()
.filter_map(|(buffer_num, buffer)| {
match self.script.buffers()[buffer_num].buffer_type {
BufferType::Ssbo => Some(vk::VkBufferMemoryBarrier {
sType: vk::VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
pNext: ptr::null(),
srcAccessMask: vk::VK_ACCESS_SHADER_WRITE_BIT,
dstAccessMask: vk::VK_ACCESS_HOST_READ_BIT,
srcQueueFamilyIndex: vk::VK_QUEUE_FAMILY_IGNORED as u32,
dstQueueFamilyIndex: vk::VK_QUEUE_FAMILY_IGNORED as u32,
buffer: buffer.buffer.buffer,
offset: 0,
size: vk::VK_WHOLE_SIZE as vk::VkDeviceSize,
}),
_ => None,
}
})
.collect::<Vec<_>>();
if !barriers.is_empty() {
unsafe {
self.window.device().vkCmdPipelineBarrier.unwrap()(
self.window.context().command_buffer(),
vk::VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
vk::VK_PIPELINE_STAGE_HOST_BIT,
0, // dependencyFlags
0, // memoryBarrierCount
ptr::null(), // pMemoryBarriers
barriers.len() as u32, // bufferMemoryBarrierCount
barriers.as_ptr(), // pBufferMemoryBarriers
0, // imageMemoryBarrierCount
ptr::null(), // pImageMemoryBarriers
);
}
}
}
fn flush_buffers(&mut self) -> Result<(), Error> {
for buffer in self.buffer_objects.iter_mut() {
if !buffer.pending_write {
continue;
}
buffer.pending_write = false;
flush_memory(
self.window.context(),
buffer.memory.memory_type_index as usize,
buffer.memory.memory,
0, // offset
vk::VK_WHOLE_SIZE as vk::VkDeviceSize,
)?;
}
Ok(())
}
fn begin_command_buffer(&mut self) -> Result<(), Error> {
let begin_command_buffer_info = vk::VkCommandBufferBeginInfo {
sType: vk::VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
pNext: ptr::null(),
flags: 0,
pInheritanceInfo: ptr::null(),
};
let res = unsafe {
self.window.device().vkBeginCommandBuffer.unwrap()(
self.window.context().command_buffer(),
ptr::addr_of!(begin_command_buffer_info),
)
};
if res == vk::VK_SUCCESS {
self.bound_pipeline = None;
self.bo_descriptor_set_bound = false;
Ok(())
} else {
Err(Error::BeginCommandBufferFailed)
}
}
fn reset_fence(&self) -> Result<(), Error> {
let fence = self.window.context().fence();
let res = unsafe {
self.window.device().vkResetFences.unwrap()(
self.window.vk_device(),
1, // fenceCount,
ptr::addr_of!(fence),
)
};
if res == vk::VK_SUCCESS {
Ok(())
} else {
Err(Error::ResetFencesFailed)
}
}
fn queue_submit(&self) -> Result<(), Error> {
let command_buffer = self.window.context().command_buffer();
let wait_dst_stage_mask = vk::VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
let submit_info = vk::VkSubmitInfo {
sType: vk::VK_STRUCTURE_TYPE_SUBMIT_INFO,
pNext: ptr::null(),
waitSemaphoreCount: 0,
pWaitSemaphores: ptr::null(),
pWaitDstStageMask: ptr::addr_of!(wait_dst_stage_mask),
commandBufferCount: 1,
pCommandBuffers: ptr::addr_of!(command_buffer),
signalSemaphoreCount: 0,
pSignalSemaphores: ptr::null(),
};
let res = unsafe {
self.window.device().vkQueueSubmit.unwrap()(
self.window.context().queue(),
1, // submitCount
ptr::addr_of!(submit_info),
self.window.context().fence(),
)
};
if res == vk::VK_SUCCESS {
Ok(())
} else {
Err(Error::QueueSubmitFailed)
}
}
fn wait_for_fence(&self) -> Result<(), Error> {
let fence = self.window.context().fence();
let res = unsafe {
self.window.device().vkWaitForFences.unwrap()(
self.window.vk_device(),
1, // fenceCount
ptr::addr_of!(fence),
vk::VK_TRUE, // waitAll
u64::MAX, // timeout
)
};
if res == vk::VK_SUCCESS {
Ok(())
} else {
Err(Error::WaitForFencesFailed)
}
}
fn invalidate_window_linear_memory(&self) -> Result<(), Error> {
if !self.window.need_linear_memory_invalidate() {
return Ok(());
}
let memory_range = vk::VkMappedMemoryRange {
sType: vk::VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
pNext: ptr::null(),
memory: self.window.linear_memory(),
offset: 0,
size: vk::VK_WHOLE_SIZE as vk::VkDeviceSize,
};
let res = unsafe {
self.window.device().vkInvalidateMappedMemoryRanges.unwrap()(
self.window.vk_device(),
1, // memoryRangeCount
ptr::addr_of!(memory_range),
)
};
if res == vk::VK_SUCCESS {
Ok(())
} else {
Err(Error::InvalidateMappedMemoryRangesFailed)
}
}
fn invalidate_ssbos(&self) -> Result<(), Error> {
let memory_properties = self.window.context().memory_properties();
let memory_ranges = self.buffer_objects.iter()
.enumerate()
.filter_map(|(buffer_num, buffer)| {
if self.script.buffers()[buffer_num].buffer_type
!= BufferType::Ssbo
{
return None;
}
let memory_type = &memory_properties
.memoryTypes[buffer.memory.memory_type_index as usize];
// We don’t need to do anything if the memory is
// already coherent
if memory_type.propertyFlags
& vk::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
!= 0
{
return None;
}
Some(vk::VkMappedMemoryRange {
sType: vk::VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
pNext: ptr::null(),
memory: buffer.memory.memory,
offset: 0,
size: vk::VK_WHOLE_SIZE as vk::VkDeviceSize,
})
})
.collect::<Vec<_>>();
if memory_ranges.is_empty() {
Ok(())
} else {
let res = unsafe {
self.window.device().vkInvalidateMappedMemoryRanges.unwrap()(
self.window.vk_device(),
memory_ranges.len() as u32,
memory_ranges.as_ptr(),
)
};
if res == vk::VK_SUCCESS {
Ok(())
} else {
Err(Error::InvalidateMappedMemoryRangesFailed)
}
}
}
fn end_command_buffer(&mut self) -> Result<(), Error> {
self.flush_buffers()?;
self.add_ssbo_barriers();
let res = unsafe {
self.window.device().vkEndCommandBuffer.unwrap()(
self.window.context().command_buffer(),
)
};
if res != vk::VK_SUCCESS {
return Err(Error::EndCommandBufferFailed);
}
self.reset_fence()?;
self.queue_submit()?;
self.wait_for_fence()?;
self.invalidate_window_linear_memory()?;
self.invalidate_ssbos()?;
Ok(())
}
fn begin_render_pass(&mut self) {
let render_pass_index = !self.first_render as usize;
let render_pass = self.window.render_passes()[render_pass_index];
let window_format = self.window.format();
let render_pass_begin_info = vk::VkRenderPassBeginInfo {
sType: vk::VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
pNext: ptr::null(),
renderPass: render_pass,
framebuffer: self.window.framebuffer(),
renderArea: vk::VkRect2D {
offset: vk::VkOffset2D { x: 0, y: 0 },
extent: vk::VkExtent2D {
width: window_format.width as u32,
height: window_format.height as u32,
},
},
clearValueCount: 0,
pClearValues: ptr::null(),
};
unsafe {
self.window.device().vkCmdBeginRenderPass.unwrap()(
self.window.context().command_buffer(),
ptr::addr_of!(render_pass_begin_info),
vk::VK_SUBPASS_CONTENTS_INLINE,
);
}
self.first_render = false;
}
fn add_render_finish_barrier(&self) {
// Image barrier: transition the layout but also ensure:
// - rendering is complete before vkCmdCopyImageToBuffer (below) and
// before any future color attachment accesses
// - the color attachment writes are visible to vkCmdCopyImageToBuffer
// and to any future color attachment accesses */
let render_finish_barrier = vk::VkImageMemoryBarrier {
sType: vk::VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
pNext: ptr::null(),
srcAccessMask: vk::VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
dstAccessMask: vk::VK_ACCESS_TRANSFER_READ_BIT
| vk::VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
| vk::VK_ACCESS_COLOR_ATTACHMENT_READ_BIT,
oldLayout: vk::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
newLayout: vk::VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
srcQueueFamilyIndex: vk::VK_QUEUE_FAMILY_IGNORED as u32,
dstQueueFamilyIndex: vk::VK_QUEUE_FAMILY_IGNORED as u32,
image: self.window.color_image(),
subresourceRange: vk::VkImageSubresourceRange {
aspectMask: vk::VK_IMAGE_ASPECT_COLOR_BIT,
baseMipLevel: 0,
levelCount: 1,
baseArrayLayer: 0,
layerCount: 1
},
};
unsafe {
self.window.device().vkCmdPipelineBarrier.unwrap()(
self.window.context().command_buffer(),
vk::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
vk::VK_PIPELINE_STAGE_TRANSFER_BIT
| vk::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, // dependencyFlags
0, // memoryBarrierCount
ptr::null(), // pMemoryBarriers
0, // bufferMemoryBarrierCount
ptr::null(), // pBufferMemoryBarriers
1, // imageMemoryBarrierCount
ptr::addr_of!(render_finish_barrier),
);
}
}
fn add_copy_to_linear_buffer(&self) {
let window_format = self.window.format();
let copy_region = vk::VkBufferImageCopy {
bufferOffset: 0,
bufferRowLength: window_format.width as u32,
bufferImageHeight: window_format.height as u32,
imageSubresource: vk::VkImageSubresourceLayers {
aspectMask: vk::VK_IMAGE_ASPECT_COLOR_BIT,
mipLevel: 0,
baseArrayLayer: 0,
layerCount: 1,
},
imageOffset: vk::VkOffset3D { x: 0, y: 0, z: 0 },
imageExtent: vk::VkExtent3D {
width: window_format.width as u32,
height: window_format.height as u32,
depth: 1 as u32
},
};
unsafe {
self.window.device().vkCmdCopyImageToBuffer.unwrap()(
self.window.context().command_buffer(),
self.window.color_image(),
vk::VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
self.window.linear_buffer(),
1, // regionCount
ptr::addr_of!(copy_region),
);
}
}
fn add_copy_finish_barrier(&self) {
// Image barrier: transition the layout back but also ensure:
// - the copy image operation (above) completes before any future color
// attachment operations
// No memory dependencies are needed because the first set of operations
// are reads.
let render_finish_barrier = vk::VkImageMemoryBarrier {
sType: vk::VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
pNext: ptr::null(),
srcAccessMask: 0,
dstAccessMask: 0,
oldLayout: vk::VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
newLayout: vk::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
srcQueueFamilyIndex: vk::VK_QUEUE_FAMILY_IGNORED as u32,
dstQueueFamilyIndex: vk::VK_QUEUE_FAMILY_IGNORED as u32,
image: self.window.color_image(),
subresourceRange: vk::VkImageSubresourceRange {
aspectMask: vk::VK_IMAGE_ASPECT_COLOR_BIT,
baseMipLevel: 0,
levelCount: 1,
baseArrayLayer: 0,
layerCount: 1
},
};
unsafe {
self.window.device().vkCmdPipelineBarrier.unwrap()(
self.window.context().command_buffer(),
vk::VK_PIPELINE_STAGE_TRANSFER_BIT,
vk::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, // dependencyFlags
0, // memoryBarrierCount
ptr::null(), // pMemoryBarriers
0, // bufferMemoryBarrierCount
ptr::null(), // pBufferMemoryBarriers
1, // imageMemoryBarrierCount
ptr::addr_of!(render_finish_barrier),
);
}
}
fn add_write_finish_buffer_memory_barrier(&self) {
// Buffer barrier: ensure the device transfer writes have
// completed before the host reads and are visible to host
// reads.
let write_finish_buffer_memory_barrier = vk::VkBufferMemoryBarrier {
sType: vk::VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
pNext: ptr::null(),
srcAccessMask: vk::VK_ACCESS_TRANSFER_WRITE_BIT,
dstAccessMask: vk::VK_ACCESS_HOST_READ_BIT,
srcQueueFamilyIndex: vk::VK_QUEUE_FAMILY_IGNORED as u32,
dstQueueFamilyIndex: vk::VK_QUEUE_FAMILY_IGNORED as u32,
buffer: self.window.linear_buffer(),
offset: 0,
size: vk::VK_WHOLE_SIZE as vk::VkDeviceSize,
};
unsafe {
self.window.device().vkCmdPipelineBarrier.unwrap()(
self.window.context().command_buffer(),
vk::VK_PIPELINE_STAGE_TRANSFER_BIT,
vk::VK_PIPELINE_STAGE_HOST_BIT,
0, // dependencyFlags
0, // memoryBarrierCount
ptr::null(), // pMemoryBarriers
1, // bufferMemoryBarrierCount
ptr::addr_of!(write_finish_buffer_memory_barrier),
0, // imageMemoryBarrierCount
ptr::null(), // pImageMemoryBarriers
);
}
}
fn end_render_pass(&self) {
unsafe {
self.window.device().vkCmdEndRenderPass.unwrap()(
self.window.context().command_buffer(),
);
}
self.add_render_finish_barrier();
self.add_copy_to_linear_buffer();
self.add_copy_finish_barrier();
self.add_write_finish_buffer_memory_barrier();
}
fn forward_state(&mut self) -> Result<(), Error> {
match &self.state {
State::Idle => {
self.begin_command_buffer()?;
self.state = State::CommandBuffer;
},
State::CommandBuffer => {
self.begin_render_pass();
self.state = State::RenderPass;
},
State::RenderPass => unreachable!(
"Tried to advance after last state"
),
}
Ok(())
}
fn backward_state(&mut self) -> Result<(), Error> {
match &self.state {
State::Idle => unreachable!(
"Tried to go backward to before the first state"
),
State::CommandBuffer => {
self.end_command_buffer()?;
self.state = State::Idle;
},
State::RenderPass => {
self.end_render_pass();
self.state = State::CommandBuffer;
},
}
Ok(())
}
fn goto_state(&mut self, state: State) -> Result<(), Error> {
while (self.state as usize) < state as usize {
self.forward_state()?;
}
while (self.state as usize) > state as usize {
self.backward_state()?;
}
Ok(())
}
fn bind_bo_descriptor_set_at_binding_point(
&self,
binding_point: vk::VkPipelineBindPoint
) {
unsafe {
self.window.device().vkCmdBindDescriptorSets.unwrap()(
self.window.context().command_buffer(),
binding_point,
self.pipeline_set.layout(),
0, // firstSet
self.descriptor_sets.handles.len() as u32,
self.descriptor_sets.handles.as_ptr(),
0, // dynamicOffsetCount
ptr::null(), // pDynamicOffsets
);
}
}
fn bind_bo_descriptor_set(&mut self) {
if self.bo_descriptor_set_bound
|| self.descriptor_sets.handles.is_empty()
{
return;
}
if self.pipeline_set.stages() & !vk::VK_SHADER_STAGE_COMPUTE_BIT != 0 {
self.bind_bo_descriptor_set_at_binding_point(
vk::VK_PIPELINE_BIND_POINT_GRAPHICS,
);
}
if self.pipeline_set.stages() & vk::VK_SHADER_STAGE_COMPUTE_BIT != 0 {
self.bind_bo_descriptor_set_at_binding_point(
vk::VK_PIPELINE_BIND_POINT_COMPUTE,
);
}
self.bo_descriptor_set_bound = true;
}
fn bind_pipeline(&mut self, pipeline_num: usize) {
if Some(pipeline_num) == self.bound_pipeline {
return;
}
let key = &self.script.pipeline_keys()[pipeline_num];
let bind_point = match key.pipeline_type() {
pipeline_key::Type::Graphics => vk::VK_PIPELINE_BIND_POINT_GRAPHICS,
pipeline_key::Type::Compute => vk::VK_PIPELINE_BIND_POINT_COMPUTE,
};
unsafe {
self.window.device().vkCmdBindPipeline.unwrap()(
self.window.context().command_buffer(),
bind_point,
self.pipeline_set.pipelines()[pipeline_num],
);
}
self.bound_pipeline = Some(pipeline_num);
}
fn get_buffer_object(
&mut self,
desc_set: u32,
binding: u32,
) -> Result<&mut TestBuffer, Error> {
match self.script
.buffers()
.binary_search_by(|buffer| {
buffer.desc_set
.cmp(&desc_set)
.then_with(|| buffer.binding.cmp(&binding))
})
{
Ok(buffer_num) => Ok(&mut self.buffer_objects[buffer_num]),
Err(_) => Err(Error::InvalidBufferBinding { desc_set, binding }),
}
}
fn get_vbo_buffer(&mut self) -> Result<Option<&TestBuffer>, Error> {
if let Some(ref buffer) = self.vbo_buffer {
Ok(Some(buffer))
} else if let Some(vbo) = self.script.vertex_data() {
let buffer = TestBuffer::new(
Rc::clone(self.window.context()),
vbo.raw_data().len(),
vk::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
)?;
unsafe {
std::slice::from_raw_parts_mut(
buffer.map.pointer as *mut u8,
buffer.size
).copy_from_slice(vbo.raw_data());
}
flush_memory(
self.window.context(),
buffer.memory.memory_type_index as usize,
buffer.memory.memory,
0, // offset
vk::VK_WHOLE_SIZE as vk::VkDeviceSize,
)?;
Ok(Some(&*self.vbo_buffer.insert(buffer)))
} else {
Ok(None)
}
}
fn get_index_buffer(&mut self) -> Result<&TestBuffer, Error> {
match self.index_buffer {
Some(ref buffer) => Ok(buffer),
None => {
let indices = self.script.indices();
let buffer = TestBuffer::new(
Rc::clone(self.window.context()),
indices.len() * mem::size_of::<u16>(),
vk::VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
)?;
unsafe {
std::slice::from_raw_parts_mut(
buffer.map.pointer as *mut u16,
indices.len(),
).copy_from_slice(indices);
}
flush_memory(
self.window.context(),
buffer.memory.memory_type_index as usize,
buffer.memory.memory,
0, // offset
vk::VK_WHOLE_SIZE as vk::VkDeviceSize,
)?;
Ok(&*self.index_buffer.insert(buffer))
}
}
}
fn draw_rect(
&mut self,
op: &Operation,
) -> Result<(), Error> {
let &Operation::DrawRect { x, y, w, h, pipeline_key } = op else {
unreachable!("bad op");
};
let buffer = TestBuffer::new(
Rc::clone(self.window.context()),
mem::size_of::<RectangleVertex>() * 4,
vk::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
)?;
self.goto_state(State::RenderPass)?;
let mut v: *mut RectangleVertex = buffer.map.pointer.cast();
unsafe {
*v = RectangleVertex {
x: x,
y: y,
z: 0.0,
};
v = v.add(1);
*v = RectangleVertex {
x: x + w,
y: y,
z: 0.0,
};
v = v.add(1);
*v = RectangleVertex {
x: x,
y: y + h,
z: 0.0,
};
v = v.add(1);
*v = RectangleVertex {
x: x + w,
y: y + h,
z: 0.0,
};
}
flush_memory(
self.window.context(),
buffer.memory.memory_type_index as usize,
buffer.memory.memory,
0, // offset
vk::VK_WHOLE_SIZE as vk::VkDeviceSize,
)?;
self.bind_bo_descriptor_set();
self.bind_pipeline(pipeline_key);
let command_buffer = self.window.context().command_buffer();
let buffer_handle = buffer.buffer.buffer;
let offset = 0;
unsafe {
self.window.device().vkCmdBindVertexBuffers.unwrap()(
command_buffer,
0, // firstBinding
1, // bindingCount
ptr::addr_of!(buffer_handle),
ptr::addr_of!(offset),
);
self.window.device().vkCmdDraw.unwrap()(
command_buffer,
4, // vertexCount
1, // instanceCount
0, // firstVertex
0, // firstinstance
);
}
self.test_buffers.push(buffer);
Ok(())
}
fn draw_arrays(
&mut self,
op: &Operation,
) -> Result<(), Error> {
let &Operation::DrawArrays {
indexed,
vertex_count,
instance_count,
first_vertex,
first_instance,
pipeline_key,
..
} = op else {
unreachable!("bad op");
};
self.goto_state(State::RenderPass)?;
let context = Rc::clone(self.window.context());
if let Some(buffer) = self.get_vbo_buffer()? {
let offset = 0;
unsafe {
context.device().vkCmdBindVertexBuffers.unwrap()(
context.command_buffer(),
0, // firstBinding
1, // bindingCount
ptr::addr_of!(buffer.buffer.buffer),
ptr::addr_of!(offset)
);
}
}
self.bind_bo_descriptor_set();
self.bind_pipeline(pipeline_key);
if indexed {
let index_buffer = self.get_index_buffer()?;
unsafe {
context.device().vkCmdBindIndexBuffer.unwrap()(
context.command_buffer(),
index_buffer.buffer.buffer,
0, // offset
vk::VK_INDEX_TYPE_UINT16,
);
context.device().vkCmdDrawIndexed.unwrap()(
context.command_buffer(),
vertex_count,
instance_count,
0, // firstIndex
first_vertex as i32,
first_instance,
);
}
} else {
unsafe {
context.device().vkCmdDraw.unwrap()(
context.command_buffer(),
vertex_count,
instance_count,
first_vertex,
first_instance,
);
}
}
Ok(())
}
fn dispatch_compute(
&mut self,
op: &Operation,
) -> Result<(), Error> {
let &Operation::DispatchCompute { x, y, z, pipeline_key } = op else {
unreachable!("bad op");
};
self.goto_state(State::CommandBuffer)?;
self.bind_bo_descriptor_set();
self.bind_pipeline(pipeline_key);
unsafe {
self.window.device().vkCmdDispatch.unwrap()(
self.window.context().command_buffer(),
x,
y,
z,
);
}
Ok(())
}
fn probe_rect(
&mut self,
op: &Operation,
) -> Result<(), Error> {
let &Operation::ProbeRect {
n_components,
x,
y,
w,
h,
ref color,
ref tolerance,
} = op else {
unreachable!("bad op");
};
// End the render to copy the framebuffer into the linear buffer
self.goto_state(State::Idle)?;
let linear_memory_map: *const u8 =
self.window.linear_memory_map().cast();
let stride = self.window.linear_memory_stride();
let format = self.window.format().color_format;
let format_size = format.size();
let n_components = n_components as usize;
for y_offset in 0..h {
let mut p = unsafe {
linear_memory_map.add(
(y_offset + y) as usize * stride + x as usize * format_size
)
};
for x_offset in 0..w {
let source = unsafe {
std::slice::from_raw_parts(p, format_size)
};
let pixel = format.load_pixel(source);
if !compare_pixel(
&pixel[0..n_components],
&color[0..n_components],
tolerance,
) {
return Err(Error::ProbeFailed(ProbeFailedError {
x: x + x_offset,
y: y + y_offset,
expected: color.clone(),
observed: pixel,
n_components,
}));
}
unsafe {
p = p.add(format_size);
}
}
}
Ok(())
}
fn probe_ssbo(
&mut self,
op: &Operation,
) -> Result<(), Error> {
let &Operation::ProbeSsbo {
desc_set,
binding,
comparison,
offset,
slot_type,
layout,
ref values,
ref tolerance,
} = op else {
unreachable!("bad op");
};
self.goto_state(State::Idle)?;
let buffer = self.get_buffer_object(desc_set, binding)?;
let buffer_slice = unsafe {
std::slice::from_raw_parts(
buffer.map.pointer as *const u8,
buffer.size,
)
};
let type_size = slot_type.size(layout);
let observed_stride = slot_type.array_stride(layout);
// The values are tightly packed in the operation buffer so we
// don’t want to use the observed_stride
let n_values = values.len() / type_size;
if offset
+ (n_values - 1) * observed_stride
+ type_size
> buffer_slice.len()
{
return Err(Error::InvalidBufferOffset);
}
let buffer_slice = &buffer_slice[offset..];
for i in 0..n_values {
let observed = &buffer_slice[i * observed_stride
..i * observed_stride + type_size];
let expected = &values[i * type_size..(i + 1) * type_size];
if !comparison.compare(
tolerance,
slot_type,
layout,
observed,
expected,
) {
return Err(Error::SsboProbeFailed {
slot_type,
layout,
expected: expected.into(),
observed: observed.into(),
});
}
}
Ok(())
}
fn set_push_command(
&mut self,
op: &Operation,
) -> Result<(), Error> {
let &Operation::SetPushCommand { offset, ref data } = op else {
unreachable!("bad op");
};
if (self.state as usize) < State::CommandBuffer as usize {
self.goto_state(State::CommandBuffer)?;
}
unsafe {
self.window.device().vkCmdPushConstants.unwrap()(
self.window.context().command_buffer(),
self.pipeline_set.layout(),
self.pipeline_set.stages(),
offset as u32,
data.len() as u32,
data.as_ptr().cast(),
);
}
Ok(())
}
fn set_buffer_data(
&mut self,
op: &Operation,
) -> Result<(), Error> {
let &Operation::SetBufferData {
desc_set,
binding,
offset,
ref data
} = op else {
unreachable!("bad op");
};
let buffer = self.get_buffer_object(desc_set, binding)
.expect(
"The script parser should make a buffer mentioned by \
any buffer data command and the tester should make a \
buffer for every buffer described by the script"
);
let buffer_slice = unsafe {
std::slice::from_raw_parts_mut(
(buffer.map.pointer as *mut u8).add(offset),
data.len(),
)
};
buffer_slice.copy_from_slice(data);
buffer.pending_write = true;
Ok(())
}
fn clear(
&mut self,
op: &Operation,
) -> Result<(), Error> {
let &Operation::Clear { ref color, depth, stencil } = op else {
unreachable!("bad op");
};
let window_format = self.window.format();
let depth_stencil_flags = match window_format.depth_stencil_format {
Some(format) => format.depth_stencil_aspect_flags(),
None => 0,
};
self.goto_state(State::RenderPass)?;
let clear_attachments = [
vk::VkClearAttachment {
aspectMask: vk::VK_IMAGE_ASPECT_COLOR_BIT,
colorAttachment: 0,
clearValue: vk::VkClearValue {
color: vk::VkClearColorValue {
float32: color.clone(),
},
},
},
vk::VkClearAttachment {
aspectMask: depth_stencil_flags,
colorAttachment: 0,
clearValue: vk::VkClearValue {
depthStencil: vk::VkClearDepthStencilValue {
depth,
stencil,
},
},
},
];
let clear_rect = vk::VkClearRect {
rect: vk::VkRect2D {
offset: vk::VkOffset2D { x: 0, y: 0 },
extent: vk::VkExtent2D {
width: self.window.format().width as u32,
height: self.window.format().height as u32,
},
},
baseArrayLayer: 0,
layerCount: 1,
};
let n_attachments = 1 + (depth_stencil_flags != 0) as usize;
unsafe {
self.window.device().vkCmdClearAttachments.unwrap()(
self.window.context().command_buffer(),
n_attachments as u32,
ptr::addr_of!(clear_attachments[0]),
1, // rectCount
ptr::addr_of!(clear_rect),
);
}
Ok(())
}
fn run_operation(
&mut self,
op: &Operation,
) -> Result<(), Error> {
match op {
Operation::DrawRect { .. } => self.draw_rect(op),
Operation::DrawArrays { .. } => self.draw_arrays(op),
Operation::DispatchCompute { .. } => self.dispatch_compute(op),
Operation::ProbeRect { .. } => self.probe_rect(op),
Operation::ProbeSsbo { .. } => self.probe_ssbo(op),
Operation::SetPushCommand { .. } => self.set_push_command(op),
Operation::SetBufferData { .. } => self.set_buffer_data(op),
Operation::Clear { .. } => self.clear(op),
}
}
fn inspect(&self) {
let Some(inspector) = self.inspector.as_ref() else { return; };
let buffers = self.buffer_objects
.iter()
.enumerate()
.map(|(buffer_num, buffer)| {
inspect::Buffer {
binding: self.script.buffers()[buffer_num].binding as c_int,
size: buffer.size,
data: buffer.map.pointer,
}
})
.collect::<Vec<_>>();
let window_format = self.window.format();
let data = inspect::Data {
color_buffer: inspect::Image {
width: window_format.width as c_int,
height: window_format.height as c_int,
stride: self.window.linear_memory_stride(),
format: window_format.color_format,
data: self.window.linear_memory_map(),
},
n_buffers: buffers.len(),
buffers: if buffers.is_empty() {
ptr::null()
} else {
buffers.as_ptr()
},
};
inspector.inspect(&data);
}
}
pub(crate) fn run(
window: &Window,
pipeline_set: &PipelineSet,
script: &Script,
inspector: Option<Inspector>,
) -> Result<(), Error> {
let mut tester = Tester::new(window, pipeline_set, script, inspector)?;
let mut errors = Vec::new();
for command in script.commands().iter() {
if let Err(e) = tester.run_operation(&command.op) {
errors.push(CommandError {
line_num: command.line_num,
error: e,
});
}
}
if let Err(error) = tester.goto_state(State::Idle) {
let line_num = match script.commands().last() {
Some(command) => command.line_num,
None => 1,
};
errors.push(CommandError { line_num, error });
}
tester.inspect();
if errors.is_empty() {
Ok(())
} else {
Err(Error::CommandErrors(errors))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::fake_vulkan::{FakeVulkan, Command, HandleType, ClearAttachment};
use crate::requirements::Requirements;
use crate::logger::Logger;
use crate::source::Source;
use crate::window_format::WindowFormat;
use crate::config::Config;
use std::ffi::c_void;
#[derive(Debug)]
struct TestData {
pipeline_set: PipelineSet,
window: Rc<Window>,
context: Rc<Context>,
fake_vulkan: Box<FakeVulkan>,
}
impl TestData {
fn new_full(
source: &str,
inspector: Option<Inspector>,
) -> Result<TestData, Error> {
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.set_override();
let context = Rc::new(Context::new(
&Requirements::new(),
None, // device_id
).unwrap());
let source = Source::from_string(source.to_string());
let script = Script::load(&Config::new(), &source).unwrap();
let window = Rc::new(Window::new(
Rc::clone(&context),
script.window_format(),
).unwrap());
let mut logger = Logger::new(None, ptr::null_mut());
let pipeline_set = PipelineSet::new(
&mut logger,
Rc::clone(&window),
&script,
false, // show_disassembly
).unwrap();
run(
&window,
&pipeline_set,
&script,
inspector,
)?;
Ok(TestData {
pipeline_set,
window,
context,
fake_vulkan,
})
}
fn new(source: &str) -> Result<TestData, Error> {
TestData::new_full(
source,
None, // inspector
)
}
}
#[test]
fn rectangle() {
let test_data = TestData::new(
"[test]\n\
draw rect -1 -1 2 2"
).unwrap();
let mut commands = test_data.fake_vulkan.commands.iter();
let &Command::BeginRenderPass(ref begin_info) = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(begin_info.renderPass, test_data.window.render_passes()[0]);
assert_eq!(begin_info.framebuffer, test_data.window.framebuffer());
assert_eq!(begin_info.renderArea.offset.x, 0);
assert_eq!(begin_info.renderArea.offset.y, 0);
assert_eq!(
begin_info.renderArea.extent.width as usize,
WindowFormat::default().width,
);
assert_eq!(
begin_info.renderArea.extent.height as usize,
WindowFormat::default().height,
);
assert_eq!(begin_info.clearValueCount, 0);
let &Command::BindPipeline {
bind_point,
pipeline,
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(test_data.pipeline_set.pipelines().len(), 1);
assert_eq!(test_data.pipeline_set.pipelines()[0], pipeline);
assert_eq!(bind_point, vk::VK_PIPELINE_BIND_POINT_GRAPHICS);
let &Command::BindVertexBuffers {
first_binding,
ref buffers,
ref offsets,
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(first_binding, 0);
assert_eq!(buffers.len(), 1);
assert_eq!(offsets, &[0]);
let HandleType::Buffer { memory: Some(memory), .. } =
test_data.fake_vulkan.get_freed_handle(buffers[0]).data
else { unreachable!("Failed to get buffer memory"); };
let HandleType::Memory { ref contents, .. } =
test_data.fake_vulkan.get_freed_handle(memory).data
else { unreachable!("Mismatched handle"); };
let mut expected_contents = Vec::<u8>::new();
for component in [
-1f32, -1f32, 0f32,
1f32, -1f32, 0f32,
-1f32, 1f32, 0f32,
1f32, 1f32, 0f32,
] {
expected_contents.extend(&component.to_ne_bytes());
}
assert_eq!(contents, &expected_contents);
let &Command::Draw {
vertex_count,
instance_count,
first_vertex,
first_instance,
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(vertex_count, 4);
assert_eq!(instance_count, 1);
assert_eq!(first_vertex, 0);
assert_eq!(first_instance, 0);
assert!(matches!(commands.next(), Some(Command::EndRenderPass)));
let &Command::PipelineBarrier {
ref image_memory_barriers,
..
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(image_memory_barriers.len(), 1);
assert_eq!(
image_memory_barriers[0].image,
test_data.window.color_image()
);
let &Command::CopyImageToBuffer {
src_image,
dst_buffer,
..
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(src_image, test_data.window.color_image());
assert_eq!(dst_buffer, test_data.window.linear_buffer());
let &Command::PipelineBarrier {
ref image_memory_barriers,
..
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(image_memory_barriers.len(), 1);
assert_eq!(
image_memory_barriers[0].image,
test_data.window.color_image()
);
let &Command::PipelineBarrier {
ref buffer_memory_barriers,
..
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(buffer_memory_barriers.len(), 1);
assert_eq!(
buffer_memory_barriers[0].buffer,
test_data.window.linear_buffer()
);
assert!(commands.next().is_none());
// There should only be one flush with the RectangleVertex vbo
assert_eq!(test_data.fake_vulkan.memory_flushes.len(), 1);
assert_eq!(test_data.fake_vulkan.memory_invalidations.len(), 1);
assert_eq!(
test_data.fake_vulkan.memory_invalidations[0].memory,
test_data.window.linear_memory(),
);
let HandleType::Fence { reset_count, wait_count } =
test_data.fake_vulkan.get_freed_handle(
test_data.context.fence()
).data
else { unreachable!("Bad handle"); };
assert_eq!(reset_count, 1);
assert_eq!(wait_count, 1);
}
#[test]
fn vbo() {
let test_data = TestData::new(
"[vertex data]\n\
0/R32_SFLOAT\n\
1\n\
2\n\
3\n\
[test]\n\
draw arrays TRIANGLE_LIST 0 3"
).unwrap();
let mut commands = test_data.fake_vulkan.commands.iter();
assert!(matches!(
commands.next(),
Some(Command::BeginRenderPass { .. })
));
let &Command::BindVertexBuffers {
first_binding,
ref buffers,
ref offsets,
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(first_binding, 0);
assert_eq!(buffers.len(), 1);
assert_eq!(offsets, &[0]);
let HandleType::Buffer { memory: Some(memory), .. } =
test_data.fake_vulkan.get_freed_handle(buffers[0]).data
else { unreachable!("Failed to get buffer memory"); };
let HandleType::Memory { ref contents, .. } =
test_data.fake_vulkan.get_freed_handle(memory).data
else { unreachable!("Mismatched handle"); };
let mut expected_contents = Vec::<u8>::new();
for component in [1f32, 2f32, 3f32] {
expected_contents.extend(&component.to_ne_bytes());
}
assert_eq!(contents, &expected_contents);
assert!(matches!(commands.next(), Some(Command::BindPipeline { .. })));
let &Command::Draw {
vertex_count,
instance_count,
first_vertex,
first_instance,
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(vertex_count, 3);
assert_eq!(instance_count, 1);
assert_eq!(first_vertex, 0);
assert_eq!(first_instance, 0);
}
#[test]
fn dispatch_compute() {
let test_data = TestData::new(
"[test]\n\
compute 1 2 3"
).unwrap();
let mut commands = test_data.fake_vulkan.commands.iter();
assert!(matches!(commands.next(), Some(Command::BindPipeline { .. })));
let &Command::Dispatch { x, y, z } = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!((x, y, z), (1, 2, 3));
assert!(commands.next().is_none());
}
#[test]
fn clear() {
let test_data = TestData::new(
"[test]\n\
clear color 1 2 3 4
clear"
).unwrap();
let mut commands = test_data.fake_vulkan.commands.iter();
assert!(matches!(
commands.next(),
Some(Command::BeginRenderPass { .. })
));
let &Command::ClearAttachments {
ref attachments,
ref rects,
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(attachments.len(), 1);
match &attachments[0] {
&ClearAttachment::Color { attachment, value } => {
assert_eq!(attachment, 0);
assert_eq!(value, [1f32, 2f32, 3f32, 4f32]);
},
_ => unreachable!("unexepected clear attachment type"),
}
assert_eq!(rects.len(), 1);
assert_eq!(
rects[0].rect.extent.width as usize,
WindowFormat::default().width
);
assert_eq!(
rects[0].rect.extent.height as usize,
WindowFormat::default().height
);
}
#[test]
fn clear_depth_stencil() {
let test_data = TestData::new(
"[require]\n\
depthstencil D24_UNORM_S8_UINT\n\
[test]\n\
clear depth 2.0\n\
clear stencil 5\n\
clear"
).unwrap();
let mut commands = test_data.fake_vulkan.commands.iter();
assert!(matches!(
commands.next(),
Some(Command::BeginRenderPass { .. })
));
let &Command::ClearAttachments {
ref attachments,
ref rects,
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(attachments.len(), 2);
match &attachments[1] {
&ClearAttachment::DepthStencil { aspect_mask, value } => {
assert_eq!(
aspect_mask,
vk::VK_IMAGE_ASPECT_DEPTH_BIT
| vk::VK_IMAGE_ASPECT_STENCIL_BIT
);
assert_eq!(value.depth, 2.0);
assert_eq!(value.stencil, 5);
},
_ => unreachable!("unexepected clear attachment type"),
}
assert_eq!(rects.len(), 1);
assert_eq!(
rects[0].rect.extent.width as usize,
WindowFormat::default().width
);
assert_eq!(
rects[0].rect.extent.height as usize,
WindowFormat::default().height
);
}
#[test]
fn push_constants() {
let test_data = TestData::new(
"[test]\n\
push uint8_t 1 12\n\
push u8vec2 2 13 14"
).unwrap();
let mut commands = test_data.fake_vulkan.commands.iter();
let &Command::PushConstants {
layout,
stage_flags,
offset,
ref values,
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(layout, test_data.pipeline_set.layout());
assert_eq!(stage_flags, 0);
assert_eq!(offset, 1);
assert_eq!(values.as_slice(), [12].as_slice());
let &Command::PushConstants {
layout,
stage_flags,
offset,
ref values,
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(layout, test_data.pipeline_set.layout());
assert_eq!(stage_flags, 0);
assert_eq!(offset, 2);
assert_eq!(values.as_slice(), [13, 14].as_slice());
}
#[test]
fn set_buffer_data() {
let test_data = TestData::new(
"[fragment shader]\n\
03 02 23 07\n\
[test]\n\
ssbo 5 subdata uint8_t 1 1 2 3\n\
# draw command to make it flush the memory\n\
draw rect -1 -1 2 2"
).unwrap();
let &Command::BindDescriptorSets {
first_set,
ref descriptor_sets,
..
} = test_data.fake_vulkan.commands.iter().find(|command| {
matches!(command, Command::BindDescriptorSets { .. })
}).unwrap()
else { unreachable!() };
assert_eq!(first_set, 0);
assert_eq!(descriptor_sets.len(), 1);
let HandleType::DescriptorSet {
ref bindings
} = test_data.fake_vulkan.get_freed_handle(descriptor_sets[0]).data
else { unreachable!("bad handle"); };
let descriptor_type = bindings[&5].descriptor_type;
assert_eq!(descriptor_type, vk::VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
let buffer_handle = bindings[&5].info.buffer;
let HandleType::Buffer {
memory: Some(memory_handle),
..
} = test_data.fake_vulkan.get_freed_handle(buffer_handle).data
else { unreachable!("failed to get buffer memory"); };
let HandleType::Memory {
ref contents,
..
} = test_data.fake_vulkan.get_freed_handle(memory_handle).data
else { unreachable!("bad handle"); };
assert_eq!(contents, &[0, 1, 2, 3]);
test_data.fake_vulkan.memory_flushes.iter().find(|flush| {
flush.memory == memory_handle
}).expect("expected ssbo memory to be flushed");
}
#[test]
fn probe_ssbo_success() {
TestData::new(
"[test]\n\
ssbo 5 subdata uint8_t 1 1 2 3\n\
probe ssbo u8vec4 5 0 == 0 1 2 3"
).expect("expected ssbo probe to succeed");
}
#[test]
fn probe_ssbo_fail() {
let error = TestData::new(
"[test]\n\
ssbo 5 subdata uint8_t 1 1 2 3\n\
probe ssbo u8vec4 5 0 == 0 1 2 4"
).unwrap_err();
assert_eq!(
&error.to_string(),
"line 3: SSBO probe failed\n\
\x20 Reference: 0 1 2 4\n\
\x20 Observed: 0 1 2 3",
);
}
#[test]
fn probe_rect_success() {
TestData::new(
"[test]\n\
probe all rgba 0 0 0 0"
).expect("expected probe to succeed");
}
#[test]
fn probe_rect_fail() {
let error = TestData::new(
"[test]\n\
probe all rgba 1 0 0 0\n\
probe all rgba 1 2 0 0"
).unwrap_err();
assert_eq!(
&error.to_string(),
"line 2: Probe color at (0,0)\n\
\x20 Expected: 1 0 0 0\n\
\x20 Observed: 0 0 0 0\n\
line 3: Probe color at (0,0)\n\
\x20 Expected: 1 2 0 0\n\
\x20 Observed: 0 0 0 0"
);
}
#[test]
fn indices() {
let test_data = TestData::new(
"[indices]\n\
0 1 2\n\
[test]\n\
draw arrays indexed TRIANGLE_LIST 0 3"
).unwrap();
let mut commands = test_data.fake_vulkan.commands.iter();
println!("{:#?}",commands);
assert!(matches!(
commands.next(),
Some(Command::BeginRenderPass { .. })
));
assert!(matches!(commands.next(), Some(Command::BindPipeline { .. })));
let &Command::BindIndexBuffer {
buffer,
offset,
index_type,
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(offset, 0);
assert_eq!(index_type, vk::VK_INDEX_TYPE_UINT16);
let HandleType::Buffer { memory: Some(memory), .. } =
test_data.fake_vulkan.get_freed_handle(buffer).data
else { unreachable!("Failed to get buffer memory"); };
let HandleType::Memory { ref contents, .. } =
test_data.fake_vulkan.get_freed_handle(memory).data
else { unreachable!("Mismatched handle"); };
let mut expected_contents = Vec::<u8>::new();
for component in 0u16..3u16 {
expected_contents.extend(&component.to_ne_bytes());
}
assert_eq!(contents, &expected_contents);
let &Command::DrawIndexed {
index_count,
instance_count,
first_index,
vertex_offset,
first_instance,
} = commands.next().unwrap()
else { unreachable!("Bad command"); };
assert_eq!(index_count, 3);
assert_eq!(instance_count, 1);
assert_eq!(first_index, 0);
assert_eq!(vertex_offset, 0);
assert_eq!(first_instance, 0);
}
extern "C" fn inspector_cb(data: &inspect::Data, user_data: *mut c_void) {
unsafe {
*(user_data as *mut bool) = true;
}
let window_format = WindowFormat::default();
assert_eq!(data.color_buffer.width as usize, window_format.width);
assert_eq!(data.color_buffer.height as usize, window_format.height);
assert!(data.color_buffer.stride >= window_format.width * 4);
assert_eq!(
data.color_buffer.format,
window_format.color_format,
);
assert!(!data.color_buffer.data.is_null());
assert_eq!(data.n_buffers, 1);
let buffer = unsafe { &*data.buffers };
assert_eq!(buffer.binding, 5);
assert_eq!(buffer.size, 1024);
assert!(!buffer.data.is_null());
}
#[test]
fn inspector() {
let mut inspector_called = false;
let inspector = inspect::Inspector::new(
inspector_cb,
ptr::addr_of_mut!(inspector_called).cast(),
);
TestData::new_full(
"[test]\n\
ssbo 5 1024",
Some(inspector),
).expect("expected test to pass");
assert!(inspector_called);
}
}
```
--------------------------------------------------------------------------------
/vkrunner/vkrunner/fake_vulkan.rs:
--------------------------------------------------------------------------------
```rust
// vkrunner
//
// 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.
//! Sets up a fake Vulkan driver which can be manipulated to report
//! different extensions and features. This is only build in test
//! configurations and it is intended to help unit testing.
use crate::vk;
use crate::vulkan_funcs;
use crate::requirements;
use std::cell::Cell;
use std::mem;
use std::mem::transmute;
use std::ffi::{c_char, CStr, c_void};
use std::ptr;
use std::cmp::min;
use std::collections::{HashMap, VecDeque};
// Pointer to the current FakeVulkan instance that was created in
// this thread. There can only be one instance per thread.
thread_local! {
static CURRENT_FAKE_VULKAN: Cell<Option<*mut FakeVulkan>> = Cell::new(None);
}
fn add_extension_to_vec(
extension_vec: &mut Vec<vk::VkExtensionProperties>,
ext: &str,
) {
let old_len = extension_vec.len();
extension_vec.resize(old_len + 1, Default::default());
let props = extension_vec.last_mut().unwrap();
for (i, b) in ext
.bytes()
.take(min(props.extensionName.len() - 1, ext.len()))
.enumerate()
{
props.extensionName[i] = b as c_char;
}
}
/// A structure containing the physical device infos that will be
/// reported by the driver.
#[derive(Debug, Clone)]
pub struct PhysicalDeviceInfo {
pub properties: vk::VkPhysicalDeviceProperties,
pub memory_properties: vk::VkPhysicalDeviceMemoryProperties,
pub features: vk::VkPhysicalDeviceFeatures,
pub queue_families: Vec<vk::VkQueueFamilyProperties>,
pub extensions: Vec<vk::VkExtensionProperties>,
pub format_properties: HashMap<vk::VkFormat, vk::VkFormatProperties>,
// Two random extension feature sets to report when asked
pub shader_atomic: vk::VkPhysicalDeviceShaderAtomicInt64FeaturesKHR,
pub multiview: vk::VkPhysicalDeviceMultiviewFeaturesKHR,
}
impl PhysicalDeviceInfo {
pub fn add_extension(&mut self, ext: &str) {
add_extension_to_vec(&mut self.extensions, ext);
}
}
impl Default for PhysicalDeviceInfo {
fn default() -> PhysicalDeviceInfo {
PhysicalDeviceInfo {
properties: vk::VkPhysicalDeviceProperties {
apiVersion: requirements::make_version(1, 0, 0),
driverVersion: 0,
vendorID: 0xfa4eed,
deviceID: 0xfa4ede,
deviceType: vk::VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU,
deviceName: [0; 256],
pipelineCacheUUID: *b"fakevulkan123456",
limits: Default::default(),
sparseProperties: Default::default(),
},
memory_properties: Default::default(),
format_properties: HashMap::new(),
features: Default::default(),
queue_families: vec![vk::VkQueueFamilyProperties {
queueFlags: vk::VK_QUEUE_GRAPHICS_BIT,
queueCount: 1,
timestampValidBits: 32,
minImageTransferGranularity: Default::default(),
}],
extensions: Vec::new(),
shader_atomic: Default::default(),
multiview: Default::default(),
}
}
}
const ATOMIC_TYPE: vk::VkStructureType =
vk::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_INT64_FEATURES_KHR;
const MULTIVIEW_TYPE: vk::VkStructureType =
vk::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR;
#[derive(Debug, Clone)]
pub struct GraphicsPipelineCreateInfo {
pub create_info: vk::VkGraphicsPipelineCreateInfo,
pub bindings: Vec<vk::VkVertexInputBindingDescription>,
pub attribs: Vec<vk::VkVertexInputAttributeDescription>,
}
impl GraphicsPipelineCreateInfo {
fn new(
create_info: &vk::VkGraphicsPipelineCreateInfo
) -> GraphicsPipelineCreateInfo {
let vertex_input_state = unsafe {
&*create_info.pVertexInputState
};
let bindings = vec_from_raw_parts(
vertex_input_state.pVertexBindingDescriptions,
vertex_input_state.vertexBindingDescriptionCount as usize,
);
let attribs = vec_from_raw_parts(
vertex_input_state.pVertexAttributeDescriptions,
vertex_input_state.vertexAttributeDescriptionCount as usize,
);
GraphicsPipelineCreateInfo {
create_info: create_info.clone(),
bindings,
attribs,
}
}
}
#[derive(Debug)]
pub enum PipelineCreateInfo {
Graphics(GraphicsPipelineCreateInfo),
Compute(vk::VkComputePipelineCreateInfo),
}
#[derive(Debug, Clone)]
pub struct PipelineLayoutCreateInfo {
pub create_info: vk::VkPipelineLayoutCreateInfo,
pub push_constant_ranges: Vec<vk::VkPushConstantRange>,
pub layouts: Vec<vk::VkDescriptorSetLayout>,
}
#[derive(Debug)]
// It would be nice to just store the VkClearAttachment directly but
// that can’t derive Debug because it is a union and it would be
// annoying to have to manually implement Debug.
pub enum ClearAttachment {
Color {
attachment: u32,
value: [f32; 4],
},
DepthStencil {
aspect_mask: vk::VkImageAspectFlags,
value: vk::VkClearDepthStencilValue,
},
}
#[derive(Debug)]
#[allow(dead_code)]
pub enum Command {
BeginRenderPass(vk::VkRenderPassBeginInfo),
EndRenderPass,
BindPipeline {
bind_point: vk::VkPipelineBindPoint,
pipeline: vk::VkPipeline,
},
BindVertexBuffers {
first_binding: u32,
buffers: Vec<vk::VkBuffer>,
offsets: Vec<vk::VkDeviceSize>,
},
BindIndexBuffer {
buffer: vk::VkBuffer,
offset: vk::VkDeviceSize,
index_type: vk::VkIndexType,
},
Draw {
vertex_count: u32,
instance_count: u32,
first_vertex: u32,
first_instance: u32,
},
DrawIndexed {
index_count: u32,
instance_count: u32,
first_index: u32,
vertex_offset: i32,
first_instance: u32,
},
Dispatch {
x: u32,
y: u32,
z: u32,
},
ClearAttachments {
attachments: Vec<ClearAttachment>,
rects: Vec<vk::VkClearRect>,
},
PipelineBarrier {
src_stage_mask: vk::VkPipelineStageFlags,
dst_stage_mask: vk::VkPipelineStageFlags,
dependency_flags: vk::VkDependencyFlags,
memory_barriers: Vec<vk::VkMemoryBarrier>,
buffer_memory_barriers: Vec<vk::VkBufferMemoryBarrier>,
image_memory_barriers: Vec<vk::VkImageMemoryBarrier>,
},
CopyImageToBuffer {
src_image: vk::VkImage,
src_image_layout: vk::VkImageLayout,
dst_buffer: vk::VkBuffer,
regions: Vec<vk::VkBufferImageCopy>,
},
PushConstants {
layout: vk::VkPipelineLayout,
stage_flags: vk::VkShaderStageFlags,
offset: u32,
values: Vec<u8>,
},
BindDescriptorSets {
pipeline_bind_point: vk::VkPipelineBindPoint,
layout: vk::VkPipelineLayout,
first_set: u32,
descriptor_sets: Vec<vk::VkDescriptorSet>,
},
}
#[derive(Debug)]
pub enum HandleType {
Instance,
Device,
CommandPool,
CommandBuffer {
command_pool: usize,
commands: Vec<Command>,
begun: bool,
},
Fence {
reset_count: usize,
wait_count: usize,
},
Memory {
contents: Vec<u8>,
mapped: bool,
},
RenderPass { attachments: Vec<vk::VkAttachmentDescription> },
Image,
ImageView,
Buffer {
create_info: vk::VkBufferCreateInfo,
memory: Option<vk::VkDeviceMemory>,
},
Framebuffer,
ShaderModule { code: Vec<u32> },
PipelineCache,
DescriptorPool,
DescriptorSetLayout { bindings: Vec<vk::VkDescriptorSetLayoutBinding> },
PipelineLayout(PipelineLayoutCreateInfo),
Pipeline(PipelineCreateInfo),
DescriptorSet { bindings: HashMap<u32, Binding> },
}
#[derive(Debug)]
pub struct Binding {
pub descriptor_type: vk::VkDescriptorType,
pub info: vk::VkDescriptorBufferInfo,
}
#[derive(Debug)]
pub struct Handle {
pub freed: bool,
pub data: HandleType,
}
fn vec_from_raw_parts<T: std::clone::Clone>(data: *const T, len: usize) -> Vec<T> {
if len > 0 {
unsafe {
std::slice::from_raw_parts(data, len).to_vec()
}
} else {
Vec::new()
}
}
/// A fake Vulkan driver. Note that there can only be one FakeVulkan
/// instance per-thread because it needs to use thread-local storage
/// to figure out the current fake driver when the fake
/// vkCreateInstance is called. The FakeVulkan should always be stored
/// in a box so that its address can be tracked in
/// [CURRENT_FAKE_VULKAN].
#[derive(Debug)]
pub struct FakeVulkan {
pub physical_devices: Vec<PhysicalDeviceInfo>,
pub instance_extensions: Vec<vk::VkExtensionProperties>,
// A fake set of requirements to return from the next call to
// vkGetBufferMemoryRequirements or vkGetImageMemoryRequirements.
pub memory_requirements: vk::VkMemoryRequirements,
/// Whether to claim that the vkEnumerateInstanceVersion function
/// is available.
pub has_enumerate_instance_version: bool,
/// Log of calls to vkFlushMappedMemoryRanges
pub memory_flushes: Vec<vk::VkMappedMemoryRange>,
/// Log of calls to vkInvalidateMappedMemoryRanges
pub memory_invalidations: Vec<vk::VkMappedMemoryRange>,
/// All of the commands from command queues that were submitted
/// with vkQueueSubmit
pub commands: Vec<Command>,
handles: Vec<Handle>,
// Queue of values to return instead of VK_SUCCESS to simulate
// function call failures. This is indexed by the function name
// and the value is a queue of override values to return.
result_queue: HashMap<String, VecDeque<vk::VkResult>>,
}
impl FakeVulkan {
pub fn new() -> Box<FakeVulkan> {
let mut fake_vulkan = Box::new(FakeVulkan {
physical_devices: Vec::new(),
instance_extensions: Vec::new(),
memory_requirements: Default::default(),
handles: Vec::new(),
has_enumerate_instance_version: false,
result_queue: HashMap::new(),
memory_flushes: Vec::new(),
memory_invalidations: Vec::new(),
commands: Vec::new(),
});
CURRENT_FAKE_VULKAN.with(|f| {
let old_value = f.replace(Some(
fake_vulkan.as_mut() as *mut FakeVulkan
));
// There can only be one FakeVulkan instance per thread at a time
assert!(old_value.is_none());
});
fake_vulkan
}
pub fn current() -> &'static mut FakeVulkan {
unsafe { &mut *CURRENT_FAKE_VULKAN.with(|f| f.get().unwrap()) }
}
fn next_result(&mut self, func_name: &str) -> vk::VkResult {
match self.result_queue.get_mut(func_name) {
Some(queue) => match queue.pop_front() {
Some(res) => res,
None => vk::VK_SUCCESS,
},
None => vk::VK_SUCCESS,
}
}
/// Queue a VkResult to return the next time the named function is
/// called. The value will be used only once and after that the
/// function will revert to always returning VK_SUCCESS. This can
/// be called multiple times to queue multiple results before
/// reverting.
pub fn queue_result(&mut self, func_name: String, result: vk::VkResult) {
self.result_queue
.entry(func_name)
.or_insert_with(Default::default)
.push_back(result);
}
/// Sets the get_proc_addr override on the [vulkan_funcs] so that
/// it will use this FakeVulkan driver the next time a
/// [Library](vulkan_funcs::Library) is created.
pub fn set_override(&self) {
vulkan_funcs::override_get_instance_proc_addr(
ptr::addr_of!(*self).cast(),
Some(FakeVulkan::get_instance_proc_addr),
);
}
pub fn add_instance_extension(&mut self, ext: &str) {
add_extension_to_vec(&mut self.instance_extensions, ext);
}
pub fn get_function(&self, name: *const c_char) -> vk::PFN_vkVoidFunction {
let name = unsafe { CStr::from_ptr(name).to_str().unwrap() };
match name {
"vkGetDeviceProcAddr" => unsafe {
transmute::<vk::PFN_vkGetDeviceProcAddr, _>(
Some(FakeVulkan::get_device_proc_addr)
)
},
"vkCreateInstance" => unsafe {
transmute::<vk::PFN_vkCreateInstance, _>(
Some(FakeVulkan::create_instance)
)
},
"vkEnumeratePhysicalDevices" => unsafe {
transmute::<vk::PFN_vkEnumeratePhysicalDevices, _>(
Some(FakeVulkan::enumerate_physical_devices)
)
},
"vkGetPhysicalDeviceMemoryProperties" => unsafe {
transmute::<vk::PFN_vkGetPhysicalDeviceMemoryProperties, _>(
Some(FakeVulkan::get_physical_device_memory_properties)
)
},
"vkGetPhysicalDeviceFormatProperties" => unsafe {
transmute::<vk::PFN_vkGetPhysicalDeviceFormatProperties, _>(
Some(FakeVulkan::get_physical_device_format_properties)
)
},
"vkGetPhysicalDeviceProperties" => unsafe {
transmute::<vk::PFN_vkGetPhysicalDeviceProperties, _>(
Some(FakeVulkan::get_physical_device_properties)
)
},
"vkGetPhysicalDeviceProperties2" => unsafe {
transmute::<vk::PFN_vkGetPhysicalDeviceProperties2, _>(
Some(FakeVulkan::get_physical_device_properties2)
)
},
"vkGetPhysicalDeviceFeatures" => unsafe {
transmute::<vk::PFN_vkGetPhysicalDeviceFeatures, _>(
Some(FakeVulkan::get_physical_device_features)
)
},
"vkGetPhysicalDeviceQueueFamilyProperties" => unsafe {
type T = vk::PFN_vkGetPhysicalDeviceQueueFamilyProperties;
transmute::<T, _>(Some(
FakeVulkan::get_physical_device_queue_family_properties
))
},
"vkGetDeviceQueue" => unsafe {
transmute::<vk::PFN_vkGetDeviceQueue, _>(
Some(FakeVulkan::get_device_queue)
)
},
"vkEnumerateInstanceExtensionProperties" => unsafe {
transmute::<vk::PFN_vkEnumerateInstanceExtensionProperties, _>(
Some(FakeVulkan::enumerate_instance_extension_properties)
)
},
"vkEnumerateDeviceExtensionProperties" => unsafe {
transmute::<vk::PFN_vkEnumerateDeviceExtensionProperties, _>(
Some(FakeVulkan::enumerate_device_extension_properties)
)
},
"vkCreateDevice" => unsafe {
transmute::<vk::PFN_vkCreateDevice, _>(
Some(FakeVulkan::create_device)
)
},
"vkDestroyInstance" => unsafe {
transmute::<vk::PFN_vkDestroyInstance, _>(
Some(FakeVulkan::destroy_instance)
)
},
"vkDestroyDevice" => unsafe {
transmute::<vk::PFN_vkDestroyDevice, _>(
Some(FakeVulkan::destroy_device)
)
},
"vkCreateCommandPool" => unsafe {
transmute::<vk::PFN_vkCreateCommandPool, _>(
Some(FakeVulkan::create_command_pool)
)
},
"vkDestroyCommandPool" => unsafe {
transmute::<vk::PFN_vkDestroyCommandPool, _>(
Some(FakeVulkan::destroy_command_pool)
)
},
"vkAllocateCommandBuffers" => unsafe {
transmute::<vk::PFN_vkAllocateCommandBuffers, _>(
Some(FakeVulkan::allocate_command_buffers)
)
},
"vkFreeCommandBuffers" => unsafe {
transmute::<vk::PFN_vkFreeCommandBuffers, _>(
Some(FakeVulkan::free_command_buffers)
)
},
"vkCreateFence" => unsafe {
transmute::<vk::PFN_vkCreateFence, _>(
Some(FakeVulkan::create_fence)
)
},
"vkDestroyFence" => unsafe {
transmute::<vk::PFN_vkDestroyFence, _>(
Some(FakeVulkan::destroy_fence)
)
},
"vkCreateRenderPass" => unsafe {
transmute::<vk::PFN_vkCreateRenderPass, _>(
Some(FakeVulkan::create_render_pass)
)
},
"vkDestroyRenderPass" => unsafe {
transmute::<vk::PFN_vkDestroyRenderPass, _>(
Some(FakeVulkan::destroy_render_pass)
)
},
"vkCreateImageView" => unsafe {
transmute::<vk::PFN_vkCreateImageView, _>(
Some(FakeVulkan::create_image_view)
)
},
"vkDestroyImageView" => unsafe {
transmute::<vk::PFN_vkDestroyImageView, _>(
Some(FakeVulkan::destroy_image_view)
)
},
"vkCreateImage" => unsafe {
transmute::<vk::PFN_vkCreateImage, _>(
Some(FakeVulkan::create_image)
)
},
"vkDestroyImage" => unsafe {
transmute::<vk::PFN_vkDestroyImage, _>(
Some(FakeVulkan::destroy_image)
)
},
"vkCreateBuffer" => unsafe {
transmute::<vk::PFN_vkCreateBuffer, _>(
Some(FakeVulkan::create_buffer)
)
},
"vkDestroyBuffer" => unsafe {
transmute::<vk::PFN_vkDestroyBuffer, _>(
Some(FakeVulkan::destroy_buffer)
)
},
"vkCreateFramebuffer" => unsafe {
transmute::<vk::PFN_vkCreateFramebuffer, _>(
Some(FakeVulkan::create_framebuffer)
)
},
"vkDestroyFramebuffer" => unsafe {
transmute::<vk::PFN_vkDestroyFramebuffer, _>(
Some(FakeVulkan::destroy_framebuffer)
)
},
"vkEnumerateInstanceVersion" => unsafe {
if self.has_enumerate_instance_version {
transmute::<vk::PFN_vkEnumerateInstanceVersion, _>(
Some(FakeVulkan::enumerate_instance_version)
)
} else {
None
}
},
"vkGetPhysicalDeviceFeatures2KHR" => unsafe {
transmute::<vk::PFN_vkGetPhysicalDeviceFeatures2, _>(
Some(FakeVulkan::get_physical_device_features2)
)
},
"vkGetImageMemoryRequirements" => unsafe {
transmute::<vk::PFN_vkGetImageMemoryRequirements, _>(
Some(FakeVulkan::get_image_memory_requirements)
)
},
"vkGetBufferMemoryRequirements" => unsafe {
transmute::<vk::PFN_vkGetBufferMemoryRequirements, _>(
Some(FakeVulkan::get_buffer_memory_requirements)
)
},
"vkBindBufferMemory" => unsafe {
transmute::<vk::PFN_vkBindBufferMemory, _>(
Some(FakeVulkan::bind_buffer_memory)
)
},
"vkBindImageMemory" => unsafe {
transmute::<vk::PFN_vkBindImageMemory, _>(
Some(FakeVulkan::bind_image_memory)
)
},
"vkAllocateMemory" => unsafe {
transmute::<vk::PFN_vkAllocateMemory, _>(
Some(FakeVulkan::allocate_memory)
)
},
"vkFreeMemory" => unsafe {
transmute::<vk::PFN_vkFreeMemory, _>(
Some(FakeVulkan::free_memory)
)
},
"vkMapMemory" => unsafe {
transmute::<vk::PFN_vkMapMemory, _>(
Some(FakeVulkan::map_memory)
)
},
"vkUnmapMemory" => unsafe {
transmute::<vk::PFN_vkUnmapMemory, _>(
Some(FakeVulkan::unmap_memory)
)
},
"vkCreateShaderModule" => unsafe {
transmute::<vk::PFN_vkCreateShaderModule, _>(
Some(FakeVulkan::create_shader_module)
)
},
"vkDestroyShaderModule" => unsafe {
transmute::<vk::PFN_vkDestroyShaderModule, _>(
Some(FakeVulkan::destroy_shader_module)
)
},
"vkCreatePipelineCache" => unsafe {
transmute::<vk::PFN_vkCreatePipelineCache, _>(
Some(FakeVulkan::create_pipeline_cache)
)
},
"vkDestroyPipelineCache" => unsafe {
transmute::<vk::PFN_vkDestroyPipelineCache, _>(
Some(FakeVulkan::destroy_pipeline_cache)
)
},
"vkCreateDescriptorPool" => unsafe {
transmute::<vk::PFN_vkCreateDescriptorPool, _>(
Some(FakeVulkan::create_descriptor_pool)
)
},
"vkDestroyDescriptorPool" => unsafe {
transmute::<vk::PFN_vkDestroyDescriptorPool, _>(
Some(FakeVulkan::destroy_descriptor_pool)
)
},
"vkCreateDescriptorSetLayout" => unsafe {
transmute::<vk::PFN_vkCreateDescriptorSetLayout, _>(
Some(FakeVulkan::create_descriptor_set_layout)
)
},
"vkDestroyDescriptorSetLayout" => unsafe {
transmute::<vk::PFN_vkDestroyDescriptorSetLayout, _>(
Some(FakeVulkan::destroy_descriptor_set_layout)
)
},
"vkCreatePipelineLayout" => unsafe {
transmute::<vk::PFN_vkCreatePipelineLayout, _>(
Some(FakeVulkan::create_pipeline_layout)
)
},
"vkDestroyPipelineLayout" => unsafe {
transmute::<vk::PFN_vkDestroyPipelineLayout, _>(
Some(FakeVulkan::destroy_pipeline_layout)
)
},
"vkCreateGraphicsPipelines" => unsafe {
transmute::<vk::PFN_vkCreateGraphicsPipelines, _>(
Some(FakeVulkan::create_graphics_pipelines)
)
},
"vkCreateComputePipelines" => unsafe {
transmute::<vk::PFN_vkCreateComputePipelines, _>(
Some(FakeVulkan::create_compute_pipelines)
)
},
"vkDestroyPipeline" => unsafe {
transmute::<vk::PFN_vkDestroyPipeline, _>(
Some(FakeVulkan::destroy_pipeline)
)
},
"vkFlushMappedMemoryRanges" => unsafe {
transmute::<vk::PFN_vkFlushMappedMemoryRanges, _>(
Some(FakeVulkan::flush_mapped_memory_ranges)
)
},
"vkInvalidateMappedMemoryRanges" => unsafe {
transmute::<vk::PFN_vkInvalidateMappedMemoryRanges, _>(
Some(FakeVulkan::invalidate_mapped_memory_ranges)
)
},
"vkQueueSubmit" => unsafe {
transmute::<vk::PFN_vkQueueSubmit, _>(
Some(FakeVulkan::queue_submit)
)
},
"vkAllocateDescriptorSets" => unsafe {
transmute::<vk::PFN_vkAllocateDescriptorSets, _>(
Some(FakeVulkan::allocate_descriptor_sets)
)
},
"vkFreeDescriptorSets" => unsafe {
transmute::<vk::PFN_vkFreeDescriptorSets, _>(
Some(FakeVulkan::free_descriptor_sets)
)
},
"vkUpdateDescriptorSets" => unsafe {
transmute::<vk::PFN_vkUpdateDescriptorSets, _>(
Some(FakeVulkan::update_descriptor_sets)
)
},
"vkBeginCommandBuffer" => unsafe {
transmute::<vk::PFN_vkBeginCommandBuffer, _>(
Some(FakeVulkan::begin_command_buffer)
)
},
"vkEndCommandBuffer" => unsafe {
transmute::<vk::PFN_vkEndCommandBuffer, _>(
Some(FakeVulkan::end_command_buffer)
)
},
"vkCmdBeginRenderPass" => unsafe {
transmute::<vk::PFN_vkCmdBeginRenderPass, _>(
Some(FakeVulkan::begin_render_pass)
)
},
"vkCmdEndRenderPass" => unsafe {
transmute::<vk::PFN_vkCmdEndRenderPass, _>(
Some(FakeVulkan::end_render_pass)
)
},
"vkCmdBindPipeline" => unsafe {
transmute::<vk::PFN_vkCmdBindPipeline, _>(
Some(FakeVulkan::bind_pipeline)
)
},
"vkCmdBindVertexBuffers" => unsafe {
transmute::<vk::PFN_vkCmdBindVertexBuffers, _>(
Some(FakeVulkan::bind_vertex_buffers)
)
},
"vkCmdBindIndexBuffer" => unsafe {
transmute::<vk::PFN_vkCmdBindIndexBuffer, _>(
Some(FakeVulkan::bind_index_buffer)
)
},
"vkCmdDraw" => unsafe {
transmute::<vk::PFN_vkCmdDraw, _>(
Some(FakeVulkan::draw)
)
},
"vkCmdDrawIndexed" => unsafe {
transmute::<vk::PFN_vkCmdDrawIndexed, _>(
Some(FakeVulkan::draw_indexed)
)
},
"vkCmdDispatch" => unsafe {
transmute::<vk::PFN_vkCmdDispatch, _>(
Some(FakeVulkan::dispatch)
)
},
"vkCmdClearAttachments" => unsafe {
transmute::<vk::PFN_vkCmdClearAttachments, _>(
Some(FakeVulkan::clear_attachments)
)
},
"vkCmdPipelineBarrier" => unsafe {
transmute::<vk::PFN_vkCmdPipelineBarrier, _>(
Some(FakeVulkan::pipeline_barrier)
)
},
"vkCmdCopyImageToBuffer" => unsafe {
transmute::<vk::PFN_vkCmdCopyImageToBuffer, _>(
Some(FakeVulkan::copy_image_to_buffer)
)
},
"vkCmdPushConstants" => unsafe {
transmute::<vk::PFN_vkCmdPushConstants, _>(
Some(FakeVulkan::push_constants)
)
},
"vkCmdBindDescriptorSets" => unsafe {
transmute::<vk::PFN_vkCmdBindDescriptorSets, _>(
Some(FakeVulkan::bind_descriptor_sets)
)
},
"vkResetFences" => unsafe {
transmute::<vk::PFN_vkResetFences, _>(
Some(FakeVulkan::reset_fences)
)
},
"vkWaitForFences" => unsafe {
transmute::<vk::PFN_vkWaitForFences, _>(
Some(FakeVulkan::wait_for_fences)
)
},
_ => None,
}
}
extern "C" fn get_instance_proc_addr(
_instance: vk::VkInstance,
name: *const c_char,
) -> vk::PFN_vkVoidFunction {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.get_function(name)
}
extern "C" fn get_device_proc_addr(
_device: vk::VkDevice,
name: *const c_char,
) -> vk::PFN_vkVoidFunction {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.get_function(name)
}
fn copy_with_count<T>(
values: &[T],
count_ptr: *mut u32,
array_ptr: *mut T,
) where
T: Clone,
{
if array_ptr.is_null() {
unsafe {
*count_ptr = values.len() as u32;
}
return;
}
let count = min(
unsafe { *count_ptr } as usize,
values.len(),
);
for (i, value) in values.iter().take(count).enumerate() {
unsafe {
*array_ptr.add(i) = value.clone();
}
}
}
extern "C" fn enumerate_physical_devices(
_instance: vk::VkInstance,
physical_device_count: *mut u32,
physical_devices: *mut vk::VkPhysicalDevice,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
if physical_devices.is_null() {
unsafe {
*physical_device_count =
fake_vulkan.physical_devices.len() as u32;
}
return fake_vulkan.next_result("vkEnumeratePhysicalDevices");
}
let count = min(
unsafe { *physical_device_count as usize },
fake_vulkan.physical_devices.len()
);
for i in 0..count {
unsafe {
// Store the device index as a pointer. We add 1 so
// that it won’t be null.
*physical_devices.add(i) =
fake_vulkan.index_to_physical_device(i);
}
}
unsafe {
*physical_device_count = count as u32;
}
fake_vulkan.next_result("vkEnumeratePhysicalDevices")
}
/// Get the physical device that would point to the device at the
/// given index.
pub fn index_to_physical_device(
&self,
index: usize,
) -> vk::VkPhysicalDevice {
assert!(index < self.physical_devices.len());
unsafe { transmute(index + 1) }
}
/// Get the index of the physical device represented by the
/// VkPhysicalDevice pointer.
pub fn physical_device_to_index(
&self,
physical_device: vk::VkPhysicalDevice
) -> usize {
assert!(!physical_device.is_null());
let index = unsafe { transmute::<_, usize>(physical_device) - 1 };
assert!(index < self.physical_devices.len());
index
}
extern "C" fn get_physical_device_memory_properties(
physical_device: vk::VkPhysicalDevice,
memory_properties_out: *mut vk::VkPhysicalDeviceMemoryProperties,
) {
let fake_vulkan = FakeVulkan::current();
unsafe {
let device_num =
fake_vulkan.physical_device_to_index(physical_device);
let device = &fake_vulkan.physical_devices[device_num];
*memory_properties_out = device.memory_properties.clone();
}
}
extern "C" fn get_physical_device_properties(
physical_device: vk::VkPhysicalDevice,
properties_out: *mut vk::VkPhysicalDeviceProperties,
) {
let fake_vulkan = FakeVulkan::current();
unsafe {
let device_num =
fake_vulkan.physical_device_to_index(physical_device);
let device = &fake_vulkan.physical_devices[device_num];
*properties_out = device.properties.clone();
}
}
extern "C" fn get_physical_device_properties2(
physical_device: vk::VkPhysicalDevice,
properties_out: *mut vk::VkPhysicalDeviceProperties2,
) {
let fake_vulkan = FakeVulkan::current();
let device_num = fake_vulkan.physical_device_to_index(physical_device);
let device = &fake_vulkan.physical_devices[device_num];
unsafe {
assert_eq!(
(*properties_out).sType,
vk::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2
);
(*properties_out).properties = device.properties.clone();
}
}
extern "C" fn get_physical_device_features(
physical_device: vk::VkPhysicalDevice,
features_out: *mut vk::VkPhysicalDeviceFeatures,
) {
let fake_vulkan = FakeVulkan::current();
unsafe {
let device_num =
fake_vulkan.physical_device_to_index(physical_device);
let device = &fake_vulkan.physical_devices[device_num];
*features_out = device.features.clone();
}
}
extern "C" fn get_physical_device_format_properties(
physical_device: vk::VkPhysicalDevice,
format: vk::VkFormat,
properties: *mut vk::VkFormatProperties,
) {
let fake_vulkan = FakeVulkan::current();
let device_num =
fake_vulkan.physical_device_to_index(physical_device);
let device = &fake_vulkan.physical_devices[device_num];
unsafe {
*properties = device.format_properties[&format];
}
}
fn extract_struct_data(
ptr: *mut u8
) -> (vk::VkStructureType, *mut u8) {
let mut type_bytes =
[0u8; mem::size_of::<vk::VkStructureType>()];
unsafe {
ptr.copy_to(type_bytes.as_mut_ptr(), type_bytes.len());
}
let mut next_bytes =
[0u8; mem::size_of::<*mut u8>()];
unsafe {
ptr.add(vulkan_funcs::NEXT_PTR_OFFSET).copy_to(
next_bytes.as_mut_ptr(), next_bytes.len()
);
}
(
vk::VkStructureType::from_ne_bytes(type_bytes),
usize::from_ne_bytes(next_bytes) as *mut u8,
)
}
extern "C" fn get_physical_device_features2(
physical_device: vk::VkPhysicalDevice,
features: *mut vk::VkPhysicalDeviceFeatures2,
) {
let fake_vulkan = FakeVulkan::current();
let device_num = fake_vulkan.physical_device_to_index(physical_device);
let device = &fake_vulkan.physical_devices[device_num];
let (struct_type, mut struct_ptr) =
FakeVulkan::extract_struct_data(features.cast());
assert_eq!(
struct_type,
vk::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2
);
while !struct_ptr.is_null() {
let (struct_type, next_ptr) =
FakeVulkan::extract_struct_data(struct_ptr);
let to_copy = match struct_type {
ATOMIC_TYPE => vec![
device.shader_atomic.shaderBufferInt64Atomics,
device.shader_atomic.shaderSharedInt64Atomics,
],
MULTIVIEW_TYPE => vec![
device.multiview.multiview,
device.multiview.multiviewGeometryShader,
device.multiview.multiviewTessellationShader,
],
_ => unreachable!("unexpected struct type {}", struct_type),
};
unsafe {
std::ptr::copy(
to_copy.as_ptr(),
struct_ptr.add(vulkan_funcs::FIRST_FEATURE_OFFSET).cast(),
to_copy.len(),
);
}
struct_ptr = next_ptr;
}
}
extern "C" fn get_physical_device_queue_family_properties(
physical_device: vk::VkPhysicalDevice,
property_count_out: *mut u32,
properties: *mut vk::VkQueueFamilyProperties,
) {
let fake_vulkan = FakeVulkan::current();
let device_num = fake_vulkan.physical_device_to_index(physical_device);
let device = &fake_vulkan.physical_devices[device_num];
FakeVulkan::copy_with_count(
&device.queue_families,
property_count_out,
properties,
);
}
#[inline]
pub fn make_queue(
queue_family_index: u32,
queue_index: u32
) -> vk::VkQueue {
let queue =
((queue_family_index << 9) | (queue_index << 1) | 1) as usize;
queue as vk::VkQueue
}
#[inline]
pub fn unmake_queue(
queue: vk::VkQueue,
) -> (u32, u32) {
let queue = queue as usize;
((queue >> 9) as u32, ((queue >> 1) & 0xff) as u32)
}
extern "C" fn get_device_queue(
_device: vk::VkDevice,
queue_family_index: u32,
queue_index: u32,
queue_out: *mut vk::VkQueue,
) {
unsafe {
*queue_out = FakeVulkan::make_queue(
queue_family_index,
queue_index
);
}
}
extern "C" fn enumerate_instance_extension_properties(
_layer_name: *const c_char,
property_count: *mut u32,
properties: *mut vk::VkExtensionProperties,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
FakeVulkan::copy_with_count(
&fake_vulkan.instance_extensions,
property_count,
properties,
);
fake_vulkan.next_result("vkEnumerateInstanceExtensionProperties")
}
extern "C" fn enumerate_device_extension_properties(
physical_device: vk::VkPhysicalDevice,
_layer_name: *const c_char,
property_count: *mut u32,
properties: *mut vk::VkExtensionProperties,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let device_num = fake_vulkan.physical_device_to_index(physical_device);
let device = &fake_vulkan.physical_devices[device_num];
FakeVulkan::copy_with_count(
&device.extensions,
property_count,
properties,
);
fake_vulkan.next_result("vkEnumerateDeviceExtensionProperties")
}
pub fn add_dispatchable_handle<T>(&mut self, data: HandleType) -> *mut T {
self.handles.push(Handle {
freed: false,
data,
});
self.handles.len() as *mut T
}
#[cfg(target_pointer_width = "64")]
pub fn add_handle<T>(&mut self, data: HandleType) -> *mut T {
self.add_dispatchable_handle(data)
}
#[cfg(not(target_pointer_width = "64"))]
pub fn add_handle(&mut self, data: HandleType) -> u64 {
self.handles.push(Handle {
freed: false,
data,
});
self.handles.len() as u64
}
pub fn dispatchable_handle_to_index<T>(handle: *mut T) -> usize {
let handle_num = handle as usize;
assert!(handle_num > 0);
handle_num - 1
}
#[cfg(target_pointer_width = "64")]
pub fn handle_to_index<T>(handle: *mut T) -> usize {
FakeVulkan::dispatchable_handle_to_index(handle)
}
#[cfg(not(target_pointer_width = "64"))]
pub fn handle_to_index(handle: u64) -> usize {
assert!(handle > 0);
(handle - 1) as usize
}
pub fn get_dispatchable_handle<T>(&self, handle: *mut T) -> &Handle {
let index = FakeVulkan::dispatchable_handle_to_index(handle);
let handle = &self.handles[index];
assert!(!handle.freed);
handle
}
pub fn get_dispatchable_handle_mut<T>(
&mut self,
handle: *mut T,
) -> &mut Handle {
let index = FakeVulkan::dispatchable_handle_to_index(handle);
let handle = &mut self.handles[index];
assert!(!handle.freed);
handle
}
#[cfg(target_pointer_width = "64")]
pub fn get_handle<T>(&self, handle: *mut T) -> &Handle {
let handle = &self.handles[FakeVulkan::handle_to_index(handle)];
assert!(!handle.freed);
handle
}
#[cfg(not(target_pointer_width = "64"))]
pub fn get_handle(&self, handle: u64) -> &Handle {
let handle = &self.handles[FakeVulkan::handle_to_index(handle)];
assert!(!handle.freed);
handle
}
#[cfg(target_pointer_width = "64")]
pub fn get_freed_handle<T>(&self, handle: *mut T) -> &Handle {
&self.handles[FakeVulkan::handle_to_index(handle)]
}
#[cfg(not(target_pointer_width = "64"))]
pub fn get_freed_handle(&self, handle: u64) -> &Handle {
&self.handles[FakeVulkan::handle_to_index(handle)]
}
#[cfg(target_pointer_width = "64")]
pub fn get_handle_mut<T>(&mut self, handle: *mut T) -> &mut Handle {
let handle = &mut self.handles[FakeVulkan::handle_to_index(handle)];
assert!(!handle.freed);
handle
}
#[cfg(not(target_pointer_width = "64"))]
pub fn get_handle_mut(&mut self, handle: u64) -> &mut Handle {
let handle = &mut self.handles[FakeVulkan::handle_to_index(handle)];
assert!(!handle.freed);
handle
}
fn check_device(&self, device: vk::VkDevice) {
let handle = self.get_dispatchable_handle(device);
assert!(matches!(handle.data, HandleType::Device));
}
fn check_command_pool(&self, command_pool: vk::VkCommandPool) {
let handle = self.get_handle(command_pool);
assert!(matches!(handle.data, HandleType::CommandPool));
}
fn check_image(&self, image: vk::VkImage) {
let handle = self.get_handle(image);
assert!(matches!(handle.data, HandleType::Image));
}
fn check_descriptor_pool(&self, descriptor_pool: vk::VkDescriptorPool) {
let handle = self.get_handle(descriptor_pool);
assert!(matches!(handle.data, HandleType::DescriptorPool));
}
fn check_image_view(&self, image_view: vk::VkImageView) {
let handle = self.get_handle(image_view);
assert!(matches!(handle.data, HandleType::ImageView));
}
fn check_pipeline_cache(&self, pipeline_cache: vk::VkPipelineCache) {
let handle = self.get_handle(pipeline_cache);
assert!(matches!(handle.data, HandleType::PipelineCache));
}
fn check_fence(&self, fence: vk::VkFence) {
let handle = self.get_handle(fence);
assert!(matches!(handle.data, HandleType::Fence { .. }));
}
fn check_framebuffer(&self, framebuffer: vk::VkFramebuffer) {
let handle = self.get_handle(framebuffer);
assert!(matches!(handle.data, HandleType::Framebuffer));
}
fn check_render_pass(&self, render_pass: vk::VkRenderPass) {
let handle = self.get_handle(render_pass);
assert!(matches!(handle.data, HandleType::RenderPass { .. }));
}
fn check_pipeline(&self, pipeline: vk::VkPipeline) {
let handle = self.get_handle(pipeline);
assert!(matches!(handle.data, HandleType::Pipeline { .. }));
}
fn check_buffer(&self, buffer: vk::VkBuffer) {
let handle = self.get_handle(buffer);
assert!(matches!(handle.data, HandleType::Buffer { .. }));
}
fn check_memory(&self, memory: vk::VkDeviceMemory) {
let handle = self.get_handle(memory);
assert!(matches!(handle.data, HandleType::Memory { .. }));
}
extern "C" fn create_instance(
_create_info: *const vk::VkInstanceCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
instance_out: *mut vk::VkInstance,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateInstance");
if res != vk::VK_SUCCESS {
return res;
}
unsafe {
*instance_out =
fake_vulkan.add_dispatchable_handle(HandleType::Instance);
}
res
}
extern "C" fn create_device(
_physical_device: vk::VkPhysicalDevice,
_create_info: *const vk::VkDeviceCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
device_out: *mut vk::VkDevice,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateDevice");
if res != vk::VK_SUCCESS {
return res;
}
unsafe {
*device_out =
fake_vulkan.add_dispatchable_handle(HandleType::Device);
}
res
}
extern "C" fn destroy_device(
device: vk::VkDevice,
_allocator: *const vk::VkAllocationCallbacks
) {
let fake_vulkan = FakeVulkan::current();
let handle = fake_vulkan.get_dispatchable_handle_mut(device);
assert!(matches!(handle.data, HandleType::Device));
handle.freed = true;
}
extern "C" fn destroy_instance(
instance: vk::VkInstance,
_allocator: *const vk::VkAllocationCallbacks
) {
let fake_vulkan = FakeVulkan::current();
let handle = fake_vulkan.get_dispatchable_handle_mut(instance);
assert!(matches!(handle.data, HandleType::Instance));
handle.freed = true;
}
extern "C" fn create_command_pool(
device: vk::VkDevice,
_create_info: *const vk::VkCommandPoolCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
command_pool_out: *mut vk::VkCommandPool,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateCommandPool");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
unsafe {
*command_pool_out = fake_vulkan.add_handle(HandleType::CommandPool);
}
res
}
extern "C" fn destroy_command_pool(
device: vk::VkDevice,
command_pool: vk::VkCommandPool,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(command_pool);
assert!(matches!(handle.data, HandleType::CommandPool));
handle.freed = true;
}
extern "C" fn allocate_command_buffers(
device: vk::VkDevice,
allocate_info: *const vk::VkCommandBufferAllocateInfo,
command_buffers: *mut vk::VkCommandBuffer,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkAllocateCommandBuffers");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
let command_pool_handle = unsafe { (*allocate_info).commandPool };
fake_vulkan.check_command_pool(command_pool_handle);
let n_buffers = unsafe { (*allocate_info).commandBufferCount };
for i in 0..(n_buffers as usize) {
unsafe {
*command_buffers.add(i) = fake_vulkan.add_dispatchable_handle(
HandleType::CommandBuffer {
command_pool: FakeVulkan::handle_to_index(
command_pool_handle
),
commands: Vec::new(),
begun: false,
},
);
}
}
res
}
extern "C" fn free_command_buffers(
device: vk::VkDevice,
command_pool: vk::VkCommandPool,
command_buffer_count: u32,
command_buffers: *const vk::VkCommandBuffer,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
fake_vulkan.check_command_pool(command_pool);
for i in 0..command_buffer_count as usize {
let command_buffer = unsafe {
*command_buffers.add(i)
};
let command_buffer_handle =
fake_vulkan.get_dispatchable_handle_mut(command_buffer);
match command_buffer_handle.data {
HandleType::CommandBuffer { command_pool: handle_pool, .. } => {
assert_eq!(
handle_pool,
FakeVulkan::handle_to_index(command_pool),
);
command_buffer_handle.freed = true;
},
_ => unreachable!("mismatched handle"),
}
}
}
extern "C" fn create_fence(
device: vk::VkDevice,
_create_info: *const vk::VkFenceCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
fence_out: *mut vk::VkFence,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateFence");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
unsafe {
*fence_out = fake_vulkan.add_handle(HandleType::Fence {
reset_count: 0,
wait_count: 0,
});
}
res
}
extern "C" fn destroy_fence(
device: vk::VkDevice,
fence: vk::VkFence,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(fence);
assert!(matches!(handle.data, HandleType::Fence { .. }));
handle.freed = true;
}
extern "C" fn create_render_pass(
device: vk::VkDevice,
create_info: *const vk::VkRenderPassCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
render_pass_out: *mut vk::VkRenderPass,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateRenderPass");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
unsafe {
let create_info = &*create_info;
*render_pass_out = fake_vulkan.add_handle(HandleType::RenderPass {
attachments: vec_from_raw_parts(
create_info.pAttachments,
create_info.attachmentCount as usize,
),
});
}
res
}
extern "C" fn destroy_render_pass(
device: vk::VkDevice,
render_pass: vk::VkRenderPass,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(render_pass);
assert!(matches!(handle.data, HandleType::RenderPass { .. }));
handle.freed = true;
}
extern "C" fn create_image_view(
device: vk::VkDevice,
create_info: *const vk::VkImageViewCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
image_view_out: *mut vk::VkImageView,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateImageView");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
fake_vulkan.check_image(unsafe { *create_info }.image);
unsafe {
*image_view_out = fake_vulkan.add_handle(HandleType::ImageView);
}
res
}
extern "C" fn destroy_image_view(
device: vk::VkDevice,
image_view: vk::VkImageView,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(image_view);
assert!(matches!(handle.data, HandleType::ImageView));
handle.freed = true;
}
extern "C" fn create_image(
device: vk::VkDevice,
_create_info: *const vk::VkImageCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
image_out: *mut vk::VkImage,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateImage");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
unsafe {
*image_out = fake_vulkan.add_handle(HandleType::Image);
}
res
}
extern "C" fn destroy_image(
device: vk::VkDevice,
image: vk::VkImage,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(image);
assert!(matches!(handle.data, HandleType::Image));
handle.freed = true;
}
extern "C" fn create_buffer(
device: vk::VkDevice,
create_info: *const vk::VkBufferCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
buffer_out: *mut vk::VkBuffer,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateBuffer");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
let create_info = unsafe { &*create_info };
unsafe {
*buffer_out = fake_vulkan.add_handle(
HandleType::Buffer {
create_info: create_info.clone(),
memory: None,
}
);
}
res
}
extern "C" fn destroy_buffer(
device: vk::VkDevice,
buffer: vk::VkBuffer,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(buffer);
assert!(matches!(handle.data, HandleType::Buffer { .. }));
handle.freed = true;
}
extern "C" fn create_framebuffer(
device: vk::VkDevice,
create_info: *const vk::VkFramebufferCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
framebuffer_out: *mut vk::VkFramebuffer,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateFramebuffer");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
let attachments = unsafe {
let attachment_count = (*create_info).attachmentCount as usize;
std::slice::from_raw_parts(
(*create_info).pAttachments,
attachment_count
)
};
for &attachment in attachments {
fake_vulkan.check_image_view(attachment);
}
unsafe {
*framebuffer_out = fake_vulkan.add_handle(HandleType::Framebuffer);
}
res
}
extern "C" fn destroy_framebuffer(
device: vk::VkDevice,
framebuffer: vk::VkFramebuffer,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(framebuffer);
assert!(matches!(handle.data, HandleType::Framebuffer));
handle.freed = true;
}
extern "C" fn enumerate_instance_version(
api_version: *mut u32
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
unsafe { *api_version = requirements::make_version(1, 1, 0) }
fake_vulkan.next_result("vkEnumerateInstanceVersion")
}
extern "C" fn get_buffer_memory_requirements(
device: vk::VkDevice,
buffer: vk::VkBuffer,
memory_requirements: *mut vk::VkMemoryRequirements,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let size = match fake_vulkan.get_handle(buffer).data {
HandleType::Buffer { ref create_info, .. } => create_info.size,
_ => unreachable!("mismatched handle"),
};
unsafe {
*memory_requirements = fake_vulkan.memory_requirements.clone();
(*memory_requirements).size = size;
}
}
extern "C" fn get_image_memory_requirements(
device: vk::VkDevice,
image: vk::VkImage,
memory_requirements: *mut vk::VkMemoryRequirements,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
fake_vulkan.check_image(image);
unsafe {
*memory_requirements = fake_vulkan.memory_requirements;
}
}
extern "C" fn allocate_memory(
device: vk::VkDevice,
allocate_info: *const vk::VkMemoryAllocateInfo,
_allocator: *const vk::VkAllocationCallbacks,
memory: *mut vk::VkDeviceMemory,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkAllocateMemory");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
unsafe {
*memory = fake_vulkan.add_handle(HandleType::Memory {
contents: vec![
0u8;
(*allocate_info).allocationSize as usize
],
mapped: false,
});
}
res
}
extern "C" fn free_memory(
device: vk::VkDevice,
memory: vk::VkDeviceMemory,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(memory);
match handle.data {
HandleType::Memory { mapped, .. } => assert!(!mapped),
_ => unreachable!("mismatched handle"),
}
handle.freed = true;
}
extern "C" fn bind_buffer_memory(
device: vk::VkDevice,
buffer: vk::VkBuffer,
memory: vk::VkDeviceMemory,
_memory_offset: vk::VkDeviceSize,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkBindBufferMemory");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
fake_vulkan.check_memory(memory);
let HandleType::Buffer { memory: ref mut buffer_memory, .. } =
fake_vulkan.get_handle_mut(buffer).data
else { unreachable!("mismatched handle"); };
assert!(buffer_memory.is_none());
*buffer_memory = Some(memory);
res
}
extern "C" fn bind_image_memory(
_device: vk::VkDevice,
_image: vk::VkImage,
_memory: vk::VkDeviceMemory,
_memory_offset: vk::VkDeviceSize,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.next_result("vkBindImageMemory")
}
extern "C" fn map_memory(
device: vk::VkDevice,
memory: vk::VkDeviceMemory,
offset: vk::VkDeviceSize,
size: vk::VkDeviceSize,
_flags: vk::VkMemoryMapFlags,
data_out: *mut *mut c_void,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkMapMemory");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
let HandleType::Memory { ref mut mapped, ref mut contents } =
fake_vulkan.get_handle_mut(memory).data
else { unreachable!("mismatched handle"); };
assert!(!*mapped);
assert!(
size == vk::VK_WHOLE_SIZE as vk::VkDeviceSize
|| (offset + size) as usize <= contents.len()
);
unsafe {
*data_out = contents[offset as usize..].as_mut_ptr().cast();
}
*mapped = true;
res
}
extern "C" fn unmap_memory(
device: vk::VkDevice,
memory: vk::VkDeviceMemory
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let HandleType::Memory { ref mut mapped, .. } =
fake_vulkan.get_handle_mut(memory).data
else { unreachable!("mismatched handle"); };
assert!(*mapped);
*mapped = false;
}
extern "C" fn create_shader_module(
device: vk::VkDevice,
create_info: *const vk::VkShaderModuleCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
shader_module_out: *mut vk::VkShaderModule,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateShaderModule");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
unsafe {
assert_eq!((*create_info).codeSize % (u32::BITS as usize / 8), 0);
let code = vec_from_raw_parts(
(*create_info).pCode,
(*create_info).codeSize / (u32::BITS as usize / 8),
);
*shader_module_out = fake_vulkan.add_handle(
HandleType::ShaderModule { code }
);
}
res
}
extern "C" fn destroy_shader_module(
device: vk::VkDevice,
shader_module: vk::VkShaderModule,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(shader_module);
assert!(matches!(handle.data, HandleType::ShaderModule { .. }));
handle.freed = true;
}
extern "C" fn create_pipeline_cache(
device: vk::VkDevice,
_create_info: *const vk::VkPipelineCacheCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
pipeline_cache_out: *mut vk::VkPipelineCache,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreatePipelineCache");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
unsafe {
*pipeline_cache_out = fake_vulkan.add_handle(
HandleType::PipelineCache
);
}
res
}
extern "C" fn destroy_pipeline_cache(
device: vk::VkDevice,
pipeline_cache: vk::VkPipelineCache,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(pipeline_cache);
assert!(matches!(handle.data, HandleType::PipelineCache));
handle.freed = true;
}
extern "C" fn create_descriptor_pool(
device: vk::VkDevice,
_create_info: *const vk::VkDescriptorPoolCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
descriptor_pool_out: *mut vk::VkDescriptorPool,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateDescriptorPool");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
unsafe {
*descriptor_pool_out = fake_vulkan.add_handle(
HandleType::DescriptorPool
);
}
res
}
extern "C" fn destroy_descriptor_pool(
device: vk::VkDevice,
descriptor_pool: vk::VkDescriptorPool,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(descriptor_pool);
assert!(matches!(handle.data, HandleType::DescriptorPool));
handle.freed = true;
}
extern "C" fn create_descriptor_set_layout(
device: vk::VkDevice,
create_info: *const vk::VkDescriptorSetLayoutCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
descriptor_set_layout_out: *mut vk::VkDescriptorSetLayout,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateDescriptorSetLayout");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
unsafe {
let bindings = vec_from_raw_parts(
(*create_info).pBindings,
(*create_info).bindingCount as usize,
);
*descriptor_set_layout_out = fake_vulkan.add_handle(
HandleType::DescriptorSetLayout { bindings }
);
}
res
}
extern "C" fn destroy_descriptor_set_layout(
device: vk::VkDevice,
descriptor_set_layout: vk::VkDescriptorSetLayout,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(descriptor_set_layout);
assert!(matches!(handle.data, HandleType::DescriptorSetLayout { .. }));
handle.freed = true;
}
extern "C" fn create_pipeline_layout(
device: vk::VkDevice,
create_info: *const vk::VkPipelineLayoutCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
pipeline_layout_out: *mut vk::VkPipelineLayout,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreatePipelineLayout");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
unsafe {
let push_constant_ranges = vec_from_raw_parts(
(*create_info).pPushConstantRanges,
(*create_info).pushConstantRangeCount as usize,
);
let layouts = vec_from_raw_parts(
(*create_info).pSetLayouts,
(*create_info).setLayoutCount as usize,
);
*pipeline_layout_out = fake_vulkan.add_handle(
HandleType::PipelineLayout(PipelineLayoutCreateInfo {
create_info: (*create_info).clone(),
push_constant_ranges,
layouts,
}),
);
}
res
}
extern "C" fn destroy_pipeline_layout(
device: vk::VkDevice,
pipeline_layout: vk::VkPipelineLayout,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(pipeline_layout);
assert!(matches!(handle.data, HandleType::PipelineLayout(_)));
handle.freed = true;
}
extern "C" fn create_graphics_pipelines(
device: vk::VkDevice,
pipeline_cache: vk::VkPipelineCache,
create_info_count: u32,
create_infos: *const vk::VkGraphicsPipelineCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
pipelines_out: *mut vk::VkPipeline,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateGraphicsPipelines");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
fake_vulkan.check_pipeline_cache(pipeline_cache);
for i in 0..create_info_count as usize {
unsafe {
let create_info = &*create_infos.add(i);
*pipelines_out.add(i) = fake_vulkan.add_handle(
HandleType::Pipeline(
PipelineCreateInfo::Graphics(
GraphicsPipelineCreateInfo::new(create_info)
)
),
);
}
}
res
}
extern "C" fn create_compute_pipelines(
device: vk::VkDevice,
pipeline_cache: vk::VkPipelineCache,
create_info_count: u32,
create_infos: *const vk::VkComputePipelineCreateInfo,
_allocator: *const vk::VkAllocationCallbacks,
pipelines_out: *mut vk::VkPipeline,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkCreateComputePipelines");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
fake_vulkan.check_pipeline_cache(pipeline_cache);
for i in 0..create_info_count as usize {
unsafe {
*pipelines_out.add(i) = fake_vulkan.add_handle(
HandleType::Pipeline(PipelineCreateInfo::Compute(
(*create_infos.add(i)).clone()
)),
);
}
};
res
}
extern "C" fn destroy_pipeline(
device: vk::VkDevice,
pipeline: vk::VkPipeline,
_allocator: *const vk::VkAllocationCallbacks,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let handle = fake_vulkan.get_handle_mut(pipeline);
assert!(matches!(handle.data, HandleType::Pipeline { .. }));
handle.freed = true;
}
extern "C" fn flush_mapped_memory_ranges(
device: vk::VkDevice,
memory_range_count: u32,
memory_ranges: *const vk::VkMappedMemoryRange,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkFlushMappedMemoryRanges");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
let memory_ranges = unsafe {
std::slice::from_raw_parts(
memory_ranges,
memory_range_count as usize,
)
};
fake_vulkan.memory_flushes.extend_from_slice(memory_ranges);
res
}
extern "C" fn invalidate_mapped_memory_ranges(
device: vk::VkDevice,
memory_range_count: u32,
memory_ranges: *const vk::VkMappedMemoryRange,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkInvalidateMappedMemoryRanges");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
let memory_ranges = unsafe {
std::slice::from_raw_parts(
memory_ranges,
memory_range_count as usize,
)
};
fake_vulkan.memory_invalidations.extend_from_slice(memory_ranges);
res
}
extern "C" fn queue_submit(
_queue: vk::VkQueue,
submit_count: u32,
submits: *const vk::VkSubmitInfo,
fence: vk::VkFence,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_fence(fence);
let res = fake_vulkan.next_result("vkQueueSubmit");
if res != vk::VK_SUCCESS {
return res;
}
let submits = unsafe {
std::slice::from_raw_parts(
submits,
submit_count as usize,
)
};
for submit in submits.iter() {
let command_buffers = unsafe {
std::slice::from_raw_parts(
submit.pCommandBuffers,
submit.commandBufferCount as usize,
)
};
for &command_buffer in command_buffers.iter() {
let HandleType::CommandBuffer { ref mut commands, begun, .. } =
fake_vulkan.get_dispatchable_handle_mut(command_buffer).data
else {
unreachable!("bad handle type")
};
assert!(!begun);
let commands = mem::take(commands);
fake_vulkan.commands.extend(commands);
}
}
res
}
extern "C" fn allocate_descriptor_sets(
device: vk::VkDevice,
allocate_info: *const vk::VkDescriptorSetAllocateInfo,
descriptor_sets: *mut vk::VkDescriptorSet,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkAllocateDescriptorSets");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
let descriptor_pool_handle = unsafe { (*allocate_info).descriptorPool };
fake_vulkan.check_descriptor_pool(descriptor_pool_handle);
let n_buffers = unsafe { (*allocate_info).descriptorSetCount };
for i in 0..(n_buffers as usize) {
unsafe {
*descriptor_sets.add(i) = fake_vulkan.add_handle(
HandleType::DescriptorSet { bindings: HashMap::new() }
);
}
}
res
}
extern "C" fn free_descriptor_sets(
device: vk::VkDevice,
descriptor_pool: vk::VkDescriptorPool,
descriptor_set_count: u32,
descriptor_sets: *const vk::VkDescriptorSet,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkAllocateDescriptorSets");
if res != vk::VK_SUCCESS {
return res;
}
fake_vulkan.check_device(device);
fake_vulkan.check_descriptor_pool(descriptor_pool);
for i in 0..descriptor_set_count as usize {
let descriptor_set = unsafe {
*descriptor_sets.add(i)
};
let descriptor_set_handle =
fake_vulkan.get_handle_mut(descriptor_set);
match descriptor_set_handle.data {
HandleType::DescriptorSet { .. } => {
descriptor_set_handle.freed = true;
},
_ => unreachable!("mismatched handle"),
}
}
res
}
extern "C" fn update_descriptor_sets(
device: vk::VkDevice,
descriptor_write_count: u32,
descriptor_writes: *const vk::VkWriteDescriptorSet,
_descriptor_copy_count: u32,
_descriptor_copies: *const vk::VkCopyDescriptorSet,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let descriptor_writes = unsafe {
std::slice::from_raw_parts(
descriptor_writes,
descriptor_write_count as usize,
)
};
for write in descriptor_writes.iter() {
assert_eq!(write.sType, vk::VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET);
assert_eq!(write.descriptorCount, 1);
let HandleType::DescriptorSet { ref mut bindings } =
fake_vulkan.get_handle_mut(write.dstSet).data
else { unreachable!("mismatched handle type"); };
bindings.insert(
write.dstBinding,
Binding {
descriptor_type: write.descriptorType,
info: unsafe { &*write.pBufferInfo }.clone(),
},
);
}
}
extern "C" fn begin_command_buffer(
command_buffer: vk::VkCommandBuffer,
_begin_info: *const vk::VkCommandBufferBeginInfo,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkBeginCommandBuffer");
if res != vk::VK_SUCCESS {
return res;
}
let HandleType::CommandBuffer { ref mut begun, .. } =
fake_vulkan.get_dispatchable_handle_mut(command_buffer).data
else { unreachable!("mismatched handle"); };
assert!(!*begun);
*begun = true;
res
}
extern "C" fn end_command_buffer(
command_buffer: vk::VkCommandBuffer,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
let res = fake_vulkan.next_result("vkEndCommandBuffer");
if res != vk::VK_SUCCESS {
return res;
}
let HandleType::CommandBuffer { ref mut begun, .. } =
fake_vulkan.get_dispatchable_handle_mut(command_buffer).data
else { unreachable!("mismatched handle"); };
assert!(*begun);
*begun = false;
res
}
fn add_command(
&mut self,
command_buffer: vk::VkCommandBuffer,
command: Command,
) {
let HandleType::CommandBuffer { ref mut commands, begun, .. } =
self.get_dispatchable_handle_mut(command_buffer).data
else { unreachable!("mismatched handle"); };
assert!(begun);
commands.push(command);
}
extern "C" fn begin_render_pass(
command_buffer: vk::VkCommandBuffer,
render_pass_begin: *const vk::VkRenderPassBeginInfo,
_contents: vk::VkSubpassContents,
) {
let render_pass_begin = unsafe { &*render_pass_begin };
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_render_pass(render_pass_begin.renderPass);
fake_vulkan.check_framebuffer(render_pass_begin.framebuffer);
fake_vulkan.add_command(
command_buffer,
Command::BeginRenderPass(render_pass_begin.clone()),
);
}
extern "C" fn end_render_pass(
command_buffer: vk::VkCommandBuffer,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.add_command(command_buffer, Command::EndRenderPass);
}
extern "C" fn bind_pipeline(
command_buffer: vk::VkCommandBuffer,
pipeline_bind_point: vk::VkPipelineBindPoint,
pipeline: vk::VkPipeline,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_pipeline(pipeline);
fake_vulkan.add_command(
command_buffer,
Command::BindPipeline {
bind_point: pipeline_bind_point,
pipeline,
},
);
}
extern "C" fn bind_vertex_buffers(
command_buffer: vk::VkCommandBuffer,
first_binding: u32,
binding_count: u32,
buffers: *const vk::VkBuffer,
offsets: *const vk::VkDeviceSize,
) {
let fake_vulkan = FakeVulkan::current();
let buffers = vec_from_raw_parts(
buffers,
binding_count as usize,
);
for &buffer in buffers.iter() {
fake_vulkan.check_buffer(buffer);
}
let offsets = vec_from_raw_parts(
offsets,
binding_count as usize,
);
fake_vulkan.add_command(
command_buffer,
Command::BindVertexBuffers {
first_binding,
buffers,
offsets,
},
);
}
extern "C" fn bind_index_buffer(
command_buffer: vk::VkCommandBuffer,
buffer: vk::VkBuffer,
offset: vk::VkDeviceSize,
index_type: vk::VkIndexType,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.add_command(
command_buffer,
Command::BindIndexBuffer {
buffer,
offset,
index_type,
},
);
}
extern "C" fn draw(
command_buffer: vk::VkCommandBuffer,
vertex_count: u32,
instance_count: u32,
first_vertex: u32,
first_instance: u32,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.add_command(
command_buffer,
Command::Draw {
vertex_count,
instance_count,
first_vertex,
first_instance,
},
);
}
extern "C" fn draw_indexed(
command_buffer: vk::VkCommandBuffer,
index_count: u32,
instance_count: u32,
first_index: u32,
vertex_offset: i32,
first_instance: u32,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.add_command(
command_buffer,
Command::DrawIndexed {
index_count,
instance_count,
first_index,
vertex_offset,
first_instance,
},
);
}
extern "C" fn dispatch(
command_buffer: vk::VkCommandBuffer,
x: u32,
y: u32,
z: u32
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.add_command(command_buffer, Command::Dispatch { x, y, z });
}
extern "C" fn clear_attachments(
command_buffer: vk::VkCommandBuffer,
attachment_count: u32,
attachments: *const vk::VkClearAttachment,
rect_count: u32,
rects: *const vk::VkClearRect,
) {
let fake_vulkan = FakeVulkan::current();
let attachments = unsafe {
std::slice::from_raw_parts(
attachments,
attachment_count as usize,
)
}.iter().map(|attachment| {
if attachment.aspectMask == vk::VK_IMAGE_ASPECT_COLOR_BIT {
ClearAttachment::Color {
attachment: attachment.colorAttachment,
value: unsafe {
attachment.clearValue.color.float32.clone()
},
}
} else {
assert!(
attachment.aspectMask
& (vk::VK_IMAGE_ASPECT_DEPTH_BIT
| vk::VK_IMAGE_ASPECT_STENCIL_BIT)
!= 0
);
assert_eq!(
attachment.aspectMask
& !(vk::VK_IMAGE_ASPECT_DEPTH_BIT
| vk::VK_IMAGE_ASPECT_STENCIL_BIT),
0,
);
ClearAttachment::DepthStencil {
aspect_mask: attachment.aspectMask,
value: unsafe {
attachment.clearValue.depthStencil
},
}
}
}).collect::<Vec<ClearAttachment>>();
let rects = vec_from_raw_parts(
rects,
rect_count as usize,
);
fake_vulkan.add_command(
command_buffer,
Command::ClearAttachments {
attachments,
rects,
}
);
}
extern "C" fn pipeline_barrier(
command_buffer: vk::VkCommandBuffer,
src_stage_mask: vk::VkPipelineStageFlags,
dst_stage_mask: vk::VkPipelineStageFlags,
dependency_flags: vk::VkDependencyFlags,
memory_barrier_count: u32,
memory_barriers: *const vk::VkMemoryBarrier,
buffer_memory_barrier_count: u32,
buffer_memory_barriers: *const vk::VkBufferMemoryBarrier,
image_memory_barrier_count: u32,
image_memory_barriers: *const vk::VkImageMemoryBarrier,
) {
let fake_vulkan = FakeVulkan::current();
let memory_barriers = vec_from_raw_parts(
memory_barriers,
memory_barrier_count as usize,
);
let buffer_memory_barriers = vec_from_raw_parts(
buffer_memory_barriers,
buffer_memory_barrier_count as usize,
);
for barrier in buffer_memory_barriers.iter() {
fake_vulkan.check_buffer(barrier.buffer);
}
let image_memory_barriers = vec_from_raw_parts(
image_memory_barriers,
image_memory_barrier_count as usize,
);
for barrier in image_memory_barriers.iter() {
fake_vulkan.check_image(barrier.image);
}
fake_vulkan.add_command(
command_buffer,
Command::PipelineBarrier {
src_stage_mask,
dst_stage_mask,
dependency_flags,
memory_barriers,
buffer_memory_barriers,
image_memory_barriers,
},
);
}
extern "C" fn copy_image_to_buffer(
command_buffer: vk::VkCommandBuffer,
src_image: vk::VkImage,
src_image_layout: vk::VkImageLayout,
dst_buffer: vk::VkBuffer,
region_count: u32,
regions: *const vk::VkBufferImageCopy,
) {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_image(src_image);
fake_vulkan.check_buffer(dst_buffer);
let regions = vec_from_raw_parts(
regions,
region_count as usize,
);
fake_vulkan.add_command(
command_buffer,
Command::CopyImageToBuffer {
src_image,
src_image_layout,
dst_buffer,
regions,
},
);
}
extern "C" fn push_constants(
command_buffer: vk::VkCommandBuffer,
layout: vk::VkPipelineLayout,
stage_flags: vk::VkShaderStageFlags,
offset: u32,
size: u32,
values: *const c_void,
) {
let fake_vulkan = FakeVulkan::current();
let values = vec_from_raw_parts(
values as *const u8,
size as usize,
);
fake_vulkan.add_command(
command_buffer,
Command::PushConstants {
layout,
stage_flags,
offset,
values,
},
);
}
extern "C" fn bind_descriptor_sets(
command_buffer: vk::VkCommandBuffer,
pipeline_bind_point: vk::VkPipelineBindPoint,
layout: vk::VkPipelineLayout,
first_set: u32,
descriptor_set_count: u32,
descriptor_sets: *const vk::VkDescriptorSet,
_dynamic_offset_count: u32,
_dynamic_offsets: *const u32,
) {
let fake_vulkan = FakeVulkan::current();
let descriptor_sets = vec_from_raw_parts(
descriptor_sets,
descriptor_set_count as usize,
);
fake_vulkan.add_command(
command_buffer,
Command::BindDescriptorSets {
pipeline_bind_point,
layout,
first_set,
descriptor_sets,
},
);
}
extern "C" fn reset_fences(
device: vk::VkDevice,
fence_count: u32,
fences: *const vk::VkFence,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let res = fake_vulkan.next_result("vkResetFences");
if res != vk::VK_SUCCESS {
return res;
}
let fences = unsafe {
std::slice::from_raw_parts(
fences,
fence_count as usize,
)
};
for &fence in fences.iter() {
let HandleType::Fence { ref mut reset_count, .. } =
fake_vulkan.get_handle_mut(fence).data
else { unreachable!("bad handle"); };
*reset_count += 1;
}
res
}
extern "C" fn wait_for_fences(
device: vk::VkDevice,
fence_count: u32,
fences: *const vk::VkFence,
_wait_all: vk::VkBool32,
_timeout: u64,
) -> vk::VkResult {
let fake_vulkan = FakeVulkan::current();
fake_vulkan.check_device(device);
let res = fake_vulkan.next_result("vkWaitForFences");
if res != vk::VK_SUCCESS {
return res;
}
let fences = unsafe {
std::slice::from_raw_parts(
fences,
fence_count as usize,
)
};
for &fence in fences.iter() {
let HandleType::Fence { ref mut wait_count, .. } =
fake_vulkan.get_handle_mut(fence).data
else { unreachable!("bad handle"); };
*wait_count += 1;
}
res
}
}
impl Drop for FakeVulkan {
fn drop(&mut self) {
if !std::thread::panicking() {
let old_value = CURRENT_FAKE_VULKAN.with(|f| f.replace(None));
// There should only be one FakeVulkan at a time so the
// one we just dropped should be the one that was set for
// the current thread.
assert_eq!(old_value.unwrap(), self as *mut FakeVulkan);
}
}
}
```