mod types;
mod utils;
use types::*;
use utils::{format_to_vk, index_format_to_vk, vertex_format_to_vk, topology_to_vk};
use super::*;
use crate::types::Color;
use anyhow::{Context, Result};
use ash::{vk, khr};
use std::collections::HashMap;
use std::ffi::CStr;
#[cfg(target_os = "windows")]
use raw_window_handle::RawWindowHandle;
#[cfg(target_os = "linux")]
use raw_window_handle::{RawWindowHandle, RawDisplayHandle};
pub struct VulkanBackend {
entry: ash::Entry,
instance: ash::Instance,
physical_devices: Vec<PhysicalDeviceInfo>,
devices: HashMap<DeviceHandle, LogicalDevice>,
next_device_handle: DeviceHandle,
buffers: HashMap<BufferHandle, BufferState>,
next_buffer_handle: BufferHandle,
shaders: HashMap<ShaderHandle, ShaderState>,
next_shader_handle: ShaderHandle,
pipelines: HashMap<PipelineHandle, PipelineState>,
next_pipeline_handle: PipelineHandle,
compute_pipelines: HashMap<ComputePipelineHandle, ComputePipelineState>,
next_compute_pipeline_handle: ComputePipelineHandle,
bind_group_layouts: HashMap<BindGroupLayoutHandle, BindGroupLayoutState>,
next_bind_group_layout_handle: BindGroupLayoutHandle,
bind_groups: HashMap<BindGroupHandle, BindGroupState>,
next_bind_group_handle: BindGroupHandle,
render_targets: HashMap<RenderTargetHandle, RenderTargetState>,
next_render_target_handle: RenderTargetHandle,
surfaces: HashMap<SurfaceHandle, SurfaceState>,
next_surface_handle: SurfaceHandle,
textures: HashMap<TextureHandle, TextureState>,
next_texture_handle: TextureHandle,
samplers: HashMap<SamplerHandle, SamplerState>,
next_sampler_handle: SamplerHandle,
slang_compiler: crate::slang::SlangCompiler,
}
impl VulkanBackend {
pub fn new() -> Result<Self> {
tracing::info!("Initializing Vulkan backend");
let entry = unsafe { ash::Entry::load() }.context("Failed to load Vulkan library")?;
let instance_version = unsafe { entry.try_enumerate_instance_version() }
.context("Failed to enumerate instance version")?
.unwrap_or(vk::API_VERSION_1_0);
let major = vk::api_version_major(instance_version);
let minor = vk::api_version_minor(instance_version);
tracing::info!("Vulkan instance version: {}.{}", major, minor);
if major < 1 || (major == 1 && minor < 3) {
anyhow::bail!("Goldy requires Vulkan 1.3+, found {}.{}", major, minor);
}
let app_info = vk::ApplicationInfo::default()
.application_name(c"goldy")
.application_version(vk::make_api_version(0, 0, 1, 0))
.engine_name(c"goldy")
.engine_version(vk::make_api_version(0, 0, 1, 0))
.api_version(vk::API_VERSION_1_3);
let mut extensions: Vec<*const i8> = vec![
khr::surface::NAME.as_ptr(),
];
#[cfg(target_os = "windows")]
extensions.push(khr::win32_surface::NAME.as_ptr());
#[cfg(target_os = "linux")]
extensions.push(khr::wayland_surface::NAME.as_ptr());
let enable_validation = std::env::var("RAG_VALIDATION").map(|v| v == "1").unwrap_or(false);
let validation_layers: Vec<*const i8> = if enable_validation {
tracing::info!("Vulkan validation layers ENABLED");
extensions.push(ash::ext::debug_utils::NAME.as_ptr());
vec![c"VK_LAYER_KHRONOS_validation".as_ptr()]
} else {
vec![]
};
let create_info = vk::InstanceCreateInfo::default()
.application_info(&app_info)
.enabled_extension_names(&extensions)
.enabled_layer_names(&validation_layers);
let instance = unsafe { entry.create_instance(&create_info, None) }
.context("Failed to create Vulkan instance")?;
if enable_validation {
tracing::info!("Vulkan instance created with validation layers");
}
let physical_devices_raw = unsafe { instance.enumerate_physical_devices() }
.context("Failed to enumerate physical devices")?;
let physical_devices: Vec<PhysicalDeviceInfo> = physical_devices_raw
.into_iter()
.enumerate()
.map(|(idx, handle)| {
let properties = unsafe { instance.get_physical_device_properties(handle) };
PhysicalDeviceInfo {
handle,
properties,
adapter_id: idx as u32,
}
})
.collect();
tracing::info!("Found {} Vulkan physical devices", physical_devices.len());
for dev in &physical_devices {
let name = unsafe { CStr::from_ptr(dev.properties.device_name.as_ptr()) };
tracing::info!(
" [{}] {} ({:?})",
dev.adapter_id,
name.to_string_lossy(),
dev.properties.device_type
);
}
let slang_compiler = crate::slang::SlangCompiler::new()
.context("Failed to create Slang compiler")?;
Ok(Self {
entry,
instance,
physical_devices,
devices: HashMap::new(),
next_device_handle: 1,
buffers: HashMap::new(),
next_buffer_handle: 1,
shaders: HashMap::new(),
next_shader_handle: 1,
pipelines: HashMap::new(),
next_pipeline_handle: 1,
compute_pipelines: HashMap::new(),
next_compute_pipeline_handle: 1,
bind_group_layouts: HashMap::new(),
next_bind_group_layout_handle: 1,
bind_groups: HashMap::new(),
next_bind_group_handle: 1,
render_targets: HashMap::new(),
next_render_target_handle: 1,
surfaces: HashMap::new(),
next_surface_handle: 1,
textures: HashMap::new(),
next_texture_handle: 1,
samplers: HashMap::new(),
next_sampler_handle: 1,
slang_compiler,
})
}
fn find_memory_type(
&self,
physical_device: vk::PhysicalDevice,
type_filter: u32,
properties: vk::MemoryPropertyFlags,
) -> Option<u32> {
utils::find_memory_type(&self.instance, physical_device, type_filter, properties)
}
fn create_platform_surface(
&self,
window: &dyn raw_window_handle::HasWindowHandle,
_display: &dyn raw_window_handle::HasDisplayHandle,
) -> Result<vk::SurfaceKHR> {
let window_handle = window.window_handle()
.map_err(|e| anyhow::anyhow!("Failed to get window handle: {:?}", e))?;
#[cfg(target_os = "windows")]
{
match window_handle.as_raw() {
RawWindowHandle::Win32(h) => {
let create_info = vk::Win32SurfaceCreateInfoKHR::default()
.hwnd(h.hwnd.get() as isize)
.hinstance(
h.hinstance
.map(|i| i.get() as isize)
.unwrap_or(0)
);
let win32_surface = khr::win32_surface::Instance::new(&self.entry, &self.instance);
unsafe { win32_surface.create_win32_surface(&create_info, None) }
.context("Failed to create Win32 surface")
}
_ => anyhow::bail!("Expected Win32 window handle on Windows"),
}
}
#[cfg(target_os = "linux")]
{
let display_handle = _display.display_handle()
.map_err(|e| anyhow::anyhow!("Failed to get display handle: {:?}", e))?;
match (window_handle.as_raw(), display_handle.as_raw()) {
(RawWindowHandle::Wayland(w), RawDisplayHandle::Wayland(d)) => {
let create_info = vk::WaylandSurfaceCreateInfoKHR::default()
.display(d.display.as_ptr())
.surface(w.surface.as_ptr());
let wayland_surface = khr::wayland_surface::Instance::new(&self.entry, &self.instance);
unsafe { wayland_surface.create_wayland_surface(&create_info, None) }
.context("Failed to create Wayland surface")
}
_ => anyhow::bail!("Expected Wayland window/display handles on Linux (X11 not supported)"),
}
}
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
{
anyhow::bail!("Surface creation not supported on this platform - use Metal backend on macOS")
}
}
fn ensure_shader_stage_compiled(
&mut self,
shader_handle: ShaderHandle,
stage: crate::slang::SlangStage,
) -> Result<vk::ShaderModule> {
let shader = self.shaders.get_mut(&shader_handle)
.context("Invalid shader handle")?;
let cached_module = match stage {
crate::slang::SlangStage::Vertex => shader.vertex_module,
crate::slang::SlangStage::Fragment => shader.fragment_module,
crate::slang::SlangStage::Compute => shader.compute_module,
_ => anyhow::bail!("Unsupported shader stage: {:?}", stage),
};
if let Some(module) = cached_module {
return Ok(module);
}
let entry_point_name = match stage {
crate::slang::SlangStage::Vertex => "vs_main",
crate::slang::SlangStage::Fragment => "fs_main",
crate::slang::SlangStage::Compute => "cs_main",
_ => anyhow::bail!("Unsupported shader stage: {:?}", stage),
};
let slang_source = shader.slang_source.clone();
let search_paths: Vec<&str> = shader.search_paths.iter().map(|s| s.as_str()).collect();
let device_handle = shader.device_handle;
let compiled = self.slang_compiler.compile_with_options(
&slang_source,
crate::slang::ShaderTarget::Spirv,
&[(entry_point_name, stage)],
&search_paths,
).with_context(|| format!("Failed to compile {} shader", entry_point_name))?;
let spirv = compiled.as_spirv()
.context("Invalid SPIR-V output")?;
let logical_device = self.devices.get(&device_handle)
.context("Shader's device no longer valid")?;
let create_info = vk::ShaderModuleCreateInfo::default().code(spirv);
let module = unsafe { logical_device.device.create_shader_module(&create_info, None) }
.context("Failed to create Vulkan shader module")?;
tracing::debug!("Compiled {} ({} SPIR-V words)", entry_point_name, spirv.len());
let shader = self.shaders.get_mut(&shader_handle).unwrap();
match stage {
crate::slang::SlangStage::Vertex => shader.vertex_module = Some(module),
crate::slang::SlangStage::Fragment => shader.fragment_module = Some(module),
crate::slang::SlangStage::Compute => shader.compute_module = Some(module),
_ => {} }
Ok(module)
}
}
impl Drop for VulkanBackend {
fn drop(&mut self) {
tracing::info!("Shutting down Vulkan backend");
let device_handles: Vec<_> = self.devices.keys().copied().collect();
for handle in device_handles {
self.destroy_device(handle);
}
unsafe {
self.instance.destroy_instance(None);
}
}
}
include!("impl_gpu_backend.rs");