1797 lines
61 KiB
Rust
1797 lines
61 KiB
Rust
use std::{mem::ManuallyDrop, sync::Arc};
|
|
|
|
use ::util::ResultExt;
|
|
use anyhow::{Context, Result};
|
|
use windows::{
|
|
Win32::{
|
|
Foundation::{HMODULE, HWND},
|
|
Graphics::{
|
|
Direct3D::*,
|
|
Direct3D11::*,
|
|
DirectComposition::*,
|
|
Dxgi::{Common::*, *},
|
|
},
|
|
},
|
|
core::Interface,
|
|
};
|
|
|
|
use crate::{
|
|
platform::windows::directx_renderer::shader_resources::{
|
|
RawShaderBytes, ShaderModule, ShaderTarget,
|
|
},
|
|
*,
|
|
};
|
|
|
|
pub(crate) const DISABLE_DIRECT_COMPOSITION: &str = "GPUI_DISABLE_DIRECT_COMPOSITION";
|
|
const RENDER_TARGET_FORMAT: DXGI_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM;
|
|
// This configuration is used for MSAA rendering on paths only, and it's guaranteed to be supported by DirectX 11.
|
|
const PATH_MULTISAMPLE_COUNT: u32 = 4;
|
|
|
|
pub(crate) struct DirectXRenderer {
|
|
hwnd: HWND,
|
|
atlas: Arc<DirectXAtlas>,
|
|
devices: ManuallyDrop<DirectXDevices>,
|
|
resources: ManuallyDrop<DirectXResources>,
|
|
globals: DirectXGlobalElements,
|
|
pipelines: DirectXRenderPipelines,
|
|
direct_composition: Option<DirectComposition>,
|
|
}
|
|
|
|
/// Direct3D objects
|
|
#[derive(Clone)]
|
|
pub(crate) struct DirectXDevices {
|
|
adapter: IDXGIAdapter1,
|
|
dxgi_factory: IDXGIFactory6,
|
|
pub(crate) device: ID3D11Device,
|
|
pub(crate) device_context: ID3D11DeviceContext,
|
|
dxgi_device: Option<IDXGIDevice>,
|
|
}
|
|
|
|
struct DirectXResources {
|
|
// Direct3D rendering objects
|
|
swap_chain: IDXGISwapChain1,
|
|
render_target: ManuallyDrop<ID3D11Texture2D>,
|
|
render_target_view: [Option<ID3D11RenderTargetView>; 1],
|
|
|
|
// Path intermediate textures (with MSAA)
|
|
path_intermediate_texture: ID3D11Texture2D,
|
|
path_intermediate_srv: [Option<ID3D11ShaderResourceView>; 1],
|
|
path_intermediate_msaa_texture: ID3D11Texture2D,
|
|
path_intermediate_msaa_view: [Option<ID3D11RenderTargetView>; 1],
|
|
|
|
// Cached window size and viewport
|
|
width: u32,
|
|
height: u32,
|
|
viewport: [D3D11_VIEWPORT; 1],
|
|
}
|
|
|
|
struct DirectXRenderPipelines {
|
|
shadow_pipeline: PipelineState<Shadow>,
|
|
quad_pipeline: PipelineState<Quad>,
|
|
path_rasterization_pipeline: PipelineState<PathRasterizationSprite>,
|
|
path_sprite_pipeline: PipelineState<PathSprite>,
|
|
underline_pipeline: PipelineState<Underline>,
|
|
mono_sprites: PipelineState<MonochromeSprite>,
|
|
poly_sprites: PipelineState<PolychromeSprite>,
|
|
}
|
|
|
|
struct DirectXGlobalElements {
|
|
global_params_buffer: [Option<ID3D11Buffer>; 1],
|
|
sampler: [Option<ID3D11SamplerState>; 1],
|
|
}
|
|
|
|
struct DirectComposition {
|
|
comp_device: IDCompositionDevice,
|
|
comp_target: IDCompositionTarget,
|
|
comp_visual: IDCompositionVisual,
|
|
}
|
|
|
|
impl DirectXDevices {
|
|
pub(crate) fn new(disable_direct_composition: bool) -> Result<ManuallyDrop<Self>> {
|
|
let dxgi_factory = get_dxgi_factory().context("Creating DXGI factory")?;
|
|
let adapter = get_adapter(&dxgi_factory).context("Getting DXGI adapter")?;
|
|
let (device, device_context) = {
|
|
let mut device: Option<ID3D11Device> = None;
|
|
let mut context: Option<ID3D11DeviceContext> = None;
|
|
let mut feature_level = D3D_FEATURE_LEVEL::default();
|
|
get_device(
|
|
&adapter,
|
|
Some(&mut device),
|
|
Some(&mut context),
|
|
Some(&mut feature_level),
|
|
)
|
|
.context("Creating Direct3D device")?;
|
|
match feature_level {
|
|
D3D_FEATURE_LEVEL_11_1 => {
|
|
log::info!("Created device with Direct3D 11.1 feature level.")
|
|
}
|
|
D3D_FEATURE_LEVEL_11_0 => {
|
|
log::info!("Created device with Direct3D 11.0 feature level.")
|
|
}
|
|
D3D_FEATURE_LEVEL_10_1 => {
|
|
log::info!("Created device with Direct3D 10.1 feature level.")
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
(device.unwrap(), context.unwrap())
|
|
};
|
|
let dxgi_device = if disable_direct_composition {
|
|
None
|
|
} else {
|
|
Some(device.cast().context("Creating DXGI device")?)
|
|
};
|
|
|
|
Ok(ManuallyDrop::new(Self {
|
|
adapter,
|
|
dxgi_factory,
|
|
dxgi_device,
|
|
device,
|
|
device_context,
|
|
}))
|
|
}
|
|
}
|
|
|
|
impl DirectXRenderer {
|
|
pub(crate) fn new(hwnd: HWND, disable_direct_composition: bool) -> Result<Self> {
|
|
if disable_direct_composition {
|
|
log::info!("Direct Composition is disabled.");
|
|
}
|
|
|
|
let devices =
|
|
DirectXDevices::new(disable_direct_composition).context("Creating DirectX devices")?;
|
|
let atlas = Arc::new(DirectXAtlas::new(&devices.device, &devices.device_context));
|
|
|
|
let resources = DirectXResources::new(&devices, 1, 1, hwnd, disable_direct_composition)
|
|
.context("Creating DirectX resources")?;
|
|
let globals = DirectXGlobalElements::new(&devices.device)
|
|
.context("Creating DirectX global elements")?;
|
|
let pipelines = DirectXRenderPipelines::new(&devices.device)
|
|
.context("Creating DirectX render pipelines")?;
|
|
|
|
let direct_composition = if disable_direct_composition {
|
|
None
|
|
} else {
|
|
let composition = DirectComposition::new(devices.dxgi_device.as_ref().unwrap(), hwnd)
|
|
.context("Creating DirectComposition")?;
|
|
composition
|
|
.set_swap_chain(&resources.swap_chain)
|
|
.context("Setting swap chain for DirectComposition")?;
|
|
Some(composition)
|
|
};
|
|
|
|
Ok(DirectXRenderer {
|
|
hwnd,
|
|
atlas,
|
|
devices,
|
|
resources,
|
|
globals,
|
|
pipelines,
|
|
direct_composition,
|
|
})
|
|
}
|
|
|
|
pub(crate) fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
|
self.atlas.clone()
|
|
}
|
|
|
|
fn pre_draw(&self) -> Result<()> {
|
|
#[cfg(not(feature = "enable-renderdoc"))]
|
|
let premultiplied_alpha = 1;
|
|
#[cfg(feature = "enable-renderdoc")]
|
|
let premultiplied_alpha = 0;
|
|
|
|
update_buffer(
|
|
&self.devices.device_context,
|
|
self.globals.global_params_buffer[0].as_ref().unwrap(),
|
|
&[GlobalParams {
|
|
viewport_size: [
|
|
self.resources.viewport[0].Width,
|
|
self.resources.viewport[0].Height,
|
|
],
|
|
premultiplied_alpha,
|
|
..Default::default()
|
|
}],
|
|
)?;
|
|
unsafe {
|
|
self.devices.device_context.ClearRenderTargetView(
|
|
self.resources.render_target_view[0].as_ref().unwrap(),
|
|
&[0.0; 4],
|
|
);
|
|
self.devices
|
|
.device_context
|
|
.OMSetRenderTargets(Some(&self.resources.render_target_view), None);
|
|
self.devices
|
|
.device_context
|
|
.RSSetViewports(Some(&self.resources.viewport));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn present(&mut self) -> Result<()> {
|
|
unsafe {
|
|
let result = self.resources.swap_chain.Present(1, DXGI_PRESENT(0));
|
|
// Presenting the swap chain can fail if the DirectX device was removed or reset.
|
|
if result == DXGI_ERROR_DEVICE_REMOVED || result == DXGI_ERROR_DEVICE_RESET {
|
|
let reason = self.devices.device.GetDeviceRemovedReason();
|
|
log::error!(
|
|
"DirectX device removed or reset when drawing. Reason: {:?}",
|
|
reason
|
|
);
|
|
self.handle_device_lost()?;
|
|
} else {
|
|
result.ok()?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_device_lost(&mut self) -> Result<()> {
|
|
// Here we wait a bit to ensure the the system has time to recover from the device lost state.
|
|
// If we don't wait, the final drawing result will be blank.
|
|
std::thread::sleep(std::time::Duration::from_millis(300));
|
|
let disable_direct_composition = self.direct_composition.is_none();
|
|
|
|
unsafe {
|
|
#[cfg(debug_assertions)]
|
|
report_live_objects(&self.devices.device)
|
|
.context("Failed to report live objects after device lost")
|
|
.log_err();
|
|
|
|
ManuallyDrop::drop(&mut self.resources);
|
|
self.devices.device_context.OMSetRenderTargets(None, None);
|
|
self.devices.device_context.ClearState();
|
|
self.devices.device_context.Flush();
|
|
|
|
#[cfg(debug_assertions)]
|
|
report_live_objects(&self.devices.device)
|
|
.context("Failed to report live objects after device lost")
|
|
.log_err();
|
|
|
|
drop(self.direct_composition.take());
|
|
ManuallyDrop::drop(&mut self.devices);
|
|
}
|
|
|
|
let devices = DirectXDevices::new(disable_direct_composition)
|
|
.context("Recreating DirectX devices")?;
|
|
let resources = DirectXResources::new(
|
|
&devices,
|
|
self.resources.width,
|
|
self.resources.height,
|
|
self.hwnd,
|
|
disable_direct_composition,
|
|
)?;
|
|
let globals = DirectXGlobalElements::new(&devices.device)?;
|
|
let pipelines = DirectXRenderPipelines::new(&devices.device)?;
|
|
|
|
let direct_composition = if disable_direct_composition {
|
|
None
|
|
} else {
|
|
let composition =
|
|
DirectComposition::new(devices.dxgi_device.as_ref().unwrap(), self.hwnd)?;
|
|
composition.set_swap_chain(&resources.swap_chain)?;
|
|
Some(composition)
|
|
};
|
|
|
|
self.atlas
|
|
.handle_device_lost(&devices.device, &devices.device_context);
|
|
self.devices = devices;
|
|
self.resources = resources;
|
|
self.globals = globals;
|
|
self.pipelines = pipelines;
|
|
self.direct_composition = direct_composition;
|
|
|
|
unsafe {
|
|
self.devices
|
|
.device_context
|
|
.OMSetRenderTargets(Some(&self.resources.render_target_view), None);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn draw(&mut self, scene: &Scene) -> Result<()> {
|
|
self.pre_draw()?;
|
|
for batch in scene.batches() {
|
|
match batch {
|
|
PrimitiveBatch::Shadows(shadows) => self.draw_shadows(shadows),
|
|
PrimitiveBatch::Quads(quads) => self.draw_quads(quads),
|
|
PrimitiveBatch::Paths(paths) => {
|
|
self.draw_paths_to_intermediate(paths)?;
|
|
self.draw_paths_from_intermediate(paths)
|
|
}
|
|
PrimitiveBatch::Underlines(underlines) => self.draw_underlines(underlines),
|
|
PrimitiveBatch::MonochromeSprites {
|
|
texture_id,
|
|
sprites,
|
|
} => self.draw_monochrome_sprites(texture_id, sprites),
|
|
PrimitiveBatch::PolychromeSprites {
|
|
texture_id,
|
|
sprites,
|
|
} => self.draw_polychrome_sprites(texture_id, sprites),
|
|
PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(surfaces),
|
|
}.context(format!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
|
|
scene.paths.len(),
|
|
scene.shadows.len(),
|
|
scene.quads.len(),
|
|
scene.underlines.len(),
|
|
scene.monochrome_sprites.len(),
|
|
scene.polychrome_sprites.len(),
|
|
scene.surfaces.len(),))?;
|
|
}
|
|
self.present()
|
|
}
|
|
|
|
pub(crate) fn resize(&mut self, new_size: Size<DevicePixels>) -> Result<()> {
|
|
let width = new_size.width.0.max(1) as u32;
|
|
let height = new_size.height.0.max(1) as u32;
|
|
if self.resources.width == width && self.resources.height == height {
|
|
return Ok(());
|
|
}
|
|
unsafe {
|
|
// Clear the render target before resizing
|
|
self.devices.device_context.OMSetRenderTargets(None, None);
|
|
ManuallyDrop::drop(&mut self.resources.render_target);
|
|
drop(self.resources.render_target_view[0].take().unwrap());
|
|
|
|
let result = self.resources.swap_chain.ResizeBuffers(
|
|
BUFFER_COUNT as u32,
|
|
width,
|
|
height,
|
|
RENDER_TARGET_FORMAT,
|
|
DXGI_SWAP_CHAIN_FLAG(0),
|
|
);
|
|
// Resizing the swap chain requires a call to the underlying DXGI adapter, which can return the device removed error.
|
|
// The app might have moved to a monitor that's attached to a different graphics device.
|
|
// When a graphics device is removed or reset, the desktop resolution often changes, resulting in a window size change.
|
|
match result {
|
|
Ok(_) => {}
|
|
Err(e) => {
|
|
if e.code() == DXGI_ERROR_DEVICE_REMOVED || e.code() == DXGI_ERROR_DEVICE_RESET
|
|
{
|
|
let reason = self.devices.device.GetDeviceRemovedReason();
|
|
log::error!(
|
|
"DirectX device removed or reset when resizing. Reason: {:?}",
|
|
reason
|
|
);
|
|
self.resources.width = width;
|
|
self.resources.height = height;
|
|
self.handle_device_lost()?;
|
|
return Ok(());
|
|
} else {
|
|
log::error!("Failed to resize swap chain: {:?}", e);
|
|
return Err(e.into());
|
|
}
|
|
}
|
|
}
|
|
|
|
self.resources
|
|
.recreate_resources(&self.devices, width, height)?;
|
|
self.devices
|
|
.device_context
|
|
.OMSetRenderTargets(Some(&self.resources.render_target_view), None);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn draw_shadows(&mut self, shadows: &[Shadow]) -> Result<()> {
|
|
if shadows.is_empty() {
|
|
return Ok(());
|
|
}
|
|
self.pipelines.shadow_pipeline.update_buffer(
|
|
&self.devices.device,
|
|
&self.devices.device_context,
|
|
shadows,
|
|
)?;
|
|
self.pipelines.shadow_pipeline.draw(
|
|
&self.devices.device_context,
|
|
&self.resources.viewport,
|
|
&self.globals.global_params_buffer,
|
|
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
|
|
4,
|
|
shadows.len() as u32,
|
|
)
|
|
}
|
|
|
|
fn draw_quads(&mut self, quads: &[Quad]) -> Result<()> {
|
|
if quads.is_empty() {
|
|
return Ok(());
|
|
}
|
|
self.pipelines.quad_pipeline.update_buffer(
|
|
&self.devices.device,
|
|
&self.devices.device_context,
|
|
quads,
|
|
)?;
|
|
self.pipelines.quad_pipeline.draw(
|
|
&self.devices.device_context,
|
|
&self.resources.viewport,
|
|
&self.globals.global_params_buffer,
|
|
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
|
|
4,
|
|
quads.len() as u32,
|
|
)
|
|
}
|
|
|
|
fn draw_paths_to_intermediate(&mut self, paths: &[Path<ScaledPixels>]) -> Result<()> {
|
|
if paths.is_empty() {
|
|
return Ok(());
|
|
}
|
|
|
|
// Clear intermediate MSAA texture
|
|
unsafe {
|
|
self.devices.device_context.ClearRenderTargetView(
|
|
self.resources.path_intermediate_msaa_view[0]
|
|
.as_ref()
|
|
.unwrap(),
|
|
&[0.0; 4],
|
|
);
|
|
// Set intermediate MSAA texture as render target
|
|
self.devices
|
|
.device_context
|
|
.OMSetRenderTargets(Some(&self.resources.path_intermediate_msaa_view), None);
|
|
}
|
|
|
|
// Collect all vertices and sprites for a single draw call
|
|
let mut vertices = Vec::new();
|
|
|
|
for path in paths {
|
|
vertices.extend(path.vertices.iter().map(|v| PathRasterizationSprite {
|
|
xy_position: v.xy_position,
|
|
st_position: v.st_position,
|
|
color: path.color,
|
|
bounds: path.bounds.intersect(&path.content_mask.bounds),
|
|
}));
|
|
}
|
|
|
|
self.pipelines.path_rasterization_pipeline.update_buffer(
|
|
&self.devices.device,
|
|
&self.devices.device_context,
|
|
&vertices,
|
|
)?;
|
|
self.pipelines.path_rasterization_pipeline.draw(
|
|
&self.devices.device_context,
|
|
&self.resources.viewport,
|
|
&self.globals.global_params_buffer,
|
|
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
|
|
vertices.len() as u32,
|
|
1,
|
|
)?;
|
|
|
|
// Resolve MSAA to non-MSAA intermediate texture
|
|
unsafe {
|
|
self.devices.device_context.ResolveSubresource(
|
|
&self.resources.path_intermediate_texture,
|
|
0,
|
|
&self.resources.path_intermediate_msaa_texture,
|
|
0,
|
|
RENDER_TARGET_FORMAT,
|
|
);
|
|
// Restore main render target
|
|
self.devices
|
|
.device_context
|
|
.OMSetRenderTargets(Some(&self.resources.render_target_view), None);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn draw_paths_from_intermediate(&mut self, paths: &[Path<ScaledPixels>]) -> Result<()> {
|
|
let Some(first_path) = paths.first() else {
|
|
return Ok(());
|
|
};
|
|
|
|
// When copying paths from the intermediate texture to the drawable,
|
|
// each pixel must only be copied once, in case of transparent paths.
|
|
//
|
|
// If all paths have the same draw order, then their bounds are all
|
|
// disjoint, so we can copy each path's bounds individually. If this
|
|
// batch combines different draw orders, we perform a single copy
|
|
// for a minimal spanning rect.
|
|
let sprites = if paths.last().unwrap().order == first_path.order {
|
|
paths
|
|
.iter()
|
|
.map(|path| PathSprite {
|
|
bounds: path.bounds,
|
|
})
|
|
.collect::<Vec<_>>()
|
|
} else {
|
|
let mut bounds = first_path.bounds;
|
|
for path in paths.iter().skip(1) {
|
|
bounds = bounds.union(&path.bounds);
|
|
}
|
|
vec![PathSprite { bounds }]
|
|
};
|
|
|
|
self.pipelines.path_sprite_pipeline.update_buffer(
|
|
&self.devices.device,
|
|
&self.devices.device_context,
|
|
&sprites,
|
|
)?;
|
|
|
|
// Draw the sprites with the path texture
|
|
self.pipelines.path_sprite_pipeline.draw_with_texture(
|
|
&self.devices.device_context,
|
|
&self.resources.path_intermediate_srv,
|
|
&self.resources.viewport,
|
|
&self.globals.global_params_buffer,
|
|
&self.globals.sampler,
|
|
sprites.len() as u32,
|
|
)
|
|
}
|
|
|
|
fn draw_underlines(&mut self, underlines: &[Underline]) -> Result<()> {
|
|
if underlines.is_empty() {
|
|
return Ok(());
|
|
}
|
|
self.pipelines.underline_pipeline.update_buffer(
|
|
&self.devices.device,
|
|
&self.devices.device_context,
|
|
underlines,
|
|
)?;
|
|
self.pipelines.underline_pipeline.draw(
|
|
&self.devices.device_context,
|
|
&self.resources.viewport,
|
|
&self.globals.global_params_buffer,
|
|
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
|
|
4,
|
|
underlines.len() as u32,
|
|
)
|
|
}
|
|
|
|
fn draw_monochrome_sprites(
|
|
&mut self,
|
|
texture_id: AtlasTextureId,
|
|
sprites: &[MonochromeSprite],
|
|
) -> Result<()> {
|
|
if sprites.is_empty() {
|
|
return Ok(());
|
|
}
|
|
self.pipelines.mono_sprites.update_buffer(
|
|
&self.devices.device,
|
|
&self.devices.device_context,
|
|
sprites,
|
|
)?;
|
|
let texture_view = self.atlas.get_texture_view(texture_id);
|
|
self.pipelines.mono_sprites.draw_with_texture(
|
|
&self.devices.device_context,
|
|
&texture_view,
|
|
&self.resources.viewport,
|
|
&self.globals.global_params_buffer,
|
|
&self.globals.sampler,
|
|
sprites.len() as u32,
|
|
)
|
|
}
|
|
|
|
fn draw_polychrome_sprites(
|
|
&mut self,
|
|
texture_id: AtlasTextureId,
|
|
sprites: &[PolychromeSprite],
|
|
) -> Result<()> {
|
|
if sprites.is_empty() {
|
|
return Ok(());
|
|
}
|
|
self.pipelines.poly_sprites.update_buffer(
|
|
&self.devices.device,
|
|
&self.devices.device_context,
|
|
sprites,
|
|
)?;
|
|
let texture_view = self.atlas.get_texture_view(texture_id);
|
|
self.pipelines.poly_sprites.draw_with_texture(
|
|
&self.devices.device_context,
|
|
&texture_view,
|
|
&self.resources.viewport,
|
|
&self.globals.global_params_buffer,
|
|
&self.globals.sampler,
|
|
sprites.len() as u32,
|
|
)
|
|
}
|
|
|
|
fn draw_surfaces(&mut self, surfaces: &[PaintSurface]) -> Result<()> {
|
|
if surfaces.is_empty() {
|
|
return Ok(());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn gpu_specs(&self) -> Result<GpuSpecs> {
|
|
let desc = unsafe { self.devices.adapter.GetDesc1() }?;
|
|
let is_software_emulated = (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32) != 0;
|
|
let device_name = String::from_utf16_lossy(&desc.Description)
|
|
.trim_matches(char::from(0))
|
|
.to_string();
|
|
let driver_name = match desc.VendorId {
|
|
0x10DE => "NVIDIA Corporation".to_string(),
|
|
0x1002 => "AMD Corporation".to_string(),
|
|
0x8086 => "Intel Corporation".to_string(),
|
|
id => format!("Unknown Vendor (ID: {:#X})", id),
|
|
};
|
|
let driver_version = match desc.VendorId {
|
|
0x10DE => nvidia::get_driver_version(),
|
|
0x1002 => amd::get_driver_version(),
|
|
// For Intel and other vendors, we use the DXGI API to get the driver version.
|
|
_ => dxgi::get_driver_version(&self.devices.adapter),
|
|
}
|
|
.context("Failed to get gpu driver info")
|
|
.log_err()
|
|
.unwrap_or("Unknown Driver".to_string());
|
|
Ok(GpuSpecs {
|
|
is_software_emulated,
|
|
device_name,
|
|
driver_name,
|
|
driver_info: driver_version,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl DirectXResources {
|
|
pub fn new(
|
|
devices: &DirectXDevices,
|
|
width: u32,
|
|
height: u32,
|
|
hwnd: HWND,
|
|
disable_direct_composition: bool,
|
|
) -> Result<ManuallyDrop<Self>> {
|
|
let swap_chain = if disable_direct_composition {
|
|
create_swap_chain(&devices.dxgi_factory, &devices.device, hwnd, width, height)?
|
|
} else {
|
|
create_swap_chain_for_composition(
|
|
&devices.dxgi_factory,
|
|
&devices.device,
|
|
width,
|
|
height,
|
|
)?
|
|
};
|
|
|
|
let (
|
|
render_target,
|
|
render_target_view,
|
|
path_intermediate_texture,
|
|
path_intermediate_srv,
|
|
path_intermediate_msaa_texture,
|
|
path_intermediate_msaa_view,
|
|
viewport,
|
|
) = create_resources(devices, &swap_chain, width, height)?;
|
|
set_rasterizer_state(&devices.device, &devices.device_context)?;
|
|
|
|
Ok(ManuallyDrop::new(Self {
|
|
swap_chain,
|
|
render_target,
|
|
render_target_view,
|
|
path_intermediate_texture,
|
|
path_intermediate_msaa_texture,
|
|
path_intermediate_msaa_view,
|
|
path_intermediate_srv,
|
|
viewport,
|
|
width,
|
|
height,
|
|
}))
|
|
}
|
|
|
|
#[inline]
|
|
fn recreate_resources(
|
|
&mut self,
|
|
devices: &DirectXDevices,
|
|
width: u32,
|
|
height: u32,
|
|
) -> Result<()> {
|
|
let (
|
|
render_target,
|
|
render_target_view,
|
|
path_intermediate_texture,
|
|
path_intermediate_srv,
|
|
path_intermediate_msaa_texture,
|
|
path_intermediate_msaa_view,
|
|
viewport,
|
|
) = create_resources(devices, &self.swap_chain, width, height)?;
|
|
self.render_target = render_target;
|
|
self.render_target_view = render_target_view;
|
|
self.path_intermediate_texture = path_intermediate_texture;
|
|
self.path_intermediate_msaa_texture = path_intermediate_msaa_texture;
|
|
self.path_intermediate_msaa_view = path_intermediate_msaa_view;
|
|
self.path_intermediate_srv = path_intermediate_srv;
|
|
self.viewport = viewport;
|
|
self.width = width;
|
|
self.height = height;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl DirectXRenderPipelines {
|
|
pub fn new(device: &ID3D11Device) -> Result<Self> {
|
|
let shadow_pipeline = PipelineState::new(
|
|
device,
|
|
"shadow_pipeline",
|
|
ShaderModule::Shadow,
|
|
4,
|
|
create_blend_state(device)?,
|
|
)?;
|
|
let quad_pipeline = PipelineState::new(
|
|
device,
|
|
"quad_pipeline",
|
|
ShaderModule::Quad,
|
|
64,
|
|
create_blend_state(device)?,
|
|
)?;
|
|
let path_rasterization_pipeline = PipelineState::new(
|
|
device,
|
|
"path_rasterization_pipeline",
|
|
ShaderModule::PathRasterization,
|
|
32,
|
|
create_blend_state_for_path_rasterization(device)?,
|
|
)?;
|
|
let path_sprite_pipeline = PipelineState::new(
|
|
device,
|
|
"path_sprite_pipeline",
|
|
ShaderModule::PathSprite,
|
|
4,
|
|
create_blend_state_for_path_sprite(device)?,
|
|
)?;
|
|
let underline_pipeline = PipelineState::new(
|
|
device,
|
|
"underline_pipeline",
|
|
ShaderModule::Underline,
|
|
4,
|
|
create_blend_state(device)?,
|
|
)?;
|
|
let mono_sprites = PipelineState::new(
|
|
device,
|
|
"monochrome_sprite_pipeline",
|
|
ShaderModule::MonochromeSprite,
|
|
512,
|
|
create_blend_state(device)?,
|
|
)?;
|
|
let poly_sprites = PipelineState::new(
|
|
device,
|
|
"polychrome_sprite_pipeline",
|
|
ShaderModule::PolychromeSprite,
|
|
16,
|
|
create_blend_state(device)?,
|
|
)?;
|
|
|
|
Ok(Self {
|
|
shadow_pipeline,
|
|
quad_pipeline,
|
|
path_rasterization_pipeline,
|
|
path_sprite_pipeline,
|
|
underline_pipeline,
|
|
mono_sprites,
|
|
poly_sprites,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl DirectComposition {
|
|
pub fn new(dxgi_device: &IDXGIDevice, hwnd: HWND) -> Result<Self> {
|
|
let comp_device = get_comp_device(&dxgi_device)?;
|
|
let comp_target = unsafe { comp_device.CreateTargetForHwnd(hwnd, true) }?;
|
|
let comp_visual = unsafe { comp_device.CreateVisual() }?;
|
|
|
|
Ok(Self {
|
|
comp_device,
|
|
comp_target,
|
|
comp_visual,
|
|
})
|
|
}
|
|
|
|
pub fn set_swap_chain(&self, swap_chain: &IDXGISwapChain1) -> Result<()> {
|
|
unsafe {
|
|
self.comp_visual.SetContent(swap_chain)?;
|
|
self.comp_target.SetRoot(&self.comp_visual)?;
|
|
self.comp_device.Commit()?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl DirectXGlobalElements {
|
|
pub fn new(device: &ID3D11Device) -> Result<Self> {
|
|
let global_params_buffer = unsafe {
|
|
let desc = D3D11_BUFFER_DESC {
|
|
ByteWidth: std::mem::size_of::<GlobalParams>() as u32,
|
|
Usage: D3D11_USAGE_DYNAMIC,
|
|
BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32,
|
|
CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
|
|
..Default::default()
|
|
};
|
|
let mut buffer = None;
|
|
device.CreateBuffer(&desc, None, Some(&mut buffer))?;
|
|
[buffer]
|
|
};
|
|
|
|
let sampler = unsafe {
|
|
let desc = D3D11_SAMPLER_DESC {
|
|
Filter: D3D11_FILTER_MIN_MAG_MIP_LINEAR,
|
|
AddressU: D3D11_TEXTURE_ADDRESS_WRAP,
|
|
AddressV: D3D11_TEXTURE_ADDRESS_WRAP,
|
|
AddressW: D3D11_TEXTURE_ADDRESS_WRAP,
|
|
MipLODBias: 0.0,
|
|
MaxAnisotropy: 1,
|
|
ComparisonFunc: D3D11_COMPARISON_ALWAYS,
|
|
BorderColor: [0.0; 4],
|
|
MinLOD: 0.0,
|
|
MaxLOD: D3D11_FLOAT32_MAX,
|
|
};
|
|
let mut output = None;
|
|
device.CreateSamplerState(&desc, Some(&mut output))?;
|
|
[output]
|
|
};
|
|
|
|
Ok(Self {
|
|
global_params_buffer,
|
|
sampler,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
#[repr(C)]
|
|
struct GlobalParams {
|
|
viewport_size: [f32; 2],
|
|
premultiplied_alpha: u32,
|
|
_pad: u32,
|
|
}
|
|
|
|
struct PipelineState<T> {
|
|
label: &'static str,
|
|
vertex: ID3D11VertexShader,
|
|
fragment: ID3D11PixelShader,
|
|
buffer: ID3D11Buffer,
|
|
buffer_size: usize,
|
|
view: [Option<ID3D11ShaderResourceView>; 1],
|
|
blend_state: ID3D11BlendState,
|
|
_marker: std::marker::PhantomData<T>,
|
|
}
|
|
|
|
impl<T> PipelineState<T> {
|
|
fn new(
|
|
device: &ID3D11Device,
|
|
label: &'static str,
|
|
shader_module: ShaderModule,
|
|
buffer_size: usize,
|
|
blend_state: ID3D11BlendState,
|
|
) -> Result<Self> {
|
|
let vertex = {
|
|
let raw_shader = RawShaderBytes::new(shader_module, ShaderTarget::Vertex)?;
|
|
create_vertex_shader(device, raw_shader.as_bytes())?
|
|
};
|
|
let fragment = {
|
|
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)?;
|
|
|
|
Ok(PipelineState {
|
|
label,
|
|
vertex,
|
|
fragment,
|
|
buffer,
|
|
buffer_size,
|
|
view,
|
|
blend_state,
|
|
_marker: std::marker::PhantomData,
|
|
})
|
|
}
|
|
|
|
fn update_buffer(
|
|
&mut self,
|
|
device: &ID3D11Device,
|
|
device_context: &ID3D11DeviceContext,
|
|
data: &[T],
|
|
) -> Result<()> {
|
|
if self.buffer_size < data.len() {
|
|
let new_buffer_size = data.len().next_power_of_two();
|
|
log::info!(
|
|
"Updating {} buffer size from {} to {}",
|
|
self.label,
|
|
self.buffer_size,
|
|
new_buffer_size
|
|
);
|
|
let buffer = create_buffer(device, std::mem::size_of::<T>(), new_buffer_size)?;
|
|
let view = create_buffer_view(device, &buffer)?;
|
|
self.buffer = buffer;
|
|
self.view = view;
|
|
self.buffer_size = new_buffer_size;
|
|
}
|
|
update_buffer(device_context, &self.buffer, data)
|
|
}
|
|
|
|
fn draw(
|
|
&self,
|
|
device_context: &ID3D11DeviceContext,
|
|
viewport: &[D3D11_VIEWPORT],
|
|
global_params: &[Option<ID3D11Buffer>],
|
|
topology: D3D_PRIMITIVE_TOPOLOGY,
|
|
vertex_count: u32,
|
|
instance_count: u32,
|
|
) -> Result<()> {
|
|
set_pipeline_state(
|
|
device_context,
|
|
&self.view,
|
|
topology,
|
|
viewport,
|
|
&self.vertex,
|
|
&self.fragment,
|
|
global_params,
|
|
&self.blend_state,
|
|
);
|
|
unsafe {
|
|
device_context.DrawInstanced(vertex_count, instance_count, 0, 0);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn draw_with_texture(
|
|
&self,
|
|
device_context: &ID3D11DeviceContext,
|
|
texture: &[Option<ID3D11ShaderResourceView>],
|
|
viewport: &[D3D11_VIEWPORT],
|
|
global_params: &[Option<ID3D11Buffer>],
|
|
sampler: &[Option<ID3D11SamplerState>],
|
|
instance_count: u32,
|
|
) -> Result<()> {
|
|
set_pipeline_state(
|
|
device_context,
|
|
&self.view,
|
|
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
|
|
viewport,
|
|
&self.vertex,
|
|
&self.fragment,
|
|
global_params,
|
|
&self.blend_state,
|
|
);
|
|
unsafe {
|
|
device_context.PSSetSamplers(0, Some(sampler));
|
|
device_context.VSSetShaderResources(0, Some(texture));
|
|
device_context.PSSetShaderResources(0, Some(texture));
|
|
|
|
device_context.DrawInstanced(4, instance_count, 0, 0);
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
#[repr(C)]
|
|
struct PathRasterizationSprite {
|
|
xy_position: Point<ScaledPixels>,
|
|
st_position: Point<f32>,
|
|
color: Background,
|
|
bounds: Bounds<ScaledPixels>,
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
#[repr(C)]
|
|
struct PathSprite {
|
|
bounds: Bounds<ScaledPixels>,
|
|
}
|
|
|
|
impl Drop for DirectXRenderer {
|
|
fn drop(&mut self) {
|
|
#[cfg(debug_assertions)]
|
|
report_live_objects(&self.devices.device).ok();
|
|
unsafe {
|
|
ManuallyDrop::drop(&mut self.devices);
|
|
ManuallyDrop::drop(&mut self.resources);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for DirectXResources {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
ManuallyDrop::drop(&mut self.render_target);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn get_dxgi_factory() -> Result<IDXGIFactory6> {
|
|
#[cfg(debug_assertions)]
|
|
let factory_flag = if unsafe { DXGIGetDebugInterface1::<IDXGIInfoQueue>(0) }
|
|
.log_err()
|
|
.is_some()
|
|
{
|
|
DXGI_CREATE_FACTORY_DEBUG
|
|
} else {
|
|
log::warn!(
|
|
"Failed to get DXGI debug interface. DirectX debugging features will be disabled."
|
|
);
|
|
DXGI_CREATE_FACTORY_FLAGS::default()
|
|
};
|
|
#[cfg(not(debug_assertions))]
|
|
let factory_flag = DXGI_CREATE_FACTORY_FLAGS::default();
|
|
unsafe { Ok(CreateDXGIFactory2(factory_flag)?) }
|
|
}
|
|
|
|
fn get_adapter(dxgi_factory: &IDXGIFactory6) -> Result<IDXGIAdapter1> {
|
|
for adapter_index in 0.. {
|
|
let adapter: IDXGIAdapter1 = unsafe {
|
|
dxgi_factory
|
|
.EnumAdapterByGpuPreference(adapter_index, DXGI_GPU_PREFERENCE_MINIMUM_POWER)
|
|
}?;
|
|
if let Ok(desc) = unsafe { adapter.GetDesc1() } {
|
|
let gpu_name = String::from_utf16_lossy(&desc.Description)
|
|
.trim_matches(char::from(0))
|
|
.to_string();
|
|
log::info!("Using GPU: {}", gpu_name);
|
|
}
|
|
// Check to see whether the adapter supports Direct3D 11, but don't
|
|
// create the actual device yet.
|
|
if get_device(&adapter, None, None, None).log_err().is_some() {
|
|
return Ok(adapter);
|
|
}
|
|
}
|
|
|
|
unreachable!()
|
|
}
|
|
|
|
fn get_device(
|
|
adapter: &IDXGIAdapter1,
|
|
device: Option<*mut Option<ID3D11Device>>,
|
|
context: Option<*mut Option<ID3D11DeviceContext>>,
|
|
feature_level: Option<*mut D3D_FEATURE_LEVEL>,
|
|
) -> Result<()> {
|
|
#[cfg(debug_assertions)]
|
|
let device_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG;
|
|
#[cfg(not(debug_assertions))]
|
|
let device_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
|
unsafe {
|
|
D3D11CreateDevice(
|
|
adapter,
|
|
D3D_DRIVER_TYPE_UNKNOWN,
|
|
HMODULE::default(),
|
|
device_flags,
|
|
// 4x MSAA is required for Direct3D Feature Level 10.1 or better
|
|
Some(&[
|
|
D3D_FEATURE_LEVEL_11_1,
|
|
D3D_FEATURE_LEVEL_11_0,
|
|
D3D_FEATURE_LEVEL_10_1,
|
|
]),
|
|
D3D11_SDK_VERSION,
|
|
device,
|
|
feature_level,
|
|
context,
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn get_comp_device(dxgi_device: &IDXGIDevice) -> Result<IDCompositionDevice> {
|
|
Ok(unsafe { DCompositionCreateDevice(dxgi_device)? })
|
|
}
|
|
|
|
fn create_swap_chain_for_composition(
|
|
dxgi_factory: &IDXGIFactory6,
|
|
device: &ID3D11Device,
|
|
width: u32,
|
|
height: u32,
|
|
) -> Result<IDXGISwapChain1> {
|
|
let desc = DXGI_SWAP_CHAIN_DESC1 {
|
|
Width: width,
|
|
Height: height,
|
|
Format: RENDER_TARGET_FORMAT,
|
|
Stereo: false.into(),
|
|
SampleDesc: DXGI_SAMPLE_DESC {
|
|
Count: 1,
|
|
Quality: 0,
|
|
},
|
|
BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
|
|
BufferCount: BUFFER_COUNT as u32,
|
|
// Composition SwapChains only support the DXGI_SCALING_STRETCH Scaling.
|
|
Scaling: DXGI_SCALING_STRETCH,
|
|
SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
|
|
AlphaMode: DXGI_ALPHA_MODE_IGNORE,
|
|
Flags: 0,
|
|
};
|
|
Ok(unsafe { dxgi_factory.CreateSwapChainForComposition(device, &desc, None)? })
|
|
}
|
|
|
|
fn create_swap_chain(
|
|
dxgi_factory: &IDXGIFactory6,
|
|
device: &ID3D11Device,
|
|
hwnd: HWND,
|
|
width: u32,
|
|
height: u32,
|
|
) -> Result<IDXGISwapChain1> {
|
|
use windows::Win32::Graphics::Dxgi::DXGI_MWA_NO_ALT_ENTER;
|
|
|
|
let desc = DXGI_SWAP_CHAIN_DESC1 {
|
|
Width: width,
|
|
Height: height,
|
|
Format: RENDER_TARGET_FORMAT,
|
|
Stereo: false.into(),
|
|
SampleDesc: DXGI_SAMPLE_DESC {
|
|
Count: 1,
|
|
Quality: 0,
|
|
},
|
|
BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
|
|
BufferCount: BUFFER_COUNT as u32,
|
|
Scaling: DXGI_SCALING_NONE,
|
|
SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
|
|
AlphaMode: DXGI_ALPHA_MODE_IGNORE,
|
|
Flags: 0,
|
|
};
|
|
let swap_chain =
|
|
unsafe { dxgi_factory.CreateSwapChainForHwnd(device, hwnd, &desc, None, None) }?;
|
|
unsafe { dxgi_factory.MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER) }?;
|
|
Ok(swap_chain)
|
|
}
|
|
|
|
#[inline]
|
|
fn create_resources(
|
|
devices: &DirectXDevices,
|
|
swap_chain: &IDXGISwapChain1,
|
|
width: u32,
|
|
height: u32,
|
|
) -> Result<(
|
|
ManuallyDrop<ID3D11Texture2D>,
|
|
[Option<ID3D11RenderTargetView>; 1],
|
|
ID3D11Texture2D,
|
|
[Option<ID3D11ShaderResourceView>; 1],
|
|
ID3D11Texture2D,
|
|
[Option<ID3D11RenderTargetView>; 1],
|
|
[D3D11_VIEWPORT; 1],
|
|
)> {
|
|
let (render_target, render_target_view) =
|
|
create_render_target_and_its_view(&swap_chain, &devices.device)?;
|
|
let (path_intermediate_texture, path_intermediate_srv) =
|
|
create_path_intermediate_texture(&devices.device, width, height)?;
|
|
let (path_intermediate_msaa_texture, path_intermediate_msaa_view) =
|
|
create_path_intermediate_msaa_texture_and_view(&devices.device, width, height)?;
|
|
let viewport = set_viewport(&devices.device_context, width as f32, height as f32);
|
|
Ok((
|
|
render_target,
|
|
render_target_view,
|
|
path_intermediate_texture,
|
|
path_intermediate_srv,
|
|
path_intermediate_msaa_texture,
|
|
path_intermediate_msaa_view,
|
|
viewport,
|
|
))
|
|
}
|
|
|
|
#[inline]
|
|
fn create_render_target_and_its_view(
|
|
swap_chain: &IDXGISwapChain1,
|
|
device: &ID3D11Device,
|
|
) -> Result<(
|
|
ManuallyDrop<ID3D11Texture2D>,
|
|
[Option<ID3D11RenderTargetView>; 1],
|
|
)> {
|
|
let render_target: ID3D11Texture2D = unsafe { swap_chain.GetBuffer(0) }?;
|
|
let mut render_target_view = None;
|
|
unsafe { device.CreateRenderTargetView(&render_target, None, Some(&mut render_target_view))? };
|
|
Ok((
|
|
ManuallyDrop::new(render_target),
|
|
[Some(render_target_view.unwrap())],
|
|
))
|
|
}
|
|
|
|
#[inline]
|
|
fn create_path_intermediate_texture(
|
|
device: &ID3D11Device,
|
|
width: u32,
|
|
height: u32,
|
|
) -> Result<(ID3D11Texture2D, [Option<ID3D11ShaderResourceView>; 1])> {
|
|
let texture = unsafe {
|
|
let mut output = None;
|
|
let desc = D3D11_TEXTURE2D_DESC {
|
|
Width: width,
|
|
Height: height,
|
|
MipLevels: 1,
|
|
ArraySize: 1,
|
|
Format: RENDER_TARGET_FORMAT,
|
|
SampleDesc: DXGI_SAMPLE_DESC {
|
|
Count: 1,
|
|
Quality: 0,
|
|
},
|
|
Usage: D3D11_USAGE_DEFAULT,
|
|
BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32,
|
|
CPUAccessFlags: 0,
|
|
MiscFlags: 0,
|
|
};
|
|
device.CreateTexture2D(&desc, None, Some(&mut output))?;
|
|
output.unwrap()
|
|
};
|
|
|
|
let mut shader_resource_view = None;
|
|
unsafe { device.CreateShaderResourceView(&texture, None, Some(&mut shader_resource_view))? };
|
|
|
|
Ok((texture, [Some(shader_resource_view.unwrap())]))
|
|
}
|
|
|
|
#[inline]
|
|
fn create_path_intermediate_msaa_texture_and_view(
|
|
device: &ID3D11Device,
|
|
width: u32,
|
|
height: u32,
|
|
) -> Result<(ID3D11Texture2D, [Option<ID3D11RenderTargetView>; 1])> {
|
|
let msaa_texture = unsafe {
|
|
let mut output = None;
|
|
let desc = D3D11_TEXTURE2D_DESC {
|
|
Width: width,
|
|
Height: height,
|
|
MipLevels: 1,
|
|
ArraySize: 1,
|
|
Format: RENDER_TARGET_FORMAT,
|
|
SampleDesc: DXGI_SAMPLE_DESC {
|
|
Count: PATH_MULTISAMPLE_COUNT,
|
|
Quality: D3D11_STANDARD_MULTISAMPLE_PATTERN.0 as u32,
|
|
},
|
|
Usage: D3D11_USAGE_DEFAULT,
|
|
BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32,
|
|
CPUAccessFlags: 0,
|
|
MiscFlags: 0,
|
|
};
|
|
device.CreateTexture2D(&desc, None, Some(&mut output))?;
|
|
output.unwrap()
|
|
};
|
|
let mut msaa_view = None;
|
|
unsafe { device.CreateRenderTargetView(&msaa_texture, None, Some(&mut msaa_view))? };
|
|
Ok((msaa_texture, [Some(msaa_view.unwrap())]))
|
|
}
|
|
|
|
#[inline]
|
|
fn set_viewport(
|
|
device_context: &ID3D11DeviceContext,
|
|
width: f32,
|
|
height: f32,
|
|
) -> [D3D11_VIEWPORT; 1] {
|
|
let viewport = [D3D11_VIEWPORT {
|
|
TopLeftX: 0.0,
|
|
TopLeftY: 0.0,
|
|
Width: width,
|
|
Height: height,
|
|
MinDepth: 0.0,
|
|
MaxDepth: 1.0,
|
|
}];
|
|
unsafe { device_context.RSSetViewports(Some(&viewport)) };
|
|
viewport
|
|
}
|
|
|
|
#[inline]
|
|
fn set_rasterizer_state(device: &ID3D11Device, device_context: &ID3D11DeviceContext) -> Result<()> {
|
|
let desc = D3D11_RASTERIZER_DESC {
|
|
FillMode: D3D11_FILL_SOLID,
|
|
CullMode: D3D11_CULL_NONE,
|
|
FrontCounterClockwise: false.into(),
|
|
DepthBias: 0,
|
|
DepthBiasClamp: 0.0,
|
|
SlopeScaledDepthBias: 0.0,
|
|
DepthClipEnable: true.into(),
|
|
ScissorEnable: false.into(),
|
|
MultisampleEnable: true.into(),
|
|
AntialiasedLineEnable: false.into(),
|
|
};
|
|
let rasterizer_state = unsafe {
|
|
let mut state = None;
|
|
device.CreateRasterizerState(&desc, Some(&mut state))?;
|
|
state.unwrap()
|
|
};
|
|
unsafe { device_context.RSSetState(&rasterizer_state) };
|
|
Ok(())
|
|
}
|
|
|
|
// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_blend_desc
|
|
#[inline]
|
|
fn create_blend_state(device: &ID3D11Device) -> Result<ID3D11BlendState> {
|
|
// If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
|
|
// device performs the blend in linear space, which is ideal.
|
|
let mut desc = D3D11_BLEND_DESC::default();
|
|
desc.RenderTarget[0].BlendEnable = true.into();
|
|
desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
|
|
desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
|
desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
|
|
desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
|
desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
|
|
desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
|
|
desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
|
|
unsafe {
|
|
let mut state = None;
|
|
device.CreateBlendState(&desc, Some(&mut state))?;
|
|
Ok(state.unwrap())
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn create_blend_state_for_path_rasterization(device: &ID3D11Device) -> Result<ID3D11BlendState> {
|
|
// If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
|
|
// device performs the blend in linear space, which is ideal.
|
|
let mut desc = D3D11_BLEND_DESC::default();
|
|
desc.RenderTarget[0].BlendEnable = true.into();
|
|
desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
|
|
desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
|
desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
|
|
desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
|
desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
|
|
desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
|
|
desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
|
|
unsafe {
|
|
let mut state = None;
|
|
device.CreateBlendState(&desc, Some(&mut state))?;
|
|
Ok(state.unwrap())
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn create_blend_state_for_path_sprite(device: &ID3D11Device) -> Result<ID3D11BlendState> {
|
|
// If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
|
|
// device performs the blend in linear space, which is ideal.
|
|
let mut desc = D3D11_BLEND_DESC::default();
|
|
desc.RenderTarget[0].BlendEnable = true.into();
|
|
desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
|
|
desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
|
desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
|
|
desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
|
desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
|
|
desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE;
|
|
desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
|
|
unsafe {
|
|
let mut state = None;
|
|
device.CreateBlendState(&desc, Some(&mut state))?;
|
|
Ok(state.unwrap())
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn create_vertex_shader(device: &ID3D11Device, bytes: &[u8]) -> Result<ID3D11VertexShader> {
|
|
unsafe {
|
|
let mut shader = None;
|
|
device.CreateVertexShader(bytes, None, Some(&mut shader))?;
|
|
Ok(shader.unwrap())
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn create_fragment_shader(device: &ID3D11Device, bytes: &[u8]) -> Result<ID3D11PixelShader> {
|
|
unsafe {
|
|
let mut shader = None;
|
|
device.CreatePixelShader(bytes, None, Some(&mut shader))?;
|
|
Ok(shader.unwrap())
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn create_buffer(
|
|
device: &ID3D11Device,
|
|
element_size: usize,
|
|
buffer_size: usize,
|
|
) -> Result<ID3D11Buffer> {
|
|
let desc = D3D11_BUFFER_DESC {
|
|
ByteWidth: (element_size * buffer_size) as u32,
|
|
Usage: D3D11_USAGE_DYNAMIC,
|
|
BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32,
|
|
CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
|
|
MiscFlags: D3D11_RESOURCE_MISC_BUFFER_STRUCTURED.0 as u32,
|
|
StructureByteStride: element_size as u32,
|
|
};
|
|
let mut buffer = None;
|
|
unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?;
|
|
Ok(buffer.unwrap())
|
|
}
|
|
|
|
#[inline]
|
|
fn create_buffer_view(
|
|
device: &ID3D11Device,
|
|
buffer: &ID3D11Buffer,
|
|
) -> Result<[Option<ID3D11ShaderResourceView>; 1]> {
|
|
let mut view = None;
|
|
unsafe { device.CreateShaderResourceView(buffer, None, Some(&mut view)) }?;
|
|
Ok([view])
|
|
}
|
|
|
|
#[inline]
|
|
fn update_buffer<T>(
|
|
device_context: &ID3D11DeviceContext,
|
|
buffer: &ID3D11Buffer,
|
|
data: &[T],
|
|
) -> Result<()> {
|
|
unsafe {
|
|
let mut dest = std::mem::zeroed();
|
|
device_context.Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, Some(&mut dest))?;
|
|
std::ptr::copy_nonoverlapping(data.as_ptr(), dest.pData as _, data.len());
|
|
device_context.Unmap(buffer, 0);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn set_pipeline_state(
|
|
device_context: &ID3D11DeviceContext,
|
|
buffer_view: &[Option<ID3D11ShaderResourceView>],
|
|
topology: D3D_PRIMITIVE_TOPOLOGY,
|
|
viewport: &[D3D11_VIEWPORT],
|
|
vertex_shader: &ID3D11VertexShader,
|
|
fragment_shader: &ID3D11PixelShader,
|
|
global_params: &[Option<ID3D11Buffer>],
|
|
blend_state: &ID3D11BlendState,
|
|
) {
|
|
unsafe {
|
|
device_context.VSSetShaderResources(1, Some(buffer_view));
|
|
device_context.PSSetShaderResources(1, Some(buffer_view));
|
|
device_context.IASetPrimitiveTopology(topology);
|
|
device_context.RSSetViewports(Some(viewport));
|
|
device_context.VSSetShader(vertex_shader, None);
|
|
device_context.PSSetShader(fragment_shader, None);
|
|
device_context.VSSetConstantBuffers(0, Some(global_params));
|
|
device_context.PSSetConstantBuffers(0, Some(global_params));
|
|
device_context.OMSetBlendState(blend_state, None, 0xFFFFFFFF);
|
|
}
|
|
}
|
|
|
|
#[cfg(debug_assertions)]
|
|
fn report_live_objects(device: &ID3D11Device) -> Result<()> {
|
|
let debug_device: ID3D11Debug = device.cast()?;
|
|
unsafe {
|
|
debug_device.ReportLiveDeviceObjects(D3D11_RLDO_DETAIL)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
const BUFFER_COUNT: usize = 3;
|
|
|
|
pub(crate) mod shader_resources {
|
|
use anyhow::Result;
|
|
|
|
#[cfg(debug_assertions)]
|
|
use windows::{
|
|
Win32::Graphics::Direct3D::{
|
|
Fxc::{D3DCOMPILE_DEBUG, D3DCOMPILE_SKIP_OPTIMIZATION, D3DCompileFromFile},
|
|
ID3DBlob,
|
|
},
|
|
core::{HSTRING, PCSTR},
|
|
};
|
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
pub(crate) enum ShaderModule {
|
|
Quad,
|
|
Shadow,
|
|
Underline,
|
|
PathRasterization,
|
|
PathSprite,
|
|
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::PathRasterization => match target {
|
|
ShaderTarget::Vertex => PATH_RASTERIZATION_VERTEX_BYTES,
|
|
ShaderTarget::Fragment => PATH_RASTERIZATION_FRAGMENT_BYTES,
|
|
},
|
|
ShaderModule::PathSprite => match target {
|
|
ShaderTarget::Vertex => PATH_SPRITE_VERTEX_BYTES,
|
|
ShaderTarget::Fragment => PATH_SPRITE_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 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_4_1\0",
|
|
ShaderTarget::Fragment => "ps_4_1\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/{}", shader_name))
|
|
.canonicalize()?;
|
|
|
|
let entry_point = PCSTR::from_raw(entry.as_ptr());
|
|
let target_cstr = PCSTR::from_raw(target.as_ptr());
|
|
|
|
let ret = D3DCompileFromFile(
|
|
&HSTRING::from(shader_path.to_str().unwrap()),
|
|
None,
|
|
None,
|
|
entry_point,
|
|
target_cstr,
|
|
D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION,
|
|
0,
|
|
&mut compile_blob,
|
|
Some(&mut error_blob),
|
|
);
|
|
if ret.is_err() {
|
|
let Some(error_blob) = error_blob else {
|
|
return Err(anyhow::anyhow!("{ret:?}"));
|
|
};
|
|
|
|
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::PathRasterization => "path_rasterization",
|
|
ShaderModule::PathSprite => "path_sprite",
|
|
ShaderModule::MonochromeSprite => "monochrome_sprite",
|
|
ShaderModule::PolychromeSprite => "polychrome_sprite",
|
|
ShaderModule::EmojiRasterization => "emoji_rasterization",
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mod nvidia {
|
|
use std::{
|
|
ffi::CStr,
|
|
os::raw::{c_char, c_int, c_uint},
|
|
};
|
|
|
|
use anyhow::{Context, Result};
|
|
use windows::{
|
|
Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA},
|
|
core::s,
|
|
};
|
|
|
|
// https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L180
|
|
const NVAPI_SHORT_STRING_MAX: usize = 64;
|
|
|
|
// https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L235
|
|
#[allow(non_camel_case_types)]
|
|
type NvAPI_ShortString = [c_char; NVAPI_SHORT_STRING_MAX];
|
|
|
|
// https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L447
|
|
#[allow(non_camel_case_types)]
|
|
type NvAPI_SYS_GetDriverAndBranchVersion_t = unsafe extern "C" fn(
|
|
driver_version: *mut c_uint,
|
|
build_branch_string: *mut NvAPI_ShortString,
|
|
) -> c_int;
|
|
|
|
pub(super) fn get_driver_version() -> Result<String> {
|
|
unsafe {
|
|
// Try to load the NVIDIA driver DLL
|
|
#[cfg(target_pointer_width = "64")]
|
|
let nvidia_dll = LoadLibraryA(s!("nvapi64.dll")).context("Can't load nvapi64.dll")?;
|
|
#[cfg(target_pointer_width = "32")]
|
|
let nvidia_dll = LoadLibraryA(s!("nvapi.dll")).context("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);
|
|
|
|
// https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_interface.h#L41
|
|
let nvapi_get_driver_version_ptr = nvapi_query(0x2926aaad);
|
|
if nvapi_get_driver_version_ptr.is_null() {
|
|
anyhow::bail!("Failed to get NVIDIA driver version function pointer");
|
|
}
|
|
let nvapi_get_driver_version: NvAPI_SYS_GetDriverAndBranchVersion_t =
|
|
std::mem::transmute(nvapi_get_driver_version_ptr);
|
|
|
|
let mut driver_version: c_uint = 0;
|
|
let mut build_branch_string: NvAPI_ShortString = [0; NVAPI_SHORT_STRING_MAX];
|
|
let result = nvapi_get_driver_version(
|
|
&mut driver_version as *mut c_uint,
|
|
&mut build_branch_string as *mut NvAPI_ShortString,
|
|
);
|
|
|
|
if result != 0 {
|
|
anyhow::bail!(
|
|
"Failed to get NVIDIA driver version, error code: {}",
|
|
result
|
|
);
|
|
}
|
|
let major = driver_version / 100;
|
|
let minor = driver_version % 100;
|
|
let branch_string = CStr::from_ptr(build_branch_string.as_ptr());
|
|
Ok(format!(
|
|
"{}.{} {}",
|
|
major,
|
|
minor,
|
|
branch_string.to_string_lossy()
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
mod amd {
|
|
use std::os::raw::{c_char, c_int, c_void};
|
|
|
|
use anyhow::{Context, Result};
|
|
use windows::{
|
|
Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA},
|
|
core::s,
|
|
};
|
|
|
|
// 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);
|
|
|
|
// 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,
|
|
}
|
|
|
|
// https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L429
|
|
#[allow(non_camel_case_types)]
|
|
type agsInitialize_t = unsafe extern "C" fn(
|
|
version: c_int,
|
|
config: *const c_void,
|
|
context: *mut *mut AGSContext,
|
|
gpu_info: *mut AGSGPUInfo,
|
|
) -> c_int;
|
|
|
|
// https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L436
|
|
#[allow(non_camel_case_types)]
|
|
type agsDeInitialize_t = unsafe extern "C" fn(context: *mut AGSContext) -> c_int;
|
|
|
|
pub(super) fn get_driver_version() -> Result<String> {
|
|
unsafe {
|
|
#[cfg(target_pointer_width = "64")]
|
|
let amd_dll =
|
|
LoadLibraryA(s!("amd_ags_x64.dll")).context("Failed to load AMD AGS library")?;
|
|
#[cfg(target_pointer_width = "32")]
|
|
let amd_dll =
|
|
LoadLibraryA(s!("amd_ags_x86.dll")).context("Failed to load AMD AGS library")?;
|
|
|
|
let ags_initialize_addr = GetProcAddress(amd_dll, s!("agsInitialize"))
|
|
.ok_or_else(|| anyhow::anyhow!("Failed to get agsInitialize address"))?;
|
|
let ags_deinitialize_addr = GetProcAddress(amd_dll, s!("agsDeInitialize"))
|
|
.ok_or_else(|| anyhow::anyhow!("Failed to get agsDeInitialize address"))?;
|
|
|
|
let ags_initialize: agsInitialize_t = std::mem::transmute(ags_initialize_addr);
|
|
let ags_deinitialize: agsDeInitialize_t = std::mem::transmute(ags_deinitialize_addr);
|
|
|
|
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 = ags_initialize(
|
|
AGS_CURRENT_VERSION,
|
|
std::ptr::null(),
|
|
&mut context,
|
|
&mut gpu_info,
|
|
);
|
|
if result != 0 {
|
|
anyhow::bail!("Failed to initialize AMD 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()
|
|
};
|
|
|
|
ags_deinitialize(context);
|
|
Ok(format!("{} ({})", software_version, driver_version))
|
|
}
|
|
}
|
|
}
|
|
|
|
mod dxgi {
|
|
use windows::{
|
|
Win32::Graphics::Dxgi::{IDXGIAdapter1, IDXGIDevice},
|
|
core::Interface,
|
|
};
|
|
|
|
pub(super) fn get_driver_version(adapter: &IDXGIAdapter1) -> anyhow::Result<String> {
|
|
let number = unsafe { adapter.CheckInterfaceSupport(&IDXGIDevice::IID as _) }?;
|
|
Ok(format!(
|
|
"{}.{}.{}.{}",
|
|
number >> 48,
|
|
(number >> 32) & 0xFFFF,
|
|
(number >> 16) & 0xFFFF,
|
|
number & 0xFFFF
|
|
))
|
|
}
|
|
}
|