#![allow(
dead_code,
reason = "one module, two examples, each using a subset of it"
)]
use std::ffi::CString;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::sync::Arc;
use std::time::{Duration, Instant};
use anyhow::{Context as _, Result, anyhow};
use ash::vk;
use gpui::{DmaBufHandle, GpuCanvasContext, Point, SurfaceFormatKind};
use gpui_interop::{Fence, GpuDevice, SharedBuffer, Tiling, attach};
use gpui_wgpu::WgpuRenderer;
const SLOTS: usize = 3;
const RELEASE_TIMEOUT: Duration = Duration::from_millis(250);
pub const OPAQUE: [f32; 4] = [0.055, 0.06, 0.08, 1.0];
pub const TRANSPARENT: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
const DEFAULT_DISTANCE: f32 = 5.0;
const MIN_DISTANCE: f32 = 2.5;
const MAX_DISTANCE: f32 = 12.0;
const DEPTH_FORMAT: vk::Format = vk::Format::D32_SFLOAT;
const SHADER: &str = r#"
struct Uniforms {
mvp: mat4x4<f32>,
};
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) normal: vec3<f32>,
};
@vertex
fn vs_main(@location(0) position: vec3<f32>, @location(1) normal: vec3<f32>) -> VertexOutput {
var output: VertexOutput;
output.clip_position = uniforms.mvp * vec4<f32>(position, 1.0);
output.normal = normal;
return output;
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
let n = normalize(input.normal);
let light = normalize(vec3<f32>(0.35, 0.75, 0.55));
let shade = 0.22 + 0.78 * max(dot(n, light), 0.0);
let tint = vec3<f32>(0.42, 0.48, 0.62) + 0.5 * abs(n);
return vec4<f32>(tint * shade, 1.0);
}
"#;
pub struct Cube {
slots: Vec<Slot>,
graphics: Graphics,
device: GpuDevice,
gpu: String,
background: [f32; 4],
distance: f32,
yaw: f32,
pitch: f32,
last: Option<DmaBufHandle>,
rendered: u64,
dropped: u64,
}
impl Cube {
pub fn build(gpu: &mut GpuCanvasContext, size: u32, background: [f32; 4]) -> Result<Self> {
let device = attach(gpu.try_device::<WgpuRenderer>())
.ok()
.and_then(|interop| interop.adapter().gpu_device())
.context(
"a rendered, driver-tiled buffer needs the renderer's own GPU, and this window's \
renderer lent no dma-buf device (is it wgpu on Vulkan?)",
)?;
let name = device.gpu_id().name;
let graphics = Graphics::build(&device, size)?;
let sets = graphics.allocate_sets(&device, SLOTS)?;
let commands = graphics.allocate_commands(&device, SLOTS)?;
let slots = (0..SLOTS)
.map(|index| Slot::new(&device, &graphics, size, sets[index], commands[index]))
.collect::<Result<Vec<_>>>()?;
Ok(Self {
slots,
graphics,
device,
gpu: name,
background,
distance: DEFAULT_DISTANCE,
yaw: 0.0,
pitch: 0.0,
last: None,
rendered: 0,
dropped: 0,
})
}
pub fn turn(&mut self, yaw: f32, pitch: f32) {
self.yaw += yaw;
self.pitch += pitch;
}
pub fn reset(&mut self) {
self.yaw = 0.0;
self.pitch = 0.0;
self.distance = DEFAULT_DISTANCE;
}
pub fn zoom(&mut self, factor: f32) {
self.distance = (self.distance * factor).clamp(MIN_DISTANCE, MAX_DISTANCE);
}
pub fn gpu(&self) -> &str {
&self.gpu
}
pub fn rendered(&self) -> u64 {
self.rendered
}
pub fn dropped(&self) -> u64 {
self.dropped
}
pub fn covers(&self, uv: Point<f32>) -> bool {
let mvp = self.mvp();
let mut corners = [Point::default(); 8];
let mut count = 0;
for x in [-1.0f32, 1.0] {
for y in [-1.0f32, 1.0] {
for z in [-1.0f32, 1.0] {
let clip = transform(&mvp, [x, y, z, 1.0]);
if clip[3] <= 0.0 {
continue;
}
corners[count] = Point {
x: (clip[0] / clip[3] + 1.0) * 0.5,
y: (1.0 - clip[1] / clip[3]) * 0.5,
};
count += 1;
}
}
}
point_in_convex_hull(&convex_hull(&corners[..count]), uv)
}
pub fn frame(&mut self) -> Option<DmaBufHandle> {
let Some(index) = self.free_slot() else {
self.dropped += 1;
return self.last.clone();
};
let mvp = self.mvp();
match self.render_into(index, &mvp) {
Ok(handle) => {
self.rendered += 1;
self.last = Some(handle.clone());
Some(handle)
}
Err(error) => {
log::error!("interop_cube: rendering frame failed: {error:#}");
self.last.clone()
}
}
}
fn free_slot(&self) -> Option<usize> {
(0..self.slots.len()).find(|&index| self.slots[index].released())
}
fn render_into(&mut self, index: usize, mvp: &[f32; 16]) -> Result<DmaBufHandle> {
let slot = &mut self.slots[index];
slot.fence = None;
slot.release = None;
let device = &self.device;
write_uniform(device, slot.uniform_memory, mvp)?;
let fence = Fence::new(device)?;
submit_cube(device, &self.graphics, slot, self.background, fence.fence())?;
let acquire = fence.sync_file()?;
let release =
release_descriptor().context("making the release descriptor the compositor signals")?;
slot.fence = Some(fence);
slot.release = Some(release.clone());
slot.handed = Some(Instant::now());
let mut handle = slot.buffer.handle();
handle.acquire_fence = Some(Arc::new(acquire));
handle.release = Some(release);
Ok(handle)
}
fn mvp(&self) -> [f32; 16] {
let projection = perspective_rh(45f32.to_radians(), 1.0, 0.1, 100.0);
let view = look_at_rh([0.0, 0.0, self.distance], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let model = mul(&rotation_y(self.yaw), &rotation_x(self.pitch));
mul(&projection, &mul(&view, &model))
}
}
struct Slot {
buffer: SharedBuffer,
device: ash::Device,
view: vk::ImageView,
depth: vk::Image,
depth_memory: vk::DeviceMemory,
depth_view: vk::ImageView,
framebuffer: vk::Framebuffer,
uniform_buffer: vk::Buffer,
uniform_memory: vk::DeviceMemory,
descriptor_set: vk::DescriptorSet,
command_buffer: vk::CommandBuffer,
size: u32,
fence: Option<Fence>,
release: Option<Arc<OwnedFd>>,
handed: Option<Instant>,
}
impl Slot {
fn new(
device: &GpuDevice,
graphics: &Graphics,
size: u32,
descriptor_set: vk::DescriptorSet,
command_buffer: vk::CommandBuffer,
) -> Result<Self> {
let buffer = SharedBuffer::render_target(
device,
size,
size,
SurfaceFormatKind::bgra8(),
Tiling::Tiled,
)
.context("allocating a shareable render target")?;
let color_format = vk::Format::B8G8R8A8_UNORM;
let view = create_view(
device,
buffer.image().context("a render target has an image")?,
color_format,
vk::ImageAspectFlags::COLOR,
)?;
let (depth, depth_memory) = create_image(
device,
DEPTH_FORMAT,
size,
size,
vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT,
)?;
let depth_view = create_view(device, depth, DEPTH_FORMAT, vk::ImageAspectFlags::DEPTH)?;
let attachments = [view, depth_view];
let framebuffer = unsafe {
device.device().create_framebuffer(
&vk::FramebufferCreateInfo::default()
.render_pass(graphics.render_pass)
.attachments(&attachments)
.width(size)
.height(size)
.layers(1),
None,
)
}
.context("creating the framebuffer")?;
let (uniform_buffer, uniform_memory) = create_buffer(
device,
UNIFORM_SIZE,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
write_descriptor(device, descriptor_set, uniform_buffer)?;
Ok(Self {
buffer,
device: device.device().clone(),
view,
depth,
depth_memory,
depth_view,
framebuffer,
uniform_buffer,
uniform_memory,
descriptor_set,
command_buffer,
size,
fence: None,
release: None,
handed: None,
})
}
fn released(&self) -> bool {
match &self.release {
None => true,
Some(release) => {
release_signalled(release)
|| self
.handed
.is_some_and(|handed| handed.elapsed() >= RELEASE_TIMEOUT)
}
}
}
}
impl Drop for Slot {
fn drop(&mut self) {
unsafe {
self.device.destroy_framebuffer(self.framebuffer, None);
self.device.destroy_image_view(self.depth_view, None);
self.device.destroy_image(self.depth, None);
self.device.free_memory(self.depth_memory, None);
self.device.destroy_image_view(self.view, None);
self.device.destroy_buffer(self.uniform_buffer, None);
self.device.free_memory(self.uniform_memory, None);
}
}
}
struct Graphics {
device: ash::Device,
command_pool: vk::CommandPool,
descriptor_set_layout: vk::DescriptorSetLayout,
descriptor_pool: vk::DescriptorPool,
pipeline_layout: vk::PipelineLayout,
render_pass: vk::RenderPass,
pipeline: vk::Pipeline,
vertex_buffer: vk::Buffer,
vertex_memory: vk::DeviceMemory,
index_buffer: vk::Buffer,
index_memory: vk::DeviceMemory,
index_count: u32,
}
impl Graphics {
fn build(device: &GpuDevice, size: u32) -> Result<Self> {
let raw = device.device();
let pool_info = vk::CommandPoolCreateInfo::default()
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
.queue_family_index(device.queue_family_index());
let command_pool = unsafe { raw.create_command_pool(&pool_info, None) }
.context("creating the producer's command pool")?;
let bindings = [vk::DescriptorSetLayoutBinding::default()
.binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::VERTEX)];
let descriptor_set_layout = unsafe {
raw.create_descriptor_set_layout(
&vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings),
None,
)
}
.context("creating the uniform's set layout")?;
let pool_sizes = [vk::DescriptorPoolSize::default()
.ty(vk::DescriptorType::UNIFORM_BUFFER)
.descriptor_count(SLOTS as u32)];
let descriptor_pool = unsafe {
raw.create_descriptor_pool(
&vk::DescriptorPoolCreateInfo::default()
.pool_sizes(&pool_sizes)
.max_sets(SLOTS as u32),
None,
)
}
.context("creating the uniform's descriptor pool")?;
let set_layouts = [descriptor_set_layout];
let pipeline_layout = unsafe {
raw.create_pipeline_layout(
&vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts),
None,
)
}
.context("creating the pipeline layout")?;
let render_pass = create_render_pass(device)?;
let pipeline = create_pipeline(device, render_pass, pipeline_layout, size)?;
let mesh = Mesh::new(device)?;
Ok(Self {
device: raw.clone(),
command_pool,
descriptor_set_layout,
descriptor_pool,
pipeline_layout,
render_pass,
pipeline,
vertex_buffer: mesh.vertex_buffer,
vertex_memory: mesh.vertex_memory,
index_buffer: mesh.index_buffer,
index_memory: mesh.index_memory,
index_count: mesh.index_count,
})
}
fn allocate_sets(&self, device: &GpuDevice, count: usize) -> Result<Vec<vk::DescriptorSet>> {
let layouts = vec![self.descriptor_set_layout; count];
let sets = unsafe {
device.device().allocate_descriptor_sets(
&vk::DescriptorSetAllocateInfo::default()
.descriptor_pool(self.descriptor_pool)
.set_layouts(&layouts),
)
}
.context("allocating the uniform descriptor sets")?;
Ok(sets)
}
fn allocate_commands(
&self,
device: &GpuDevice,
count: usize,
) -> Result<Vec<vk::CommandBuffer>> {
let commands = unsafe {
device.device().allocate_command_buffers(
&vk::CommandBufferAllocateInfo::default()
.command_pool(self.command_pool)
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(count as u32),
)
}
.context("allocating the frame command buffers")?;
Ok(commands)
}
}
impl Drop for Graphics {
fn drop(&mut self) {
unsafe {
self.device.destroy_pipeline(self.pipeline, None);
self.device
.destroy_pipeline_layout(self.pipeline_layout, None);
self.device.destroy_render_pass(self.render_pass, None);
self.device
.destroy_descriptor_pool(self.descriptor_pool, None);
self.device
.destroy_descriptor_set_layout(self.descriptor_set_layout, None);
self.device.destroy_buffer(self.vertex_buffer, None);
self.device.free_memory(self.vertex_memory, None);
self.device.destroy_buffer(self.index_buffer, None);
self.device.free_memory(self.index_memory, None);
self.device.destroy_command_pool(self.command_pool, None);
}
}
}
const UNIFORM_SIZE: u64 = 64;
struct Mesh {
vertex_buffer: vk::Buffer,
vertex_memory: vk::DeviceMemory,
index_buffer: vk::Buffer,
index_memory: vk::DeviceMemory,
index_count: u32,
}
impl Mesh {
fn new(device: &GpuDevice) -> Result<Self> {
#[repr(C)]
#[derive(Clone, Copy)]
struct Vertex {
position: [f32; 3],
normal: [f32; 3],
}
let faces: [([f32; 3], [[f32; 3]; 4]); 6] = [
(
[1.0, 0.0, 0.0],
[[1., -1., -1.], [1., -1., 1.], [1., 1., 1.], [1., 1., -1.]],
),
(
[-1.0, 0.0, 0.0],
[
[-1., -1., 1.],
[-1., -1., -1.],
[-1., 1., -1.],
[-1., 1., 1.],
],
),
(
[0.0, 1.0, 0.0],
[[-1., 1., -1.], [1., 1., -1.], [1., 1., 1.], [-1., 1., 1.]],
),
(
[0.0, -1.0, 0.0],
[
[-1., -1., 1.],
[1., -1., 1.],
[1., -1., -1.],
[-1., -1., -1.],
],
),
(
[0.0, 0.0, 1.0],
[[-1., -1., 1.], [1., -1., 1.], [1., 1., 1.], [-1., 1., 1.]],
),
(
[0.0, 0.0, -1.0],
[
[1., -1., -1.],
[-1., -1., -1.],
[-1., 1., -1.],
[1., 1., -1.],
],
),
];
let mut vertices = Vec::with_capacity(24);
let mut indices = Vec::with_capacity(36);
for (normal, corners) in faces {
let base = vertices.len() as u16;
for corner in corners {
vertices.push(Vertex {
position: corner,
normal,
});
}
indices.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
}
let flags = vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT;
let (vertex_buffer, vertex_memory) = create_buffer(
device,
std::mem::size_of_val(vertices.as_slice()) as u64,
vk::BufferUsageFlags::VERTEX_BUFFER,
flags,
)?;
upload(device, vertex_memory, &vertices)?;
let (index_buffer, index_memory) = create_buffer(
device,
std::mem::size_of_val(indices.as_slice()) as u64,
vk::BufferUsageFlags::INDEX_BUFFER,
flags,
)?;
upload(device, index_memory, &indices)?;
Ok(Self {
vertex_buffer,
vertex_memory,
index_buffer,
index_memory,
index_count: indices.len() as u32,
})
}
}
fn submit_cube(
device: &GpuDevice,
graphics: &Graphics,
slot: &mut Slot,
background: [f32; 4],
fence: vk::Fence,
) -> Result<()> {
let raw = device.device();
unsafe {
raw.reset_command_buffer(slot.command_buffer, vk::CommandBufferResetFlags::empty())
.context("resetting the frame command buffer")?;
raw.begin_command_buffer(slot.command_buffer, &vk::CommandBufferBeginInfo::default())
.context("beginning the frame command buffer")?;
let clears = [
vk::ClearValue {
color: vk::ClearColorValue {
float32: background,
},
},
vk::ClearValue {
depth_stencil: vk::ClearDepthStencilValue {
depth: 1.0,
stencil: 0,
},
},
];
let begin = vk::RenderPassBeginInfo::default()
.render_pass(graphics.render_pass)
.framebuffer(slot.framebuffer)
.render_area(vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: vk::Extent2D {
width: slot.size,
height: slot.size,
},
})
.clear_values(&clears);
raw.cmd_begin_render_pass(slot.command_buffer, &begin, vk::SubpassContents::INLINE);
raw.cmd_bind_pipeline(
slot.command_buffer,
vk::PipelineBindPoint::GRAPHICS,
graphics.pipeline,
);
raw.cmd_bind_descriptor_sets(
slot.command_buffer,
vk::PipelineBindPoint::GRAPHICS,
graphics.pipeline_layout,
0,
&[slot.descriptor_set],
&[],
);
raw.cmd_bind_vertex_buffers(slot.command_buffer, 0, &[graphics.vertex_buffer], &[0]);
raw.cmd_bind_index_buffer(
slot.command_buffer,
graphics.index_buffer,
0,
vk::IndexType::UINT16,
);
raw.cmd_draw_indexed(slot.command_buffer, graphics.index_count, 1, 0, 0, 0);
raw.cmd_end_render_pass(slot.command_buffer);
raw.end_command_buffer(slot.command_buffer)
.context("ending the frame command buffer")?;
let buffers = [slot.command_buffer];
let submit = vk::SubmitInfo::default().command_buffers(&buffers);
raw.queue_submit(device.queue(), &[submit], fence)
.context("submitting the cube's frame")?;
}
Ok(())
}
fn create_render_pass(device: &GpuDevice) -> Result<vk::RenderPass> {
let attachments = [
vk::AttachmentDescription::default()
.format(vk::Format::B8G8R8A8_UNORM)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::CLEAR)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::GENERAL),
vk::AttachmentDescription::default()
.format(DEPTH_FORMAT)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::CLEAR)
.store_op(vk::AttachmentStoreOp::DONT_CARE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL),
];
let color = vk::AttachmentReference::default()
.attachment(0)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
let depth = vk::AttachmentReference::default()
.attachment(1)
.layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
let subpasses = [vk::SubpassDescription::default()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(std::slice::from_ref(&color))
.depth_stencil_attachment(&depth)];
unsafe {
device.device().create_render_pass(
&vk::RenderPassCreateInfo::default()
.attachments(&attachments)
.subpasses(&subpasses),
None,
)
}
.context("creating the cube's render pass")
}
fn create_pipeline(
device: &GpuDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
size: u32,
) -> Result<vk::Pipeline> {
let raw = device.device();
let vertex = shader_module(device, SHADER, naga::ShaderStage::Vertex, "vs_main")?;
let fragment = shader_module(device, SHADER, naga::ShaderStage::Fragment, "fs_main")?;
let vs_name = CString::new("vs_main").expect("a literal without a NUL");
let fs_name = CString::new("fs_main").expect("a literal without a NUL");
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vertex)
.name(&vs_name),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(fragment)
.name(&fs_name),
];
let bindings = [vk::VertexInputBindingDescription::default()
.binding(0)
.stride(std::mem::size_of::<[f32; 6]>() as u32)
.input_rate(vk::VertexInputRate::VERTEX)];
let attributes = [
vk::VertexInputAttributeDescription::default()
.location(0)
.binding(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0),
vk::VertexInputAttributeDescription::default()
.location(1)
.binding(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(std::mem::size_of::<[f32; 3]>() as u32),
];
let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(&bindings)
.vertex_attribute_descriptions(&attributes);
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
let viewport = [vk::Viewport {
x: 0.0,
y: 0.0,
width: size as f32,
height: size as f32,
min_depth: 0.0,
max_depth: 1.0,
}];
let scissor = [vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: vk::Extent2D {
width: size,
height: size,
},
}];
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewports(&viewport)
.scissors(&scissor);
let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
.polygon_mode(vk::PolygonMode::FILL)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.line_width(1.0);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
.depth_test_enable(true)
.depth_write_enable(true)
.depth_compare_op(vk::CompareOp::LESS);
let blend_attachments = [vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA)];
let color_blend =
vk::PipelineColorBlendStateCreateInfo::default().attachments(&blend_attachments);
let info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vertex_input)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&rasterization)
.multisample_state(&multisample)
.depth_stencil_state(&depth_stencil)
.color_blend_state(&color_blend)
.layout(layout)
.render_pass(render_pass)
.subpass(0);
let pipeline =
unsafe { raw.create_graphics_pipelines(vk::PipelineCache::null(), &[info], None) }
.map_err(|(_, error)| anyhow!("creating the cube's pipeline: {error:?}"))?;
let pipeline = pipeline[0];
unsafe {
raw.destroy_shader_module(vertex, None);
raw.destroy_shader_module(fragment, None);
}
Ok(pipeline)
}
fn shader_module(
device: &GpuDevice,
wgsl: &str,
stage: naga::ShaderStage,
entry: &str,
) -> Result<vk::ShaderModule> {
let module = naga::front::wgsl::parse_str(wgsl)
.map_err(|error| anyhow!("parsing the cube's WGSL: {}", error.emit_to_string(wgsl)))?;
let info = naga::valid::Validator::new(
naga::valid::ValidationFlags::all(),
naga::valid::Capabilities::all(),
)
.validate(&module)
.map_err(|error| anyhow!("validating the cube's WGSL: {error:?}"))?;
let pipeline = naga::back::spv::PipelineOptions {
shader_stage: stage,
entry_point: entry.to_owned(),
};
let words = naga::back::spv::write_vec(
&module,
&info,
&naga::back::spv::Options::default(),
Some(&pipeline),
)
.map_err(|error| anyhow!("writing the cube's SPIR-V: {error:?}"))?;
unsafe {
device
.device()
.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&words), None)
}
.map_err(|error| anyhow!("creating the {entry} shader module: {error:?}"))
}
fn create_buffer(
device: &GpuDevice,
size: u64,
usage: vk::BufferUsageFlags,
flags: vk::MemoryPropertyFlags,
) -> Result<(vk::Buffer, vk::DeviceMemory)> {
let raw = device.device();
unsafe {
let buffer = raw
.create_buffer(
&vk::BufferCreateInfo::default()
.size(size)
.usage(usage)
.sharing_mode(vk::SharingMode::EXCLUSIVE),
None,
)
.context("creating a buffer")?;
let requirements = raw.get_buffer_memory_requirements(buffer);
let memory = match device.memory_type(requirements.memory_type_bits, flags) {
Ok(index) => raw
.allocate_memory(
&vk::MemoryAllocateInfo::default()
.allocation_size(requirements.size)
.memory_type_index(index),
None,
)
.context("allocating the buffer's memory")?,
Err(error) => {
raw.destroy_buffer(buffer, None);
return Err(error);
}
};
if let Err(error) = raw.bind_buffer_memory(buffer, memory, 0) {
raw.free_memory(memory, None);
raw.destroy_buffer(buffer, None);
return Err(anyhow!("binding the buffer's memory: {error:?}"));
}
Ok((buffer, memory))
}
}
fn upload<T: Copy>(device: &GpuDevice, memory: vk::DeviceMemory, data: &[T]) -> Result<()> {
let bytes = std::mem::size_of_val(data);
unsafe {
let mapped = device
.device()
.map_memory(memory, 0, bytes as u64, vk::MemoryMapFlags::empty())
.context("mapping a host-visible buffer")? as *mut u8;
std::ptr::copy_nonoverlapping(data.as_ptr().cast::<u8>(), mapped, bytes);
device.device().unmap_memory(memory);
}
Ok(())
}
fn write_uniform(device: &GpuDevice, memory: vk::DeviceMemory, mvp: &[f32; 16]) -> Result<()> {
upload(device, memory, mvp.as_slice())
}
fn write_descriptor(device: &GpuDevice, set: vk::DescriptorSet, buffer: vk::Buffer) -> Result<()> {
let buffer_info = [vk::DescriptorBufferInfo::default()
.buffer(buffer)
.offset(0)
.range(UNIFORM_SIZE)];
let writes = [vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(&buffer_info)];
unsafe { device.device().update_descriptor_sets(&writes, &[]) };
Ok(())
}
fn create_image(
device: &GpuDevice,
format: vk::Format,
width: u32,
height: u32,
usage: vk::ImageUsageFlags,
) -> Result<(vk::Image, vk::DeviceMemory)> {
let raw = device.device();
unsafe {
let image = raw
.create_image(
&vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(format)
.extent(vk::Extent3D {
width,
height,
depth: 1,
})
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(usage)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED),
None,
)
.context("creating an image")?;
let requirements = raw.get_image_memory_requirements(image);
let memory = match device.memory_type(
requirements.memory_type_bits,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
) {
Ok(index) => raw
.allocate_memory(
&vk::MemoryAllocateInfo::default()
.allocation_size(requirements.size)
.memory_type_index(index),
None,
)
.context("allocating the image's memory")?,
Err(error) => {
raw.destroy_image(image, None);
return Err(error);
}
};
if let Err(error) = raw.bind_image_memory(image, memory, 0) {
raw.free_memory(memory, None);
raw.destroy_image(image, None);
return Err(anyhow!("binding the image's memory: {error:?}"));
}
Ok((image, memory))
}
}
fn create_view(
device: &GpuDevice,
image: vk::Image,
format: vk::Format,
aspect: vk::ImageAspectFlags,
) -> Result<vk::ImageView> {
unsafe {
device.device().create_image_view(
&vk::ImageViewCreateInfo::default()
.image(image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(format)
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: aspect,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
}),
None,
)
}
.context("creating an image view")
}
fn release_descriptor() -> Result<Arc<OwnedFd>> {
let fd = unsafe { libc::eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK) };
if fd < 0 {
return Err(anyhow!(
"creating a release descriptor: {}",
std::io::Error::last_os_error()
));
}
Ok(Arc::new(unsafe { OwnedFd::from_raw_fd(fd) }))
}
fn release_signalled(release: &OwnedFd) -> bool {
let mut descriptor = libc::pollfd {
fd: release.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
(unsafe { libc::poll(&mut descriptor, 1, 0) }) > 0
}
fn perspective_rh(fov_y: f32, aspect: f32, near: f32, far: f32) -> [f32; 16] {
let h = 1.0 / (0.5 * fov_y).tan();
let w = h / aspect;
let r = far / (near - far);
[
w,
0.0,
0.0,
0.0, 0.0,
h,
0.0,
0.0, 0.0,
0.0,
r,
-1.0, 0.0,
0.0,
r * near,
0.0,
]
}
fn look_at_rh(eye: [f32; 3], center: [f32; 3], up: [f32; 3]) -> [f32; 16] {
let f = normalize(sub(center, eye));
let s = normalize(cross(f, up));
let u = cross(s, f);
[
s[0],
u[0],
-f[0],
0.0, s[1],
u[1],
-f[1],
0.0, s[2],
u[2],
-f[2],
0.0, -dot(s, eye),
-dot(u, eye),
dot(f, eye),
1.0,
]
}
fn rotation_y(angle: f32) -> [f32; 16] {
let (s, c) = angle.sin_cos();
[
c, 0.0, -s, 0.0, 0.0, 1.0, 0.0, 0.0, s, 0.0, c, 0.0, 0.0, 0.0, 0.0, 1.0,
]
}
fn rotation_x(angle: f32) -> [f32; 16] {
let (s, c) = angle.sin_cos();
[
1.0, 0.0, 0.0, 0.0, 0.0, c, s, 0.0, 0.0, -s, c, 0.0, 0.0, 0.0, 0.0, 1.0,
]
}
fn mul(a: &[f32; 16], b: &[f32; 16]) -> [f32; 16] {
let mut out = [0.0f32; 16];
for column in 0..4 {
for row in 0..4 {
let mut sum = 0.0;
for k in 0..4 {
sum += a[k * 4 + row] * b[column * 4 + k];
}
out[column * 4 + row] = sum;
}
}
out
}
fn transform(m: &[f32; 16], v: [f32; 4]) -> [f32; 4] {
let mut out = [0.0f32; 4];
for row in 0..4 {
out[row] = (0..4).map(|k| m[k * 4 + row] * v[k]).sum();
}
out
}
fn convex_hull(points: &[Point<f32>]) -> Vec<Point<f32>> {
let mut points = points.to_vec();
points.sort_by(|a, b| a.x.total_cmp(&b.x).then(a.y.total_cmp(&b.y)));
points.dedup();
if points.len() < 3 {
return points;
}
let turn = |o: &Point<f32>, a: &Point<f32>, b: &Point<f32>| {
(a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x)
};
let mut hull: Vec<Point<f32>> = Vec::with_capacity(points.len() + 1);
for point in &points {
while hull.len() >= 2 && turn(&hull[hull.len() - 2], &hull[hull.len() - 1], point) <= 0.0 {
hull.pop();
}
hull.push(*point);
}
let lower = hull.len() + 1;
for point in points.iter().rev().skip(1) {
while hull.len() >= lower
&& turn(&hull[hull.len() - 2], &hull[hull.len() - 1], point) <= 0.0
{
hull.pop();
}
hull.push(*point);
}
hull.pop();
hull
}
fn point_in_convex_hull(hull: &[Point<f32>], p: Point<f32>) -> bool {
if hull.len() < 3 {
return false;
}
let (mut left, mut right) = (false, false);
for i in 0..hull.len() {
let a = hull[i];
let b = hull[(i + 1) % hull.len()];
let side = (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x);
left |= side > 0.0;
right |= side < 0.0;
if left && right {
return false;
}
}
true
}
fn sub(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
fn dot(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
fn cross(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
fn normalize(v: [f32; 3]) -> [f32; 3] {
let length = dot(v, v).sqrt();
[v[0] / length, v[1] / length, v[2] / length]
}