vk: write uniforms directly to host-visible mapped memory

This commit is contained in:
chyyran 2023-01-16 00:16:31 -05:00
parent fceda0e99d
commit e947271f9a
9 changed files with 195 additions and 53 deletions

View file

@ -1,6 +1,7 @@
use crate::error; use crate::error;
use crate::vulkan_primitives::VulkanBuffer; use crate::vulkan_primitives::VulkanBuffer;
use ash::vk; use ash::vk;
use std::sync::Arc;
#[rustfmt::skip] #[rustfmt::skip]
pub(crate) static VBO_DEFAULT_FINAL: &[f32; 16] = &[ pub(crate) static VBO_DEFAULT_FINAL: &[f32; 16] = &[
@ -13,12 +14,12 @@ pub(crate) static VBO_DEFAULT_FINAL: &[f32; 16] = &[
pub struct DrawQuad { pub struct DrawQuad {
buffer: VulkanBuffer, buffer: VulkanBuffer,
device: ash::Device, device: Arc<ash::Device>,
} }
impl DrawQuad { impl DrawQuad {
pub fn new( pub fn new(
device: &ash::Device, device: &Arc<ash::Device>,
mem_props: &vk::PhysicalDeviceMemoryProperties, mem_props: &vk::PhysicalDeviceMemoryProperties,
) -> error::Result<DrawQuad> { ) -> error::Result<DrawQuad> {
let mut buffer = VulkanBuffer::new( let mut buffer = VulkanBuffer::new(

View file

@ -8,7 +8,8 @@ use crate::queue_selection::get_graphics_queue;
use crate::render_target::{RenderTarget, DEFAULT_MVP}; use crate::render_target::{RenderTarget, DEFAULT_MVP};
use crate::samplers::SamplerSet; use crate::samplers::SamplerSet;
use crate::texture::{InputImage, OwnedImage, OwnedImageLayout, VulkanImage}; use crate::texture::{InputImage, OwnedImage, OwnedImageLayout, VulkanImage};
use crate::ubo_ring::VkUboRing; // use crate::ubo_ring::VkUboRing;
use crate::vulkan_primitives::RawVulkanBuffer;
use crate::vulkan_state::VulkanGraphicsPipeline; use crate::vulkan_state::VulkanGraphicsPipeline;
use crate::{error, util}; use crate::{error, util};
use ash::vk; use ash::vk;
@ -352,8 +353,13 @@ impl FilterChainVulkan {
.as_ref() .as_ref()
.map(|ubo| ubo.size as usize) .map(|ubo| ubo.size as usize)
.unwrap_or(0); .unwrap_or(0);
let uniform_storage = UniformStorage::new( let uniform_storage = UniformStorage::new_with_storage(
ubo_size, RawVulkanBuffer::new(
&vulkan.device,
&vulkan.memory_properties,
vk::BufferUsageFlags::UNIFORM_BUFFER,
ubo_size,
)?,
reflection reflection
.push_constant .push_constant
.as_ref() .as_ref()
@ -383,13 +389,12 @@ impl FilterChainVulkan {
frames_in_flight, frames_in_flight,
)?; )?;
let ubo_ring = VkUboRing::new( // let ubo_ring = VkUboRing::new(
&vulkan.device, // &vulkan.device,
&vulkan.memory_properties, // &vulkan.memory_properties,
frames_in_flight as usize, // frames_in_flight as usize,
ubo_size, // ubo_size,
)?; // )?;
// shader_vulkan: 2026
filters.push(FilterPass { filters.push(FilterPass {
device: vulkan.device.clone(), device: vulkan.device.clone(),
reflection, reflection,
@ -399,7 +404,7 @@ impl FilterChainVulkan {
source, source,
config, config,
graphics_pipeline, graphics_pipeline,
ubo_ring, // ubo_ring,
frames_in_flight, frames_in_flight,
}); });
} }

View file

@ -2,7 +2,7 @@ use crate::filter_chain::FilterCommon;
use crate::render_target::RenderTarget; use crate::render_target::RenderTarget;
use crate::samplers::SamplerSet; use crate::samplers::SamplerSet;
use crate::texture::InputImage; use crate::texture::InputImage;
use crate::ubo_ring::VkUboRing; use crate::vulkan_primitives::RawVulkanBuffer;
use crate::vulkan_state::VulkanGraphicsPipeline; use crate::vulkan_state::VulkanGraphicsPipeline;
use crate::{error, VulkanImage}; use crate::{error, VulkanImage};
use ash::vk; use ash::vk;
@ -14,7 +14,7 @@ use librashader_reflect::reflect::semantics::{
}; };
use librashader_reflect::reflect::ShaderReflection; use librashader_reflect::reflect::ShaderReflection;
use librashader_runtime::binding::{BindSemantics, TextureInput}; use librashader_runtime::binding::{BindSemantics, TextureInput};
use librashader_runtime::uniforms::{UniformStorage, UniformStorageAccess}; use librashader_runtime::uniforms::{NoUniformBinder, UniformStorage, UniformStorageAccess};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::sync::Arc; use std::sync::Arc;
@ -22,12 +22,12 @@ pub struct FilterPass {
pub device: Arc<ash::Device>, pub device: Arc<ash::Device>,
pub reflection: ShaderReflection, pub reflection: ShaderReflection,
// pub(crate) compiled: ShaderCompilerOutput<Vec<u32>>, // pub(crate) compiled: ShaderCompilerOutput<Vec<u32>>,
pub(crate) uniform_storage: UniformStorage, pub(crate) uniform_storage: UniformStorage<NoUniformBinder, Option<()>, RawVulkanBuffer>,
pub uniform_bindings: FxHashMap<UniformBinding, MemberOffset>, pub uniform_bindings: FxHashMap<UniformBinding, MemberOffset>,
pub source: ShaderSource, pub source: ShaderSource,
pub config: ShaderPassConfig, pub config: ShaderPassConfig,
pub graphics_pipeline: VulkanGraphicsPipeline, pub graphics_pipeline: VulkanGraphicsPipeline,
pub ubo_ring: VkUboRing, // pub ubo_ring: VkUboRing,
pub frames_in_flight: u32, pub frames_in_flight: u32,
} }
@ -37,7 +37,7 @@ impl TextureInput for InputImage {
} }
} }
impl BindSemantics for FilterPass { impl BindSemantics<NoUniformBinder, Option<()>, RawVulkanBuffer> for FilterPass {
type InputTexture = InputImage; type InputTexture = InputImage;
type SamplerSet = SamplerSet; type SamplerSet = SamplerSet;
type DescriptorSet<'a> = vk::DescriptorSet; type DescriptorSet<'a> = vk::DescriptorSet;
@ -113,10 +113,21 @@ impl FilterPass {
); );
if let Some(ubo) = &self.reflection.ubo { if let Some(ubo) = &self.reflection.ubo {
// shader_vulkan: 2554 (ra uses uses one big buffer) // RetroArch uses uses one big buffer for this and handles alignment themselves.
// itll be simpler for us if we just use a RingBuffer<vk::Buffer> tbh. // It's simpler here if we just use a buffer for each pass but not ideal.
self.ubo_ring //
.bind_to_descriptor_set(descriptor, ubo.binding, &self.uniform_storage)?; // May want to switch to something like VMA in the future.
// VkUboRing is no longer used, we just write directly to the storage now.
// self.ubo_ring
// .bind_to_descriptor_set(descriptor, ubo.binding, &self.uniform_storage)?;
self.uniform_storage.inner_ubo().bind_to_descriptor_set(
descriptor,
ubo.binding,
&self.uniform_storage,
)?;
} }
output.output.begin_pass(cmd); output.output.begin_pass(cmd);

View file

@ -12,6 +12,8 @@ use std::mem::align_of;
const ENTRY_POINT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"main\0") }; const ENTRY_POINT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"main\0") };
#[derive(Default, Clone, Debug, Copy)] #[derive(Default, Clone, Debug, Copy)]
#[repr(C)]
#[allow(dead_code)]
struct Vertex { struct Vertex {
pos: [f32; 4], pos: [f32; 4],
color: [f32; 4], color: [f32; 4],

View file

@ -14,7 +14,6 @@ mod queue_selection;
mod render_target; mod render_target;
mod samplers; mod samplers;
mod texture; mod texture;
mod ubo_ring;
mod util; mod util;
mod vulkan_primitives; mod vulkan_primitives;
mod vulkan_state; mod vulkan_state;

View file

@ -1,6 +1,10 @@
use crate::{error, util}; use crate::{error, util};
use ash::vk; use ash::vk;
use librashader_runtime::uniforms::UniformStorageAccess;
use std::ffi::c_void; use std::ffi::c_void;
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;
use std::sync::Arc; use std::sync::Arc;
pub struct VulkanImageMemory { pub struct VulkanImageMemory {
@ -36,9 +40,9 @@ impl Drop for VulkanImageMemory {
pub struct VulkanBuffer { pub struct VulkanBuffer {
pub handle: vk::Buffer, pub handle: vk::Buffer,
device: ash::Device, device: Arc<ash::Device>,
pub memory: vk::DeviceMemory, memory: vk::DeviceMemory,
pub size: vk::DeviceSize, size: vk::DeviceSize,
} }
pub struct VulkanBufferMapHandle<'a> { pub struct VulkanBufferMapHandle<'a> {
@ -48,7 +52,7 @@ pub struct VulkanBufferMapHandle<'a> {
impl VulkanBuffer { impl VulkanBuffer {
pub fn new( pub fn new(
device: &ash::Device, device: &Arc<ash::Device>,
mem_props: &vk::PhysicalDeviceMemoryProperties, mem_props: &vk::PhysicalDeviceMemoryProperties,
usage: vk::BufferUsageFlags, usage: vk::BufferUsageFlags,
size: usize, size: usize,
@ -112,13 +116,15 @@ impl Drop for VulkanBuffer {
impl<'a> VulkanBufferMapHandle<'a> { impl<'a> VulkanBufferMapHandle<'a> {
pub unsafe fn copy_from(&mut self, offset: usize, src: &[u8]) { pub unsafe fn copy_from(&mut self, offset: usize, src: &[u8]) {
std::ptr::copy_nonoverlapping( let mut align = ash::util::Align::new(
src.as_ptr(),
self.ptr self.ptr
.map_addr(|original| original.wrapping_add(offset)) .map_addr(|original| original.wrapping_add(offset))
.cast(), .cast(),
src.len(), std::mem::align_of::<u8>() as u64,
self.buffer.size,
); );
align.copy_from_slice(src);
} }
} }
@ -127,3 +133,87 @@ impl<'a> Drop for VulkanBufferMapHandle<'a> {
unsafe { self.buffer.device.unmap_memory(self.buffer.memory) } unsafe { self.buffer.device.unmap_memory(self.buffer.memory) }
} }
} }
pub struct RawVulkanBuffer {
buffer: ManuallyDrop<VulkanBuffer>,
ptr: NonNull<c_void>,
}
impl RawVulkanBuffer {
pub fn new(
device: &Arc<ash::Device>,
mem_props: &vk::PhysicalDeviceMemoryProperties,
usage: vk::BufferUsageFlags,
size: usize,
) -> error::Result<Self> {
let buffer = ManuallyDrop::new(VulkanBuffer::new(device, mem_props, usage, size)?);
let ptr = unsafe {
NonNull::new_unchecked(device.map_memory(
buffer.memory,
0,
buffer.size,
vk::MemoryMapFlags::empty(),
)?)
};
Ok(RawVulkanBuffer { buffer, ptr })
}
pub fn bind_to_descriptor_set(
&self,
descriptor_set: vk::DescriptorSet,
binding: u32,
storage: &impl UniformStorageAccess,
) -> error::Result<()> {
unsafe {
let buffer_info = [vk::DescriptorBufferInfo::builder()
.buffer(self.buffer.handle)
.offset(0)
.range(storage.ubo_slice().len() as vk::DeviceSize)
.build()];
let write_info = [vk::WriteDescriptorSet::builder()
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.dst_set(descriptor_set)
.dst_binding(binding)
.dst_array_element(0)
.buffer_info(&buffer_info)
.build()];
self.buffer.device.update_descriptor_sets(&write_info, &[])
}
Ok(())
}
}
impl Drop for RawVulkanBuffer {
fn drop(&mut self) {
unsafe {
self.buffer.device.unmap_memory(self.buffer.memory);
self.ptr = NonNull::dangling();
if self.buffer.memory != vk::DeviceMemory::null() {
self.buffer.device.free_memory(self.buffer.memory, None);
}
if self.buffer.handle != vk::Buffer::null() {
self.buffer.device.destroy_buffer(self.buffer.handle, None);
}
}
}
}
impl Deref for RawVulkanBuffer {
type Target = [u8];
fn deref(&self) -> &Self::Target {
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr().cast(), self.buffer.size as usize) }
}
}
impl DerefMut for RawVulkanBuffer {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe {
std::slice::from_raw_parts_mut(self.ptr.as_ptr().cast(), self.buffer.size as usize)
}
}
}

View file

@ -183,7 +183,6 @@ impl VulkanGraphicsPipeline {
reflection: &ShaderReflection, reflection: &ShaderReflection,
replicas: u32, replicas: u32,
) -> error::Result<VulkanGraphicsPipeline> { ) -> error::Result<VulkanGraphicsPipeline> {
// shader_vulkan 1927 (init_pipeline_layout)
let pipeline_layout = PipelineLayoutObjects::new(reflection, replicas, device)?; let pipeline_layout = PipelineLayoutObjects::new(reflection, replicas, device)?;
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::builder() let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::builder()

View file

@ -6,6 +6,7 @@ use librashader_reflect::reflect::semantics::{
}; };
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::BuildHasher; use std::hash::BuildHasher;
use std::ops::{Deref, DerefMut};
/// Trait for input textures used during uniform binding, /// Trait for input textures used during uniform binding,
pub trait TextureInput { pub trait TextureInput {
@ -47,8 +48,9 @@ where
} }
/// Trait that abstracts binding of semantics to shader uniforms. /// Trait that abstracts binding of semantics to shader uniforms.
pub trait BindSemantics<H = NoUniformBinder, C = Option<()>> pub trait BindSemantics<H = NoUniformBinder, C = Option<()>, S = Box<[u8]>>
where where
S: Deref<Target = [u8]> + DerefMut,
H: BindUniform<C, f32>, H: BindUniform<C, f32>,
H: BindUniform<C, u32>, H: BindUniform<C, u32>,
H: BindUniform<C, i32>, H: BindUniform<C, i32>,
@ -84,7 +86,7 @@ where
fn bind_semantics<'a>( fn bind_semantics<'a>(
device: &Self::DeviceContext, device: &Self::DeviceContext,
sampler_set: &Self::SamplerSet, sampler_set: &Self::SamplerSet,
uniform_storage: &mut UniformStorage<H, C>, uniform_storage: &mut UniformStorage<H, C, S>,
descriptor_set: &mut Self::DescriptorSet<'a>, descriptor_set: &mut Self::DescriptorSet<'a>,
mvp: &[f32; 16], mvp: &[f32; 16],
frame_count: u32, frame_count: u32,

View file

@ -1,5 +1,6 @@
use librashader_reflect::reflect::semantics::MemberOffset; use librashader_reflect::reflect::semantics::MemberOffset;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
/// A scalar value that is valid as a uniform member /// A scalar value that is valid as a uniform member
pub trait UniformScalar: Copy + bytemuck::Pod {} pub trait UniformScalar: Copy + bytemuck::Pod {}
@ -36,7 +37,10 @@ pub trait UniformStorageAccess {
fn push_slice(&self) -> &[u8]; fn push_slice(&self) -> &[u8];
} }
impl<T, H> UniformStorageAccess for UniformStorage<T, H> { impl<T, H, S> UniformStorageAccess for UniformStorage<T, H, S>
where
S: Deref<Target = [u8]> + DerefMut,
{
fn ubo_pointer(&self) -> *const u8 { fn ubo_pointer(&self) -> *const u8 {
self.ubo.as_ptr() self.ubo.as_ptr()
} }
@ -64,24 +68,29 @@ impl<T> BindUniform<Option<()>, T> for NoUniformBinder {
} }
/// A helper to bind uniform variables to UBO or Push Constant Buffers. /// A helper to bind uniform variables to UBO or Push Constant Buffers.
pub struct UniformStorage<H = NoUniformBinder, C = Option<()>> { pub struct UniformStorage<H = NoUniformBinder, C = Option<()>, S = Box<[u8]>>
ubo: Box<[u8]>, where
S: Deref<Target = [u8]> + DerefMut,
{
ubo: S,
push: Box<[u8]>, push: Box<[u8]>,
_h: PhantomData<H>, _h: PhantomData<H>,
_c: PhantomData<C>, _c: PhantomData<C>,
} }
impl<H, C> UniformStorage<H, C> { impl<H, C, S> UniformStorage<H, C, S>
/// Create a new `UniformStorage` with the given size for UBO and Push Constant Buffer sizes. where
pub fn new(ubo_size: usize, push_size: usize) -> Self { S: Deref<Target = [u8]> + DerefMut,
UniformStorage { {
ubo: vec![0u8; ubo_size].into_boxed_slice(), pub fn inner_ubo(&self) -> &S {
push: vec![0u8; push_size].into_boxed_slice(), &self.ubo
_h: Default::default(),
_c: Default::default(),
}
} }
}
impl<H, C, S> UniformStorage<H, C, S>
where
S: Deref<Target = [u8]> + DerefMut,
{
#[inline(always)] #[inline(always)]
fn write_scalar_inner<T: UniformScalar>(buffer: &mut [u8], value: T, ctx: C) fn write_scalar_inner<T: UniformScalar>(buffer: &mut [u8], value: T, ctx: C)
where where
@ -100,8 +109,8 @@ impl<H, C> UniformStorage<H, C> {
H: BindUniform<C, T>, H: BindUniform<C, T>,
{ {
let (buffer, offset) = match offset { let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.ubo, offset), MemberOffset::Ubo(offset) => (self.ubo.deref_mut(), offset),
MemberOffset::PushConstant(offset) => (&mut self.push, offset), MemberOffset::PushConstant(offset) => (self.push.deref_mut(), offset),
}; };
Self::write_scalar_inner( Self::write_scalar_inner(
@ -110,10 +119,33 @@ impl<H, C> UniformStorage<H, C> {
ctx, ctx,
) )
} }
/// Create a new `UniformStorage` with the given backing storage
pub fn new_with_storage(storage: S, push_size: usize) -> UniformStorage<H, C, S> {
UniformStorage {
ubo: storage,
push: vec![0u8; push_size].into_boxed_slice(),
_h: Default::default(),
_c: Default::default(),
}
}
} }
impl<H, C> UniformStorage<H, C> impl<H, C> UniformStorage<H, C, Box<[u8]>> {
/// Create a new `UniformStorage` with the given size for UBO and Push Constant Buffer sizes.
pub fn new(ubo_size: usize, push_size: usize) -> UniformStorage<H, C, Box<[u8]>> {
UniformStorage {
ubo: vec![0u8; ubo_size].into_boxed_slice(),
push: vec![0u8; push_size].into_boxed_slice(),
_h: Default::default(),
_c: Default::default(),
}
}
}
impl<H, C, S> UniformStorage<H, C, S>
where where
S: Deref<Target = [u8]> + DerefMut,
H: for<'a> BindUniform<C, &'a [f32; 4]>, H: for<'a> BindUniform<C, &'a [f32; 4]>,
{ {
#[inline(always)] #[inline(always)]
@ -128,8 +160,8 @@ where
#[inline(always)] #[inline(always)]
pub fn bind_vec4(&mut self, offset: MemberOffset, value: impl Into<[f32; 4]>, ctx: C) { pub fn bind_vec4(&mut self, offset: MemberOffset, value: impl Into<[f32; 4]>, ctx: C) {
let (buffer, offset) = match offset { let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.ubo, offset), MemberOffset::Ubo(offset) => (self.ubo.deref_mut(), offset),
MemberOffset::PushConstant(offset) => (&mut self.push, offset), MemberOffset::PushConstant(offset) => (self.push.deref_mut(), offset),
}; };
Self::write_vec4_inner( Self::write_vec4_inner(
@ -140,8 +172,9 @@ where
} }
} }
impl<H, C> UniformStorage<H, C> impl<H, C, S> UniformStorage<H, C, S>
where where
S: Deref<Target = [u8]> + DerefMut,
H: for<'a> BindUniform<C, &'a [f32; 16]>, H: for<'a> BindUniform<C, &'a [f32; 16]>,
{ {
#[inline(always)] #[inline(always)]
@ -156,8 +189,8 @@ where
#[inline(always)] #[inline(always)]
pub fn bind_mat4(&mut self, offset: MemberOffset, value: &[f32; 16], ctx: C) { pub fn bind_mat4(&mut self, offset: MemberOffset, value: &[f32; 16], ctx: C) {
let (buffer, offset) = match offset { let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.ubo, offset), MemberOffset::Ubo(offset) => (self.ubo.deref_mut(), offset),
MemberOffset::PushConstant(offset) => (&mut self.push, offset), MemberOffset::PushConstant(offset) => (self.push.deref_mut(), offset),
}; };
Self::write_mat4_inner( Self::write_mat4_inner(
&mut buffer[offset..][..16 * std::mem::size_of::<f32>()], &mut buffer[offset..][..16 * std::mem::size_of::<f32>()],