diff --git a/librashader-capi/src/lib.rs b/librashader-capi/src/lib.rs index 7e0c4d6..1981840 100644 --- a/librashader-capi/src/lib.rs +++ b/librashader-capi/src/lib.rs @@ -27,13 +27,30 @@ //! ## Booleans //! Some option structs take `bool` values. //! Any booleans passed to librashader **must have a bit pattern equivalent to either `1` or `0`**. Any other value will cause -//! **immediate undefined behaviour**. +//! **immediate undefined behaviour**. Using `_Bool` from `stdbool.h` should maintain this invariant. //! //! ## Errors //! The librashader C API provides a robust, reflective error system. Every function returns a `libra_error_t`, which is either //! a null pointer, or a handle to an opaque allocated error object. If the returned error is null, then the function was successful. //! Otherwise, error information can be accessed via the `libra_error_` set of APIs. If an error indeed occurs, it may be freed by //! `libra_error_free`. +//! +//! It is **highly recommended** to check for errors after every call to a librashader API like in the following example. +//! +//! ```c +//! libra_preset_t preset; +//! libra_error_t error = libra.preset_create( +//! "slang-shaders/crt/crt-lottes.slangp", &preset); +//! if (error != NULL) { +//! libra.error_print(error); +//! libra.error_free(&error); +//! exit(1); +//! } +//! ``` +//! +//! There is a case to be made for skipping error checking for `*_filter_chain_frame` due to performance reasons, +//! but only if you are certain that the safety invariants are upheld on each call. Failure to check for errors +//! may result in **undefined behaviour** stemming from failure to uphold safety invariants. #![allow(non_camel_case_types)] #![feature(try_blocks)] #![feature(pointer_is_aligned)] diff --git a/librashader-preprocess/src/error.rs b/librashader-preprocess/src/error.rs index f7b974d..75e04d6 100644 --- a/librashader-preprocess/src/error.rs +++ b/librashader-preprocess/src/error.rs @@ -12,7 +12,9 @@ pub enum PreprocessError { #[error("the file was not found during resolution")] IOError(PathBuf, std::io::Error), /// A known encoding was not found for the file. - #[error("a known encoding was not found for the file. supported encodings are UTF-8 and Latin-1")] + #[error( + "a known encoding was not found for the file. supported encodings are UTF-8 and Latin-1" + )] EncodingError(PathBuf), /// Unexpected EOF when reading the source file. #[error("unexpected end of file")] diff --git a/librashader-preprocess/src/include.rs b/librashader-preprocess/src/include.rs index 075001b..9e17443 100644 --- a/librashader-preprocess/src/include.rs +++ b/librashader-preprocess/src/include.rs @@ -1,9 +1,9 @@ use crate::{PreprocessError, SourceOutput}; +use encoding_rs::{DecoderResult, WINDOWS_1252}; use std::fs::File; use std::io::Read; use std::path::Path; use std::str::Lines; -use encoding_rs::{DecoderResult, WINDOWS_1252}; #[cfg(feature = "line_directives")] const GL_GOOGLE_CPP_STYLE_LINE_DIRECTIVE: &str = @@ -30,7 +30,8 @@ fn read_file(path: impl AsRef) -> Result { let mut latin1_string = String::with_capacity(len); - let (result, _) = WINDOWS_1252.new_decoder() + let (result, _) = WINDOWS_1252 + .new_decoder() .decode_to_string_without_replacement(&buf, &mut latin1_string, true); if result == DecoderResult::InputEmpty { Ok(latin1_string) diff --git a/librashader-reflect/src/error.rs b/librashader-reflect/src/error.rs index c508556..0abb47d 100644 --- a/librashader-reflect/src/error.rs +++ b/librashader-reflect/src/error.rs @@ -1,4 +1,4 @@ -use crate::reflect::semantics::{MemberOffset, UniformMemberBlock}; +use crate::reflect::semantics::UniformMemberBlock; use thiserror::Error; /// Error type for shader compilation. @@ -82,7 +82,7 @@ pub enum ShaderReflectError { expected: usize, received: usize, ty: UniformMemberBlock, - pass: usize + pass: usize, }, /// The size of the given uniform did not match up in both the vertex and fragment shader. #[error("mismatched component")] @@ -90,7 +90,7 @@ pub enum ShaderReflectError { semantic: String, vertex: u32, fragment: u32, - pass: usize + pass: usize, }, /// The binding number is already in use. #[error("the binding is already in use")] diff --git a/librashader-reflect/src/reflect/cross.rs b/librashader-reflect/src/reflect/cross.rs index 28c0f54..0ad535a 100644 --- a/librashader-reflect/src/reflect/cross.rs +++ b/librashader-reflect/src/reflect/cross.rs @@ -1,6 +1,11 @@ use crate::error::{SemanticsErrorKind, ShaderCompileError, ShaderReflectError}; use crate::front::GlslangCompilation; -use crate::reflect::semantics::{BindingMeta, BindingStage, MemberOffset, PushReflection, ShaderReflection, ShaderSemantics, TextureBinding, TextureSemanticMap, TextureSemantics, TextureSizeMeta, TypeInfo, UboReflection, UniqueSemanticMap, UniqueSemantics, ValidateTypeSemantics, VariableMeta, MAX_BINDINGS_COUNT, MAX_PUSH_BUFFER_SIZE, UniformMemberBlock}; +use crate::reflect::semantics::{ + BindingMeta, BindingStage, MemberOffset, PushReflection, ShaderReflection, ShaderSemantics, + TextureBinding, TextureSemanticMap, TextureSemantics, TextureSizeMeta, TypeInfo, UboReflection, + UniformMemberBlock, UniqueSemanticMap, UniqueSemantics, ValidateTypeSemantics, VariableMeta, + MAX_BINDINGS_COUNT, MAX_PUSH_BUFFER_SIZE, +}; use crate::reflect::{align_uniform_size, ReflectShader}; use std::ops::Deref; @@ -311,7 +316,7 @@ where semantic: name, vertex: meta.size, fragment: typeinfo.size, - pass: pass_number + pass: pass_number, }); } @@ -346,7 +351,7 @@ where semantic: name, vertex: meta.size, fragment: typeinfo.size, - pass: pass_number + pass: pass_number, }); } @@ -851,7 +856,7 @@ mod test { use rustc_hash::FxHashMap; use crate::back::CompileShader; - use crate::front::shaderc::GlslangCompilation; + use crate::front::GlslangCompilation; use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics}; use librashader_preprocess::ShaderSource; use spirv_cross::glsl; diff --git a/librashader-reflect/src/reflect/semantics.rs b/librashader-reflect/src/reflect/semantics.rs index 27876b2..9554459 100644 --- a/librashader-reflect/src/reflect/semantics.rs +++ b/librashader-reflect/src/reflect/semantics.rs @@ -201,50 +201,46 @@ pub struct MemberOffset { pub push: Option, } - #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// The block where a uniform member is located. pub enum UniformMemberBlock { /// The offset is for a UBO. Ubo, /// The offset is for a push constant block. - PushConstant + PushConstant, } impl UniformMemberBlock { /// A list of valid member block types. - pub const TYPES: [UniformMemberBlock; 2] = [UniformMemberBlock::Ubo, UniformMemberBlock::PushConstant]; + pub const TYPES: [UniformMemberBlock; 2] = + [UniformMemberBlock::Ubo, UniformMemberBlock::PushConstant]; } impl MemberOffset { pub(crate) fn new(off: usize, ty: UniformMemberBlock) -> Self { match ty { - UniformMemberBlock::Ubo => { - MemberOffset { - ubo: Some(off), - push: None, - } - } - UniformMemberBlock::PushConstant => { - MemberOffset { - ubo: None, - push: Some(off), - } - } + UniformMemberBlock::Ubo => MemberOffset { + ubo: Some(off), + push: None, + }, + UniformMemberBlock::PushConstant => MemberOffset { + ubo: None, + push: Some(off), + }, } } pub fn offset(&self, ty: UniformMemberBlock) -> Option { match ty { - UniformMemberBlock::Ubo => {self.ubo} - UniformMemberBlock::PushConstant => {self.push} + UniformMemberBlock::Ubo => self.ubo, + UniformMemberBlock::PushConstant => self.push, } } pub(crate) fn offset_mut(&mut self, ty: UniformMemberBlock) -> &mut Option { match ty { - UniformMemberBlock::Ubo => {&mut self.ubo} - UniformMemberBlock::PushConstant => {&mut self.push} + UniformMemberBlock::Ubo => &mut self.ubo, + UniformMemberBlock::PushConstant => &mut self.push, } } } diff --git a/librashader-runtime-d3d11/src/hello_triangle.rs b/librashader-runtime-d3d11/src/hello_triangle.rs index f50e33c..cdc1d6c 100644 --- a/librashader-runtime-d3d11/src/hello_triangle.rs +++ b/librashader-runtime-d3d11/src/hello_triangle.rs @@ -411,7 +411,7 @@ pub mod d3d11_hello_triangle { .ClearRenderTargetView(&resources.backbuffer_rtv, color.as_ptr()); self.context.ClearDepthStencilView( &resources.depth_stencil_view, - D3D11_CLEAR_DEPTH.0 as u32, + D3D11_CLEAR_DEPTH.0, 1.0, 0, ); diff --git a/librashader-runtime-d3d11/src/lib.rs b/librashader-runtime-d3d11/src/lib.rs index 251e992..1bb6895 100644 --- a/librashader-runtime-d3d11/src/lib.rs +++ b/librashader-runtime-d3d11/src/lib.rs @@ -33,8 +33,7 @@ mod tests { #[test] fn triangle_d3d11() { let sample = hello_triangle::d3d11_hello_triangle::Sample::new( - // "../test/slang-shaders/presets/crt-royale-kurozumi.slangp", - "../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp", + "../test/slang-shaders/crt/crt-royale.slangp", // "../test/basic.slangp", Some(&FilterChainOptionsD3D11 { use_deferred_context: false, diff --git a/librashader-runtime-d3d11/src/util.rs b/librashader-runtime-d3d11/src/util.rs index 4362b5e..06aed7c 100644 --- a/librashader-runtime-d3d11/src/util.rs +++ b/librashader-runtime-d3d11/src/util.rs @@ -2,7 +2,9 @@ use crate::error; use crate::error::assume_d3d11_init; use std::slice; use windows::core::PCSTR; -use windows::Win32::Graphics::Direct3D::Fxc::{D3DCompile, D3DCOMPILE_DEBUG, D3DCOMPILE_OPTIMIZATION_LEVEL3, D3DCOMPILE_SKIP_OPTIMIZATION}; +use windows::Win32::Graphics::Direct3D::Fxc::{ + D3DCompile, D3DCOMPILE_DEBUG, D3DCOMPILE_OPTIMIZATION_LEVEL3, D3DCOMPILE_SKIP_OPTIMIZATION, +}; use windows::Win32::Graphics::Direct3D::ID3DBlob; use windows::Win32::Graphics::Direct3D11::*; use windows::Win32::Graphics::Dxgi::Common::*; diff --git a/librashader-runtime-gl/src/binding.rs b/librashader-runtime-gl/src/binding.rs index 2778f6b..24d0572 100644 --- a/librashader-runtime-gl/src/binding.rs +++ b/librashader-runtime-gl/src/binding.rs @@ -10,26 +10,10 @@ pub struct VariableLocation { impl VariableLocation { pub fn location(&self, offset_type: UniformMemberBlock) -> Option> { - let value = match offset_type { - UniformMemberBlock::Ubo => { - self.ubo - } - UniformMemberBlock::PushConstant => { - self.push - } - }; - value - } - pub fn is_valid(&self, offset_type: UniformMemberBlock, stage: BindingStage) -> bool { - let value = self.location(offset_type); - let mut validity = false; - if stage.contains(BindingStage::FRAGMENT) { - validity = validity || value.is_some_and(|f| f.fragment >= 0); + match offset_type { + UniformMemberBlock::Ubo => self.ubo, + UniformMemberBlock::PushConstant => self.push, } - if stage.contains(BindingStage::VERTEX) { - validity = validity || value.is_some_and(|f| f.vertex >= 0); - } - validity } } @@ -97,7 +81,11 @@ where } impl BindUniform for GlUniformBinder { - fn bind_uniform(block: UniformMemberBlock, vec4: &[f32; 4], location: VariableLocation) -> Option<()> { + fn bind_uniform( + block: UniformMemberBlock, + vec4: &[f32; 4], + location: VariableLocation, + ) -> Option<()> { if let Some(location) = location.location(block) && location.bindable() { @@ -117,7 +105,11 @@ impl BindUniform for GlUniformBinder { } impl BindUniform for GlUniformBinder { - fn bind_uniform(block: UniformMemberBlock, mat4: &[f32; 16], location: VariableLocation) -> Option<()> { + fn bind_uniform( + block: UniformMemberBlock, + mat4: &[f32; 16], + location: VariableLocation, + ) -> Option<()> { if let Some(location) = location.location(block) && location.bindable() { diff --git a/librashader-runtime-gl/src/filter_chain/filter_impl.rs b/librashader-runtime-gl/src/filter_chain/filter_impl.rs index 869e486..75ca6b1 100644 --- a/librashader-runtime-gl/src/filter_chain/filter_impl.rs +++ b/librashader-runtime-gl/src/filter_chain/filter_impl.rs @@ -17,7 +17,7 @@ use librashader_reflect::back::targets::GLSL; use librashader_reflect::back::{CompileReflectShader, CompileShader}; use librashader_reflect::front::GlslangCompilation; use librashader_reflect::reflect::semantics::{ - MemberOffset, ShaderSemantics, TextureSemantics, UniformBinding, UniformMeta, + ShaderSemantics, TextureSemantics, UniformBinding, UniformMeta, }; use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact}; @@ -53,7 +53,6 @@ pub struct FilterMutable { impl FilterChainImpl { fn reflect_uniform_location(pipeline: GLuint, meta: &impl UniformMeta) -> VariableLocation { - let mut location = VariableLocation { ubo: None, push: None, diff --git a/librashader-runtime-gl/src/filter_pass.rs b/librashader-runtime-gl/src/filter_pass.rs index f963f56..94c953f 100644 --- a/librashader-runtime-gl/src/filter_pass.rs +++ b/librashader-runtime-gl/src/filter_pass.rs @@ -1,4 +1,4 @@ -use gl::types::{GLint, GLsizei, GLuint}; +use gl::types::{GLsizei, GLuint}; use librashader_reflect::back::cross::CrossGlslContext; use librashader_reflect::back::ShaderCompilerOutput; use librashader_reflect::reflect::ShaderReflection; diff --git a/librashader-runtime-vk/src/filter_chain.rs b/librashader-runtime-vk/src/filter_chain.rs index 8e6a3e9..7efcea4 100644 --- a/librashader-runtime-vk/src/filter_chain.rs +++ b/librashader-runtime-vk/src/filter_chain.rs @@ -149,7 +149,7 @@ struct FrameResiduals { device: ash::Device, image_views: Vec, owned: Vec, - framebuffers: Vec> + framebuffers: Vec>, } impl FrameResiduals { @@ -232,8 +232,15 @@ impl FilterChainVulkan { } // initialize passes - let filters = Self::init_passes(&device, passes, &semantics, frames_in_flight, options - .map(|o| o.render_pass_format).unwrap_or(vk::Format::UNDEFINED))?; + let filters = Self::init_passes( + &device, + passes, + &semantics, + frames_in_flight, + options + .map(|o| o.render_pass_format) + .unwrap_or(vk::Format::UNDEFINED), + )?; let luts = FilterChainVulkan::load_luts(&device, &preset.textures)?; let samplers = SamplerSet::new(&device.device)?; @@ -348,7 +355,7 @@ impl FilterChainVulkan { &spirv_words, &reflection, frames_in_flight, - render_pass_format + render_pass_format, )?; // let ubo_ring = VkUboRing::new( diff --git a/librashader-runtime-vk/src/filter_pass.rs b/librashader-runtime-vk/src/filter_pass.rs index c235335..aeb4de1 100644 --- a/librashader-runtime-vk/src/filter_pass.rs +++ b/librashader-runtime-vk/src/filter_pass.rs @@ -132,7 +132,9 @@ impl FilterPass { output.output.begin_pass(cmd); - let residual = self.graphics_pipeline.begin_rendering(&parent.device, output, cmd)?; + let residual = self + .graphics_pipeline + .begin_rendering(&parent.device, output, cmd)?; unsafe { parent.device.cmd_bind_pipeline( diff --git a/librashader-runtime-vk/src/hello_triangle/vulkan_base.rs b/librashader-runtime-vk/src/hello_triangle/vulkan_base.rs index 42fb58c..9835b30 100644 --- a/librashader-runtime-vk/src/hello_triangle/vulkan_base.rs +++ b/librashader-runtime-vk/src/hello_triangle/vulkan_base.rs @@ -2,7 +2,7 @@ use ash::vk; use crate::error::FilterChainError; use crate::filter_chain::VulkanObjects; -use crate::hello_triangle::debug::VulkanDebug; + use crate::hello_triangle::physicaldevice::{find_queue_family, pick_physical_device}; use ash::prelude::VkResult; @@ -76,7 +76,7 @@ impl VulkanBase { instance: &ash::Instance, physical_device: &vk::PhysicalDevice, ) -> VkResult<(ash::Device, vk::Queue)> { - let debug = [unsafe { CStr::from_bytes_with_nul_unchecked(KHRONOS_VALIDATION).as_ptr() }]; + let _debug = [unsafe { CStr::from_bytes_with_nul_unchecked(KHRONOS_VALIDATION).as_ptr() }]; let indices = find_queue_family(instance, *physical_device); let queue_info = [vk::DeviceQueueCreateInfo::builder() diff --git a/librashader-runtime-vk/src/lib.rs b/librashader-runtime-vk/src/lib.rs index 1da1218..9625ee9 100644 --- a/librashader-runtime-vk/src/lib.rs +++ b/librashader-runtime-vk/src/lib.rs @@ -34,10 +34,10 @@ mod render_pass; #[cfg(test)] mod tests { - use ash::vk; use crate::filter_chain::FilterChainVulkan; use crate::hello_triangle::vulkan_base::VulkanBase; use crate::options::FilterChainOptionsVulkan; + use ash::vk; #[test] fn triangle_vk() { diff --git a/librashader-runtime-vk/src/options.rs b/librashader-runtime-vk/src/options.rs index b04b53e..4711412 100644 --- a/librashader-runtime-vk/src/options.rs +++ b/librashader-runtime-vk/src/options.rs @@ -24,5 +24,5 @@ pub struct FilterChainOptionsVulkan { /// will be used instead of a render pass. If this is set to some format, the render passes /// will be created with such format. It is recommended if possible to use dynamic rendering, /// because render-pass mode will create new framebuffers per pass. - pub render_pass_format: vk::Format + pub render_pass_format: vk::Format, } diff --git a/librashader-runtime-vk/src/render_pass.rs b/librashader-runtime-vk/src/render_pass.rs index 863d48b..881807f 100644 --- a/librashader-runtime-vk/src/render_pass.rs +++ b/librashader-runtime-vk/src/render_pass.rs @@ -10,10 +10,7 @@ pub struct VulkanRenderPass { } impl VulkanRenderPass { - pub fn create_render_pass( - device: &ash::Device, - mut format: vk::Format, - ) -> error::Result { + pub fn create_render_pass(device: &ash::Device, format: vk::Format) -> error::Result { // format should never be undefined. let attachment = [vk::AttachmentDescription::builder() .flags(vk::AttachmentDescriptionFlags::empty()) @@ -48,4 +45,4 @@ impl VulkanRenderPass { Ok(Self { handle: rp, format }) } } -} \ No newline at end of file +} diff --git a/librashader-runtime-vk/src/vulkan_state.rs b/librashader-runtime-vk/src/vulkan_state.rs index ec395d0..1fb25cc 100644 --- a/librashader-runtime-vk/src/vulkan_state.rs +++ b/librashader-runtime-vk/src/vulkan_state.rs @@ -1,14 +1,12 @@ use crate::{error, util}; use ash::vk; +use crate::render_pass::VulkanRenderPass; +use crate::render_target::RenderTarget; use librashader_reflect::back::ShaderCompilerOutput; use librashader_reflect::reflect::semantics::{TextureBinding, UboReflection}; use librashader_reflect::reflect::ShaderReflection; use std::ffi::CStr; -use crate::framebuffer::OutputImage; -use crate::render_target::RenderTarget; -use crate::render_pass::VulkanRenderPass; - const ENTRY_POINT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"main\0") }; @@ -177,7 +175,7 @@ impl Drop for VulkanShaderModule { pub struct VulkanGraphicsPipeline { pub layout: PipelineLayoutObjects, pub pipeline: vk::Pipeline, - pub render_pass: Option + pub render_pass: Option, } impl VulkanGraphicsPipeline { @@ -187,7 +185,7 @@ impl VulkanGraphicsPipeline { shader_assembly: &ShaderCompilerOutput>, reflection: &ShaderReflection, replicas: u32, - render_pass_format: vk::Format + render_pass_format: vk::Format, ) -> error::Result { let pipeline_layout = PipelineLayoutObjects::new(reflection, replicas, device)?; @@ -300,7 +298,10 @@ impl VulkanGraphicsPipeline { let mut render_pass = None; if render_pass_format != vk::Format::UNDEFINED { - render_pass = Some(VulkanRenderPass::create_render_pass(&device, render_pass_format)?); + render_pass = Some(VulkanRenderPass::create_render_pass( + device, + render_pass_format, + )?); pipeline_info = pipeline_info.render_pass(render_pass.as_ref().unwrap().handle) } @@ -322,7 +323,12 @@ impl VulkanGraphicsPipeline { } #[inline(always)] - pub(crate) fn begin_rendering(&self, device: &ash::Device, output: &RenderTarget, cmd: vk::CommandBuffer) -> error::Result>{ + pub(crate) fn begin_rendering( + &self, + device: &ash::Device, + output: &RenderTarget, + cmd: vk::CommandBuffer, + ) -> error::Result> { if let Some(render_pass) = &self.render_pass { let attachments = [output.output.image_view]; let framebuffer = unsafe { @@ -340,8 +346,8 @@ impl VulkanGraphicsPipeline { let clear_values = [vk::ClearValue { color: vk::ClearColorValue { - float32: [0.0, 0.0, 0.0, 0.0] - } + float32: [0.0, 0.0, 0.0, 0.0], + }, }]; let render_pass_info = vk::RenderPassBeginInfo::builder() @@ -350,12 +356,10 @@ impl VulkanGraphicsPipeline { .clear_values(&clear_values) // always render into the full output, regardless of viewport settings. .render_area(vk::Rect2D { - offset: vk::Offset2D { - x: 0, - y: 0, - }, + offset: vk::Offset2D { x: 0, y: 0 }, extent: output.output.size.into(), - }).build(); + }) + .build(); unsafe { device.cmd_begin_render_pass(cmd, &render_pass_info, vk::SubpassContents::INLINE); } @@ -382,7 +386,6 @@ impl VulkanGraphicsPipeline { } Ok(None) } - } pub(crate) fn end_rendering(&self, device: &ash::Device, cmd: vk::CommandBuffer) { diff --git a/librashader-runtime/src/uniforms.rs b/librashader-runtime/src/uniforms.rs index d516bbb..72fe3a1 100644 --- a/librashader-runtime/src/uniforms.rs +++ b/librashader-runtime/src/uniforms.rs @@ -89,12 +89,8 @@ where pub(crate) fn buffer(&mut self, ty: UniformMemberBlock) -> &mut [u8] { match ty { - UniformMemberBlock::Ubo => { - self.ubo.deref_mut() - } - UniformMemberBlock::PushConstant => { - self.push.deref_mut() - } + UniformMemberBlock::Ubo => self.ubo.deref_mut(), + UniformMemberBlock::PushConstant => self.push.deref_mut(), } } } @@ -105,8 +101,7 @@ where S: Deref + DerefMut, { #[inline(always)] - fn write_scalar_inner(buffer: &mut [u8], value: T) - { + fn write_scalar_inner(buffer: &mut [u8], value: T) { let buffer = bytemuck::cast_slice_mut(buffer); buffer[0] = value; } @@ -124,10 +119,7 @@ where if let Some(offset) = offset.offset(ty) { let buffer = self.buffer(ty); - Self::write_scalar_inner( - &mut buffer[offset..][..std::mem::size_of::()], - value, - ) + Self::write_scalar_inner(&mut buffer[offset..][..std::mem::size_of::()], value) } } } @@ -171,7 +163,6 @@ where pub fn bind_vec4(&mut self, offset: MemberOffset, value: impl Into<[f32; 4]>, ctx: C) { let vec4 = value.into(); - for ty in UniformMemberBlock::TYPES { if H::bind_uniform(ty, &vec4, ctx).is_some() { continue;