mod class_wrapper;
mod op_gpu;
pub mod processor;
pub mod shader_text;
pub use op_gpu::{
default_extract_gpu_shader_info, extract_op_gpu_shader_info, supported_by_legacy_shader,
};
pub use processor::GpuProcessor;
pub use shader_text::GpuShaderText;
use crate::config::logging::{log_debug, logging_level};
use crate::dynamic_property::DynamicProperty;
use crate::error::{Error, Result};
use crate::types::{DynamicPropertyType, GpuLanguage, Interpolation, LoggingLevel};
use class_wrapper::ClassWrapper;
use std::fmt;
use std::sync::Arc;
pub const MAX_3D_LUT_LENGTH: u32 = 129;
pub(crate) fn cache_id_hash(s: &str) -> String {
let h = xxhash_rust::xxh3::xxh3_128(s.as_bytes());
format!("{:016x}{:016x}", h as u64, (h >> 64) as u64)
}
pub type DoubleGetter = Arc<dyn Fn() -> f64 + Send + Sync>;
pub type BoolGetter = Arc<dyn Fn() -> bool + Send + Sync>;
pub type Float3Getter = Arc<dyn Fn() -> [f32; 3] + Send + Sync>;
pub type SizeGetter = Arc<dyn Fn() -> i32 + Send + Sync>;
pub type VectorFloatGetter = Arc<dyn Fn() -> Vec<f32> + Send + Sync>;
pub type VectorIntGetter = Arc<dyn Fn() -> Vec<i32> + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UniformDataType {
Double,
Bool,
Float3,
VectorFloat,
VectorInt,
Unknown,
}
#[derive(Clone)]
pub enum UniformValue {
Double(DoubleGetter),
Bool(BoolGetter),
Float3(Float3Getter),
VectorFloat(SizeGetter, VectorFloatGetter),
VectorInt(SizeGetter, VectorIntGetter),
}
impl fmt::Debug for UniformValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UniformValue::Double(g) => write!(f, "Double({})", g()),
UniformValue::Bool(g) => write!(f, "Bool({})", g()),
UniformValue::Float3(g) => write!(f, "Float3({:?})", g()),
UniformValue::VectorFloat(s, _) => write!(f, "VectorFloat(size {})", s()),
UniformValue::VectorInt(s, _) => write!(f, "VectorInt(size {})", s()),
}
}
}
#[derive(Debug, Clone)]
pub struct UniformData {
pub value: UniformValue,
pub buffer_offset: usize,
}
impl UniformData {
pub fn data_type(&self) -> UniformDataType {
match self.value {
UniformValue::Double(_) => UniformDataType::Double,
UniformValue::Bool(_) => UniformDataType::Bool,
UniformValue::Float3(_) => UniformDataType::Float3,
UniformValue::VectorFloat(..) => UniformDataType::VectorFloat,
UniformValue::VectorInt(..) => UniformDataType::VectorInt,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TextureType {
RedChannel,
RgbChannel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TextureDimensions {
Texture1D = 1,
Texture2D = 2,
}
#[derive(Debug, Clone)]
pub struct GpuShaderCreatorBase {
uid: String,
language: GpuLanguage,
function_name: String,
resource_prefix: String,
pixel_name: String,
num_resources: u32,
parameter_declarations: String,
texture_declarations: String,
helper_methods: String,
function_header: String,
function_body: String,
function_footer: String,
shader_code: String,
shader_code_id: String,
dynamic_properties: Vec<DynamicProperty>,
class_wrapper: ClassWrapper,
descriptor_set_index: u32,
texture_binding_start: u32,
}
impl Default for GpuShaderCreatorBase {
fn default() -> Self {
let language = GpuLanguage::Glsl1_2;
Self {
uid: String::new(),
language,
function_name: "OCIOMain".to_string(),
resource_prefix: "ocio".to_string(),
pixel_name: "outColor".to_string(),
num_resources: 0,
parameter_declarations: String::new(),
texture_declarations: String::new(),
helper_methods: String::new(),
function_header: String::new(),
function_body: String::new(),
function_footer: String::new(),
shader_code: String::new(),
shader_code_id: String::new(),
dynamic_properties: Vec::new(),
class_wrapper: ClassWrapper::create(language),
descriptor_set_index: 0,
texture_binding_start: 1,
}
}
}
impl GpuShaderCreatorBase {
pub fn new() -> Self {
Self::default()
}
pub fn clone_for_creator(&self) -> Self {
let mut c = self.clone();
c.dynamic_properties.clear();
c.shader_code.clear();
c.shader_code_id.clear();
c
}
pub fn shader_text(&self) -> &str {
&self.shader_code
}
}
fn remove_double_underscores(s: &str) -> String {
s.replace("__", "_")
}
pub trait GpuShaderCreator {
fn base(&self) -> &GpuShaderCreatorBase;
fn base_mut(&mut self) -> &mut GpuShaderCreatorBase;
fn clone_creator(&self) -> Box<dyn GpuShaderCreator>;
fn unique_id(&self) -> &str {
&self.base().uid
}
fn set_unique_id(&mut self, uid: &str) {
self.base_mut().uid = uid.to_string();
}
fn language(&self) -> GpuLanguage {
self.base().language
}
fn set_language(&mut self, lang: GpuLanguage) {
let b = self.base_mut();
b.language = lang;
b.class_wrapper = ClassWrapper::create(lang);
}
fn function_name(&self) -> &str {
&self.base().function_name
}
fn set_function_name(&mut self, name: &str) {
self.base_mut().function_name = remove_double_underscores(name);
}
fn pixel_name(&self) -> &str {
&self.base().pixel_name
}
fn set_pixel_name(&mut self, name: &str) {
self.base_mut().pixel_name = remove_double_underscores(name);
}
fn resource_prefix(&self) -> &str {
&self.base().resource_prefix
}
fn set_resource_prefix(&mut self, prefix: &str) {
self.base_mut().resource_prefix = remove_double_underscores(prefix);
}
fn set_descriptor_set_index(&mut self, index: u32, texture_binding_start: u32) -> Result<()> {
if texture_binding_start == 0 {
return Err(Error::msg(
"Texture binding start index must be greater than 0.",
));
}
let b = self.base_mut();
b.descriptor_set_index = index;
b.texture_binding_start = texture_binding_start;
Ok(())
}
fn descriptor_set_index(&self) -> u32 {
self.base().descriptor_set_index
}
fn texture_binding_start(&self) -> u32 {
self.base().texture_binding_start
}
fn cache_id(&self) -> String {
let b = self.base();
format!(
"{} {} {} {} {} {} {} {}",
b.language.as_str(),
b.function_name,
b.resource_prefix,
b.pixel_name,
b.num_resources,
b.descriptor_set_index,
b.texture_binding_start,
b.shader_code_id
)
}
fn begin(&mut self, _uid: &str) {}
fn end(&mut self) {}
fn set_texture_max_width(&mut self, max_width: u32);
fn texture_max_width(&self) -> u32;
fn set_allow_texture_1d(&mut self, allowed: bool);
fn allow_texture_1d(&self) -> bool;
fn next_resource_index(&mut self) -> u32 {
let b = self.base_mut();
let i = b.num_resources;
b.num_resources += 1;
i
}
fn add_uniform_double(&mut self, name: &str, getter: DoubleGetter) -> Result<bool>;
fn add_uniform_bool(&mut self, name: &str, getter: BoolGetter) -> Result<bool>;
fn add_uniform_float3(&mut self, name: &str, getter: Float3Getter) -> Result<bool>;
fn add_uniform_vector_float(
&mut self,
name: &str,
get_size: SizeGetter,
getter: VectorFloatGetter,
max_size: u32,
) -> Result<bool>;
fn add_uniform_vector_int(
&mut self,
name: &str,
get_size: SizeGetter,
getter: VectorIntGetter,
max_size: u32,
) -> Result<bool>;
fn add_dynamic_property(&mut self, prop: DynamicProperty) -> Result<()> {
let ty = prop.property_type();
if self.has_dynamic_property(ty) {
return Err(Error::msg(format!(
"Dynamic property already here: {}.",
ty as i32
)));
}
self.base_mut().dynamic_properties.push(prop);
Ok(())
}
fn num_dynamic_properties(&self) -> usize {
self.base().dynamic_properties.len()
}
fn dynamic_property_at(&self, index: usize) -> Result<DynamicProperty> {
let props = &self.base().dynamic_properties;
props.get(index).cloned().ok_or_else(|| {
Error::msg(format!(
"Dynamic properties access error: index = {} where size = {}",
index,
props.len()
))
})
}
fn has_dynamic_property(&self, ty: DynamicPropertyType) -> bool {
self.base()
.dynamic_properties
.iter()
.any(|p| p.property_type() == ty)
}
fn dynamic_property(&self, ty: DynamicPropertyType) -> Result<DynamicProperty> {
self.base()
.dynamic_properties
.iter()
.find(|p| p.property_type() == ty)
.cloned()
.ok_or_else(|| Error::msg("Dynamic property not found."))
}
#[allow(clippy::too_many_arguments)]
fn add_texture(
&mut self,
texture_name: &str,
sampler_name: &str,
width: u32,
height: u32,
channel: TextureType,
dimensions: TextureDimensions,
interpolation: Interpolation,
values: &[f32],
) -> Result<u32>;
fn add_3d_texture(
&mut self,
texture_name: &str,
sampler_name: &str,
edgelen: u32,
interpolation: Interpolation,
values: &[f32],
) -> Result<u32>;
fn add_to_parameter_declare_shader_code(&mut self, shader_code: &str) {
let b = self.base_mut();
if b.parameter_declarations.is_empty() {
b.parameter_declarations
.push_str("\n// Declaration of all variables\n\n");
}
b.parameter_declarations.push_str(shader_code);
}
fn add_to_texture_declare_shader_code(&mut self, shader_code: &str) {
let b = self.base_mut();
if b.texture_declarations.is_empty() {
b.texture_declarations
.push_str("\n// Declaration of all textures\n\n");
}
b.texture_declarations.push_str(shader_code);
}
fn add_to_helper_shader_code(&mut self, shader_code: &str) {
let b = self.base_mut();
if b.helper_methods.is_empty() {
b.helper_methods
.push_str("\n// Declaration of all helper methods\n\n");
}
b.helper_methods.push_str(shader_code);
}
fn add_to_function_header_shader_code(&mut self, shader_code: &str) {
self.base_mut().function_header.push_str(shader_code);
}
fn add_to_function_shader_code(&mut self, shader_code: &str) {
self.base_mut().function_body.push_str(shader_code);
}
fn add_to_function_footer_shader_code(&mut self, shader_code: &str) {
self.base_mut().function_footer.push_str(shader_code);
}
fn create_shader_text(
&mut self,
parameter_declarations: &str,
texture_declarations: &str,
helper_methods: &str,
function_header: &str,
function_body: &str,
function_footer: &str,
) {
let b = self.base_mut();
let mut code = String::new();
let vulkan_block =
b.language == GpuLanguage::GlslVk4_6 && !parameter_declarations.is_empty();
if vulkan_block {
code.push_str(&format!(
"layout (set = {}, binding = 0) uniform {}_Parameters\n{{\n",
b.descriptor_set_index, b.function_name
));
}
code.push_str(parameter_declarations);
if vulkan_block {
code.push_str("\n};\n");
}
code.push_str(texture_declarations);
code.push_str(helper_methods);
code.push_str(function_header);
code.push_str(function_body);
code.push_str(function_footer);
b.shader_code_id = cache_id_hash(&code);
b.shader_code = code;
}
fn finalize(&mut self) -> Result<()> {
let (params, textures, helpers, header, body, footer) = {
let b = self.base_mut();
let original_header =
format!("{}\n{}", b.parameter_declarations, b.texture_declarations);
let prefix = b.resource_prefix.clone();
let fn_name = b.function_name.clone();
b.class_wrapper.prepare(&prefix, &fn_name, &original_header);
if b.class_wrapper.has_header() {
b.parameter_declarations = b.class_wrapper.header(&original_header)?;
b.texture_declarations.clear();
}
b.function_footer = b.class_wrapper.footer(&b.function_footer)?;
(
b.parameter_declarations.clone(),
b.texture_declarations.clone(),
b.helper_methods.clone(),
b.function_header.clone(),
b.function_body.clone(),
b.function_footer.clone(),
)
};
self.create_shader_text(¶ms, &textures, &helpers, &header, &body, &footer);
if logging_level() >= LoggingLevel::Debug && logging_level() != LoggingLevel::Unknown {
log_debug(&format!(
"\n**\nGPU Fragment Shader program\n{}\n",
self.base().shader_code
));
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GpuTexture {
pub texture_name: String,
pub sampler_name: String,
pub width: u32,
pub height: u32,
pub depth: u32,
pub channel: TextureType,
pub dimensions: u32,
pub interpolation: Interpolation,
pub shader_binding_index: u32,
pub values: Vec<f32>,
}
impl GpuTexture {
#[allow(clippy::too_many_arguments)]
fn new(
texture_name: &str,
sampler_name: &str,
w: u32,
h: u32,
d: u32,
channel: TextureType,
dimensions: u32,
interpolation: Interpolation,
shader_binding_index: u32,
values: &[f32],
) -> Result<Self> {
if texture_name.is_empty() {
return Err(Error::msg("The texture name is invalid."));
}
if sampler_name.is_empty() {
return Err(Error::msg("The texture sampler name is invalid."));
}
if w == 0 || h == 0 || d == 0 {
return Err(Error::msg(format!(
"The texture buffer size is invalid: [{w} x {h} x {d}]."
)));
}
let size = w as usize
* h as usize
* d as usize
* if channel == TextureType::RgbChannel {
3
} else {
1
};
if values.len() < size {
return Err(Error::msg("The buffer is invalid"));
}
Ok(Self {
texture_name: texture_name.to_string(),
sampler_name: sampler_name.to_string(),
width: w,
height: h,
depth: d,
channel,
dimensions,
interpolation,
shader_binding_index,
values: values[..size].to_vec(),
})
}
}
#[derive(Debug, Clone)]
struct Uniform {
name: String,
data: UniformData,
}
const GPU_FLOAT_SIZE: usize = 4;
const GPU_FLOAT_ALIGNMENT: usize = 4;
const GPU_INT_SIZE: usize = 4;
const GPU_INT_ALIGNMENT: usize = 4;
const GPU_VEC3_SIZE: usize = 12;
const GPU_VEC3_ALIGNMENT: usize = 16;
const GPU_ARRAY_ALIGNMENT: usize = 16;
const GPU_ARRAY_STRIDE: usize = 16;
fn align_offset(offset: usize, alignment: usize) -> usize {
offset.div_ceil(alignment) * alignment
}
#[derive(Debug, Clone)]
pub struct GpuShaderDesc {
base: GpuShaderCreatorBase,
max_1d_lut_width: u32,
allow_texture_1d: bool,
textures: Vec<GpuTexture>,
textures_3d: Vec<GpuTexture>,
uniforms: Vec<Uniform>,
uniform_buffer_size: usize,
}
impl Default for GpuShaderDesc {
fn default() -> Self {
Self::new()
}
}
impl GpuShaderDesc {
pub fn new() -> Self {
Self {
base: GpuShaderCreatorBase::new(),
max_1d_lut_width: 4 * 1024,
allow_texture_1d: true,
textures: Vec::new(),
textures_3d: Vec::new(),
uniforms: Vec::new(),
uniform_buffer_size: 0,
}
}
pub fn shader_text(&self) -> &str {
self.base.shader_text()
}
fn uniform_name_used(&self, name: &str) -> bool {
self.uniforms.iter().any(|u| u.name == name)
}
fn push_uniform(
&mut self,
name: &str,
value: UniformValue,
alignment: usize,
size: usize,
) -> Result<bool> {
if name.is_empty() {
return Err(Error::msg("The dynamic property name is invalid."));
}
if self.uniform_name_used(name) {
return Ok(false);
}
self.uniform_buffer_size = align_offset(self.uniform_buffer_size, alignment);
self.uniforms.push(Uniform {
name: name.to_string(),
data: UniformData {
value,
buffer_offset: self.uniform_buffer_size,
},
});
self.uniform_buffer_size += size;
Ok(true)
}
pub fn num_uniforms(&self) -> usize {
self.uniforms.len()
}
pub fn uniform(&self, index: usize) -> Result<(&str, &UniformData)> {
self.uniforms
.get(index)
.map(|u| (u.name.as_str(), &u.data))
.ok_or_else(|| {
Error::msg(format!(
"Uniforms access error: index = {} where size = {}",
index,
self.uniforms.len()
))
})
}
pub fn uniform_buffer_size(&self) -> usize {
self.uniform_buffer_size
}
pub fn num_textures(&self) -> usize {
self.textures.len()
}
fn lut1d_access_error(&self, index: usize) -> Error {
Error::msg(format!(
"1D LUT access error: index = {} where size = {}",
index,
self.textures.len()
))
}
fn lut3d_access_error(&self, index: usize) -> Error {
Error::msg(format!(
"3D LUT access error: index = {} where size = {}",
index,
self.textures_3d.len()
))
}
pub fn texture(&self, index: usize) -> Result<&GpuTexture> {
let t = self
.textures
.get(index)
.ok_or_else(|| self.lut1d_access_error(index))?;
if t.dimensions > 2 {
return Err(Error::msg(format!(
"1D LUT cannot have more than two dimensions: {} > 2",
t.dimensions
)));
}
Ok(t)
}
pub fn texture_dimensions(&self, index: usize) -> Result<TextureDimensions> {
let t = self.texture(index)?;
Ok(if t.dimensions == 1 {
TextureDimensions::Texture1D
} else {
TextureDimensions::Texture2D
})
}
pub fn texture_values(&self, index: usize) -> Result<&[f32]> {
self.textures
.get(index)
.map(|t| t.values.as_slice())
.ok_or_else(|| self.lut1d_access_error(index))
}
pub fn texture_shader_binding_index(&self, index: usize) -> Result<u32> {
self.textures
.get(index)
.map(|t| t.shader_binding_index + self.base.texture_binding_start)
.ok_or_else(|| self.lut1d_access_error(index))
}
pub fn num_3d_textures(&self) -> usize {
self.textures_3d.len()
}
pub fn texture_3d(&self, index: usize) -> Result<&GpuTexture> {
self.textures_3d
.get(index)
.ok_or_else(|| self.lut3d_access_error(index))
}
pub fn texture_3d_values(&self, index: usize) -> Result<&[f32]> {
self.texture_3d(index).map(|t| t.values.as_slice())
}
pub fn texture_3d_shader_binding_index(&self, index: usize) -> Result<u32> {
self.texture_3d(index)
.map(|t| t.shader_binding_index + self.base.texture_binding_start)
}
fn next_binding_index(&self) -> u32 {
(self.textures.len() + self.textures_3d.len()) as u32
}
}
impl GpuShaderCreator for GpuShaderDesc {
fn base(&self) -> &GpuShaderCreatorBase {
&self.base
}
fn base_mut(&mut self) -> &mut GpuShaderCreatorBase {
&mut self.base
}
fn clone_creator(&self) -> Box<dyn GpuShaderCreator> {
let mut desc = GpuShaderDesc::new();
desc.base = self.base.clone_for_creator();
Box::new(desc)
}
fn set_texture_max_width(&mut self, max_width: u32) {
self.max_1d_lut_width = max_width;
}
fn texture_max_width(&self) -> u32 {
self.max_1d_lut_width
}
fn set_allow_texture_1d(&mut self, allowed: bool) {
self.allow_texture_1d = allowed;
}
fn allow_texture_1d(&self) -> bool {
self.allow_texture_1d
}
fn add_uniform_double(&mut self, name: &str, getter: DoubleGetter) -> Result<bool> {
self.push_uniform(
name,
UniformValue::Double(getter),
GPU_FLOAT_ALIGNMENT,
GPU_FLOAT_SIZE,
)
}
fn add_uniform_bool(&mut self, name: &str, getter: BoolGetter) -> Result<bool> {
self.push_uniform(
name,
UniformValue::Bool(getter),
GPU_INT_ALIGNMENT,
GPU_INT_SIZE,
)
}
fn add_uniform_float3(&mut self, name: &str, getter: Float3Getter) -> Result<bool> {
self.push_uniform(
name,
UniformValue::Float3(getter),
GPU_VEC3_ALIGNMENT,
GPU_VEC3_SIZE,
)
}
fn add_uniform_vector_float(
&mut self,
name: &str,
get_size: SizeGetter,
getter: VectorFloatGetter,
max_size: u32,
) -> Result<bool> {
self.push_uniform(
name,
UniformValue::VectorFloat(get_size, getter),
GPU_ARRAY_ALIGNMENT,
GPU_ARRAY_STRIDE * max_size as usize,
)
}
fn add_uniform_vector_int(
&mut self,
name: &str,
get_size: SizeGetter,
getter: VectorIntGetter,
max_size: u32,
) -> Result<bool> {
self.push_uniform(
name,
UniformValue::VectorInt(get_size, getter),
GPU_ARRAY_ALIGNMENT,
GPU_ARRAY_STRIDE * max_size as usize,
)
}
fn add_texture(
&mut self,
texture_name: &str,
sampler_name: &str,
width: u32,
height: u32,
channel: TextureType,
dimensions: TextureDimensions,
interpolation: Interpolation,
values: &[f32],
) -> Result<u32> {
if width > self.max_1d_lut_width {
return Err(Error::msg(format!(
"1D LUT size exceeds the maximum: {} > {}",
width, self.max_1d_lut_width
)));
}
let index = self.next_binding_index();
let t = GpuTexture::new(
texture_name,
sampler_name,
width,
height,
1,
channel,
dimensions as u32,
interpolation,
index,
values,
)?;
self.textures.push(t);
Ok(index + self.base.texture_binding_start)
}
fn add_3d_texture(
&mut self,
texture_name: &str,
sampler_name: &str,
edgelen: u32,
interpolation: Interpolation,
values: &[f32],
) -> Result<u32> {
if edgelen > MAX_3D_LUT_LENGTH {
return Err(Error::msg(format!(
"3D LUT edge length exceeds the maximum: {} > {}",
edgelen, MAX_3D_LUT_LENGTH
)));
}
let index = self.next_binding_index();
let t = GpuTexture::new(
texture_name,
sampler_name,
edgelen,
edgelen,
edgelen,
TextureType::RgbChannel,
3,
interpolation,
index,
values,
)?;
self.textures_3d.push(t);
Ok(index + self.base.texture_binding_start)
}
}
#[cfg(test)]
mod tests;