reflect: allow binding uniform names to both UBO and Push Constants (#4)

This commit is contained in:
Ronny Chan 2023-01-29 01:57:09 -05:00 committed by GitHub
parent c3aecd336b
commit dffea95370
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 240 additions and 129 deletions

View file

@ -88,11 +88,6 @@ shaders.
Please report an issue if you run into a shader that works in RetroArch, but not under librashader.
* Variables can only be bound in *either* the UBO or push constant block.
* RetroArch allows a variable to be present in both a push constant block and a UBO block at the same time. To make the
implementation a little cleaner, librashader only allows a variable to be in *either* a push constant block or a UBO
block. As far as I have tested, there are no shaders in [libretro/slang-shaders](https://github.com/libretro/slang-shaders)
that bind the same variable in both the push constant and the UBO block.
* Filter chains do not terminate at the backbuffer.
* Unlike RetroArch, librashader does not have full knowledge of the entire rendering state and is designed to be pluggable
at any point in your render pipeline. Instead, filter chains terminate at a caller-provided output surface and viewport.

View file

@ -1,4 +1,4 @@
use crate::reflect::semantics::MemberOffset;
use crate::reflect::semantics::{MemberOffset, UniformMemberBlock};
use thiserror::Error;
/// Error type for shader compilation.
@ -79,8 +79,10 @@ pub enum ShaderReflectError {
#[error("mismatched offset")]
MismatchedOffset {
semantic: String,
vertex: MemberOffset,
fragment: MemberOffset,
expected: usize,
received: usize,
ty: UniformMemberBlock,
pass: usize
},
/// The size of the given uniform did not match up in both the vertex and fragment shader.
#[error("mismatched component")]
@ -88,6 +90,7 @@ pub enum ShaderReflectError {
semantic: String,
vertex: u32,
fragment: u32,
pass: usize
},
/// The binding number is already in use.
#[error("the binding is already in use")]

View file

@ -46,6 +46,7 @@
//! In the meanwhile, the only supported compilation type is [GlslangCompilation](crate::front::GlslangCompilation),
//! which does transpilation via [shaderc](https://github.com/google/shaderc) and [SPIRV-Cross](https://github.com/KhronosGroup/SPIRV-Cross).
#![feature(type_alias_impl_trait)]
#![feature(let_chains)]
/// Shader codegen backends.
pub mod back;

View file

@ -1,11 +1,6 @@
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,
};
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::{align_uniform_size, ReflectShader};
use std::ops::Deref;
@ -273,7 +268,7 @@ where
pass_number: usize,
semantics: &ShaderSemantics,
meta: &mut BindingMeta,
offset_type: impl Fn(usize) -> MemberOffset,
offset_type: UniformMemberBlock,
blame: SemanticErrorBlame,
) -> Result<(), ShaderReflectError> {
let ranges = ast.get_active_buffer_ranges(resource.id)?;
@ -298,13 +293,17 @@ where
match &parameter.semantics {
UniqueSemantics::FloatParameter => {
let offset = offset_type(range.offset);
if let Some(meta) = meta.parameter_meta.get(&name) {
if offset != meta.offset {
let offset = range.offset;
if let Some(meta) = meta.parameter_meta.get_mut(&name) {
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
vertex: meta.offset,
fragment: offset,
expected,
received: offset,
ty: offset_type,
pass: pass_number
});
}
if meta.size != typeinfo.size {
@ -312,27 +311,34 @@ where
semantic: name,
vertex: meta.size,
fragment: typeinfo.size,
pass: pass_number
});
}
*meta.offset.offset_mut(offset_type) = Some(offset);
} else {
meta.parameter_meta.insert(
name.clone(),
VariableMeta {
id: name,
offset,
offset: MemberOffset::new(offset, offset_type),
size: typeinfo.size,
},
);
}
}
semantics => {
let offset = offset_type(range.offset);
if let Some(meta) = meta.unique_meta.get(semantics) {
if offset != meta.offset {
let offset = range.offset;
if let Some(meta) = meta.unique_meta.get_mut(semantics) {
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
vertex: meta.offset,
fragment: offset,
expected,
received: offset,
ty: offset_type,
pass: pass_number
});
}
if meta.size != typeinfo.size * typeinfo.columns {
@ -340,14 +346,17 @@ where
semantic: name,
vertex: meta.size,
fragment: typeinfo.size,
pass: pass_number
});
}
*meta.offset.offset_mut(offset_type) = Some(offset);
} else {
meta.unique_meta.insert(
*semantics,
VariableMeta {
id: name,
offset,
offset: MemberOffset::new(offset, offset_type),
size: typeinfo.size * typeinfo.columns,
},
);
@ -368,14 +377,17 @@ where
}
}
// this will break if range is both ubo and push buf whatever
let offset = offset_type(range.offset);
let offset = range.offset;
if let Some(meta) = meta.texture_size_meta.get_mut(&texture) {
if offset != meta.offset {
if let Some(expected) = meta.offset.offset(offset_type)
&& expected != offset
{
return Err(ShaderReflectError::MismatchedOffset {
semantic: name,
vertex: meta.offset,
fragment: offset,
expected,
received: offset,
ty: offset_type,
pass: pass_number
});
}
@ -383,12 +395,13 @@ where
SemanticErrorBlame::Vertex => BindingStage::VERTEX,
SemanticErrorBlame::Fragment => BindingStage::FRAGMENT,
});
*meta.offset.offset_mut(offset_type) = Some(offset);
} else {
meta.texture_size_meta.insert(
texture,
TextureSizeMeta {
offset,
// todo: fix this. to allow both
offset: MemberOffset::new(offset, offset_type),
stage_mask: match blame {
SemanticErrorBlame::Vertex => BindingStage::VERTEX,
SemanticErrorBlame::Fragment => BindingStage::FRAGMENT,
@ -607,7 +620,7 @@ where
pass_number,
semantics,
&mut meta,
MemberOffset::Ubo,
UniformMemberBlock::Ubo,
SemanticErrorBlame::Vertex,
)?;
}
@ -619,7 +632,7 @@ where
pass_number,
semantics,
&mut meta,
MemberOffset::Ubo,
UniformMemberBlock::Ubo,
SemanticErrorBlame::Fragment,
)?;
}
@ -631,7 +644,7 @@ where
pass_number,
semantics,
&mut meta,
MemberOffset::PushConstant,
UniformMemberBlock::PushConstant,
SemanticErrorBlame::Vertex,
)?;
}
@ -643,7 +656,7 @@ where
pass_number,
semantics,
&mut meta,
MemberOffset::PushConstant,
UniformMemberBlock::PushConstant,
SemanticErrorBlame::Fragment,
)?;
}

View file

@ -194,11 +194,59 @@ pub struct PushReflection {
/// A uniform can be bound to **either** the UBO, or as a Push Constant. Binding
/// the same variable name to both locations will result in indeterminate results.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MemberOffset {
pub struct MemberOffset {
/// The offset of the uniform member within the UBO.
Ubo(usize),
pub ubo: Option<usize>,
/// The offset of the uniform member within the Push Constant range.
PushConstant(usize),
pub push: Option<usize>,
}
#[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
}
impl UniformMemberBlock {
/// A list of valid member block types.
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),
}
}
}
}
pub fn offset(&self, ty: UniformMemberBlock) -> Option<usize> {
match ty {
UniformMemberBlock::Ubo => {self.ubo}
UniformMemberBlock::PushConstant => {self.push}
}
}
pub(crate) fn offset_mut(&mut self, ty: UniformMemberBlock) -> &mut Option<usize> {
match ty {
UniformMemberBlock::Ubo => {&mut self.ubo}
UniformMemberBlock::PushConstant => {&mut self.push}
}
}
}
/// Reflection information about a non-texture related uniform variable.

View file

@ -33,7 +33,8 @@ 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/presets/crt-royale-kurozumi.slangp",
"../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp",
// "../test/basic.slangp",
Some(&FilterChainOptionsD3D11 {
use_deferred_context: false,

View file

@ -1,18 +1,35 @@
use gl::types::GLint;
use librashader_reflect::reflect::semantics::BindingStage;
use librashader_reflect::reflect::semantics::{BindingStage, UniformMemberBlock};
use librashader_runtime::uniforms::{BindUniform, UniformScalar, UniformStorage};
#[derive(Debug)]
pub enum VariableLocation {
Ubo(UniformLocation<GLint>),
Push(UniformLocation<GLint>),
#[derive(Debug, Copy, Clone)]
pub struct VariableLocation {
pub(crate) ubo: Option<UniformLocation<GLint>>,
pub(crate) push: Option<UniformLocation<GLint>>,
}
impl VariableLocation {
pub fn location(&self) -> UniformLocation<GLint> {
match self {
VariableLocation::Ubo(l) | VariableLocation::Push(l) => *l,
pub fn location(&self, offset_type: UniformMemberBlock) -> Option<UniformLocation<GLint>> {
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);
}
if stage.contains(BindingStage::VERTEX) {
validity = validity || value.is_some_and(|f| f.vertex >= 0);
}
validity
}
}
@ -33,9 +50,13 @@ impl UniformLocation<GLint> {
}
validity
}
pub fn bindable(&self) -> bool {
self.is_valid(BindingStage::VERTEX | BindingStage::FRAGMENT)
}
}
pub(crate) type GlUniformStorage = UniformStorage<GlUniformBinder, UniformLocation<GLint>>;
pub(crate) type GlUniformStorage = UniformStorage<GlUniformBinder, VariableLocation>;
pub trait GlUniformScalar: UniformScalar {
const FACTORY: unsafe fn(GLint, Self) -> ();
@ -54,19 +75,19 @@ impl GlUniformScalar for u32 {
}
pub(crate) struct GlUniformBinder;
impl<T> BindUniform<UniformLocation<GLint>, T> for GlUniformBinder
impl<T> BindUniform<VariableLocation, T> for GlUniformBinder
where
T: GlUniformScalar,
{
fn bind_uniform(value: T, location: UniformLocation<GLint>) -> Option<()> {
if location.is_valid(BindingStage::VERTEX | BindingStage::FRAGMENT) {
unsafe {
if location.is_valid(BindingStage::VERTEX) {
T::FACTORY(location.vertex, value);
}
if location.is_valid(BindingStage::FRAGMENT) {
T::FACTORY(location.fragment, value);
}
fn bind_uniform(block: UniformMemberBlock, value: T, location: VariableLocation) -> Option<()> {
if let Some(location) = location.location(block)
&& location.bindable()
{
if location.is_valid(BindingStage::VERTEX) {
unsafe { T::FACTORY(location.vertex, value); }
}
if location.is_valid(BindingStage::FRAGMENT) {
unsafe { T::FACTORY(location.fragment, value); }
}
Some(())
} else {
@ -75,9 +96,11 @@ where
}
}
impl BindUniform<UniformLocation<GLint>, &[f32; 4]> for GlUniformBinder {
fn bind_uniform(vec4: &[f32; 4], location: UniformLocation<GLint>) -> Option<()> {
if location.is_valid(BindingStage::VERTEX | BindingStage::FRAGMENT) {
impl BindUniform<VariableLocation, &[f32; 4]> for GlUniformBinder {
fn bind_uniform(block: UniformMemberBlock, vec4: &[f32; 4], location: VariableLocation) -> Option<()> {
if let Some(location) = location.location(block)
&& location.bindable()
{
unsafe {
if location.is_valid(BindingStage::VERTEX) {
gl::Uniform4fv(location.vertex, 1, vec4.as_ptr());
@ -93,9 +116,11 @@ impl BindUniform<UniformLocation<GLint>, &[f32; 4]> for GlUniformBinder {
}
}
impl BindUniform<UniformLocation<GLint>, &[f32; 16]> for GlUniformBinder {
fn bind_uniform(mat4: &[f32; 16], location: UniformLocation<GLint>) -> Option<()> {
if location.is_valid(BindingStage::VERTEX | BindingStage::FRAGMENT) {
impl BindUniform<VariableLocation, &[f32; 16]> for GlUniformBinder {
fn bind_uniform(block: UniformMemberBlock, mat4: &[f32; 16], location: VariableLocation) -> Option<()> {
if let Some(location) = location.location(block)
&& location.bindable()
{
unsafe {
if location.is_valid(BindingStage::VERTEX) {
gl::UniformMatrix4fv(location.vertex, 1, gl::FALSE, mat4.as_ptr());

View file

@ -53,30 +53,37 @@ pub struct FilterMutable {
impl<T: GLInterface> FilterChainImpl<T> {
fn reflect_uniform_location(pipeline: GLuint, meta: &impl UniformMeta) -> VariableLocation {
// todo: support both ubo and pushco
// todo: fix this.
match meta.offset() {
MemberOffset::Ubo(_) => {
let vert_name = format!("LIBRA_UBO_VERTEX_INSTANCE.{}\0", meta.id());
let frag_name = format!("LIBRA_UBO_FRAGMENT_INSTANCE.{}\0", meta.id());
unsafe {
let vertex = gl::GetUniformLocation(pipeline, vert_name.as_ptr().cast());
let fragment = gl::GetUniformLocation(pipeline, frag_name.as_ptr().cast());
VariableLocation::Ubo(UniformLocation { vertex, fragment })
}
}
MemberOffset::PushConstant(_) => {
let vert_name = format!("LIBRA_PUSH_VERTEX_INSTANCE.{}\0", meta.id());
let frag_name = format!("LIBRA_PUSH_FRAGMENT_INSTANCE.{}\0", meta.id());
unsafe {
let vertex = gl::GetUniformLocation(pipeline, vert_name.as_ptr().cast());
let fragment = gl::GetUniformLocation(pipeline, frag_name.as_ptr().cast());
let mut location = VariableLocation {
ubo: None,
push: None,
};
VariableLocation::Push(UniformLocation { vertex, fragment })
}
let offset = meta.offset();
if offset.ubo.is_some() {
let vert_name = format!("LIBRA_UBO_VERTEX_INSTANCE.{}\0", meta.id());
let frag_name = format!("LIBRA_UBO_FRAGMENT_INSTANCE.{}\0", meta.id());
unsafe {
let vertex = gl::GetUniformLocation(pipeline, vert_name.as_ptr().cast());
let fragment = gl::GetUniformLocation(pipeline, frag_name.as_ptr().cast());
location.ubo = Some(UniformLocation { vertex, fragment })
}
}
if offset.push.is_some() {
let vert_name = format!("LIBRA_PUSH_VERTEX_INSTANCE.{}\0", meta.id());
let frag_name = format!("LIBRA_PUSH_FRAGMENT_INSTANCE.{}\0", meta.id());
unsafe {
let vertex = gl::GetUniformLocation(pipeline, vert_name.as_ptr().cast());
let fragment = gl::GetUniformLocation(pipeline, frag_name.as_ptr().cast());
location.push = Some(UniformLocation { vertex, fragment })
}
}
location
}
}

View file

@ -48,17 +48,17 @@ impl TextureInput for InputTexture {
}
}
impl ContextOffset<GlUniformBinder, UniformLocation<GLint>> for UniformOffset {
impl ContextOffset<GlUniformBinder, VariableLocation> for UniformOffset {
fn offset(&self) -> MemberOffset {
self.offset
}
fn context(&self) -> UniformLocation<GLint> {
self.location.location()
fn context(&self) -> VariableLocation {
self.location
}
}
impl<T: GLInterface> BindSemantics<GlUniformBinder, UniformLocation<GLint>> for FilterPass<T> {
impl<T: GLInterface> BindSemantics<GlUniformBinder, VariableLocation> for FilterPass<T> {
type InputTexture = InputTexture;
type SamplerSet = SamplerSet;
type DescriptorSet<'a> = ();

View file

@ -6,6 +6,7 @@
#![feature(strict_provenance)]
#![feature(type_alias_impl_trait)]
#![feature(let_chains)]
#![feature(is_some_and)]
mod binding;
mod filter_chain;
@ -35,7 +36,7 @@ mod tests {
fn triangle_gl() {
let (glfw, window, events, shader, vao) = gl::gl3::hello_triangle::setup();
let mut filter = FilterChainGL::load_from_path(
"../test/slang-shaders/crt/crt-lottes.slangp",
"../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp",
Some(&FilterChainOptionsGL {
gl_version: 0,
use_dsa: false,

View file

@ -50,6 +50,7 @@ where
/// Trait that abstracts binding of semantics to shader uniforms.
pub trait BindSemantics<H = NoUniformBinder, C = Option<()>, S = Box<[u8]>>
where
C: Copy,
S: Deref<Target = [u8]> + DerefMut,
H: BindUniform<C, f32>,
H: BindUniform<C, u32>,

View file

@ -1,4 +1,4 @@
use librashader_reflect::reflect::semantics::MemberOffset;
use librashader_reflect::reflect::semantics::{MemberOffset, UniformMemberBlock};
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
@ -15,7 +15,7 @@ pub trait BindUniform<C, T> {
/// A `BindUniform` implementation should not write to a backing buffer from a [`UniformStorage`](crate::uniforms::UniformStorage).
/// If the binding is successful and no writes to a backing buffer is necessary, this function should return `Some(())`.
/// If this function returns `None`, then the value will instead be written to the backing buffer.
fn bind_uniform(value: T, ctx: C) -> Option<()>;
fn bind_uniform(block: UniformMemberBlock, value: T, ctx: C) -> Option<()>;
}
/// A trait to access the raw pointer to a backing uniform storage.
@ -62,7 +62,7 @@ where
/// All uniform data is thus written into the backing buffer storage.
pub struct NoUniformBinder;
impl<T> BindUniform<Option<()>, T> for NoUniformBinder {
fn bind_uniform(_: T, _: Option<()>) -> Option<()> {
fn bind_uniform(_: UniformMemberBlock, _: T, _: Option<()>) -> Option<()> {
None
}
}
@ -86,10 +86,22 @@ where
pub fn inner_ubo(&self) -> &S {
&self.ubo
}
pub(crate) fn buffer(&mut self, ty: UniformMemberBlock) -> &mut [u8] {
match ty {
UniformMemberBlock::Ubo => {
self.ubo.deref_mut()
}
UniformMemberBlock::PushConstant => {
self.push.deref_mut()
}
}
}
}
impl<H, C, S> UniformStorage<H, C, S>
where
C: Copy,
S: Deref<Target = [u8]> + DerefMut,
{
#[inline(always)]
@ -105,19 +117,19 @@ where
where
H: BindUniform<C, T>,
{
if H::bind_uniform(value, ctx).is_some() {
return;
for ty in UniformMemberBlock::TYPES {
if H::bind_uniform(ty, value, ctx).is_some() {
continue;
}
if let Some(offset) = offset.offset(ty) {
let buffer = self.buffer(ty);
Self::write_scalar_inner(
&mut buffer[offset..][..std::mem::size_of::<T>()],
value,
)
}
}
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (self.ubo.deref_mut(), offset),
MemberOffset::PushConstant(offset) => (self.push.deref_mut(), offset),
};
Self::write_scalar_inner(
&mut buffer[offset..][..std::mem::size_of::<T>()],
value,
)
}
/// Create a new `UniformStorage` with the given backing storage
@ -145,6 +157,7 @@ impl<H, C> UniformStorage<H, C, Box<[u8]>> {
impl<H, C, S> UniformStorage<H, C, S>
where
C: Copy,
S: Deref<Target = [u8]> + DerefMut,
H: for<'a> BindUniform<C, &'a [f32; 4]>,
{
@ -157,24 +170,26 @@ where
#[inline(always)]
pub fn bind_vec4(&mut self, offset: MemberOffset, value: impl Into<[f32; 4]>, ctx: C) {
let vec4 = value.into();
if H::bind_uniform(&vec4, ctx).is_some() {
return;
for ty in UniformMemberBlock::TYPES {
if H::bind_uniform(ty, &vec4, ctx).is_some() {
continue;
}
if let Some(offset) = offset.offset(ty) {
let buffer = self.buffer(ty);
Self::write_vec4_inner(
&mut buffer[offset..][..4 * std::mem::size_of::<f32>()],
&vec4,
);
}
}
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (self.ubo.deref_mut(), offset),
MemberOffset::PushConstant(offset) => (self.push.deref_mut(), offset),
};
Self::write_vec4_inner(
&mut buffer[offset..][..4 * std::mem::size_of::<f32>()],
&vec4,
);
}
}
impl<H, C, S> UniformStorage<H, C, S>
where
C: Copy,
S: Deref<Target = [u8]> + DerefMut,
H: for<'a> BindUniform<C, &'a [f32; 16]>,
{
@ -187,16 +202,17 @@ where
/// Bind a `mat4` to the given offset.
#[inline(always)]
pub fn bind_mat4(&mut self, offset: MemberOffset, value: &[f32; 16], ctx: C) {
if H::bind_uniform(value, ctx).is_some() {
return;
for ty in UniformMemberBlock::TYPES {
if H::bind_uniform(ty, value, ctx).is_some() {
continue;
}
if let Some(offset) = offset.offset(ty) {
let buffer = self.buffer(ty);
Self::write_mat4_inner(
&mut buffer[offset..][..16 * std::mem::size_of::<f32>()],
value,
);
}
}
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (self.ubo.deref_mut(), offset),
MemberOffset::PushConstant(offset) => (self.push.deref_mut(), offset),
};
Self::write_mat4_inner(
&mut buffer[offset..][..16 * std::mem::size_of::<f32>()],
value,
);
}
}