1use hassle_rs::*;
4use std::{
5 collections::HashMap,
6 path::{Path, PathBuf},
7};
8
9use crate::{
10 include::IncludeHandler,
11 position::{ShaderFileRange, ShaderPosition},
12 shader::{HlslShaderModel, HlslVersion, ShaderParams, ShaderStage},
13 shader_error::{ShaderDiagnostic, ShaderDiagnosticList, ShaderDiagnosticSeverity, ShaderError},
14};
15
16use super::validator::ValidatorImpl;
17
18pub struct Dxc {
19 compiler: hassle_rs::DxcCompiler,
20 library: hassle_rs::DxcLibrary,
21
22 validator: Option<hassle_rs::DxcValidator>,
23 dxil: Option<hassle_rs::wrapper::Dxil>,
24
25 #[allow(dead_code)] dxc: hassle_rs::wrapper::Dxc,
27
28 diagnostic_regex: regex::Regex,
30 internal_diagnostic_regex: regex::Regex,
31}
32
33struct DxcIncludeHandler<'a> {
34 include_handler: IncludeHandler,
35 include_callback: &'a mut dyn FnMut(&Path) -> Option<String>,
36}
37
38impl<'a> DxcIncludeHandler<'a> {
39 pub fn new(
40 file: &Path,
41 includes: Vec<PathBuf>,
42 path_remapping: HashMap<PathBuf, PathBuf>,
43 include_callback: &'a mut dyn FnMut(&Path) -> Option<String>,
44 ) -> Self {
45 Self {
46 include_handler: IncludeHandler::main(file, includes, path_remapping),
47 include_callback: include_callback,
48 }
49 }
50}
51
52impl hassle_rs::wrapper::DxcIncludeHandler for DxcIncludeHandler<'_> {
53 fn load_source(&mut self, filename: String) -> Option<String> {
54 let path = Path::new(filename.as_str());
63 match self
64 .include_handler
65 .search_in_includes(&path, self.include_callback)
66 {
67 Some((content, include)) => {
68 self.include_handler.push_directory_stack(&include);
69 Some(content)
70 }
71 None => None,
72 }
73 }
74}
75
76impl Dxc {
77 pub const DXC_VERSION_MAJOR: u32 = 1;
81 pub const DXC_VERSION_MINOR: u32 = 8;
82 pub const DXC_VERSION_RELEASE: u32 = 2405;
83 pub const DXC_VERSION_COMMIT: u32 = 0;
84 pub const DXC_SPIRV_VERSION_MAJOR: u32 = 1;
85 pub const DXC_SPIRV_VERSION_MINOR: u32 = 6;
86
87 pub fn find_dxc_library() -> Option<PathBuf> {
89 let dxc_compiler_lib_name = libloading::library_filename("dxcompiler");
91 match std::env::current_exe() {
95 Ok(executable_path) => {
96 if let Some(parent_path) = executable_path.parent() {
97 let dll_path = parent_path.join(&dxc_compiler_lib_name);
98 if dll_path.is_file() {
99 dll_path.parent().map(|p| p.into())
100 } else {
101 None }
103 } else {
104 None }
106 }
107 Err(_) => None, }
109 }
110
111 pub fn new(library_path: Option<PathBuf>) -> Result<Self, hassle_rs::HassleError> {
112 let dxc_compiler_lib_name = libloading::library_filename("dxcompiler");
113 let dxil_lib_name = libloading::library_filename("dxil");
114 let dxc = hassle_rs::Dxc::new(
115 library_path
116 .clone()
117 .map(|path| path.join(dxc_compiler_lib_name)),
118 )?;
119 let library = dxc.create_library()?;
120 let compiler = dxc.create_compiler()?;
121 let (dxil, validator) = match Dxil::new(library_path.map(|path| path.join(dxil_lib_name))) {
124 Ok(dxil) => {
125 let validator_option = match dxil.create_validator() {
126 Ok(validator) => Some(validator),
127 Err(_) => None,
128 };
129 (Some(dxil), validator_option)
130 }
131 Err(_) => (None, None),
132 };
133 Ok(Self {
134 dxc,
135 compiler,
136 library,
137 dxil,
138 validator,
139 diagnostic_regex: regex::Regex::new(r"(?m)^(.*?:\d+:\d+: .*:.*?)$").unwrap(),
140 internal_diagnostic_regex: regex::Regex::new(r"(?s)^(.*?):(\d+):(\d+): (.*?):(.*)")
141 .unwrap(),
142 })
143 }
144 pub fn is_dxil_validation_available(&self) -> bool {
145 self.dxil.is_some() && self.validator.is_some()
146 }
147 fn parse_dxc_errors(
148 &self,
149 errors: &String,
150 file_path: &Path,
151 params: &ShaderParams,
152 ) -> Result<ShaderDiagnosticList, ShaderError> {
153 if errors.len() == 0 {
155 return Ok(ShaderDiagnosticList::empty());
156 }
157 let mut shader_error_list = ShaderDiagnosticList::empty();
158 let mut starts = Vec::new();
159 for capture in self.diagnostic_regex.captures_iter(errors.as_str()) {
160 if let Some(pos) = capture.get(0) {
161 starts.push(pos.start());
162 }
163 }
164 starts.push(errors.len()); let mut include_handler = IncludeHandler::main(
166 file_path,
167 params.context.includes.clone(),
168 params.context.path_remapping.clone(),
169 );
170 let mut include_cache: HashMap<String, PathBuf> = HashMap::new();
172 for start in 0..starts.len() - 1 {
173 let begin = starts[start];
174 let end = starts[start + 1];
175 let block = &errors[begin..end];
176 if let Some(capture) = self.internal_diagnostic_regex.captures(block) {
177 let relative_path = capture.get(1).map_or("", |m| m.as_str());
178 let line = capture.get(2).map_or("", |m| m.as_str());
179 let pos = capture.get(3).map_or("", |m| m.as_str());
180 let level = capture.get(4).map_or("", |m| m.as_str());
181 let msg = capture.get(5).map_or("", |m| m.as_str());
182 let file_path = include_cache
183 .entry(relative_path.into())
184 .or_insert_with(|| {
185 include_handler
186 .search_path_in_includes(Path::new(&relative_path))
187 .unwrap_or(file_path.into())
188 });
189 shader_error_list.push(ShaderDiagnostic {
190 severity: match level {
191 "error" => ShaderDiagnosticSeverity::Error,
192 "warning" => ShaderDiagnosticSeverity::Warning,
193 "note" => ShaderDiagnosticSeverity::Information,
194 "hint" => ShaderDiagnosticSeverity::Hint,
195 _ => ShaderDiagnosticSeverity::Error,
196 },
197 error: String::from(msg),
198 range: ShaderFileRange::new(
199 file_path.clone(),
200 ShaderPosition::new(
201 line.parse::<u32>().unwrap_or(1) - 1,
202 pos.parse::<u32>().unwrap_or(0),
203 ),
204 ShaderPosition::new(
205 line.parse::<u32>().unwrap_or(1) - 1,
206 pos.parse::<u32>().unwrap_or(0),
207 ),
208 ),
209 });
210 }
211 }
212
213 if shader_error_list.is_empty() {
214 let errors_to_ignore = vec![
215 "warning: DXIL signing library (dxil.dll,libdxil.so) not found.",
219 ];
220 for error_to_ignore in errors_to_ignore {
221 if errors.starts_with(error_to_ignore) {
222 return Ok(ShaderDiagnosticList::default());
223 }
224 }
225 Ok(ShaderDiagnosticList {
226 diagnostics: vec![ShaderDiagnostic {
227 severity: ShaderDiagnosticSeverity::Error,
228 error: format!("Failed to parse errors: {}", &errors),
229 range: ShaderFileRange::zero(file_path.into()),
231 }],
232 })
233 } else {
234 Ok(shader_error_list)
235 }
236 }
237 fn from_hassle_error(
238 &self,
239 error: HassleError,
240 file_path: &Path,
241 params: &ShaderParams,
242 ) -> Result<ShaderDiagnosticList, ShaderError> {
243 match error {
244 HassleError::CompileError(err) => self.parse_dxc_errors(&err, file_path, ¶ms),
245 HassleError::ValidationError(err) => Ok(ShaderDiagnosticList::from(ShaderDiagnostic {
246 severity: ShaderDiagnosticSeverity::Error,
247 error: err.to_string(),
248 range: ShaderFileRange::new(
249 file_path.into(),
250 ShaderPosition::new(0, 0),
251 ShaderPosition::new(0, 0),
252 ),
253 })),
254 HassleError::LibLoadingError(err) => Err(ShaderError::InternalErr(err.to_string())),
255 HassleError::LoadLibraryError { filename, inner } => {
256 Err(ShaderError::InternalErr(format!(
257 "Failed to load library {}: {}",
258 filename.display(),
259 inner.to_string()
260 )))
261 }
262 HassleError::Win32Error(err) => Err(ShaderError::InternalErr(format!(
263 "Win32 error: HRESULT={}",
264 err
265 ))),
266 HassleError::WindowsOnly(err) => Err(ShaderError::InternalErr(format!(
267 "Windows only error: {}",
268 err
269 ))),
270 }
271 }
272}
273
274fn get_profile(shader_stage: Option<ShaderStage>) -> &'static str {
275 match shader_stage {
277 Some(shader_stage) => match shader_stage {
278 ShaderStage::Vertex => "vs",
279 ShaderStage::Fragment => "ps",
280 ShaderStage::Compute => "cs",
281 ShaderStage::TesselationControl => "hs",
282 ShaderStage::TesselationEvaluation => "ds",
283 ShaderStage::Geometry => "gs",
284 ShaderStage::Mesh => "ms", ShaderStage::Task => "as", ShaderStage::RayGeneration
289 | ShaderStage::ClosestHit
290 | ShaderStage::AnyHit
291 | ShaderStage::Callable
292 | ShaderStage::Miss
293 | ShaderStage::Intersect => "lib",
294 },
295 None => "lib",
297 }
298}
299
300impl ValidatorImpl for Dxc {
301 fn validate_shader(
302 &self,
303 shader_source: &str,
304 file_path: &Path,
305 params: &ShaderParams,
306 include_callback: &mut dyn FnMut(&Path) -> Option<String>,
307 ) -> Result<ShaderDiagnosticList, ShaderError> {
308 let file_name = self.get_file_name(file_path);
309
310 let blob = match self
311 .library
312 .create_blob_with_encoding_from_str(shader_source)
313 {
314 Ok(blob) => blob,
315 Err(err) => match self.from_hassle_error(err, file_path, ¶ms) {
316 Ok(diagnostics) => return Ok(diagnostics),
317 Err(error) => return Err(error),
318 },
319 };
320
321 let defines_copy = params.context.defines.clone();
322 let defines: Vec<(&str, Option<&str>)> = defines_copy
323 .iter()
324 .map(|v| (&v.0 as &str, Some(&v.1 as &str)))
325 .collect();
326 let mut include_handler = DxcIncludeHandler::new(
327 file_path,
328 params.context.includes.clone(),
329 params.context.path_remapping.clone(),
330 include_callback,
331 );
332 let dxc_options = {
333 let mut options = Vec::new();
334 options.push(format!(
335 "-HV {}",
336 match params.compilation.hlsl.version {
337 HlslVersion::V2016 => "2016",
338 HlslVersion::V2017 => "2017",
339 HlslVersion::V2018 => "2018",
340 HlslVersion::V2021 => "2021",
341 }
342 ));
343
344 if params.compilation.hlsl.enable16bit_types {
345 options.push("-enable-16bit-types".into());
346 }
347 if params.compilation.hlsl.spirv {
348 options.push("-spirv".into());
349 options.push("-fspv-target-env=vulkan1.3".into());
351 }
352 options
353 };
354 let dxc_options_str: Vec<&str> = dxc_options.iter().map(|s| s.as_str()).collect();
355 let result = self.compiler.compile(
356 &blob,
357 file_name.as_str(),
358 match ¶ms.compilation.entry_point {
359 Some(entry_point) => entry_point.as_str(),
360 None => "",
361 },
362 format!(
363 "{}_{}",
364 get_profile(params.compilation.shader_stage),
365 match params.compilation.hlsl.shader_model {
366 HlslShaderModel::ShaderModel6 => "6_0",
367 HlslShaderModel::ShaderModel6_1 => "6_1",
368 HlslShaderModel::ShaderModel6_2 => "6_2",
369 HlslShaderModel::ShaderModel6_3 => "6_3",
370 HlslShaderModel::ShaderModel6_4 => "6_4",
371 HlslShaderModel::ShaderModel6_5 => "6_5",
372 HlslShaderModel::ShaderModel6_6 => "6_6",
373 HlslShaderModel::ShaderModel6_7 => "6_7",
374 HlslShaderModel::ShaderModel6_8 => "6_8",
375 sm =>
376 return Err(ShaderError::ValidationError(format!(
377 "Shader model {:?} not supported by DXC.",
378 sm
379 ))),
380 }
381 )
382 .as_str(),
383 &dxc_options_str,
384 Some(&mut include_handler),
385 &defines,
386 );
387
388 match result {
389 Ok(dxc_result) => {
390 let error_blob = match dxc_result.get_error_buffer() {
392 Ok(blob) => blob,
393 Err(err) => match self.from_hassle_error(err, file_path, ¶ms) {
394 Ok(diagnostics) => return Ok(diagnostics),
395 Err(error) => return Err(error),
396 },
397 };
398 let warning_emitted = match self.library.get_blob_as_string(&error_blob.into()) {
399 Ok(string) => string,
400 Err(err) => match self.from_hassle_error(err, file_path, ¶ms) {
401 Ok(diagnostics) => return Ok(diagnostics),
402 Err(error) => return Err(error),
403 },
404 };
405 let warning_diagnostics = match self.from_hassle_error(
406 HassleError::CompileError(warning_emitted),
407 file_path,
408 ¶ms,
409 ) {
410 Ok(diag) => diag,
411 Err(error) => return Err(error),
412 };
413 let result_blob = match dxc_result.get_result() {
415 Ok(blob) => blob,
416 Err(err) => match self.from_hassle_error(err, file_path, ¶ms) {
417 Ok(diagnostics) => {
418 return Ok(ShaderDiagnosticList::join(warning_diagnostics, diagnostics))
419 }
420 Err(error) => return Err(error),
421 },
422 };
423 if !params.compilation.hlsl.spirv {
425 if let (Some(_dxil), Some(validator)) = (&self.dxil, &self.validator) {
427 let data = result_blob.to_vec();
428 let blob_encoding =
429 match self.library.create_blob_with_encoding(data.as_ref()) {
430 Ok(blob) => blob,
431 Err(err) => match self.from_hassle_error(err, file_path, ¶ms) {
432 Ok(diagnostics) => {
433 return Ok(ShaderDiagnosticList::join(
434 warning_diagnostics,
435 diagnostics,
436 ))
437 }
438 Err(error) => return Err(error),
439 },
440 };
441 match validator.validate(blob_encoding.into()) {
442 Ok(_) => Ok(warning_diagnostics),
443 Err((_dxc_res, hassle_err)) => {
444 match self.from_hassle_error(hassle_err, file_path, ¶ms) {
447 Ok(diagnostics) => Ok(ShaderDiagnosticList::join(
448 warning_diagnostics,
449 diagnostics,
450 )),
451 Err(err) => Err(err),
452 }
453 }
454 }
455 } else {
456 Ok(warning_diagnostics)
457 }
458 } else {
459 Ok(warning_diagnostics)
460 }
461 }
462 Err((dxc_result, _hresult)) => {
463 let error_blob = match dxc_result.get_error_buffer() {
464 Ok(blob) => blob,
465 Err(err) => match self.from_hassle_error(err, file_path, ¶ms) {
466 Ok(diagnostics) => return Ok(diagnostics),
467 Err(error) => return Err(error),
468 },
469 };
470 let error_emitted = match self.library.get_blob_as_string(&error_blob.into()) {
471 Ok(string) => string,
472 Err(err) => match self.from_hassle_error(err, file_path, ¶ms) {
473 Ok(diagnostics) => return Ok(diagnostics),
474 Err(error) => return Err(error),
475 },
476 };
477 match self.from_hassle_error(
478 HassleError::CompileError(error_emitted),
479 file_path,
480 ¶ms,
481 ) {
482 Ok(diag) => Ok(diag),
483 Err(error) => Err(error),
484 }
485 }
486 }
487 }
488 fn support(&self, _shader_stage: ShaderStage) -> bool {
489 true }
491}