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::vulkan_primitives::VulkanBuffer;
use ash::vk;
use std::sync::Arc;
#[rustfmt::skip]
pub(crate) static VBO_DEFAULT_FINAL: &[f32; 16] = &[
@ -13,12 +14,12 @@ pub(crate) static VBO_DEFAULT_FINAL: &[f32; 16] = &[
pub struct DrawQuad {
buffer: VulkanBuffer,
device: ash::Device,
device: Arc<ash::Device>,
}
impl DrawQuad {
pub fn new(
device: &ash::Device,
device: &Arc<ash::Device>,
mem_props: &vk::PhysicalDeviceMemoryProperties,
) -> error::Result<DrawQuad> {
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::samplers::SamplerSet;
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::{error, util};
use ash::vk;
@ -352,8 +353,13 @@ impl FilterChainVulkan {
.as_ref()
.map(|ubo| ubo.size as usize)
.unwrap_or(0);
let uniform_storage = UniformStorage::new(
ubo_size,
let uniform_storage = UniformStorage::new_with_storage(
RawVulkanBuffer::new(
&vulkan.device,
&vulkan.memory_properties,
vk::BufferUsageFlags::UNIFORM_BUFFER,
ubo_size,
)?,
reflection
.push_constant
.as_ref()
@ -383,13 +389,12 @@ impl FilterChainVulkan {
frames_in_flight,
)?;
let ubo_ring = VkUboRing::new(
&vulkan.device,
&vulkan.memory_properties,
frames_in_flight as usize,
ubo_size,
)?;
// shader_vulkan: 2026
// let ubo_ring = VkUboRing::new(
// &vulkan.device,
// &vulkan.memory_properties,
// frames_in_flight as usize,
// ubo_size,
// )?;
filters.push(FilterPass {
device: vulkan.device.clone(),
reflection,
@ -399,7 +404,7 @@ impl FilterChainVulkan {
source,
config,
graphics_pipeline,
ubo_ring,
// ubo_ring,
frames_in_flight,
});
}

View file

@ -2,7 +2,7 @@ use crate::filter_chain::FilterCommon;
use crate::render_target::RenderTarget;
use crate::samplers::SamplerSet;
use crate::texture::InputImage;
use crate::ubo_ring::VkUboRing;
use crate::vulkan_primitives::RawVulkanBuffer;
use crate::vulkan_state::VulkanGraphicsPipeline;
use crate::{error, VulkanImage};
use ash::vk;
@ -14,7 +14,7 @@ use librashader_reflect::reflect::semantics::{
};
use librashader_reflect::reflect::ShaderReflection;
use librashader_runtime::binding::{BindSemantics, TextureInput};
use librashader_runtime::uniforms::{UniformStorage, UniformStorageAccess};
use librashader_runtime::uniforms::{NoUniformBinder, UniformStorage, UniformStorageAccess};
use rustc_hash::FxHashMap;
use std::sync::Arc;
@ -22,12 +22,12 @@ pub struct FilterPass {
pub device: Arc<ash::Device>,
pub reflection: ShaderReflection,
// 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 source: ShaderSource,
pub config: ShaderPassConfig,
pub graphics_pipeline: VulkanGraphicsPipeline,
pub ubo_ring: VkUboRing,
// pub ubo_ring: VkUboRing,
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 SamplerSet = SamplerSet;
type DescriptorSet<'a> = vk::DescriptorSet;
@ -113,10 +113,21 @@ impl FilterPass {
);
if let Some(ubo) = &self.reflection.ubo {
// shader_vulkan: 2554 (ra uses uses one big buffer)
// itll be simpler for us if we just use a RingBuffer<vk::Buffer> tbh.
self.ubo_ring
.bind_to_descriptor_set(descriptor, ubo.binding, &self.uniform_storage)?;
// RetroArch uses uses one big buffer for this and handles alignment themselves.
// It's simpler here if we just use a buffer for each pass but not ideal.
//
// 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);

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") };
#[derive(Default, Clone, Debug, Copy)]
#[repr(C)]
#[allow(dead_code)]
struct Vertex {
pos: [f32; 4],
color: [f32; 4],

View file

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

View file

@ -1,6 +1,10 @@
use crate::{error, util};
use ash::vk;
use librashader_runtime::uniforms::UniformStorageAccess;
use std::ffi::c_void;
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;
use std::sync::Arc;
pub struct VulkanImageMemory {
@ -36,9 +40,9 @@ impl Drop for VulkanImageMemory {
pub struct VulkanBuffer {
pub handle: vk::Buffer,
device: ash::Device,
pub memory: vk::DeviceMemory,
pub size: vk::DeviceSize,
device: Arc<ash::Device>,
memory: vk::DeviceMemory,
size: vk::DeviceSize,
}
pub struct VulkanBufferMapHandle<'a> {
@ -48,7 +52,7 @@ pub struct VulkanBufferMapHandle<'a> {
impl VulkanBuffer {
pub fn new(
device: &ash::Device,
device: &Arc<ash::Device>,
mem_props: &vk::PhysicalDeviceMemoryProperties,
usage: vk::BufferUsageFlags,
size: usize,
@ -112,13 +116,15 @@ impl Drop for VulkanBuffer {
impl<'a> VulkanBufferMapHandle<'a> {
pub unsafe fn copy_from(&mut self, offset: usize, src: &[u8]) {
std::ptr::copy_nonoverlapping(
src.as_ptr(),
let mut align = ash::util::Align::new(
self.ptr
.map_addr(|original| original.wrapping_add(offset))
.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) }
}
}
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,
replicas: u32,
) -> error::Result<VulkanGraphicsPipeline> {
// shader_vulkan 1927 (init_pipeline_layout)
let pipeline_layout = PipelineLayoutObjects::new(reflection, replicas, device)?;
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::builder()

View file

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

View file

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