#[cfg(test)]
#[macro_use]
extern crate assert_matches;
extern crate libc;
extern crate shaderc_sys;
use shaderc_sys as scs;
use libc::{c_char, c_int, c_void, size_t};
use std::any::Any;
use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::panic;
use std::{error, fmt, ptr, result, slice, str};
#[derive(Debug, PartialEq)]
pub enum Error {
CompilationError(u32, String),
InternalError(String),
InvalidStage(String),
InvalidAssembly(String),
NullResultObject(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::CompilationError(c, ref r) => {
if r.is_empty() {
write!(f, "{} compilation error(s)", c)
} else {
write!(f, "{} compilation error(s): {}", c, r)
}
}
Error::InternalError(ref r) => {
if r.is_empty() {
write!(f, "internal error")
} else {
write!(f, "internal error: {}", r)
}
}
Error::InvalidStage(ref r) => {
if r.is_empty() {
write!(f, "invalid stage")
} else {
write!(f, "invalid stage: {}", r)
}
}
Error::InvalidAssembly(ref r) => {
if r.is_empty() {
write!(f, "invalid assembly")
} else {
write!(f, "invalid assembly: {}", r)
}
}
Error::NullResultObject(ref r) => {
if r.is_empty() {
write!(f, "null result object")
} else {
write!(f, "null result object: {}", r)
}
}
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::CompilationError(_, _) => "compilation error",
Error::InternalError(_) => "internal error",
Error::InvalidStage(_) => "invalid stage",
Error::InvalidAssembly(_) => "invalid assembly",
Error::NullResultObject(_) => "null result object",
}
}
}
pub type Result<T> = result::Result<T, Error>;
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TargetEnv {
Vulkan,
OpenGL,
OpenGLCompat,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SourceLanguage {
GLSL,
HLSL,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResourceKind {
Image,
Sampler,
Texture,
Buffer,
StorageBuffer,
UnorderedAccessView,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ShaderKind {
Vertex,
Fragment,
Compute,
Geometry,
TessControl,
TessEvaluation,
InferFromSource,
DefaultVertex,
DefaultFragment,
DefaultCompute,
DefaultGeometry,
DefaultTessControl,
DefaultTessEvaluation,
SpirvAssembly,
RayGeneration,
AnyHit,
ClosestHit,
Miss,
Intersection,
Callable,
DefaultRayGeneration,
DefaultAnyHit,
DefaultClosestHit,
DefaultMiss,
DefaultIntersection,
DefaultCallable,
Task,
Mesh,
DefaultTask,
DefaultMesh,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GlslProfile {
None,
Core,
Compatibility,
Es,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OptimizationLevel {
Zero,
Size,
Performance,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Limit {
MaxLights,
MaxClipPlanes,
MaxTextureUnits,
MaxTextureCoords,
MaxVertexAttribs,
MaxVertexUniformComponents,
MaxVaryingFloats,
MaxVertexTextureImageUnits,
MaxCombinedTextureImageUnits,
MaxTextureImageUnits,
MaxFragmentUniformComponents,
MaxDrawBuffers,
MaxVertexUniformVectors,
MaxVaryingVectors,
MaxFragmentUniformVectors,
MaxVertexOutputVectors,
MaxFragmentInputVectors,
MinProgramTexelOffset,
MaxProgramTexelOffset,
MaxClipDistances,
MaxComputeWorkGroupCountX,
MaxComputeWorkGroupCountY,
MaxComputeWorkGroupCountZ,
MaxComputeWorkGroupSizeX,
MaxComputeWorkGroupSizeY,
MaxComputeWorkGroupSizeZ,
MaxComputeUniformComponents,
MaxComputeTextureImageUnits,
MaxComputeImageUniforms,
MaxComputeAtomicCounters,
MaxComputeAtomicCounterBuffers,
MaxVaryingComponents,
MaxVertexOutputComponents,
MaxGeometryInputComponents,
MaxGeometryOutputComponents,
MaxFragmentInputComponents,
MaxImageUnits,
MaxCombinedImageUnitsAndFragmentOutputs,
MaxCombinedShaderOutputResources,
MaxImageSamples,
MaxVertexImageUniforms,
MaxTessControlImageUniforms,
MaxTessEvaluationImageUniforms,
MaxGeometryImageUniforms,
MaxFragmentImageUniforms,
MaxCombinedImageUniforms,
MaxGeometryTextureImageUnits,
MaxGeometryOutputVertices,
MaxGeometryTotalOutputComponents,
MaxGeometryUniformComponents,
MaxGeometryVaryingComponents,
MaxTessControlInputComponents,
MaxTessControlOutputComponents,
MaxTessControlTextureImageUnits,
MaxTessControlUniformComponents,
MaxTessControlTotalOutputComponents,
MaxTessEvaluationInputComponents,
MaxTessEvaluationOutputComponents,
MaxTessEvaluationTextureImageUnits,
MaxTessEvaluationUniformComponents,
MaxTessPatchComponents,
MaxPatchVertices,
MaxTessGenLevel,
MaxViewports,
MaxVertexAtomicCounters,
MaxTessControlAtomicCounters,
MaxTessEvaluationAtomicCounters,
MaxGeometryAtomicCounters,
MaxFragmentAtomicCounters,
MaxCombinedAtomicCounters,
MaxAtomicCounterBindings,
MaxVertexAtomicCounterBuffers,
MaxTessControlAtomicCounterBuffers,
MaxTessEvaluationAtomicCounterBuffers,
MaxGeometryAtomicCounterBuffers,
MaxFragmentAtomicCounterBuffers,
MaxCombinedAtomicCounterBuffers,
MaxAtomicCounterBufferSize,
MaxTransformFeedbackBuffers,
MaxTransformFeedbackInterleavedComponents,
MaxCullDistances,
MaxCombinedClipAndCullDistances,
MaxSamples,
}
pub struct Compiler {
raw: *mut scs::ShadercCompiler,
}
fn propagate_panic<F, T>(f: F) -> T
where
F: FnOnce() -> T,
{
PANIC_ERROR.with(|panic_error| {
*panic_error.borrow_mut() = None;
});
let result = f();
let err = PANIC_ERROR.with(|panic_error| panic_error.borrow_mut().take());
if let Some(err) = err {
panic::resume_unwind(err)
} else {
result
}
}
fn safe_str_from_utf8(bytes: &[u8]) -> String {
match str::from_utf8(bytes) {
Ok(str) => str.to_string(),
Err(err) => {
if err.valid_up_to() > 0 {
return format!(
"{} (followed by invalid UTF-8 characters)",
safe_str_from_utf8(&bytes[..err.valid_up_to()])
);
} else {
return format!("invalid UTF-8 string: {}", err);
}
}
}
}
impl Compiler {
pub fn new() -> Option<Compiler> {
let p = unsafe { scs::shaderc_compiler_initialize() };
if p.is_null() {
None
} else {
Some(Compiler { raw: p })
}
}
fn handle_compilation_result(
result: *mut scs::ShadercCompilationResult,
is_binary: bool,
) -> Result<CompilationArtifact> {
let status = unsafe { scs::shaderc_result_get_compilation_status(result) };
if status == 0 {
Ok(CompilationArtifact::new(result, is_binary))
} else {
let num_errors = unsafe { scs::shaderc_result_get_num_errors(result) } as u32;
let reason = unsafe {
let p = scs::shaderc_result_get_error_message(result);
let bytes = CStr::from_ptr(p).to_bytes();
safe_str_from_utf8(bytes)
};
match status {
1 => Err(Error::InvalidStage(reason)),
2 => Err(Error::CompilationError(num_errors, reason)),
3 => Err(Error::InternalError(reason)),
4 => Err(Error::NullResultObject(reason)),
5 => Err(Error::InvalidAssembly(reason)),
_ => panic!("unhandled shaderc error case"),
}
}
}
pub fn compile_into_spirv(
&mut self,
source_text: &str,
shader_kind: ShaderKind,
input_file_name: &str,
entry_point_name: &str,
additional_options: Option<&CompileOptions>,
) -> Result<CompilationArtifact> {
let source_size = source_text.len();
let c_source = CString::new(source_text).expect("cannot convert source_text to c string");
let c_file =
CString::new(input_file_name).expect("cannot convert input_file_name to c string");
let c_entry_point =
CString::new(entry_point_name).expect("cannot convert entry_point_name to c string");
propagate_panic(|| {
let result = unsafe {
scs::shaderc_compile_into_spv(
self.raw,
c_source.as_ptr(),
source_size,
shader_kind as i32,
c_file.as_ptr(),
c_entry_point.as_ptr(),
additional_options.map_or(ptr::null(), |ref o| o.raw),
)
};
Compiler::handle_compilation_result(result, true)
})
}
pub fn compile_into_spirv_assembly(
&mut self,
source_text: &str,
shader_kind: ShaderKind,
input_file_name: &str,
entry_point_name: &str,
additional_options: Option<&CompileOptions>,
) -> Result<CompilationArtifact> {
let source_size = source_text.len();
let c_source = CString::new(source_text).expect("cannot convert source_text to c string");
let c_file =
CString::new(input_file_name).expect("cannot convert input_file_name to c string");
let c_entry_point =
CString::new(entry_point_name).expect("cannot convert entry_point_name to c string");
propagate_panic(|| {
let result = unsafe {
scs::shaderc_compile_into_spv_assembly(
self.raw,
c_source.as_ptr(),
source_size,
shader_kind as i32,
c_file.as_ptr(),
c_entry_point.as_ptr(),
additional_options.map_or(ptr::null(), |ref o| o.raw),
)
};
Compiler::handle_compilation_result(result, false)
})
}
pub fn preprocess(
&mut self,
source_text: &str,
input_file_name: &str,
entry_point_name: &str,
additional_options: Option<&CompileOptions>,
) -> Result<CompilationArtifact> {
let source_size = source_text.len();
let c_source = CString::new(source_text).expect("cannot convert source to c string");
let c_file =
CString::new(input_file_name).expect("cannot convert input_file_name to c string");
let c_entry_point =
CString::new(entry_point_name).expect("cannot convert entry_point_name to c string");
propagate_panic(|| {
let result = unsafe {
scs::shaderc_compile_into_preprocessed_text(
self.raw,
c_source.as_ptr(),
source_size,
ShaderKind::Vertex as i32,
c_file.as_ptr(),
c_entry_point.as_ptr(),
additional_options.map_or(ptr::null(), |ref o| o.raw),
)
};
Compiler::handle_compilation_result(result, false)
})
}
pub fn assemble(
&mut self,
source_assembly: &str,
additional_options: Option<&CompileOptions>,
) -> Result<CompilationArtifact> {
let source_size = source_assembly.len();
let c_source =
CString::new(source_assembly).expect("cannot convert source_assembly to c string");
propagate_panic(|| {
let result = unsafe {
scs::shaderc_assemble_into_spv(
self.raw,
c_source.as_ptr(),
source_size,
additional_options.map_or(ptr::null(), |ref o| o.raw),
)
};
Compiler::handle_compilation_result(result, true)
})
}
}
impl Drop for Compiler {
fn drop(&mut self) {
unsafe { scs::shaderc_compiler_release(self.raw) }
}
}
pub struct CompileOptions<'a> {
raw: *mut scs::ShadercCompileOptions,
f: Option<
Box<Fn(&str, IncludeType, &str, usize) -> result::Result<ResolvedInclude, String> + 'a>,
>,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Debug)]
pub enum IncludeType {
Relative,
Standard,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)]
pub struct ResolvedInclude {
pub resolved_name: String,
pub content: String,
}
thread_local! {
static PANIC_ERROR: RefCell<Option<Box<Any + Send + 'static>>> = RefCell::new(None);
}
impl<'a> CompileOptions<'a> {
pub fn new() -> Option<CompileOptions<'a>> {
let p = unsafe { scs::shaderc_compile_options_initialize() };
if p.is_null() {
None
} else {
Some(CompileOptions { raw: p, f: None })
}
}
pub fn clone(&self) -> Option<CompileOptions> {
let p = unsafe { scs::shaderc_compile_options_clone(self.raw) };
if p.is_null() {
None
} else {
Some(CompileOptions { raw: p, f: None })
}
}
pub fn set_target_env(&mut self, env: TargetEnv, version: u32) {
unsafe { scs::shaderc_compile_options_set_target_env(self.raw, env as i32, version) }
}
pub fn set_source_language(&mut self, language: SourceLanguage) {
unsafe { scs::shaderc_compile_options_set_source_language(self.raw, language as i32) }
}
pub fn set_forced_version_profile(&mut self, version: u32, profile: GlslProfile) {
unsafe {
scs::shaderc_compile_options_set_forced_version_profile(
self.raw,
version as c_int,
profile as i32,
)
}
}
pub fn set_include_callback<F>(&mut self, f: F)
where
F: Fn(&str, IncludeType, &str, usize) -> result::Result<ResolvedInclude, String> + 'a,
{
use std::mem;
let f = Box::new(f);
let f_ptr = &*f as *const F;
self.f = Some(
f as Box<
Fn(&str, IncludeType, &str, usize) -> result::Result<ResolvedInclude, String> + 'a,
>,
);
unsafe {
scs::shaderc_compile_options_set_include_callbacks(
self.raw,
resolver::<'a, F>,
releaser,
f_ptr as *const c_void as *mut c_void,
);
}
struct OkResultWrapper {
source_name: CString,
content: CString,
wrapped: scs::shaderc_include_result,
}
struct ErrResultWrapper {
error_message: CString,
wrapped: scs::shaderc_include_result,
}
extern "C" fn resolver<'a, F>(
user_data: *mut c_void,
requested_source: *const c_char,
type_: c_int,
requesting_source: *const c_char,
include_depth: size_t,
) -> *mut scs::shaderc_include_result
where
F: Fn(&str, IncludeType, &str, usize) -> result::Result<ResolvedInclude, String> + 'a,
{
let result = panic::catch_unwind(move || {
let f = unsafe { &*(user_data as *const F) };
let requested_source =
unsafe { CStr::from_ptr(requested_source).to_string_lossy() };
let type_ = match type_ {
0 => IncludeType::Relative,
1 => IncludeType::Standard,
x => panic!(
"include callback: unknown include type returned from libshaderc: {}",
x
),
};
let requesting_source =
unsafe { CStr::from_ptr(requesting_source).to_string_lossy() };
match f(&requested_source, type_, &requesting_source, include_depth) {
Ok(ResolvedInclude {
resolved_name,
content,
}) => {
if resolved_name.len() == 0 {
panic!("include callback: empty strings for resolved include names not allowed");
}
let mut result = Box::new(OkResultWrapper {
source_name: CString::new(resolved_name).expect("include callback: could not convert resolved source name to a c string"),
content: CString::new(content).expect("include callback: could not convert content string to a c string"),
wrapped: unsafe { mem::zeroed() },
});
result.wrapped = scs::shaderc_include_result {
source_name: result.source_name.as_ptr(),
source_name_length: result.source_name.as_bytes().len(),
content: result.content.as_ptr(),
content_length: result.content.as_bytes().len(),
user_data: &mut *result as *mut OkResultWrapper as *mut c_void,
};
let r = &mut result.wrapped as *mut scs::shaderc_include_result;
mem::forget(result);
r
}
Err(error_message) => {
let mut result = Box::new(ErrResultWrapper {
error_message: CString::new(error_message).expect(
"include callback: could not convert error message to a c string",
),
wrapped: unsafe { mem::zeroed() },
});
result.wrapped = scs::shaderc_include_result {
source_name: CStr::from_bytes_with_nul(b"\0").unwrap().as_ptr(),
source_name_length: 0,
content: result.error_message.as_ptr(),
content_length: result.error_message.as_bytes().len(),
user_data: &mut *result as *mut ErrResultWrapper as *mut c_void,
};
let r = &mut result.wrapped as *mut scs::shaderc_include_result;
mem::forget(result);
r
}
}
});
match result {
Ok(r) => r,
Err(e) => {
PANIC_ERROR.with(|panic_error| {
*panic_error.borrow_mut() = Some(e);
});
let mut result = Box::new(ErrResultWrapper {
error_message: CString::new("").unwrap(),
wrapped: unsafe { mem::zeroed() },
});
result.wrapped = scs::shaderc_include_result {
source_name: CStr::from_bytes_with_nul(b"\0").unwrap().as_ptr(),
source_name_length: 0,
content: result.error_message.as_ptr(),
content_length: 0,
user_data: &mut *result as *mut ErrResultWrapper as *mut c_void,
};
let r = &mut result.wrapped as *mut scs::shaderc_include_result;
mem::forget(result);
r
}
}
}
extern "C" fn releaser(_: *mut c_void, include_result: *mut scs::shaderc_include_result) {
let user_data = unsafe { &*include_result }.user_data;
if unsafe { &*include_result }.source_name_length == 0 {
let wrapper = unsafe { Box::from_raw(user_data as *mut ErrResultWrapper) };
drop(wrapper);
} else {
let wrapper = unsafe { Box::from_raw(user_data as *mut OkResultWrapper) };
drop(wrapper);
}
}
}
pub fn set_limit(&mut self, limit: Limit, value: i32) {
unsafe {
scs::shaderc_compile_options_set_limit(self.raw, limit as i32, value as c_int)
}
}
pub fn set_auto_bind_uniforms(&mut self, auto_bind: bool) {
unsafe {
scs::shaderc_compile_options_set_auto_bind_uniforms(self.raw, auto_bind);
}
}
pub fn set_hlsl_io_mapping(&mut self, hlsl_iomap: bool) {
unsafe {
scs::shaderc_compile_options_set_hlsl_io_mapping(self.raw, hlsl_iomap);
}
}
pub fn set_hlsl_offsets(&mut self, hlsl_offsets: bool) {
unsafe {
scs::shaderc_compile_options_set_hlsl_offsets(self.raw, hlsl_offsets);
}
}
pub fn set_binding_base(&mut self, resource_kind: ResourceKind, base: u32) {
unsafe {
scs::shaderc_compile_options_set_binding_base(self.raw, resource_kind as i32, base);
}
}
pub fn set_binding_base_for_stage(
&mut self,
shader_kind: ShaderKind,
resource_kind: ResourceKind,
base: u32,
) {
unsafe {
scs::shaderc_compile_options_set_binding_base_for_stage(
self.raw,
shader_kind as i32,
resource_kind as i32,
base,
);
}
}
pub fn set_hlsl_register_set_and_binding(&mut self, register: &str, set: &str, binding: &str) {
let c_register = CString::new(register).expect("cannot convert string to c string");
let c_set = CString::new(set).expect("cannot convert string to c string");
let c_binding = CString::new(binding).expect("cannot convert string to c string");
unsafe {
scs::shaderc_compile_options_set_hlsl_register_set_and_binding(
self.raw,
c_register.as_ptr(),
c_set.as_ptr(),
c_binding.as_ptr(),
);
}
}
pub fn set_hlsl_register_set_and_binding_for_stage(
&mut self,
kind: ShaderKind,
register: &str,
set: &str,
binding: &str,
) {
let c_register = CString::new(register).expect("cannot convert string to c string");
let c_set = CString::new(set).expect("cannot convert string to c string");
let c_binding = CString::new(binding).expect("cannot convert string to c string");
unsafe {
scs::shaderc_compile_options_set_hlsl_register_set_and_binding_for_stage(
self.raw,
kind as i32,
c_register.as_ptr(),
c_set.as_ptr(),
c_binding.as_ptr(),
);
}
}
pub fn add_macro_definition(&mut self, name: &str, value: Option<&str>) {
let c_name = CString::new(name).expect("cannot convert name to c string");
if value.is_some() {
let value = value.unwrap();
let c_value = CString::new(value).expect("cannot convert value to c string");
unsafe {
scs::shaderc_compile_options_add_macro_definition(
self.raw,
c_name.as_ptr(),
name.len(),
c_value.as_ptr(),
value.len(),
)
}
} else {
unsafe {
scs::shaderc_compile_options_add_macro_definition(
self.raw,
c_name.as_ptr(),
name.len(),
ptr::null(),
0,
)
}
}
}
pub fn set_optimization_level(&mut self, level: OptimizationLevel) {
unsafe { scs::shaderc_compile_options_set_optimization_level(self.raw, level as i32) }
}
pub fn set_generate_debug_info(&mut self) {
unsafe { scs::shaderc_compile_options_set_generate_debug_info(self.raw) }
}
pub fn set_suppress_warnings(&mut self) {
unsafe { scs::shaderc_compile_options_set_suppress_warnings(self.raw) }
}
pub fn set_warnings_as_errors(&mut self) {
unsafe { scs::shaderc_compile_options_set_warnings_as_errors(self.raw) }
}
}
impl<'a> Drop for CompileOptions<'a> {
fn drop(&mut self) {
unsafe { scs::shaderc_compile_options_release(self.raw) }
}
}
pub struct CompilationArtifact {
raw: *mut scs::ShadercCompilationResult,
is_binary: bool,
}
impl CompilationArtifact {
fn new(result: *mut scs::ShadercCompilationResult, is_binary: bool) -> CompilationArtifact {
CompilationArtifact {
raw: result,
is_binary: is_binary,
}
}
pub fn len(&self) -> usize {
unsafe { scs::shaderc_result_get_length(self.raw) }
}
pub fn as_binary(&self) -> &[u32] {
if !self.is_binary {
panic!("not binary result")
}
assert_eq!(0, self.len() % 4);
let num_words = self.len() / 4;
unsafe {
let p = scs::shaderc_result_get_bytes(self.raw);
slice::from_raw_parts(p as *const u32, num_words)
}
}
pub fn as_binary_u8(&self) -> &[u8] {
if !self.is_binary {
panic!("not binary result")
}
assert_eq!(0, self.len() % 4);
unsafe {
let p = scs::shaderc_result_get_bytes(self.raw);
slice::from_raw_parts(p as *const u8, self.len())
}
}
pub fn as_text(&self) -> String {
if self.is_binary {
panic!("not text result")
}
unsafe {
let p = scs::shaderc_result_get_bytes(self.raw);
let bytes = CStr::from_ptr(p).to_bytes();
str::from_utf8(bytes)
.ok()
.expect("invalid utf-8 string")
.to_string()
}
}
pub fn get_num_warnings(&self) -> u32 {
(unsafe { scs::shaderc_result_get_num_warnings(self.raw) }) as u32
}
pub fn get_warning_messages(&self) -> String {
unsafe {
let p = scs::shaderc_result_get_error_message(self.raw);
let bytes = CStr::from_ptr(p).to_bytes();
safe_str_from_utf8(bytes)
}
}
}
impl Drop for CompilationArtifact {
fn drop(&mut self) {
unsafe { scs::shaderc_result_release(self.raw) }
}
}
pub fn get_spirv_version() -> (u32, u32) {
let mut version: i32 = 0;
let mut revision: i32 = 0;
unsafe { scs::shaderc_get_spv_version(&mut version, &mut revision) };
(version as u32, revision as u32)
}
pub fn parse_version_profile(string: &str) -> Option<(u32, GlslProfile)> {
let mut version: i32 = 0;
let mut profile: i32 = 0;
let c_string = CString::new(string).expect("cannot convert string to c string");
let result = unsafe {
scs::shaderc_parse_version_profile(c_string.as_ptr(), &mut version, &mut profile)
};
if result == false {
None
} else {
let p = match profile {
0 => GlslProfile::None,
1 => GlslProfile::Core,
2 => GlslProfile::Compatibility,
3 => GlslProfile::Es,
_ => panic!("internal error: unhandled profile"),
};
Some((version as u32, p))
}
}
#[cfg(test)]
mod tests {
use super::*;
static VOID_MAIN: &'static str = "#version 310 es\n void main() {}";
static VOID_E: &'static str = "#version 310 es\n void E() {}";
static EXTRA_E: &'static str = "#version 310 es\n E\n void main() {}";
static IFDEF_E: &'static str = "#version 310 es\n #ifdef E\n void main() {}\n\
#else\n #error\n #endif";
static HLSL_VERTEX: &'static str = "float4 main(uint index: SV_VERTEXID): SV_POSITION\n\
{ return float4(1., 2., 3., 4.); }";
static TWO_ERROR: &'static str = "#version 310 es\n #error one\n #error two\n void main() {}";
static TWO_ERROR_MSG: &'static str = "shader.glsl:2: error: '#error' : one\n\
shader.glsl:3: error: '#error' : two\n";
static ONE_WARNING: &'static str = "#version 400\n\
layout(location = 0) attribute float x;\n void main() {}";
static ONE_WARNING_MSG: &'static str = "\
shader.glsl:2: warning: attribute deprecated in version 130; may be removed in future release
";
static DEBUG_INFO: &'static str = "#version 140\n \
void main() {\n vec2 debug_info_sample = vec2(1.0);\n }";
static CORE_PROFILE: &'static str = "void main() {\n gl_ClipDistance[0] = 5.;\n }";
static TWO_FN: &'static str = "\
#version 450
layout(location=0) in int inVal;
layout(location=0) out int outVal;
int foo(int a) { return a; }
void main() { outVal = foo(inVal); }";
static COMPAT_FRAG: &'static str = "\
#version 100
layout(binding = 0) uniform highp sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, vec2(0.));
}";
static VOID_MAIN_ASSEMBLY: &'static str = "\
; SPIR-V
; Version: 1.0
; Generator: Google Shaderc over Glslang; 7
; Bound: 6
; Schema: 0
OpCapability Shader
%1 = OpExtInstImport \"GLSL.std.450\"
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main \"main\"
OpSource ESSL 310
OpSourceExtension \"GL_GOOGLE_cpp_style_line_directive\"
OpSourceExtension \"GL_GOOGLE_include_directive\"
OpName %main \"main\"
%void = OpTypeVoid
%3 = OpTypeFunction %void
%main = OpFunction %void None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
";
static UNIFORMS_NO_BINDINGS: &'static str = "\
#version 450
#extension GL_ARB_sparse_texture2 : enable
uniform texture2D my_tex;
uniform sampler my_sam;
layout(rgba32f) uniform image2D my_img;
layout(rgba32f) uniform imageBuffer my_imbuf;
uniform block { float x; float y; } my_ubo;
void main() {
texture(sampler2D(my_tex,my_sam),vec2(1.0));
vec4 t;
sparseImageLoadARB(my_img,ivec2(0),t);
imageLoad(my_imbuf,42);
float x = my_ubo.x;
}";
static GLSL_WEIRD_PACKING: &'static str = "\
#version 450
buffer B { float x; vec3 foo; } my_ssbo;
void main() { my_ssbo.x = 1.0; }";
#[test]
fn test_compile_vertex_shader_into_spirv() {
let mut c = Compiler::new().unwrap();
let result = c
.compile_into_spirv(VOID_MAIN, ShaderKind::Vertex, "shader.glsl", "main", None)
.unwrap();
assert!(result.len() > 20);
assert!(result.as_binary().first() == Some(&0x07230203));
let function_end_word: u32 = (1 << 16) | 56;
assert!(result.as_binary().last() == Some(&function_end_word));
}
#[test]
fn test_compile_vertex_shader_into_spirv_assembly() {
let mut c = Compiler::new().unwrap();
let result = c
.compile_into_spirv_assembly(VOID_MAIN, ShaderKind::Vertex, "shader.glsl", "main", None)
.unwrap();
assert_eq!(VOID_MAIN_ASSEMBLY, result.as_text());
}
#[test]
fn test_preprocess() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.add_macro_definition("E", Some("main"));
let result = c
.preprocess(VOID_E, "shader.glsl", "main", Some(&options))
.unwrap();
assert_eq!("#version 310 es\n void main(){ }\n", result.as_text());
}
#[test]
fn test_assemble() {
let mut c = Compiler::new().unwrap();
let result = c.assemble(VOID_MAIN_ASSEMBLY, None).unwrap();
assert!(result.len() > 20);
assert!(result.as_binary().first() == Some(&0x07230203));
let function_end_word: u32 = (1 << 16) | 56;
assert!(result.as_binary().last() == Some(&function_end_word));
}
#[test]
fn test_compile_options_add_macro_definition_normal_value() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.add_macro_definition("E", Some("main"));
let result = c
.compile_into_spirv_assembly(
VOID_E,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap();
assert_eq!(VOID_MAIN_ASSEMBLY, result.as_text());
}
#[test]
fn test_compile_options_add_macro_definition_empty_value() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.add_macro_definition("E", Some(""));
let result = c
.compile_into_spirv_assembly(
EXTRA_E,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap();
assert_eq!(VOID_MAIN_ASSEMBLY, result.as_text());
}
#[test]
fn test_compile_options_add_macro_definition_no_value() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.add_macro_definition("E", None);
let result = c
.compile_into_spirv_assembly(
IFDEF_E,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap();
assert_eq!(VOID_MAIN_ASSEMBLY, result.as_text());
}
#[test]
fn test_compile_options_clone() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.add_macro_definition("E", None);
let o = options.clone().unwrap();
let result = c
.compile_into_spirv_assembly(
IFDEF_E,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&o),
)
.unwrap();
assert_eq!(VOID_MAIN_ASSEMBLY, result.as_text());
}
#[test]
fn test_compile_options_set_source_language() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_source_language(SourceLanguage::HLSL);
let result = c
.compile_into_spirv(
HLSL_VERTEX,
ShaderKind::Vertex,
"shader.hlsl",
"main",
Some(&options),
)
.unwrap();
assert!(result.len() > 20);
assert!(result.as_binary().first() == Some(&0x07230203));
let function_end_word: u32 = (1 << 16) | 56;
assert!(result.as_binary().last() == Some(&function_end_word));
}
#[test]
fn test_compile_options_set_generate_debug_info() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_generate_debug_info();
let result = c
.compile_into_spirv_assembly(
DEBUG_INFO,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap();
assert!(result.as_text().contains("debug_info_sample"));
}
#[test]
fn test_compile_options_set_optimization_level_zero() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_optimization_level(OptimizationLevel::Zero);
let result = c
.compile_into_spirv_assembly(
DEBUG_INFO,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap();
assert!(result.as_text().contains("OpName"));
assert!(result.as_text().contains("OpSource"));
}
#[test]
fn test_compile_options_set_optimization_level_size() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_optimization_level(OptimizationLevel::Size);
let result = c
.compile_into_spirv_assembly(
TWO_FN,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap();
assert!(!result.as_text().contains("OpFunctionCall"));
}
#[test]
fn test_compile_options_set_optimization_level_performance() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_optimization_level(OptimizationLevel::Performance);
let result = c
.compile_into_spirv_assembly(
TWO_FN,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap();
assert!(!result.as_text().contains("OpFunctionCall"));
}
#[test]
fn test_compile_options_set_forced_version_profile_ok() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_forced_version_profile(450, GlslProfile::Core);
let result = c
.compile_into_spirv(
CORE_PROFILE,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap();
assert!(result.len() > 20);
assert!(result.as_binary().first() == Some(&0x07230203));
let function_end_word: u32 = (1 << 16) | 56;
assert!(result.as_binary().last() == Some(&function_end_word));
}
#[test]
fn test_compile_options_set_forced_version_profile_err() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_forced_version_profile(310, GlslProfile::Es);
let result = c.compile_into_spirv(
CORE_PROFILE,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
);
assert!(result.is_err());
assert_matches!(result.err(),
Some(Error::CompilationError(3, ref s))
if s.contains("error: 'gl_ClipDistance' : undeclared identifier"));
}
#[test]
#[should_panic(expected = "Panic in include resolver!")]
fn test_include_directive_panic() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_include_callback(|_, _, _, _| panic!("Panic in include resolver!"));
drop(c.compile_into_spirv_assembly(
r#"
#version 400
#include "foo.glsl"
"#,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
));
}
#[test]
fn test_include_directive_err() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_include_callback(|name, _, _, _| {
Err(format!("Couldn't find header \"{}\"", name))
});
let result = c.compile_into_spirv_assembly(
r#"
#version 400
#include "foo.glsl"
"#,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
);
assert!(result.is_err());
assert_matches!(result.err(),
Some(Error::CompilationError(1, ref s))
if s.contains("Couldn't find header \"foo.glsl\""));
}
#[test]
fn test_include_directive_success() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_include_callback(|name, type_, _, _| {
if name == "foo.glsl" && type_ == IncludeType::Relative {
Ok(ResolvedInclude {
resolved_name: "std/foo.glsl".to_string(),
content: r#"
#ifndef FOO_H
#define FOO_H
void main() {}
#endif
"#
.to_string(),
})
} else {
Err(format!("Couldn't find header \"{}\"", name))
}
});
let result = c.compile_into_spirv_assembly(
r#"
#version 400
#include "foo.glsl"
#include "foo.glsl"
"#,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
);
assert_matches!(result.err(), None);
}
#[test]
fn test_compile_options_set_suppress_warnings() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_suppress_warnings();
let result = c
.compile_into_spirv(
ONE_WARNING,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap();
assert_eq!(0, result.get_num_warnings());
}
#[test]
fn test_compile_options_set_warnings_as_errors() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_warnings_as_errors();
let result = c.compile_into_spirv(
ONE_WARNING,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
);
assert!(result.is_err());
assert_matches!(result.err(),
Some(Error::CompilationError(1, ref s))
if s.contains("error: attribute deprecated in version 130;"));
}
#[test]
fn test_compile_options_set_target_env_err_vulkan() {
let mut c = Compiler::new().unwrap();
let result = c.compile_into_spirv(
COMPAT_FRAG,
ShaderKind::Fragment,
"shader.glsl",
"main",
None,
);
assert!(result.is_err());
assert_matches!(result.err(),
Some(Error::CompilationError(4, ref s))
if s.contains("error: #version: ES shaders for Vulkan SPIR-V \
require version 310 or higher"));
}
#[test]
fn test_compile_options_set_target_env_err_opengl() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_target_env(TargetEnv::OpenGL, 0);
let result = c.compile_into_spirv(
COMPAT_FRAG,
ShaderKind::Fragment,
"shader.glsl",
"main",
Some(&options),
);
assert!(result.is_err());
assert_matches!(result.err(),
Some(Error::CompilationError(3, ref s))
if s.contains("error: #version: ES shaders for OpenGL SPIR-V \
are not supported"));
}
macro_rules! texture_offset {
($offset:expr) => {{
let mut s = "#version 450
layout (binding=0) uniform sampler1D tex;
void main() {
vec4 x = textureOffset(tex, 1., "
.to_string();
s.push_str(stringify!($offset));
s.push_str(");\n}");
s
}};
}
#[test]
fn test_compile_options_set_limit() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
assert!(c
.compile_into_spirv(
&texture_offset!(7),
ShaderKind::Fragment,
"shader.glsl",
"main",
Some(&options)
)
.is_ok());
assert!(c
.compile_into_spirv(
&texture_offset!(8),
ShaderKind::Fragment,
"shader.glsl",
"main",
Some(&options)
)
.is_err());
options.set_limit(Limit::MaxProgramTexelOffset, 10);
assert!(c
.compile_into_spirv(
&texture_offset!(8),
ShaderKind::Fragment,
"shader.glsl",
"main",
Some(&options)
)
.is_ok());
assert!(c
.compile_into_spirv(
&texture_offset!(10),
ShaderKind::Fragment,
"shader.glsl",
"main",
Some(&options)
)
.is_ok());
assert!(c
.compile_into_spirv(
&texture_offset!(11),
ShaderKind::Fragment,
"shader.glsl",
"main",
Some(&options)
)
.is_err());
}
#[test]
fn test_compile_options_set_auto_bind_uniforms_false() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_auto_bind_uniforms(false);
let result = c.compile_into_spirv_assembly(
UNIFORMS_NO_BINDINGS,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
);
assert!(result.is_err());
assert_matches!(result.err(),
Some(Error::CompilationError(4, ref s))
if s.contains("error: 'binding' : sampler/texture/image requires layout(binding=X)"));
}
#[test]
fn test_compile_options_set_auto_bind_uniforms_true() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_auto_bind_uniforms(true);
let result = c
.compile_into_spirv_assembly(
UNIFORMS_NO_BINDINGS,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap()
.as_text();
assert!(result.contains("OpDecorate %my_tex Binding 0"));
assert!(result.contains("OpDecorate %my_sam Binding 1"));
assert!(result.contains("OpDecorate %my_img Binding 2"));
assert!(result.contains("OpDecorate %my_imbuf Binding 3"));
assert!(result.contains("OpDecorate %my_ubo Binding 4"));
}
#[test]
fn test_compile_options_set_hlsl_offsets_false() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_hlsl_offsets(false);
let result = c
.compile_into_spirv_assembly(
GLSL_WEIRD_PACKING,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap()
.as_text();
assert!(result.contains("OpMemberDecorate %B 1 Offset 16"));
}
#[test]
fn test_compile_options_set_hlsl_offsets_true() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_hlsl_offsets(true);
let result = c
.compile_into_spirv_assembly(
GLSL_WEIRD_PACKING,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap()
.as_text();
assert!(result.contains("OpMemberDecorate %B 1 Offset 4"));
}
#[test]
fn test_compile_options_set_binding_base() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_auto_bind_uniforms(true);
options.set_binding_base(ResourceKind::Image, 44);
let result = c
.compile_into_spirv_assembly(
UNIFORMS_NO_BINDINGS,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap()
.as_text();
assert!(result.contains("OpDecorate %my_tex Binding 0"));
assert!(result.contains("OpDecorate %my_sam Binding 1"));
assert!(result.contains("OpDecorate %my_img Binding 44"));
assert!(result.contains("OpDecorate %my_imbuf Binding 45"));
assert!(result.contains("OpDecorate %my_ubo Binding 2"));
}
#[test]
fn test_compile_options_set_binding_base_for_stage_effective() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_auto_bind_uniforms(true);
options.set_binding_base_for_stage(ShaderKind::Vertex, ResourceKind::Texture, 100);
let result = c
.compile_into_spirv_assembly(
UNIFORMS_NO_BINDINGS,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap()
.as_text();
assert!(result.contains("OpDecorate %my_tex Binding 100"));
assert!(result.contains("OpDecorate %my_sam Binding 0"));
assert!(result.contains("OpDecorate %my_img Binding 1"));
assert!(result.contains("OpDecorate %my_imbuf Binding 2"));
assert!(result.contains("OpDecorate %my_ubo Binding 3"));
}
#[test]
fn test_compile_options_set_binding_base_for_stage_ignore() {
let mut c = Compiler::new().unwrap();
let mut options = CompileOptions::new().unwrap();
options.set_auto_bind_uniforms(true);
options.set_binding_base_for_stage(ShaderKind::Fragment, ResourceKind::Texture, 100);
let result = c
.compile_into_spirv_assembly(
UNIFORMS_NO_BINDINGS,
ShaderKind::Vertex,
"shader.glsl",
"main",
Some(&options),
)
.unwrap()
.as_text();
assert!(result.contains("OpDecorate %my_tex Binding 0"));
assert!(result.contains("OpDecorate %my_sam Binding 1"));
assert!(result.contains("OpDecorate %my_img Binding 2"));
assert!(result.contains("OpDecorate %my_imbuf Binding 3"));
assert!(result.contains("OpDecorate %my_ubo Binding 4"));
}
#[test]
fn test_error_compilation_error() {
let mut c = Compiler::new().unwrap();
let result =
c.compile_into_spirv(TWO_ERROR, ShaderKind::Vertex, "shader.glsl", "main", None);
assert!(result.is_err());
assert_eq!(
Some(Error::CompilationError(2, TWO_ERROR_MSG.to_string())),
result.err()
);
}
#[test]
fn test_error_invalid_stage() {
let mut c = Compiler::new().unwrap();
let result = c.compile_into_spirv(
VOID_MAIN,
ShaderKind::InferFromSource,
"shader.glsl",
"main",
None,
);
assert!(result.is_err());
assert_eq!(Some(Error::InvalidStage("".to_string())), result.err());
}
#[test]
fn test_warning() {
let mut c = Compiler::new().unwrap();
let result = c
.compile_into_spirv(ONE_WARNING, ShaderKind::Vertex, "shader.glsl", "main", None)
.unwrap();
assert_eq!(1, result.get_num_warnings());
assert_eq!(ONE_WARNING_MSG.to_string(), result.get_warning_messages());
}
#[test]
fn test_get_spirv_version() {
let (version, _) = get_spirv_version();
assert_eq!((1 << 16) + (4 << 8), version);
}
#[test]
fn test_parse_version_profile() {
assert_eq!(Some((310, GlslProfile::Es)), parse_version_profile("310es"));
assert_eq!(
Some((450, GlslProfile::Compatibility)),
parse_version_profile("450compatibility")
);
assert_eq!(Some((140, GlslProfile::None)), parse_version_profile("140"));
assert_eq!(None, parse_version_profile("something"));
assert_eq!(None, parse_version_profile(""));
}
}