diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index b9496cc01426485cbef625c7e697bbf6082d1a67..0b9b5b44971e0087bd2e74f1afb777cb3ebedffc 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -9,7 +9,10 @@ fn main() { let target = env::var("CARGO_CFG_TARGET_OS"); println!("cargo::rustc-check-cfg=cfg(gles)"); - #[cfg(any(not(target_os = "macos"), feature = "macos-blade"))] + #[cfg(any( + not(any(target_os = "macos", target_os = "windows")), + feature = "macos-blade" + ))] check_wgsl_shaders(); match target.as_deref() { @@ -17,15 +20,9 @@ fn main() { #[cfg(target_os = "macos")] macos::build(); } - #[cfg(all(target_os = "windows", feature = "windows-manifest"))] Ok("windows") => { - let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml"); - let rc_file = std::path::Path::new("resources/windows/gpui.rc"); - println!("cargo:rerun-if-changed={}", manifest.display()); - println!("cargo:rerun-if-changed={}", rc_file.display()); - embed_resource::compile(rc_file, embed_resource::NONE) - .manifest_required() - .unwrap(); + #[cfg(target_os = "windows")] + windows::build(); } _ => (), }; @@ -242,3 +239,232 @@ mod macos { } } } + +#[cfg(target_os = "windows")] +mod windows { + use std::{ + fs, + io::Write, + path::{Path, PathBuf}, + process::{self, Command}, + }; + + pub(super) fn build() { + // Link the AMD AGS library + link_amd_ags(); + + // Compile HLSL shaders + #[cfg(not(debug_assertions))] + compile_shaders(); + + // Embed the Windows manifest and resource file + #[cfg(feature = "windows-manifest")] + embed_resource(); + } + + fn link_amd_ags() { + // We can not use relative paths in `cargo:rustc-link-search`, so we need to use the absolute path. + // See: https://stackoverflow.com/questions/41917096/how-do-i-make-rustc-link-search-relative-to-the-project-location + let lib_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("libs"); + #[cfg(target_pointer_width = "64")] + let lib_name = "amd_ags_x64_2022_MT"; + #[cfg(target_pointer_width = "32")] + let lib_name = "amd_ags_x86_2022_MT"; + + println!("cargo:rustc-link-lib=static={}", lib_name); + println!("cargo:rustc-link-search=native={}", lib_dir.display()); + println!( + "cargo:rerun-if-changed={}/{}.lib", + lib_dir.display(), + lib_name + ); + } + + #[cfg(feature = "windows-manifest")] + fn embed_resource() { + let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml"); + let rc_file = std::path::Path::new("resources/windows/gpui.rc"); + println!("cargo:rerun-if-changed={}", manifest.display()); + println!("cargo:rerun-if-changed={}", rc_file.display()); + embed_resource::compile(rc_file, embed_resource::NONE) + .manifest_required() + .unwrap(); + } + + /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler. + fn compile_shaders() { + let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("src/platform/windows/shaders.hlsl"); + let out_dir = std::env::var("OUT_DIR").unwrap(); + + println!("cargo:rerun-if-changed={}", shader_path.display()); + + // Check if fxc.exe is available + let fxc_path = find_fxc_compiler(); + + // Define all modules + let modules = [ + "quad", + "shadow", + "paths", + "underline", + "monochrome_sprite", + "polychrome_sprite", + ]; + + let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir); + if Path::new(&rust_binding_path).exists() { + fs::remove_file(&rust_binding_path) + .expect("Failed to remove existing Rust binding file"); + } + for module in modules { + compile_shader_for_module( + module, + &out_dir, + &fxc_path, + shader_path.to_str().unwrap(), + &rust_binding_path, + ); + } + + { + let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("src/platform/windows/color_text_raster.hlsl"); + compile_shader_for_module( + "emoji_rasterization", + &out_dir, + &fxc_path, + shader_path.to_str().unwrap(), + &rust_binding_path, + ); + } + } + + /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler. + fn find_fxc_compiler() -> String { + // Try to find in PATH + if let Ok(output) = std::process::Command::new("where").arg("fxc.exe").output() { + if output.status.success() { + let path = String::from_utf8_lossy(&output.stdout); + return path.trim().to_string(); + } + } + + // Check the default path + if Path::new(r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe") + .exists() + { + return r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe" + .to_string(); + } + + // Check environment variable + if let Ok(path) = std::env::var("GPUI_FXC_PATH") { + if Path::new(&path).exists() { + return path; + } + } + + panic!("Failed to find fxc.exe"); + } + + fn compile_shader_for_module( + module: &str, + out_dir: &str, + fxc_path: &str, + shader_path: &str, + rust_binding_path: &str, + ) { + // Compile vertex shader + let output_file = format!("{}/{}_vs.h", out_dir, module); + let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase()); + compile_shader_impl( + fxc_path, + &format!("{module}_vertex"), + &output_file, + &const_name, + shader_path, + "vs_5_0", + ); + generate_rust_binding(&const_name, &output_file, &rust_binding_path); + + // Compile fragment shader + let output_file = format!("{}/{}_ps.h", out_dir, module); + let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase()); + compile_shader_impl( + fxc_path, + &format!("{module}_fragment"), + &output_file, + &const_name, + shader_path, + "ps_5_0", + ); + generate_rust_binding(&const_name, &output_file, &rust_binding_path); + } + + fn compile_shader_impl( + fxc_path: &str, + entry_point: &str, + output_path: &str, + var_name: &str, + shader_path: &str, + target: &str, + ) { + let output = Command::new(fxc_path) + .args([ + "/T", + target, + "/E", + entry_point, + "/Fh", + output_path, + "/Vn", + var_name, + "/O3", + shader_path, + ]) + .output(); + + match output { + Ok(result) => { + if result.status.success() { + return; + } + eprintln!( + "Pixel shader compilation failed for {}:\n{}", + entry_point, + String::from_utf8_lossy(&result.stderr) + ); + process::exit(1); + } + Err(e) => { + eprintln!("Failed to run fxc for {}: {}", entry_point, e); + process::exit(1); + } + } + } + + fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) { + let header_content = fs::read_to_string(head_file).expect("Failed to read header file"); + let const_definition = { + let global_var_start = header_content.find("const BYTE").unwrap(); + let global_var = &header_content[global_var_start..]; + let equal = global_var.find('=').unwrap(); + global_var[equal + 1..].trim() + }; + let rust_binding = format!( + "const {}: &[u8] = &{}\n", + const_name, + const_definition.replace('{', "[").replace('}', "]") + ); + let mut options = fs::OpenOptions::new() + .write(true) + .create(true) + .append(true) + .open(output_path) + .expect("Failed to open Rust binding file"); + options + .write_all(rust_binding.as_bytes()) + .expect("Failed to write Rust binding file"); + } +} diff --git a/crates/gpui/src/platform/windows/color_text_raster.hlsl b/crates/gpui/src/platform/windows/color_text_raster.hlsl index b0307aee424e14b8e74103af3b047c4eb08f0ed2..ccc5fa26f00d57f2b69e85965a66b6ecea98a833 100644 --- a/crates/gpui/src/platform/windows/color_text_raster.hlsl +++ b/crates/gpui/src/platform/windows/color_text_raster.hlsl @@ -3,7 +3,7 @@ struct RasterVertexOutput { float2 texcoord : TEXCOORD0; }; -RasterVertexOutput vertex(uint vertexID : SV_VERTEXID) +RasterVertexOutput emoji_rasterization_vertex(uint vertexID : SV_VERTEXID) { RasterVertexOutput output; output.texcoord = float2((vertexID << 1) & 2, vertexID & 2); @@ -31,7 +31,7 @@ cbuffer GlyphLayerTextureParams : register(b0) { float4 run_color; }; -float4 pixel(PixelInput input): SV_Target { +float4 emoji_rasterization_fragment(PixelInput input): SV_Target { float3 sampled = t_layer.Sample(s_layer, input.texcoord.xy).rgb; float alpha = (sampled.r + sampled.g + sampled.b) / 3; diff --git a/crates/gpui/src/platform/windows/direct_write.rs b/crates/gpui/src/platform/windows/direct_write.rs index a86c6d5d6866544bb1c38a9dd209bb35f396665b..f8826b01717da085ca808c516b9a9aa591439276 100644 --- a/crates/gpui/src/platform/windows/direct_write.rs +++ b/crates/gpui/src/platform/windows/direct_write.rs @@ -184,30 +184,22 @@ impl GPUState { }; let vertex_shader = { - let source = - shader_resources::build_shader_blob("color_text_raster", "vertex", "vs_5_0")?; - let bytes = unsafe { - std::slice::from_raw_parts( - source.GetBufferPointer() as *mut u8, - source.GetBufferSize(), - ) - }; + let source = shader_resources::RawShaderBytes::new( + shader_resources::ShaderModule::EmojiRasterization, + shader_resources::ShaderTarget::Vertex, + )?; let mut shader = None; - unsafe { device.CreateVertexShader(bytes, None, Some(&mut shader)) }?; + unsafe { device.CreateVertexShader(source.as_bytes(), None, Some(&mut shader)) }?; shader.unwrap() }; let pixel_shader = { - let source = - shader_resources::build_shader_blob("color_text_raster", "pixel", "ps_5_0")?; - let bytes = unsafe { - std::slice::from_raw_parts( - source.GetBufferPointer() as *mut u8, - source.GetBufferSize(), - ) - }; + let source = shader_resources::RawShaderBytes::new( + shader_resources::ShaderModule::EmojiRasterization, + shader_resources::ShaderTarget::Fragment, + )?; let mut shader = None; - unsafe { device.CreatePixelShader(bytes, None, Some(&mut shader)) }?; + unsafe { device.CreatePixelShader(source.as_bytes(), None, Some(&mut shader)) }?; shader.unwrap() }; diff --git a/crates/gpui/src/platform/windows/directx_renderer.rs b/crates/gpui/src/platform/windows/directx_renderer.rs index a857b350ece7ee5feaa2edf22987b65183597ec4..9ec905567203492140fa52d805aa9f8ef4a7ce6c 100644 --- a/crates/gpui/src/platform/windows/directx_renderer.rs +++ b/crates/gpui/src/platform/windows/directx_renderer.rs @@ -13,7 +13,12 @@ use windows::Win32::{ #[cfg(not(feature = "enable-renderdoc"))] use windows::{Win32::Graphics::DirectComposition::*, core::Interface}; -use crate::*; +use crate::{ + platform::windows::directx_renderer::shader_resources::{ + RawShaderBytes, ShaderModule, ShaderTarget, + }, + *, +}; const RENDER_TARGET_FORMAT: DXGI_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM; // This configuration is used for MSAA rendering, and it's guaranteed to be supported by DirectX 11. @@ -413,7 +418,7 @@ impl DirectXRenderer { }; let driver_version = match desc.VendorId { 0x10DE => nvidia::get_driver_version(), - 0x1002 => Err(anyhow::anyhow!("AMD driver info not implemented yet")), + 0x1002 => amd::get_driver_version(), 0x8086 => intel::get_driver_version(&self.devices.adapter), _ => Err(anyhow::anyhow!("Unknown vendor detected.")), } @@ -481,35 +486,22 @@ impl DirectXResources { impl DirectXRenderPipelines { pub fn new(device: &ID3D11Device) -> Result { - let shadow_pipeline = PipelineState::new( - device, - "shadow_pipeline", - "shadow_vertex", - "shadow_fragment", - 4, - )?; - let quad_pipeline = - PipelineState::new(device, "quad_pipeline", "quad_vertex", "quad_fragment", 64)?; + let shadow_pipeline = + PipelineState::new(device, "shadow_pipeline", ShaderModule::Shadow, 4)?; + let quad_pipeline = PipelineState::new(device, "quad_pipeline", ShaderModule::Quad, 64)?; let paths_pipeline = PathsPipelineState::new(device)?; - let underline_pipeline = PipelineState::new( - device, - "underline_pipeline", - "underline_vertex", - "underline_fragment", - 4, - )?; + let underline_pipeline = + PipelineState::new(device, "underline_pipeline", ShaderModule::Underline, 4)?; let mono_sprites = PipelineState::new( device, "monochrome_sprite_pipeline", - "monochrome_sprite_vertex", - "monochrome_sprite_fragment", + ShaderModule::MonochromeSprite, 512, )?; let poly_sprites = PipelineState::new( device, "polychrome_sprite_pipeline", - "polychrome_sprite_vertex", - "polychrome_sprite_fragment", + ShaderModule::PolychromeSprite, 16, )?; @@ -625,31 +617,16 @@ impl PipelineState { fn new( device: &ID3D11Device, label: &'static str, - vertex_entry: &str, - fragment_entry: &str, + shader_module: ShaderModule, buffer_size: usize, ) -> Result { let vertex = { - let shader_blob = - shader_resources::build_shader_blob("shaders", vertex_entry, "vs_5_0")?; - let bytes = unsafe { - std::slice::from_raw_parts( - shader_blob.GetBufferPointer() as *mut u8, - shader_blob.GetBufferSize(), - ) - }; - create_vertex_shader(device, bytes)? + let raw_shader = RawShaderBytes::new(shader_module, ShaderTarget::Vertex)?; + create_vertex_shader(device, raw_shader.as_bytes())? }; let fragment = { - let shader_blob = - shader_resources::build_shader_blob("shaders", fragment_entry, "ps_5_0")?; - let bytes = unsafe { - std::slice::from_raw_parts( - shader_blob.GetBufferPointer() as *mut u8, - shader_blob.GetBufferSize(), - ) - }; - create_fragment_shader(device, bytes)? + let raw_shader = RawShaderBytes::new(shader_module, ShaderTarget::Fragment)?; + create_fragment_shader(device, raw_shader.as_bytes())? }; let buffer = create_buffer(device, std::mem::size_of::(), buffer_size)?; let view = create_buffer_view(device, &buffer)?; @@ -742,26 +719,15 @@ impl PipelineState { impl PathsPipelineState { fn new(device: &ID3D11Device) -> Result { let (vertex, vertex_shader) = { - let shader_blob = - shader_resources::build_shader_blob("shaders", "paths_vertex", "vs_5_0")?; - let bytes = unsafe { - std::slice::from_raw_parts( - shader_blob.GetBufferPointer() as *mut u8, - shader_blob.GetBufferSize(), - ) - }; - (create_vertex_shader(device, bytes)?, shader_blob) + let raw_vertex_shader = RawShaderBytes::new(ShaderModule::Paths, ShaderTarget::Vertex)?; + ( + create_vertex_shader(device, raw_vertex_shader.as_bytes())?, + raw_vertex_shader, + ) }; let fragment = { - let shader_blob = - shader_resources::build_shader_blob("shaders", "paths_fragment", "ps_5_0")?; - let bytes = unsafe { - std::slice::from_raw_parts( - shader_blob.GetBufferPointer() as *mut u8, - shader_blob.GetBufferSize(), - ) - }; - create_fragment_shader(device, bytes)? + let raw_shader = RawShaderBytes::new(ShaderModule::Paths, ShaderTarget::Fragment)?; + create_fragment_shader(device, raw_shader.as_bytes())? }; let buffer = create_buffer(device, std::mem::size_of::(), 32)?; let view = create_buffer_view(device, &buffer)?; @@ -773,10 +739,6 @@ impl PathsPipelineState { let indirect_draw_buffer = create_indirect_draw_buffer(device, 32)?; // Create input layout let input_layout = unsafe { - let shader_bytes = std::slice::from_raw_parts( - vertex_shader.GetBufferPointer() as *const u8, - vertex_shader.GetBufferSize(), - ); let mut layout = None; device.CreateInputLayout( &[ @@ -817,7 +779,7 @@ impl PathsPipelineState { InstanceDataStepRate: 0, }, ], - shader_bytes, + vertex_shader.as_bytes(), Some(&mut layout), )?; layout.unwrap() @@ -1320,37 +1282,138 @@ const BUFFER_COUNT: usize = 3; pub(crate) mod shader_resources { use anyhow::Result; - use windows::Win32::Graphics::Direct3D::{ - Fxc::{D3DCOMPILE_DEBUG, D3DCOMPILE_SKIP_OPTIMIZATION, D3DCompileFromFile}, - ID3DBlob, + + #[cfg(debug_assertions)] + use windows::{ + Win32::Graphics::Direct3D::{ + Fxc::{D3DCOMPILE_DEBUG, D3DCOMPILE_SKIP_OPTIMIZATION, D3DCompileFromFile}, + ID3DBlob, + }, + core::{HSTRING, PCSTR}, }; - use windows_core::{HSTRING, PCSTR}; - pub(crate) fn build_shader_blob(filename: &str, entry: &str, target: &str) -> Result { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub(crate) enum ShaderModule { + Quad, + Shadow, + Underline, + Paths, + MonochromeSprite, + PolychromeSprite, + EmojiRasterization, + } + + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub(crate) enum ShaderTarget { + Vertex, + Fragment, + } + + pub(crate) struct RawShaderBytes<'t> { + inner: &'t [u8], + + #[cfg(debug_assertions)] + _blob: ID3DBlob, + } + + impl<'t> RawShaderBytes<'t> { + pub(crate) fn new(module: ShaderModule, target: ShaderTarget) -> Result { + #[cfg(not(debug_assertions))] + { + Ok(Self::from_bytes(module, target)) + } + #[cfg(debug_assertions)] + { + let blob = build_shader_blob(module, target)?; + let inner = unsafe { + std::slice::from_raw_parts( + blob.GetBufferPointer() as *const u8, + blob.GetBufferSize(), + ) + }; + Ok(Self { inner, _blob: blob }) + } + } + + pub(crate) fn as_bytes(&'t self) -> &'t [u8] { + self.inner + } + + #[cfg(not(debug_assertions))] + fn from_bytes(module: ShaderModule, target: ShaderTarget) -> Self { + let bytes = match module { + ShaderModule::Quad => match target { + ShaderTarget::Vertex => QUAD_VERTEX_BYTES, + ShaderTarget::Fragment => QUAD_FRAGMENT_BYTES, + }, + ShaderModule::Shadow => match target { + ShaderTarget::Vertex => SHADOW_VERTEX_BYTES, + ShaderTarget::Fragment => SHADOW_FRAGMENT_BYTES, + }, + ShaderModule::Underline => match target { + ShaderTarget::Vertex => UNDERLINE_VERTEX_BYTES, + ShaderTarget::Fragment => UNDERLINE_FRAGMENT_BYTES, + }, + ShaderModule::Paths => match target { + ShaderTarget::Vertex => PATHS_VERTEX_BYTES, + ShaderTarget::Fragment => PATHS_FRAGMENT_BYTES, + }, + ShaderModule::MonochromeSprite => match target { + ShaderTarget::Vertex => MONOCHROME_SPRITE_VERTEX_BYTES, + ShaderTarget::Fragment => MONOCHROME_SPRITE_FRAGMENT_BYTES, + }, + ShaderModule::PolychromeSprite => match target { + ShaderTarget::Vertex => POLYCHROME_SPRITE_VERTEX_BYTES, + ShaderTarget::Fragment => POLYCHROME_SPRITE_FRAGMENT_BYTES, + }, + ShaderModule::EmojiRasterization => match target { + ShaderTarget::Vertex => EMOJI_RASTERIZATION_VERTEX_BYTES, + ShaderTarget::Fragment => EMOJI_RASTERIZATION_FRAGMENT_BYTES, + }, + }; + Self { inner: bytes } + } + } + + #[cfg(debug_assertions)] + pub(super) fn build_shader_blob(entry: ShaderModule, target: ShaderTarget) -> Result { unsafe { - let mut entry = entry.to_owned(); - let mut target = target.to_owned(); + let shader_name = if matches!(entry, ShaderModule::EmojiRasterization) { + "color_text_raster.hlsl" + } else { + "shaders.hlsl" + }; + + let entry = format!( + "{}_{}\0", + entry.as_str(), + match target { + ShaderTarget::Vertex => "vertex", + ShaderTarget::Fragment => "fragment", + } + ); + let target = match target { + ShaderTarget::Vertex => "vs_5_0\0", + ShaderTarget::Fragment => "ps_5_0\0", + }; + let mut compile_blob = None; let mut error_blob = None; + let shader_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join(&format!("src/platform/windows/{}.hlsl", filename)) - .canonicalize() - .unwrap(); - entry.push_str("\0"); - target.push_str("\0"); + .join(&format!("src/platform/windows/{}", shader_name)) + .canonicalize()?; + let entry_point = PCSTR::from_raw(entry.as_ptr()); let target_cstr = PCSTR::from_raw(target.as_ptr()); - #[cfg(debug_assertions)] - let compile_flag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; - #[cfg(not(debug_assertions))] - let compile_flag = 0; + let ret = D3DCompileFromFile( &HSTRING::from(shader_path.to_str().unwrap()), None, None, entry_point, target_cstr, - compile_flag, + D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &mut compile_blob, Some(&mut error_blob), @@ -1359,19 +1422,34 @@ pub(crate) mod shader_resources { let Some(error_blob) = error_blob else { return Err(anyhow::anyhow!("{ret:?}")); }; - let string_len = error_blob.GetBufferSize(); - let error_string_encode = Vec::from_raw_parts( - error_blob.GetBufferPointer() as *mut u8, - string_len, - string_len, - ); - let error_string = String::from_utf8_lossy(&error_string_encode).to_string(); + + let error_string = + std::ffi::CStr::from_ptr(error_blob.GetBufferPointer() as *const i8) + .to_string_lossy(); log::error!("Shader compile error: {}", error_string); return Err(anyhow::anyhow!("Compile error: {}", error_string)); } Ok(compile_blob.unwrap()) } } + + #[cfg(not(debug_assertions))] + include!(concat!(env!("OUT_DIR"), "/shaders_bytes.rs")); + + #[cfg(debug_assertions)] + impl ShaderModule { + pub fn as_str(&self) -> &str { + match self { + ShaderModule::Quad => "quad", + ShaderModule::Shadow => "shadow", + ShaderModule::Underline => "underline", + ShaderModule::Paths => "paths", + ShaderModule::MonochromeSprite => "monochrome_sprite", + ShaderModule::PolychromeSprite => "polychrome_sprite", + ShaderModule::EmojiRasterization => "emoji_rasterization", + } + } + } } mod nvidia { @@ -1403,7 +1481,13 @@ mod nvidia { pub(super) fn get_driver_version() -> Result { unsafe { // Try to load the NVIDIA driver DLL - let nvidia_dll = LoadLibraryA(s!("nvapi64.dll")).context("Can't load nvapi64.dll")?; + #[cfg(target_pointer_width = "64")] + let nvidia_dll = + LoadLibraryA(s!("nvapi64.dll")).context(format!("Can't load nvapi64.dll"))?; + #[cfg(target_pointer_width = "32")] + let nvidia_dll = + LoadLibraryA(s!("nvapi.dll")).context(format!("Can't load nvapi.dll"))?; + let nvapi_query_addr = GetProcAddress(nvidia_dll, s!("nvapi_QueryInterface")) .ok_or_else(|| anyhow::anyhow!("Failed to get nvapi_QueryInterface address"))?; let nvapi_query: extern "C" fn(u32) -> *mut () = std::mem::transmute(nvapi_query_addr); @@ -1442,6 +1526,84 @@ mod nvidia { } } +mod amd { + use std::os::raw::{c_char, c_int, c_void}; + + // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L145 + const AGS_CURRENT_VERSION: i32 = (6 << 22) | (3 << 12) | 0; + + // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L204 + // This is an opaque type, using struct to represent it properly for FFI + #[repr(C)] + struct AGSContext { + _private: [u8; 0], + } + + #[repr(C)] + pub struct AGSGPUInfo { + pub driver_version: *const c_char, + pub radeon_software_version: *const c_char, + pub num_devices: c_int, + pub devices: *mut c_void, + } + + unsafe extern "C" { + fn agsInitialize( + version: c_int, + config: *const c_void, + context: *mut *mut AGSContext, + gpu_info: *mut AGSGPUInfo, + ) -> c_int; + + fn agsDeInitialize(context: *mut AGSContext) -> c_int; + } + + pub(super) fn get_driver_version() -> anyhow::Result { + unsafe { + let mut context: *mut AGSContext = std::ptr::null_mut(); + let mut gpu_info: AGSGPUInfo = AGSGPUInfo { + driver_version: std::ptr::null(), + radeon_software_version: std::ptr::null(), + num_devices: 0, + devices: std::ptr::null_mut(), + }; + + let result = agsInitialize( + AGS_CURRENT_VERSION, + std::ptr::null(), + &mut context, + &mut gpu_info, + ); + if result != 0 { + return Err(anyhow::anyhow!( + "Failed to initialize AGS, error code: {}", + result + )); + } + + // Vulkan acctually returns this as the driver version + let software_version = if !gpu_info.radeon_software_version.is_null() { + std::ffi::CStr::from_ptr(gpu_info.radeon_software_version) + .to_string_lossy() + .into_owned() + } else { + "Unknown Radeon Software Version".to_string() + }; + + let driver_version = if !gpu_info.driver_version.is_null() { + std::ffi::CStr::from_ptr(gpu_info.driver_version) + .to_string_lossy() + .into_owned() + } else { + "Unknown Radeon Driver Version".to_string() + }; + + agsDeInitialize(context); + Ok(format!("{} ({})", software_version, driver_version)) + } + } +} + mod intel { use windows::{ Win32::Graphics::Dxgi::{IDXGIAdapter1, IDXGIDevice},