567 lines
20 KiB
Rust
567 lines
20 KiB
Rust
use ash::{util::Align, vk, Entry, Instance};
|
|
use ash_window::enumerate_required_extensions;
|
|
use librashader::runtime::vk::{FilterChain, FilterChainOptions, FrameOptions, VulkanObjects};
|
|
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle};
|
|
use std::{mem::ManuallyDrop, path::PathBuf, sync::Arc};
|
|
|
|
use crate::connect::ResolutionData;
|
|
|
|
use self::{
|
|
types::{FramebufferData, SurfaceData, SwapchainData, Vertex, VulkanData, SHADER_INPUT_FORMAT},
|
|
utils::{
|
|
begin_commandbuffer, find_memorytype_index, record_submit_commandbuffer,
|
|
submit_commandbuffer,
|
|
},
|
|
};
|
|
|
|
#[cfg(all(debug_assertions, feature = "vulkan-debug"))]
|
|
mod debug;
|
|
mod types;
|
|
mod utils;
|
|
|
|
// much of this is lifted from the Ash examples
|
|
// https://github.com/ash-rs/ash/blob/master/examples/src/lib.rs
|
|
// https://github.com/ash-rs/ash/blob/master/examples/src/bin/texture.rs
|
|
|
|
const VERTICES: [Vertex; 3] = [Vertex(-1.0, -1.0), Vertex(3.0, -1.0), Vertex(-1.0, 3.0)];
|
|
|
|
pub struct RendererBackendManager {
|
|
entry: Entry,
|
|
instance: Instance,
|
|
#[cfg(all(debug_assertions, feature = "vulkan-debug"))]
|
|
#[allow(dead_code)]
|
|
debug: debug::VulkanDebug,
|
|
}
|
|
|
|
impl RendererBackendManager {
|
|
pub fn new(display_handle: RawDisplayHandle) -> Self {
|
|
#[cfg(all(any(target_os = "macos", target_os = "ios"), feature = "vulkan-static"))]
|
|
let entry = ash_molten::load();
|
|
#[cfg(not(all(any(target_os = "macos", target_os = "ios"), feature = "vulkan-static")))]
|
|
let entry = Entry::linked();
|
|
|
|
let name = std::ffi::CString::new("gameboy").unwrap();
|
|
|
|
#[allow(unused_mut)]
|
|
let mut extension_names = enumerate_required_extensions(display_handle)
|
|
.unwrap()
|
|
.to_vec();
|
|
|
|
#[cfg(all(debug_assertions, feature = "vulkan-debug"))]
|
|
extension_names.push(ash::extensions::ext::DebugUtils::name().as_ptr());
|
|
|
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
|
{
|
|
#[cfg(not(feature = "vulkan-static"))]
|
|
extension_names.push(vk::KhrPortabilityEnumerationFn::name().as_ptr());
|
|
extension_names.push(vk::KhrGetPhysicalDeviceProperties2Fn::name().as_ptr());
|
|
}
|
|
|
|
let appinfo = vk::ApplicationInfo::builder()
|
|
.application_name(&name)
|
|
.engine_name(&name)
|
|
.application_version(0)
|
|
.engine_version(0)
|
|
.api_version(vk::make_api_version(0, 1, 0, 0))
|
|
.build();
|
|
|
|
let create_flags = if cfg!(any(target_os = "macos", target_os = "ios")) {
|
|
vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR
|
|
} else {
|
|
vk::InstanceCreateFlags::default()
|
|
};
|
|
|
|
let create_info = vk::InstanceCreateInfo::builder()
|
|
.application_info(&appinfo)
|
|
.enabled_extension_names(&extension_names)
|
|
.flags(create_flags)
|
|
.build();
|
|
|
|
let instance = unsafe { entry.create_instance(&create_info, None) }.unwrap();
|
|
|
|
Self {
|
|
#[cfg(all(debug_assertions, feature = "vulkan-debug"))]
|
|
debug: debug::VulkanDebug::new(&entry, &instance),
|
|
entry,
|
|
instance,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for RendererBackendManager {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
self.instance.destroy_instance(None);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct RendererBackend {
|
|
inner: ManuallyDrop<VulkanWindowInner>,
|
|
filter_chain: ManuallyDrop<FilterChain>,
|
|
manager: Arc<RendererBackendManager>,
|
|
}
|
|
|
|
impl Drop for RendererBackend {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
ManuallyDrop::drop(&mut self.filter_chain);
|
|
ManuallyDrop::drop(&mut self.inner);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct WindowOptions {
|
|
pub shader_path: Option<PathBuf>,
|
|
}
|
|
|
|
impl RendererBackend {
|
|
pub fn new<W: HasRawDisplayHandle + HasRawWindowHandle>(
|
|
resolutions: ResolutionData,
|
|
window: &W,
|
|
options: WindowOptions,
|
|
manager: Arc<RendererBackendManager>,
|
|
) -> Self {
|
|
let inner = unsafe { VulkanWindowInner::new(resolutions, window, manager.as_ref()) };
|
|
|
|
let filter_chain_options = FilterChainOptions {
|
|
frames_in_flight: 0,
|
|
force_no_mipmaps: false,
|
|
use_render_pass: true,
|
|
disable_cache: false,
|
|
};
|
|
|
|
let vulkan = VulkanObjects::try_from((
|
|
inner.vulkan_data.pdevice,
|
|
manager.instance.clone(),
|
|
inner.vulkan_data.device.clone(),
|
|
))
|
|
.unwrap();
|
|
|
|
let filter_chain = match options.shader_path {
|
|
Some(path) => {
|
|
unsafe { FilterChain::load_from_path(path, vulkan, Some(&filter_chain_options)) }
|
|
.unwrap()
|
|
}
|
|
None => unsafe {
|
|
let preset = librashader_presets::ShaderPreset {
|
|
shader_count: 1,
|
|
shaders: vec![librashader_presets::ShaderPassConfig {
|
|
id: 0,
|
|
name: librashader_common::ShaderStorage::String(
|
|
include_str!("./shaders/stock.slang").to_string(),
|
|
),
|
|
alias: None,
|
|
filter: librashader::FilterMode::Nearest,
|
|
wrap_mode: librashader::WrapMode::ClampToBorder,
|
|
frame_count_mod: 0,
|
|
srgb_framebuffer: false,
|
|
float_framebuffer: false,
|
|
mipmap_input: false,
|
|
scaling: librashader_presets::Scale2D {
|
|
valid: false,
|
|
x: librashader_presets::Scaling {
|
|
scale_type: librashader_presets::ScaleType::Input,
|
|
factor: librashader_presets::ScaleFactor::Float(1.0),
|
|
},
|
|
y: librashader_presets::Scaling {
|
|
scale_type: librashader_presets::ScaleType::Input,
|
|
factor: librashader_presets::ScaleFactor::Float(1.0),
|
|
},
|
|
},
|
|
}],
|
|
textures: vec![],
|
|
parameters: vec![],
|
|
};
|
|
FilterChain::load_from_preset(preset, vulkan, Some(&filter_chain_options))
|
|
}
|
|
.unwrap(),
|
|
};
|
|
|
|
Self {
|
|
inner: ManuallyDrop::new(inner),
|
|
filter_chain: ManuallyDrop::new(filter_chain),
|
|
manager,
|
|
}
|
|
}
|
|
|
|
pub fn resize<W: HasRawDisplayHandle + HasRawWindowHandle>(
|
|
&mut self,
|
|
resolutions: ResolutionData,
|
|
_window: &W,
|
|
) {
|
|
unsafe { self.inner.resize(resolutions, self.manager.as_ref()) };
|
|
}
|
|
|
|
pub fn new_frame(&mut self, buffer: &[[u8; 4]]) {
|
|
unsafe { self.inner.new_frame(buffer) };
|
|
}
|
|
|
|
pub fn render(&mut self, resolutions: ResolutionData, manager: &RendererBackendManager) {
|
|
unsafe {
|
|
self.inner
|
|
.render(&mut self.filter_chain, resolutions, manager)
|
|
};
|
|
}
|
|
}
|
|
|
|
struct VulkanWindowInner {
|
|
vulkan_data: VulkanData,
|
|
renderpass: vk::RenderPass,
|
|
swapchain: SwapchainData,
|
|
surface: SurfaceData,
|
|
framebuffers: FramebufferData,
|
|
image_slice: Align<u8>,
|
|
frame_counter: usize,
|
|
}
|
|
|
|
impl VulkanWindowInner {
|
|
unsafe fn new<W: HasRawDisplayHandle + HasRawWindowHandle>(
|
|
resolutions: ResolutionData,
|
|
window: &W,
|
|
manager: &RendererBackendManager,
|
|
) -> Self {
|
|
let surface = SurfaceData::new(window, manager);
|
|
|
|
let vulkan_data = VulkanData::new(manager, &surface);
|
|
|
|
let (swapchain, _viewport_state_info) =
|
|
SwapchainData::new(resolutions, manager, &surface, &vulkan_data);
|
|
|
|
let renderpass_attachments = [vk::AttachmentDescription {
|
|
format: swapchain.format.format,
|
|
samples: vk::SampleCountFlags::TYPE_1,
|
|
load_op: vk::AttachmentLoadOp::CLEAR,
|
|
store_op: vk::AttachmentStoreOp::STORE,
|
|
final_layout: vk::ImageLayout::PRESENT_SRC_KHR,
|
|
..Default::default()
|
|
}];
|
|
let color_attachment_refs = [vk::AttachmentReference {
|
|
attachment: 0,
|
|
layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
|
}];
|
|
let dependencies = [vk::SubpassDependency {
|
|
src_subpass: vk::SUBPASS_EXTERNAL,
|
|
src_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
|
|
dst_access_mask: vk::AccessFlags::COLOR_ATTACHMENT_READ
|
|
| vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
|
|
dst_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
|
|
..Default::default()
|
|
}];
|
|
|
|
let subpass = vk::SubpassDescription::builder()
|
|
.color_attachments(&color_attachment_refs)
|
|
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
|
|
.build();
|
|
|
|
let renderpass_create_info = vk::RenderPassCreateInfo::builder()
|
|
.attachments(&renderpass_attachments)
|
|
.subpasses(std::slice::from_ref(&subpass))
|
|
.dependencies(&dependencies)
|
|
.build();
|
|
|
|
let renderpass = vulkan_data
|
|
.device
|
|
.create_render_pass(&renderpass_create_info, None)
|
|
.unwrap();
|
|
|
|
let framebuffers = FramebufferData::new(&swapchain, &vulkan_data, renderpass);
|
|
|
|
let vertex_input_buffer_info = vk::BufferCreateInfo {
|
|
size: std::mem::size_of_val(&VERTICES) as u64,
|
|
usage: vk::BufferUsageFlags::VERTEX_BUFFER,
|
|
sharing_mode: vk::SharingMode::EXCLUSIVE,
|
|
..Default::default()
|
|
};
|
|
let vertex_input_buffer = vulkan_data
|
|
.device
|
|
.create_buffer(&vertex_input_buffer_info, None)
|
|
.unwrap();
|
|
let vertex_input_buffer_memory_req = vulkan_data
|
|
.device
|
|
.get_buffer_memory_requirements(vertex_input_buffer);
|
|
let vertex_input_buffer_memory_index = find_memorytype_index(
|
|
&vertex_input_buffer_memory_req,
|
|
&vulkan_data.device_memory_properties,
|
|
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
|
|
)
|
|
.expect("Unable to find suitable memorytype for the vertex buffer.");
|
|
|
|
let vertex_buffer_allocate_info = vk::MemoryAllocateInfo {
|
|
allocation_size: vertex_input_buffer_memory_req.size,
|
|
memory_type_index: vertex_input_buffer_memory_index,
|
|
..Default::default()
|
|
};
|
|
let vertex_input_buffer_memory = vulkan_data
|
|
.device
|
|
.allocate_memory(&vertex_buffer_allocate_info, None)
|
|
.unwrap();
|
|
|
|
let vert_ptr = vulkan_data
|
|
.device
|
|
.map_memory(
|
|
vertex_input_buffer_memory,
|
|
0,
|
|
vertex_input_buffer_memory_req.size,
|
|
vk::MemoryMapFlags::empty(),
|
|
)
|
|
.unwrap();
|
|
let mut slice = Align::new(
|
|
vert_ptr,
|
|
std::mem::align_of::<Vertex>() as u64,
|
|
vertex_input_buffer_memory_req.size,
|
|
);
|
|
slice.copy_from_slice(&VERTICES);
|
|
vulkan_data.device.unmap_memory(vertex_input_buffer_memory);
|
|
vulkan_data
|
|
.device
|
|
.bind_buffer_memory(vertex_input_buffer, vertex_input_buffer_memory, 0)
|
|
.unwrap();
|
|
|
|
let image_ptr = vulkan_data
|
|
.device
|
|
.map_memory(
|
|
swapchain.shader_input_image_buffer_memory,
|
|
0,
|
|
swapchain.shader_input_image_buffer_memory_req.size,
|
|
vk::MemoryMapFlags::empty(),
|
|
)
|
|
.unwrap();
|
|
let image_slice: Align<u8> = Align::new(
|
|
image_ptr,
|
|
std::mem::align_of::<u8>() as u64,
|
|
swapchain.shader_input_image_buffer_memory_req.size,
|
|
);
|
|
|
|
Self {
|
|
renderpass,
|
|
swapchain,
|
|
surface,
|
|
framebuffers,
|
|
vulkan_data,
|
|
image_slice,
|
|
frame_counter: 0,
|
|
}
|
|
}
|
|
|
|
unsafe fn resize(&mut self, resolutions: ResolutionData, manager: &RendererBackendManager) {
|
|
self.swapchain.manual_drop(&self.vulkan_data);
|
|
for framebuffer in &self.framebuffers.framebuffers {
|
|
self.vulkan_data
|
|
.device
|
|
.destroy_framebuffer(*framebuffer, None);
|
|
}
|
|
(self.swapchain, _) =
|
|
SwapchainData::new(resolutions, manager, &self.surface, &self.vulkan_data);
|
|
self.framebuffers =
|
|
FramebufferData::new(&self.swapchain, &self.vulkan_data, self.renderpass);
|
|
|
|
let image_ptr = self
|
|
.vulkan_data
|
|
.device
|
|
.map_memory(
|
|
self.swapchain.shader_input_image_buffer_memory,
|
|
0,
|
|
self.swapchain.shader_input_image_buffer_memory_req.size,
|
|
vk::MemoryMapFlags::empty(),
|
|
)
|
|
.unwrap();
|
|
self.image_slice = Align::new(
|
|
image_ptr,
|
|
std::mem::align_of::<u8>() as u64,
|
|
self.swapchain.shader_input_image_buffer_memory_req.size,
|
|
);
|
|
}
|
|
|
|
unsafe fn new_frame(&mut self, buffer: &[[u8; 4]]) {
|
|
self.image_slice
|
|
.copy_from_slice(bytemuck::cast_slice(buffer));
|
|
|
|
record_submit_commandbuffer(
|
|
&self.vulkan_data.device,
|
|
self.vulkan_data.texture_copy_command_buffer,
|
|
self.vulkan_data.texture_copy_commands_reuse_fence,
|
|
self.vulkan_data.present_queue,
|
|
&[],
|
|
&[],
|
|
&[],
|
|
|device, texture_command_buffer| {
|
|
let texture_barrier = vk::ImageMemoryBarrier {
|
|
dst_access_mask: vk::AccessFlags::TRANSFER_WRITE,
|
|
new_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
image: self.swapchain.shader_input_texture,
|
|
subresource_range: vk::ImageSubresourceRange {
|
|
aspect_mask: vk::ImageAspectFlags::COLOR,
|
|
level_count: 1,
|
|
layer_count: 1,
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
};
|
|
device.cmd_pipeline_barrier(
|
|
texture_command_buffer,
|
|
vk::PipelineStageFlags::BOTTOM_OF_PIPE,
|
|
vk::PipelineStageFlags::TRANSFER,
|
|
vk::DependencyFlags::empty(),
|
|
&[],
|
|
&[],
|
|
&[texture_barrier],
|
|
);
|
|
let buffer_copy_regions = vk::BufferImageCopy::builder()
|
|
.image_subresource(
|
|
vk::ImageSubresourceLayers::builder()
|
|
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
|
.layer_count(1)
|
|
.build(),
|
|
)
|
|
.image_extent(self.swapchain.shader_input_image_extent.into())
|
|
.build();
|
|
|
|
device.cmd_copy_buffer_to_image(
|
|
texture_command_buffer,
|
|
self.swapchain.shader_input_image_buffer,
|
|
self.swapchain.shader_input_texture,
|
|
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
&[buffer_copy_regions],
|
|
);
|
|
let texture_barrier_end = vk::ImageMemoryBarrier {
|
|
src_access_mask: vk::AccessFlags::TRANSFER_WRITE,
|
|
dst_access_mask: vk::AccessFlags::SHADER_READ,
|
|
old_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
new_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
|
image: self.swapchain.shader_input_texture,
|
|
subresource_range: vk::ImageSubresourceRange {
|
|
aspect_mask: vk::ImageAspectFlags::COLOR,
|
|
level_count: 1,
|
|
layer_count: 1,
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
};
|
|
device.cmd_pipeline_barrier(
|
|
texture_command_buffer,
|
|
vk::PipelineStageFlags::TRANSFER,
|
|
vk::PipelineStageFlags::FRAGMENT_SHADER,
|
|
vk::DependencyFlags::empty(),
|
|
&[],
|
|
&[],
|
|
&[texture_barrier_end],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
unsafe fn render(
|
|
&mut self,
|
|
filter_chain: &mut FilterChain,
|
|
resolutions: ResolutionData,
|
|
manager: &RendererBackendManager,
|
|
) {
|
|
let (present_index, is_suboptimal) = self
|
|
.swapchain
|
|
.swapchain_loader
|
|
.acquire_next_image(
|
|
self.swapchain.swapchain,
|
|
std::u64::MAX,
|
|
self.vulkan_data.present_complete_semaphore,
|
|
vk::Fence::null(),
|
|
)
|
|
.unwrap();
|
|
if is_suboptimal {
|
|
self.resize(resolutions, manager);
|
|
return;
|
|
}
|
|
|
|
begin_commandbuffer(
|
|
&self.vulkan_data.device,
|
|
self.vulkan_data.draw_command_buffer,
|
|
self.vulkan_data.draw_commands_reuse_fence,
|
|
);
|
|
|
|
filter_chain
|
|
.frame(
|
|
&librashader::runtime::vk::VulkanImage {
|
|
image: self.swapchain.shader_input_texture,
|
|
size: self.swapchain.shader_input_image_extent.into(),
|
|
format: SHADER_INPUT_FORMAT,
|
|
},
|
|
&librashader::runtime::Viewport {
|
|
x: 0.,
|
|
y: 0.,
|
|
mvp: None,
|
|
output: librashader::runtime::vk::VulkanImage {
|
|
image: self.swapchain.present_images[present_index as usize],
|
|
size: self.swapchain.surface_resolution.into(),
|
|
format: self.swapchain.format.format,
|
|
},
|
|
},
|
|
self.vulkan_data.draw_command_buffer,
|
|
self.frame_counter,
|
|
Some(&FrameOptions {
|
|
clear_history: true,
|
|
frame_direction: 0,
|
|
}),
|
|
)
|
|
.unwrap();
|
|
self.frame_counter += 1;
|
|
|
|
submit_commandbuffer(
|
|
&self.vulkan_data.device,
|
|
self.vulkan_data.draw_command_buffer,
|
|
self.vulkan_data.draw_commands_reuse_fence,
|
|
self.vulkan_data.present_queue,
|
|
&[vk::PipelineStageFlags::BOTTOM_OF_PIPE],
|
|
&[self.vulkan_data.present_complete_semaphore],
|
|
&[self.vulkan_data.rendering_complete_semaphore],
|
|
);
|
|
|
|
let present_info = vk::PresentInfoKHR {
|
|
wait_semaphore_count: 1,
|
|
p_wait_semaphores: &self.vulkan_data.rendering_complete_semaphore,
|
|
swapchain_count: 1,
|
|
p_swapchains: &self.swapchain.swapchain,
|
|
p_image_indices: &present_index,
|
|
..Default::default()
|
|
};
|
|
self.swapchain
|
|
.swapchain_loader
|
|
.queue_present(self.vulkan_data.present_queue, &present_info)
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
impl Drop for VulkanWindowInner {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
self.vulkan_data.device.device_wait_idle().unwrap();
|
|
|
|
for framebuffer in &self.framebuffers.framebuffers {
|
|
self.vulkan_data
|
|
.device
|
|
.destroy_framebuffer(*framebuffer, None);
|
|
}
|
|
self.vulkan_data
|
|
.device
|
|
.destroy_render_pass(self.renderpass, None);
|
|
|
|
self.vulkan_data
|
|
.device
|
|
.destroy_semaphore(self.vulkan_data.present_complete_semaphore, None);
|
|
self.vulkan_data
|
|
.device
|
|
.destroy_semaphore(self.vulkan_data.rendering_complete_semaphore, None);
|
|
self.vulkan_data
|
|
.device
|
|
.destroy_fence(self.vulkan_data.draw_commands_reuse_fence, None);
|
|
self.vulkan_data
|
|
.device
|
|
.destroy_fence(self.vulkan_data.setup_commands_reuse_fence, None);
|
|
|
|
self.swapchain.manual_drop(&self.vulkan_data);
|
|
self.vulkan_data.device.destroy_device(None);
|
|
self.surface
|
|
.surface_loader
|
|
.destroy_surface(self.surface.surface, None);
|
|
}
|
|
}
|
|
}
|