Merge branch 'windows/dx11' into windows/remove-d2d

Kate created

Change summary

crates/gpui/build.rs                                    | 244 +++++++
crates/gpui/src/platform/windows/color_text_raster.hlsl |   4 
crates/gpui/src/platform/windows/direct_write.rs        |  28 
crates/gpui/src/platform/windows/directx_renderer.rs    | 342 ++++++++--
4 files changed, 499 insertions(+), 119 deletions(-)

Detailed changes

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");
+    }
+}

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;
 

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()
         };
 

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<Self> {
-        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<T> PipelineState<T> {
     fn new(
         device: &ID3D11Device,
         label: &'static str,
-        vertex_entry: &str,
-        fragment_entry: &str,
+        shader_module: ShaderModule,
         buffer_size: usize,
     ) -> Result<Self> {
         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::<T>(), buffer_size)?;
         let view = create_buffer_view(device, &buffer)?;
@@ -742,26 +719,15 @@ impl<T> PipelineState<T> {
 impl PathsPipelineState {
     fn new(device: &ID3D11Device) -> Result<Self> {
         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::<PathSprite>(), 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<ID3DBlob> {
+    #[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<Self> {
+            #[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<ID3DBlob> {
         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<String> {
         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<String> {
+        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},