1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
use crate::*;
use crate::ctypes::*;
use crate::d3d::*;
use std::borrow::Borrow;
use std::fmt::{self, Debug, Display, Formatter};
use std::ops::Deref;
use std::path::*;
use std::ptr::*;
/// { code: [ReadOnlyBlob], errors: [TextBlob] } returned by [Compiler::compile] & friends.
#[derive(Clone, Debug)]
pub struct CompileResult {
/// The shader bytecode
pub shader: CodeBlob,
/// Any diagnostics, warnings, or non-fatal errors generated while compiling the shader.
pub errors: TextBlob,
}
impl AsRef <[u8]> for CompileResult { fn as_ref(&self) -> &[u8] { self.shader.as_bytes() } }
impl Borrow<[u8]> for CompileResult { fn borrow(&self) -> &[u8] { self.shader.as_bytes() } }
impl AsRef <Bytecode> for CompileResult { fn as_ref(&self) -> &Bytecode { self.shader.as_bytecode() } }
impl Borrow<Bytecode> for CompileResult { fn borrow(&self) -> &Bytecode { self.shader.as_bytecode() } }
impl Deref for CompileResult { fn deref(&self) -> &Bytecode { self.shader.as_bytecode() } type Target = Bytecode; }
pub use CompileResult as LinkResult;
/// { code: [TextBlob], errors: [TextBlob] } returned by [Compiler::preprocess]
#[derive(Clone, Debug)]
pub struct PreprocessResult {
/// The preprocessed HLSL
pub shader: TextBlob,
/// Any diagnostics, warnings, or non-fatal errors generated while preprocessing the shader.
pub errors: TextBlob,
}
/// { kind: [ErrorKind], shader: Option<[ReadOnlyBlob]>, errors: [TextBlob] }
#[derive(Clone)]
pub struct CompileError {
/// The [ErrorKind] / HRESULT generated when compiling the shader.
pub kind: ErrorKind,
/// The method that generated the error in question.
pub method: Option<&'static str>,
/// Any shader bytecode that may have resulted despite compilation failing.
///
/// This might be always [None]?
pub shader: Option<CodeBlob>,
/// More detailed errors/diagnostics beyond the basic error code.
///
/// May be blank for basic API parameter errors, but should be populated for errors in the HLSL code that was being compiled.
pub errors: TextBlob,
}
impl From<MethodError> for CompileError {
fn from(e: MethodError) -> Self {
Self { kind: e.kind(), method: Some(e.method()), errors: Default::default(), shader: Default::default() }
}
}
impl std::error::Error for CompileError {}
impl Debug for CompileError {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
fmt.debug_struct("CompileError")
.field("kind", &self.kind)
.field("shader", &self.shader.as_ref().map(|_| ..))
.field("errors", &self.errors.to_utf8_lossy())
.finish()
}
}
impl Display for CompileError {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
write!(fmt, "Error compiling shader: {:?}", self.kind)?;
if !self.errors.is_empty() {
writeln!(fmt, "\n{}", self.errors.to_utf8_lossy())?;
}
Ok(())
}
}
/// { kind: [ErrorKind], shader: Option<[ReadOnlyBlob]>, errors: [TextBlob] }
#[derive(Clone)]
pub struct PreprocessError {
/// The [ErrorKind] / HRESULT generated when preprocessing the shader.
pub kind: ErrorKind,
/// The method that generated the error in question.
pub method: Option<&'static str>,
/// Any preprocessed HLSL that may have been generated.
///
/// This might be always empty?
pub shader: TextBlob,
/// More detailed errors/diagnostics beyond the basic error code.
///
/// May be blank for basic API parameter errors, but should be populated for errors in the HLSL code that was being preprocessed.
pub errors: TextBlob,
}
impl From<MethodError> for PreprocessError {
fn from(e: MethodError) -> Self {
Self { kind: e.kind(), method: Some(e.method()), errors: Default::default(), shader: Default::default() }
}
}
impl std::error::Error for PreprocessError {}
impl Debug for PreprocessError {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
fmt.debug_struct("PreprocessError")
.field("kind", &self.kind)
.field("shader", &self.shader.to_utf8_lossy())
.field("errors", &self.errors.to_utf8_lossy())
.finish()
}
}
impl Display for PreprocessError {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
write!(fmt, "Error preprocessing shader: {:?}", self.kind)?;
if !self.errors.is_empty() {
writeln!(fmt, "\n{}", self.errors.to_utf8_lossy())?;
}
Ok(())
}
}
/// <h1 id="compile" class="section-header"><a href="#compile">Compile & Preprocess HLSL to Bytecode</a></h1>
impl Compiler {
/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dcompilefromfile)\]
/// D3DCompileFromFile
///
/// > **Note:** You can use this API to develop your Windows Store apps, but you can't use it in apps that you submit to the Windows Store.
/// > Refer to the section, "Compiling shaders for UWP", in the remarks for [compile2](Self::compile2).
///
/// Compiles Microsoft High Level Shader Language (HLSL) code into bytecode for a given target.
///
/// ### Arguments
/// * `file_name` - The shader file to compile
/// * `defines` - An optional array of defines. Use [None] if no extra defines are desired.
/// * `include` - An optional interface for dispatching `#include`s.
/// Use [None] if `#include`s should not be supported.
/// Use [StandardFileInclude] if you want to resolve `#include`s relative to `source_name`.
/// * `entrypoint` - An optional entrypoint such as `Some("main")`. Ignored if `target` is `fx_*`.
/// * `target` - A target shader profile such as `ps_3_0`, `vs_5_0`, `fx_4_0`, etc.
/// * `flags1` - [Compile]::\* constants.
/// * `flags2` - [CompileEffect]::\* constants.
///
/// ### Errors
/// * [THINERR::MISSING_DLL_EXPORT] - `d3dcompiler_4?.dll` and earlier
/// * [D3DERR::INVALIDCALL] - on invalid parameters such as nonexistant `target`s
/// * [E::FAIL] - if the shader failed to compile
///
/// ### Examples
/// ```rust
/// # use thindx::d3d::*; let d3dc = Compiler::load_system(47).unwrap();
/// let pixel_shader = d3dc.compile_from_file(
/// r"test\data\basic.hlsl", None, None, "ps_main", "ps_4_0",
/// Compile::Debug, CompileEffect::None,
/// ).unwrap();
///
/// let vertex_shader = d3dc.compile_from_file(
/// r"test\data\basic.hlsl", None, StandardFileInclude, "vs_main", "vs_4_0",
/// Compile::Debug, CompileEffect::None,
/// ).unwrap();
///
/// // TODO: defines/includes examples
/// ```
///
/// ### See Also
/// * [examples::d3dcompiler_02_compile]
///
/// ### Remarks
/// * You can use this API to develop your Windows Store apps, but you can't use it in apps that you submit to the Windows Store.
// /// * This was introduced by d3dcompiler_47.dll, and is unavailable in earlier versions. // ?
pub fn compile_from_file(
&self,
file_name: impl AsRef<Path>,
defines: impl AsShaderMacros,
include: impl AsInclude,
entrypoint: impl TryIntoAsOptCStr,
target: impl TryIntoAsCStr,
flags1: impl Into<Compile>,
flags2: impl Into<CompileEffect>,
) -> Result<CompileResult, CompileError> {
// Early outs
let f = self.D3DCompileFromFile.ok_or(MethodError("D3DCompileFromFile", THINERR::MISSING_DLL_EXPORT))?;
let defines = defines.as_shader_macros().map_err(|e| MethodError::new("D3DCompileFromFile", e))?;
let file_name = file_name.to_wcstr("D3DCompileFromFile")?;
let entrypoint = entrypoint.try_into().map_err(|e| MethodError::new("D3DCompileFromFile", e))?;
let target = target .try_into().map_err(|e| MethodError::new("D3DCompileFromFile", e))?;
let entrypoint = entrypoint.as_opt_cstr();
let target = target .as_cstr();
let include = include.as_id3dinclude();
let flags1 = flags1.into().into();
let flags2 = flags2.into().into();
let mut shader = null_mut();
let mut errors = null_mut();
// SAFETY: ❌ needs fuzz testing for alloc overflows
// * `f` ✔️ should be a valid/sound fn, like all of `self.*`
// * `file_name` ⚠️ huge name could cause alloc overflows... but probably causes file not found errors first?
// * `defines` ❌ huge defines could forkbomb and cause alloc overflows?
// * `include` ✔️ D3DPreprocess is documented to accept D3D_COMPILE_STANDARD_FILE_INCLUDE, which isn't really a ID3DInclude, as allowed by AsInclude
// * `include` ❌ huge source files could cause alloc overflows in final shader text blob?
// * `entrypoint` ❌ huge name could cause alloc overflows
// * `target` ⚠️ could be invalid... probably won't cause alloc overflows?
// * `flags1` ⚠️ could be invalid
// * `flags2` ⚠️ could be invalid
// * `shader` ✔️ is a trivial out parameter
// * `errors` ✔️ is a trivial out parameter
let hr = unsafe { f(
file_name.as_ptr(),
defines, include, entrypoint, target,
flags1, flags2, &mut shader, &mut errors,
)};
// SAFETY: ✔️
// * `shader` & `errors` ✔️ are either null (from_raw_opt returns None), or valid, non-dangling, unowned, ID3DBlob s (from_raw_opt takes ownership)
// * `ReadOnlyBlob` ✔️ imposes no restrictions on the contents of the blob
// * `CodeBlob` ⚠️ requires the contents of `shader` to be valid bytecode. It should be...
let shader = unsafe { ReadOnlyBlob::from_raw_opt(shader).map(|shader| CodeBlob::from_unchecked(shader)) };
let errors = TextBlob::new(unsafe { ReadOnlyBlob::from_raw_opt(errors) });
match ErrorKind::check(hr) {
Ok(()) => Ok(CompileResult { shader: shader.unwrap(), errors }),
Err(kind) => Err(CompileError { kind, method: Some("D3DCompileFromFile"), shader, errors }),
}
}
/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dcompile)\]
/// D3DCompile
///
/// Compile HLSL code or an effect file into bytecode for a given target.
///
/// ### Arguments
/// * `src_data` - The shader source code
/// * `source_name` - An optional shader name such as `Some("myshader.hlsl")` for debug purpouses.
/// * `defines` - An optional array of defines. Use [None] if no extra defines are desired.
/// * `include` - An optional interface for dispatching `#include`s.
/// Use [None] if `#include`s should not be supported.
/// Use [StandardFileInclude] if you want to resolve `#include`s relative to `source_name`.
/// * `entrypoint` - An optional entrypoint such as `Some("main")`. Ignored if `target` is `fx_*`.
/// * `target` - A target shader profile such as `ps_3_0`, `vs_5_0`, `fx_4_0`, etc.
/// * `flags1` - [Compile]::\* constants.
/// * `flags2` - [CompileEffect]::\* constants.
///
/// ### Errors
/// * [THINERR::MISSING_DLL_EXPORT] - `d3dcompiler_39.dll` and earlier
/// * [D3DERR::INVALIDCALL] - on invalid parameters such as nonexistant `target`s
/// * [E::FAIL] - if the shader failed to compile
///
/// ### Examples
/// ```rust
/// # use thindx::d3d::*; let d3dc = Compiler::load_system(47).unwrap();
/// let basic_hlsl = std::fs::read(r"test\data\basic.hlsl").unwrap();
///
/// let pixel_shader = d3dc.compile(
/// &basic_hlsl, r"test\data\basic.hlsl", None, None, "ps_main", "ps_4_0",
/// Compile::Debug, CompileEffect::None,
/// ).unwrap();
///
/// let vertex_shader = d3dc.compile(
/// &basic_hlsl, r"test\data\basic.hlsl", None, None, "vs_main", "vs_4_0",
/// Compile::Debug, CompileEffect::None,
/// ).unwrap();
/// ```
///
/// ### See Also
/// * [examples::d3dcompiler_02_compile]
///
/// ### Remarks
/// * This was introduced by d3dcompiler_40.dll, and is unavailable in earlier versions.
pub fn compile(
&self,
src_data: impl AsRef<[u8]>,
source_name: impl TryIntoAsOptCStr,
defines: impl AsShaderMacros,
include: impl AsInclude,
entrypoint: impl TryIntoAsOptCStr,
target: impl TryIntoAsCStr,
flags1: impl Into<Compile>,
flags2: impl Into<CompileEffect>,
) -> Result<CompileResult, CompileError> {
// Early outs
let f = self.D3DCompile.ok_or(MethodError("D3DCompile", THINERR::MISSING_DLL_EXPORT))?;
let defines = defines.as_shader_macros().map_err(|e| MethodError::new("D3DCompile", e))?;
let src_data = src_data.as_ref();
// Note: No error checking occurs for internal `\0`s - they will simply terminate the string earlier than expected.
// Note: We should perhaps reject non-ASCII values instead of allowing UTF8
let source_name = source_name .try_into().map_err(|e| MethodError::new("D3DCompile", e))?;
let entrypoint = entrypoint .try_into().map_err(|e| MethodError::new("D3DCompile", e))?;
let target = target .try_into().map_err(|e| MethodError::new("D3DCompile", e))?;
let source_name = source_name .as_opt_cstr();
let entrypoint = entrypoint .as_opt_cstr();
let target = target .as_cstr();
let include = include.as_id3dinclude();
let flags1 = flags1.into().into();
let flags2 = flags2.into().into();
let mut shader = null_mut();
let mut errors = null_mut();
// SAFETY: ❌ needs fuzz testing for alloc overflows
// * `f` ✔️ should be a valid/sound fn, like all of `self.*`
// * `src_data` ⚠️ should be HLSL to preprocess... but D3DCompile2 should probably be fairly tolerant of invalid as heck data?
// * `source_name` ❌ huge name could cause alloc overflows
// * `defines` ❌ huge defines could forkbomb and cause alloc overflows?
// * `include` ✔️ D3DPreprocess is documented to accept D3D_COMPILE_STANDARD_FILE_INCLUDE, which isn't really a ID3DInclude, as allowed by AsInclude
// * `include` ❌ huge source files could cause alloc overflows in final shader text blob?
// * `entrypoint` ❌ huge name could cause alloc overflows
// * `target` ⚠️ could be invalid... probably won't cause alloc overflows?
// * `flags1` ⚠️ could be invalid
// * `flags2` ⚠️ could be invalid
// * `shader` ✔️ is a trivial out parameter
// * `errors` ✔️ is a trivial out parameter
let hr = unsafe { f(
src_data.as_ptr().cast(), src_data.len(),
source_name, defines, include, entrypoint, target,
flags1, flags2, &mut shader, &mut errors,
)};
// SAFETY: ✔️
// * `shader` & `errors` ✔️ are either null (from_raw_opt returns None), or valid, non-dangling, unowned, ID3DBlob s (from_raw_opt takes ownership)
// * `ReadOnlyBlob` ✔️ imposes no restrictions on the contents of the blob
// * `CodeBlob` ⚠️ requires the contents of `shader` to be valid bytecode. It should be...
let shader = unsafe { ReadOnlyBlob::from_raw_opt(shader).map(|shader| CodeBlob::from_unchecked(shader)) };
let errors = TextBlob::new(unsafe { ReadOnlyBlob::from_raw_opt(errors) });
match ErrorKind::check(hr) {
Ok(()) => Ok(CompileResult { shader: shader.unwrap(), errors }),
Err(kind) => Err(CompileError { kind, method: Some("D3DCompile"), shader, errors }),
}
}
/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dcompile2)\]
/// D3DCompile2
///
/// Compiles Microsoft High Level Shader Language (HLSL) code into bytecode for a given target.
///
/// ### Arguments
/// * `src_data` - The shader source code
/// * `source_name` - An optional shader name such as `Some("myshader.hlsl")` for debug purpouses.
/// * `defines` - An optional array of defines. Use [None] if no extra defines are desired.
/// * `include` - An optional interface for dispatching `#include`s.
/// Use [None] if `#include`s should not be supported.
/// Use [StandardFileInclude] if you want to resolve `#include`s relative to `source_name`.
/// * `entrypoint` - An optional entrypoint such as `Some("vs_main")`. Ignored if `target` is `"fx_*"`.
/// * `target` - A target shader profile such as `Some("ps_3_0")`, `Some("vs_5_0")`, `Some("fx_4_0")`, etc.
/// * `flags1` - [Compile]::\* constants.
/// * `flags2` - [CompileEffect]::\* constants.
/// * `secondary_data_flags` - [CompileSecData]::\* constants.
/// * `secondary_data` - A pointer to secondary data. If you don't pass secondary data, set to [None].
///
/// ### Errors
/// * [THINERR::MISSING_DLL_EXPORT] - `d3dcompiler_4?.dll` and earlier
/// * [D3DERR::INVALIDCALL] - on invalid parameters such as nonexistant `target`s
/// * [E::FAIL] - if the shader failed to compile
///
/// ### Examples
/// ```rust
/// # use thindx::d3d::*; let d3dc = Compiler::load_system(47).unwrap();
/// let basic_hlsl = std::fs::read(r"test\data\basic.hlsl").unwrap();
///
/// let pixel_shader = d3dc.compile2(
/// &basic_hlsl, r"test\data\basic.hlsl", None, None, "ps_main", "ps_4_0",
/// Compile::Debug, CompileEffect::None, CompileSecData::None, None,
/// ).unwrap();
///
/// let vertex_shader = d3dc.compile2(
/// &basic_hlsl, r"test\data\basic.hlsl", None, None, "vs_main", "vs_4_0",
/// Compile::Debug, CompileEffect::None, CompileSecData::None, None,
/// ).unwrap();
/// ```
///
/// ### See Also
/// * [examples::d3dcompiler_02_compile]
///
/// ### Remarks
/// The difference between [compile2](Self::compile2) and [compile](Self::compile) is that [compile2](Self::compile2)
/// takes some optional parameters (`secondary_data_flags` and `secondary_data`) that can be used to control some
/// aspects of how bytecode is generated. Refer to the descriptions of these parameters for more details. There is
/// no difference otherwise to the efficiency of the bytecode generated between [compile2](Self::compile2) and
/// [compile](Self::compile).
// #[requires(d3dcompiler=47)] // ?
pub fn compile2<'s>(
&self,
src_data: impl AsRef<[u8]>,
source_name: impl TryIntoAsOptCStr,
defines: impl AsShaderMacros,
include: impl AsInclude,
entrypoint: impl TryIntoAsOptCStr,
target: impl TryIntoAsCStr,
flags1: impl Into<Compile>,
flags2: impl Into<CompileEffect>,
secondary_data_flags: impl Into<CompileSecData>,
secondary_data: impl Into<Option<&'s [u8]>>,
) -> Result<CompileResult, CompileError> {
// Early outs
let f = self.D3DCompile2.ok_or(MethodError("D3DCompile2", THINERR::MISSING_DLL_EXPORT))?;
let defines = defines.as_shader_macros().map_err(|e| MethodError::new("D3DCompile2", e))?;
let src_data = src_data.as_ref();
// Note: No error checking occurs for internal `\0`s - they will simply terminate the string earlier than expected.
// Note: We should perhaps reject non-ASCII values instead of allowing UTF8
let source_name = source_name .try_into().map_err(|e| MethodError::new("D3DCompile2", e))?;
let entrypoint = entrypoint .try_into().map_err(|e| MethodError::new("D3DCompile2", e))?;
let target = target .try_into().map_err(|e| MethodError::new("D3DCompile2", e))?;
let source_name = source_name .as_opt_cstr();
let entrypoint = entrypoint .as_opt_cstr();
let target = target .as_cstr();
let include = include.as_id3dinclude();
let flags1 = flags1.into().into();
let flags2 = flags2.into().into();
let secondary_data_flags = secondary_data_flags.into().into();
let secondary_data = secondary_data.into();
let secondary_data_len = secondary_data.map_or(0, |sd| sd.len());
let secondary_data = secondary_data.map_or(null(), |sd| sd.as_ptr()).cast();
let mut shader = null_mut();
let mut errors = null_mut();
// SAFETY: ❌ needs fuzz testing for alloc overflows
// * `f` ✔️ should be a valid/sound fn, like all of `self.*`
// * `src_data` ⚠️ should be HLSL to preprocess... but D3DCompile2 should probably be fairly tolerant of invalid as heck data?
// * `source_name` ❌ huge name could cause alloc overflows
// * `defines` ❌ huge defines could forkbomb and cause alloc overflows?
// * `include` ✔️ D3DPreprocess is documented to accept D3D_COMPILE_STANDARD_FILE_INCLUDE, which isn't really a ID3DInclude, as allowed by AsInclude
// * `include` ❌ huge source files could cause alloc overflows in final shader text blob?
// * `entrypoint` ❌ huge name could cause alloc overflows
// * `target` ⚠️ could be invalid... probably won't cause alloc overflows?
// * `flags1` ⚠️ could be invalid
// * `flags2` ⚠️ could be invalid
// * `secondary_data_flags` ⚠️ could be invalid
// * `secondary_data` ❌ could be invalid or cause alloc overflow?
// * `shader` ✔️ is a trivial out parameter
// * `errors` ✔️ is a trivial out parameter
let hr = unsafe { f(
src_data.as_ptr().cast(), src_data.len(),
source_name, defines, include, entrypoint, target,
flags1, flags2, secondary_data_flags, secondary_data, secondary_data_len,
&mut shader, &mut errors,
)};
// SAFETY: ✔️
// * `shader` & `errors` ✔️ are either null (from_raw_opt returns None), or valid, non-dangling, unowned, ID3DBlob s (from_raw_opt takes ownership)
// * `ReadOnlyBlob` ✔️ imposes no restrictions on the contents of the blob
// * `CodeBlob` ⚠️ requires the contents of `shader` to be valid bytecode. It should be...
let shader = unsafe { ReadOnlyBlob::from_raw_opt(shader).map(|shader| CodeBlob::from_unchecked(shader)) };
let errors = TextBlob::new(unsafe { ReadOnlyBlob::from_raw_opt(errors) });
match ErrorKind::check(hr) {
Ok(()) => Ok(CompileResult { shader: shader.unwrap(), errors }),
Err(kind) => Err(CompileError { kind, method: Some("D3DCompile2"), shader, errors }),
}
}
/// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dpreprocess)\]
/// D3DPreprocess
///
/// Preprocesses uncompiled HLSL code.
///
/// ### Arguments
/// * `src_data` - The shader source code
/// * `source_name` - An optional shader name such as `Some("myshader.hlsl")` for debug purpouses.
/// * `defines` - An optional array of defines. Use `()` if no extra defines are desired.
/// * `include` - An optional interface for dispatching `#include`s.
/// Use `()` if `#include`s should not be supported.
/// Use [StandardFileInclude] if you want to resolve `#include`s relative to `source_name`.
///
/// ### Errors
/// * [THINERR::MISSING_DLL_EXPORT] - `d3dcompiler_39.dll` and earlier
/// * [E::FAIL] - if the shader failed to preprocess
///
/// ### Example
/// ```rust
/// # use thindx::d3d::*; let d3dc = Compiler::load_system(47).unwrap();
/// let basic_hlsl = std::fs::read(r"test\data\basic.hlsl").unwrap();
/// let ps = d3dc.preprocess(&basic_hlsl, r"test\data\basic.hlsl", (), None).unwrap();
/// println!("{}", ps.shader.to_utf8_lossy());
/// ```
///
/// ### Output
/// ```hlsl
/// #line 1 "C:\\local\\thindx\\test\\data\\basic.hlsl"
///
///
/// struct Vertex {
/// float4 position : POSITION0 ;
/// float4 color : COLOR0 ;
/// } ;
///
/// struct VsToPs {
/// ...
/// ```
///
/// ### See Also
/// * [examples::d3dcompiler_02_compile]
///
/// ### Remarks
/// * This was introduced by d3dcompiler_40.dll, and is unavailable in earlier versions.
pub fn preprocess(
&self,
src_data: impl AsRef<[u8]>,
source_name: impl TryIntoAsOptCStr,
defines: impl AsShaderMacros,
include: impl AsInclude,
) -> Result<PreprocessResult, PreprocessError> {
// Early outs
let f = self.D3DPreprocess.ok_or(MethodError("D3DPreprocess", THINERR::MISSING_DLL_EXPORT))?;
let defines = defines.as_shader_macros().map_err(|e| MethodError::new("D3DPreprocess", e))?;
let src_data = src_data.as_ref();
// Note: No error checking occurs for internal `\0`s - they will simply terminate the string earlier than expected.
// Note: We should perhaps reject non-ASCII values instead of allowing UTF8
let source_name = source_name.try_into().map_err(|e| MethodError::new("D3DPreprocess", e))?;
let source_name = source_name.as_opt_cstr();
let include = include.as_id3dinclude();
let mut shader = null_mut();
let mut errors = null_mut();
// SAFETY: ❌ needs fuzz testing for alloc overflows
// * `f` ✔️ should be a valid/sound fn, like all of `self.*`
// * `src_data` ⚠️ should be HLSL to preprocess... but D3DPreprocess should probably be fairly tolerant of invalid as heck data?
// * `source_name` ❌ huge name could cause alloc overflows
// * `defines` ❌ huge defines could forkbomb and cause alloc overflows?
// * `include` ✔️ D3DPreprocess is documented to accept D3D_COMPILE_STANDARD_FILE_INCLUDE, which isn't really a ID3DInclude, as allowed by AsInclude
// * `include` ❌ huge source files could cause alloc overflows in final shader text blob?
// * `shader` ✔️ is a trivial out parameter
// * `errors` ✔️ is a trivial out parameter
let hr = unsafe { f(src_data.as_ptr().cast(), src_data.len(), source_name, defines, include, &mut shader, &mut errors)};
// SAFETY: ✔️
// * `shader` & `errors` ✔️ are either null (from_raw_opt returns None), or valid, non-dangling, unowned, ID3DBlob s (from_raw_opt takes ownership)
// * `ReadOnlyBlob` ✔️ imposes no restrictions on the contents of the blob
let shader = TextBlob::new(unsafe { ReadOnlyBlob::from_raw_opt(shader) });
let errors = TextBlob::new(unsafe { ReadOnlyBlob::from_raw_opt(errors) });
match ErrorKind::check(hr) {
Ok(()) => Ok(PreprocessResult { shader, errors }),
Err(kind) => Err(PreprocessError { kind, method: Some("D3DPreprocess"), shader, errors }),
}
}
}