diff --git a/librashader-common/src/vk.rs b/librashader-common/src/vk.rs index 7b1aa82..74a94cb 100644 --- a/librashader-common/src/vk.rs +++ b/librashader-common/src/vk.rs @@ -1,5 +1,5 @@ use ash::vk; -use crate::ImageFormat; +use crate::{ImageFormat, Size}; impl From for vk::Format { fn from(format: ImageFormat) -> Self { @@ -76,4 +76,14 @@ impl From for ImageFormat { _ => ImageFormat::Unknown, } } +} + +impl From> for vk::Extent3D { + fn from(value: Size) -> Self { + vk::Extent3D { + width: value.width, + height: value.height, + depth: 1, + } + } } \ No newline at end of file diff --git a/librashader-runtime-d3d11/src/framebuffer.rs b/librashader-runtime-d3d11/src/framebuffer.rs index 8329c8a..dfb547e 100644 --- a/librashader-runtime-d3d11/src/framebuffer.rs +++ b/librashader-runtime-d3d11/src/framebuffer.rs @@ -7,6 +7,7 @@ use windows::core::Interface; use windows::Win32::Graphics::Direct3D::D3D_SRV_DIMENSION_TEXTURE2D; use windows::Win32::Graphics::Direct3D11::{ID3D11Device, ID3D11RenderTargetView, ID3D11ShaderResourceView, ID3D11Texture2D, D3D11_BIND_RENDER_TARGET, D3D11_BIND_SHADER_RESOURCE, D3D11_CPU_ACCESS_WRITE, D3D11_FORMAT_SUPPORT_RENDER_TARGET, D3D11_FORMAT_SUPPORT_SHADER_SAMPLE, D3D11_FORMAT_SUPPORT_TEXTURE2D, D3D11_RENDER_TARGET_VIEW_DESC, D3D11_RENDER_TARGET_VIEW_DESC_0, D3D11_RTV_DIMENSION_TEXTURE2D, D3D11_SHADER_RESOURCE_VIEW_DESC, D3D11_SHADER_RESOURCE_VIEW_DESC_0, D3D11_TEX2D_RTV, D3D11_TEX2D_SRV, D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT, D3D11_VIEWPORT, D3D11_RESOURCE_MISC_GENERATE_MIPS, ID3D11DeviceContext, D3D11_BOX}; use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT, DXGI_SAMPLE_DESC}; +use librashader_runtime::scaling::ViewportSize; #[derive(Debug, Clone)] pub(crate) struct OwnedFramebuffer { @@ -60,7 +61,7 @@ impl OwnedFramebuffer { return Ok(self.size); } - let size = librashader_runtime::scaling::scale(scaling, source.view.size, *viewport_size); + let size = source.view.size.scale_viewport(scaling, *viewport_size); if self.size != size { self.size = size; diff --git a/librashader-runtime-gl/src/gl/gl3/framebuffer.rs b/librashader-runtime-gl/src/gl/gl3/framebuffer.rs index 9cd0194..4cc5435 100644 --- a/librashader-runtime-gl/src/gl/gl3/framebuffer.rs +++ b/librashader-runtime-gl/src/gl/gl3/framebuffer.rs @@ -7,6 +7,7 @@ use crate::viewport::Viewport; use gl::types::{GLenum, GLint, GLsizei, GLuint}; use librashader_common::{ImageFormat, Size}; use librashader_presets::Scale2D; +use librashader_runtime::scaling::{MipmapSize, ViewportSize}; #[derive(Debug)] pub struct Gl3Framebuffer; @@ -63,8 +64,7 @@ impl FramebufferInterface for Gl3Framebuffer { return Ok(fb.size); } - let size = - librashader_runtime::scaling::scale(scaling, source.image.size, viewport.output.size); + let size = source.image.size.scale_viewport(scaling, viewport.output.size); if fb.size != size { fb.size = size; @@ -196,7 +196,7 @@ impl FramebufferInterface for Gl3Framebuffer { size.height = 1; } - fb.mip_levels = librashader_runtime::scaling::calc_miplevel(size); + fb.mip_levels = size.calculate_miplevels(); if fb.mip_levels > fb.max_levels { fb.mip_levels = fb.max_levels; } @@ -237,7 +237,7 @@ impl FramebufferInterface for Gl3Framebuffer { gl::GenTextures(1, &mut fb.image); gl::BindTexture(gl::TEXTURE_2D, fb.image); - fb.mip_levels = librashader_runtime::scaling::calc_miplevel(size); + fb.mip_levels = size.calculate_miplevels(); if fb.mip_levels > fb.max_levels { fb.mip_levels = fb.max_levels; } diff --git a/librashader-runtime-gl/src/gl/gl3/lut_load.rs b/librashader-runtime-gl/src/gl/gl3/lut_load.rs index 0eff49c..8efd430 100644 --- a/librashader-runtime-gl/src/gl/gl3/lut_load.rs +++ b/librashader-runtime-gl/src/gl/gl3/lut_load.rs @@ -7,6 +7,7 @@ use librashader_runtime::image::{Image, UVDirection}; use librashader_common::Size; use librashader_presets::TextureConfig; use rustc_hash::FxHashMap; +use librashader_runtime::scaling::MipmapSize; pub struct Gl3LutLoad; impl LoadLut for Gl3LutLoad { @@ -19,9 +20,9 @@ impl LoadLut for Gl3LutLoad { }; for (index, texture) in textures.iter().enumerate() { - let image = Image::load(&texture.path, UVDirection::BottomLeft)?; + let image: Image = Image::load(&texture.path, UVDirection::BottomLeft)?; let levels = if texture.mipmap { - librashader_runtime::scaling::calc_miplevel(image.size) + image.size.calculate_miplevels() } else { 1u32 }; diff --git a/librashader-runtime-gl/src/gl/gl46/framebuffer.rs b/librashader-runtime-gl/src/gl/gl46/framebuffer.rs index 443b618..71122d1 100644 --- a/librashader-runtime-gl/src/gl/gl46/framebuffer.rs +++ b/librashader-runtime-gl/src/gl/gl46/framebuffer.rs @@ -7,6 +7,7 @@ use crate::viewport::Viewport; use gl::types::{GLenum, GLint, GLsizei, GLuint}; use librashader_common::{ImageFormat, Size}; use librashader_presets::Scale2D; +use librashader_runtime::scaling::{MipmapSize, ViewportSize}; #[derive(Debug)] pub struct Gl46Framebuffer; @@ -61,8 +62,7 @@ impl FramebufferInterface for Gl46Framebuffer { return Ok(fb.size); } - let size = - librashader_runtime::scaling::scale(scaling, source.image.size, viewport.output.size); + let size = source.image.size.scale_viewport(scaling, viewport.output.size); if fb.size != size { fb.size = size; @@ -146,7 +146,7 @@ impl FramebufferInterface for Gl46Framebuffer { size.height = 1; } - fb.mip_levels = librashader_runtime::scaling::calc_miplevel(size); + fb.mip_levels = size.calculate_miplevels(); if fb.mip_levels > fb.max_levels { fb.mip_levels = fb.max_levels; } @@ -174,7 +174,7 @@ impl FramebufferInterface for Gl46Framebuffer { gl::DeleteTextures(1, &fb.image); gl::CreateTextures(gl::TEXTURE_2D, 1, &mut fb.image); - fb.mip_levels = librashader_runtime::scaling::calc_miplevel(size); + fb.mip_levels = size.calculate_miplevels(); if fb.mip_levels > fb.max_levels { fb.mip_levels = fb.max_levels; } diff --git a/librashader-runtime-gl/src/gl/gl46/lut_load.rs b/librashader-runtime-gl/src/gl/gl46/lut_load.rs index 01617c4..6cc3023 100644 --- a/librashader-runtime-gl/src/gl/gl46/lut_load.rs +++ b/librashader-runtime-gl/src/gl/gl46/lut_load.rs @@ -7,6 +7,7 @@ use librashader_runtime::image::{Image, UVDirection}; use librashader_common::Size; use librashader_presets::TextureConfig; use rustc_hash::FxHashMap; +use librashader_runtime::scaling::MipmapSize; pub struct Gl46LutLoad; impl LoadLut for Gl46LutLoad { @@ -23,9 +24,9 @@ impl LoadLut for Gl46LutLoad { } for (index, texture) in textures.iter().enumerate() { - let image = Image::load(&texture.path, UVDirection::BottomLeft)?; + let image: Image = Image::load(&texture.path, UVDirection::BottomLeft)?; let levels = if texture.mipmap { - librashader_runtime::scaling::calc_miplevel(image.size) + image.size.calculate_miplevels() } else { 1u32 }; diff --git a/librashader-runtime-vk/src/filter_chain.rs b/librashader-runtime-vk/src/filter_chain.rs index e5a4ddc..9daa6ac 100644 --- a/librashader-runtime-vk/src/filter_chain.rs +++ b/librashader-runtime-vk/src/filter_chain.rs @@ -1,7 +1,7 @@ use std::error::Error; use std::path::Path; -use ash::vk; -use ash::vk::{PFN_vkGetInstanceProcAddr, StaticFn}; +use ash::{Device, vk}; +use ash::vk::{CommandPoolCreateFlags, PFN_vkGetInstanceProcAddr, Queue, StaticFn}; use rustc_hash::FxHashMap; use librashader_common::ImageFormat; use librashader_preprocess::ShaderSource; @@ -11,18 +11,21 @@ use librashader_reflect::back::targets::SpirV; use librashader_reflect::front::shaderc::GlslangCompilation; use librashader_reflect::reflect::ReflectShader; use librashader_reflect::reflect::semantics::{Semantic, ShaderSemantics, TextureSemantics, UniformBinding, UniformSemantic, UniqueSemantics}; +use librashader_runtime::image::{Image, UVDirection}; use librashader_runtime::uniforms::UniformStorage; use crate::error; use crate::filter_pass::FilterPass; +use crate::luts::LutTexture; use crate::vulkan_state::VulkanGraphicsPipeline; pub struct Vulkan { - physical_device: vk::PhysicalDevice, - device: ash::Device, - instance: ash::Instance, + // physical_device: vk::PhysicalDevice, + pub(crate) device: ash::Device, + // instance: ash::Instance, queue: vk::Queue, command_pool: vk::CommandPool, - pipelines: vk::PipelineCache, + pipeline_cache: vk::PipelineCache, + pub(crate) memory_properties: vk::PhysicalDeviceMemoryProperties, } type ShaderPassMeta = ( @@ -36,7 +39,7 @@ type ShaderPassMeta = ( #[derive(Clone)] pub struct VulkanInfo<'a> { - physical_device: &'a vk::PhysicalDevice, + // physical_device: &'a vk::PhysicalDevice, device: &'a vk::Device, instance: &'a vk::Instance, queue: &'a vk::Queue, @@ -44,13 +47,65 @@ pub struct VulkanInfo<'a> { get_instance_proc_addr: PFN_vkGetInstanceProcAddr } -impl From> for ash::Device { - fn from(vulkan: VulkanInfo) -> Self { +impl TryFrom> for Vulkan { + type Error = Box; + + fn try_from(vulkan: VulkanInfo) -> Result> { unsafe { let instance = ash::Instance::load(&StaticFn { get_instance_proc_addr: vulkan.get_instance_proc_addr, }, vulkan.instance.clone()); - ash::Device::load(instance.fp_v1_0(), vulkan.device.clone()) + + let device = ash::Device::load(instance.fp_v1_0(), vulkan.device.clone()); + + let pipeline_cache = unsafe { + device.create_pipeline_cache(&vk::PipelineCacheCreateInfo::default(), + None)? + }; + + let command_pool = unsafe { + device.create_command_pool(&vk::CommandPoolCreateInfo::builder() + .flags(CommandPoolCreateFlags::RESET_COMMAND_BUFFER) + .build(), None)? + }; + + Ok(Vulkan { + device, + // instance, + queue: vulkan.queue.clone(), + command_pool, + pipeline_cache, + memory_properties: vulkan.memory_properties.clone() + }) + } + } +} + +impl TryFrom<(ash::Device, vk::Queue, vk::PhysicalDeviceMemoryProperties)> for Vulkan { + type Error = Box; + + fn try_from(value: (Device, Queue, vk::PhysicalDeviceMemoryProperties)) -> error::Result { + unsafe { + let device = value.0; + + let pipeline_cache = unsafe { + device.create_pipeline_cache(&vk::PipelineCacheCreateInfo::default(), + None)? + }; + + let command_pool = unsafe { + device.create_command_pool(&vk::CommandPoolCreateInfo::builder() + .flags(CommandPoolCreateFlags::RESET_COMMAND_BUFFER) + .build(), None)? + }; + + Ok(Vulkan { + device, + queue: value.1, + command_pool, + pipeline_cache, + memory_properties: value.2 + }) } } } @@ -71,6 +126,7 @@ pub(crate) struct FilterCommon { // pub feedback_textures: Box<[Option]>, // pub history_textures: Box<[Option]>, // pub config: FilterMutable, + luts: FxHashMap, } pub type FilterChainOptionsVulkan = (); @@ -78,7 +134,7 @@ pub type FilterChainOptionsVulkan = (); impl FilterChainVulkan { /// Load the shader preset at the given path into a filter chain. pub fn load_from_path( - vulkan: impl Into, + vulkan: impl TryInto>, path: impl AsRef, options: Option<&FilterChainOptionsVulkan>, ) -> error::Result { @@ -88,19 +144,24 @@ impl FilterChainVulkan { } pub fn load_from_preset( - vulkan: impl Into, + vulkan: impl TryInto>, preset: ShaderPreset, options: Option<&FilterChainOptionsVulkan>, ) -> error::Result { let (passes, semantics) = FilterChainVulkan::load_preset(preset.shaders, &preset.textures)?; - let device = vulkan.into(); + let device = vulkan.try_into()?; // initialize passes let filters = Self::init_passes(&device, passes, &semantics, 3)?; + + let luts = FilterChainVulkan::load_luts(&device, &preset.textures)?; + eprintln!("filters initialized ok."); Ok(FilterChainVulkan { - common: FilterCommon {}, - passes: filters + common: FilterCommon { + luts + }, + passes: filters, }) } @@ -157,18 +218,13 @@ impl FilterChainVulkan { } fn init_passes( - device: &ash::Device, + vulkan: &Vulkan, passes: Vec, semantics: &ShaderSemantics, images: u32, ) -> error::Result> { let mut filters = Vec::new(); - let pipeline_cache = unsafe { - device.create_pipeline_cache(&vk::PipelineCacheCreateInfo::default(), - None)? - }; - // initialize passes for (index, (config, mut source, mut reflect)) in passes.into_iter().enumerate() { let reflection = reflect.reflect(index, semantics)?; @@ -206,9 +262,10 @@ impl FilterChainVulkan { source.format = ImageFormat::R8G8B8A8Unorm } - let graphics_pipeline = VulkanGraphicsPipeline::new(device, - &pipeline_cache, + let graphics_pipeline = VulkanGraphicsPipeline::new(&vulkan.device, + &vulkan.pipeline_cache, &spirv_words, &reflection, source.format, images)?; + // shader_vulkan: 2026 filters.push(FilterPass { compiled: spirv_words, @@ -223,4 +280,51 @@ impl FilterChainVulkan { Ok(filters.into_boxed_slice()) } + + fn load_luts( + vulkan: &Vulkan, + textures: &[TextureConfig], + ) -> error::Result> { + let mut luts = FxHashMap::default(); + let command_buffer = unsafe { + // panic safety: command buffer count = 1 + vulkan.device.allocate_command_buffers(&vk::CommandBufferAllocateInfo::builder() + .command_pool(vulkan.command_pool) + .level(vk::CommandBufferLevel::PRIMARY) + .command_buffer_count(1) + .build())?[0] + }; + + unsafe { + vulkan.device.begin_command_buffer(command_buffer, &vk::CommandBufferBeginInfo::builder() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT) + .build())? + } + + for (index, texture) in textures.iter().enumerate() { + let image = Image::load(&texture.path, UVDirection::TopLeft)?; + + let texture = LutTexture::new( + vulkan, + &command_buffer, + image, + texture, + )?; + luts.insert(index, texture); + } + + unsafe { + vulkan.device.end_command_buffer(command_buffer)?; + + let buffers = [command_buffer]; + let submits = [vk::SubmitInfo::builder() + .command_buffers(&buffers).build()]; + + vulkan.device.queue_submit(vulkan.queue, &submits, vk::Fence::null())?; + vulkan.device.queue_wait_idle(vulkan.queue)?; + vulkan.device.free_command_buffers(vulkan.command_pool, &buffers); + } + Ok(luts) + } + } \ No newline at end of file diff --git a/librashader-runtime-vk/src/framebuffer.rs b/librashader-runtime-vk/src/framebuffer.rs index 1265973..8dc7000 100644 --- a/librashader-runtime-vk/src/framebuffer.rs +++ b/librashader-runtime-vk/src/framebuffer.rs @@ -1,6 +1,7 @@ use ash::vk; use ash::vk::{Extent3D, ImageAspectFlags, ImageLayout, ImageTiling, ImageType, ImageUsageFlags, ImageViewType, SampleCountFlags, SharingMode}; use librashader_common::Size; +use librashader_runtime::scaling::MipmapSize; use crate::error; use crate::renderpass::VulkanRenderPass; use crate::util::find_vulkan_memory_type; @@ -64,12 +65,8 @@ impl Framebuffer { let image_create_info = vk::ImageCreateInfo::builder() .image_type(ImageType::TYPE_2D) .format(self.render_pass.format.into()) - .extent(Extent3D { - width: self.size.width, - height: self.size.height, - depth: 1 - }) - .mip_levels(std::cmp::min(self.max_levels, librashader_runtime::scaling::calc_miplevel(self.size))) + .extent(self.size.into()) + .mip_levels(std::cmp::min(self.max_levels, self.size.calculate_miplevels())) .array_layers(1) .samples(SampleCountFlags::TYPE_1) .tiling(ImageTiling::OPTIMAL) diff --git a/librashader-runtime-vk/src/hello_triangle/base.rs b/librashader-runtime-vk/src/hello_triangle/base.rs index 34a84c8..71eb31f 100644 --- a/librashader-runtime-vk/src/hello_triangle/base.rs +++ b/librashader-runtime-vk/src/hello_triangle/base.rs @@ -256,7 +256,7 @@ impl ExampleBase { .application_version(0) .engine_name(app_name) .engine_version(0) - .api_version(vk::make_api_version(0, 1, 0, 0)); + .api_version(vk::make_api_version(0, 1, 3, 0)); let create_flags = if cfg!(any(target_os = "macos", target_os = "ios")) { vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR diff --git a/librashader-runtime-vk/src/lib.rs b/librashader-runtime-vk/src/lib.rs index 7f0c2bb..ce7c21c 100644 --- a/librashader-runtime-vk/src/lib.rs +++ b/librashader-runtime-vk/src/lib.rs @@ -11,6 +11,7 @@ mod vulkan_state; mod draw_quad; mod renderpass; mod vulkan_primitives; +mod luts; #[cfg(test)] mod tests { @@ -19,12 +20,19 @@ mod tests { #[test] fn triangle_vk() { let base = hello_triangle::ExampleBase::new(900, 600); + // let mut filter = FilterChainVulkan::load_from_path( + // (base.device.clone(), base.present_queue.clone(), base.device_memory_properties.clone()), + // "../test/slang-shaders/border/gameboy-player/gameboy-player-crt-royale.slangp", + // None + // ) + let mut filter = FilterChainVulkan::load_from_path( - base.device.clone(), + (base.device.clone(), base.present_queue.clone(), base.device_memory_properties.clone()), "../test/slang-shaders/border/gameboy-player/gameboy-player-crt-royale.slangp", None ) - // FilterChain::load_from_path("../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp", None) + + // FilterChain::load_from_path("../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp", None) .unwrap(); hello_triangle::main(base); } diff --git a/librashader-runtime-vk/src/luts.rs b/librashader-runtime-vk/src/luts.rs new file mode 100644 index 0000000..4947e17 --- /dev/null +++ b/librashader-runtime-vk/src/luts.rs @@ -0,0 +1,212 @@ +use ash::vk; +use ash::vk::ImageSubresourceLayers; +use glfw::Key::P; +use librashader_common::{FilterMode, WrapMode}; +use librashader_presets::TextureConfig; +use librashader_runtime::image::{BGRA8, Image}; +use librashader_runtime::scaling::MipmapSize; +use crate::filter_chain::Vulkan; +use crate::{error, util}; +use crate::vulkan_primitives::{VulkanBuffer, VulkanImageMemory}; + +pub struct LutTexture { + pub texture: vk::Image, + pub texture_view: vk::ImageView, + pub memory: VulkanImageMemory, + pub staging: VulkanBuffer, + pub filter_mode: FilterMode, + pub wrap_mode: WrapMode, + pub mipmap: bool, +} + +impl LutTexture { + pub fn new(vulkan: &Vulkan, cmd: &vk::CommandBuffer, image: Image, config: &TextureConfig) -> error::Result { + + // todo: might need to use bgra8 + let image_info = vk::ImageCreateInfo::builder() + .image_type(vk::ImageType::TYPE_2D) + .format(vk::Format::B8G8R8A8_UNORM) + .extent(image.size.into()) + .mip_levels(if config.mipmap { + image.size.calculate_miplevels() + } else { + 1 + }) + .array_layers(1) + .samples(vk::SampleCountFlags::TYPE_1) + .tiling(vk::ImageTiling::OPTIMAL) + .usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_SRC | vk::ImageUsageFlags::TRANSFER_DST) + .initial_layout(vk::ImageLayout::UNDEFINED) + .build(); + + let texture = unsafe { + vulkan.device.create_image(&image_info, None)? + }; + + let memory = unsafe { + let mem_reqs = vulkan.device.get_image_memory_requirements(texture.clone()); + let mem_type = util::find_vulkan_memory_type(&vulkan.memory_properties, mem_reqs.memory_type_bits, vk::MemoryPropertyFlags::DEVICE_LOCAL); + crate::vulkan_primitives::VulkanImageMemory::new(&vulkan.device, &vk::MemoryAllocateInfo::builder() + .memory_type_index(mem_type) + .allocation_size(mem_reqs.size))? + }; + + memory.bind(&texture)?; + + let image_subresource = vk::ImageSubresourceRange::builder() + .level_count(image_info.mip_levels) + .layer_count(1) + .aspect_mask(vk::ImageAspectFlags::COLOR) + .build(); + + let swizzle_components = vk::ComponentMapping::builder() + .r(vk::ComponentSwizzle::R) + .g(vk::ComponentSwizzle::G) + .b(vk::ComponentSwizzle::B) + .a(vk::ComponentSwizzle::A) + .build(); + + let mut view_info = vk::ImageViewCreateInfo::builder() + .view_type(vk::ImageViewType::TYPE_2D) + .format(vk::Format::B8G8R8A8_UNORM) + .image(texture.clone()) + .subresource_range(image_subresource) + .components(swizzle_components) + .build(); + + let texture_view = unsafe { + vulkan.device.create_image_view(&view_info, None)? + }; + + let mut staging = VulkanBuffer::new(&vulkan.device, &vulkan.memory_properties, vk::BufferUsageFlags::TRANSFER_SRC, image.bytes.len())?; + unsafe { + let mut handle = staging.map()?; + handle.copy_from(&image.bytes) + } + + unsafe { + util::vulkan_image_layout_transition_levels(&vulkan.device, *cmd, texture, + vk::REMAINING_MIP_LEVELS, + vk::ImageLayout::UNDEFINED, + if config.mipmap { + vk::ImageLayout::GENERAL + } else { + vk::ImageLayout::TRANSFER_DST_OPTIMAL + }, + vk::AccessFlags::empty(), + vk::AccessFlags::TRANSFER_WRITE, + vk::PipelineStageFlags::TOP_OF_PIPE, + vk::PipelineStageFlags::TRANSFER, + vk::QUEUE_FAMILY_IGNORED, + vk::QUEUE_FAMILY_IGNORED + ); + + vulkan.device.cmd_copy_buffer_to_image(*cmd, + staging.handle, + texture, + if config.mipmap { vk::ImageLayout::GENERAL } else { vk::ImageLayout::TRANSFER_DST_OPTIMAL }, + &[vk::BufferImageCopy::builder() + .image_subresource(vk::ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(0) + .base_array_layer(0) + .layer_count(1) + .build()) + .image_extent(image.size.into()).build()]) + + } + + // generate mipmaps + for level in 1..image_info.mip_levels { + let source_size = image.size.scale_mipmap(level - 1); + let target_size = image.size.scale_mipmap(level); + + let src_offsets = [ + vk::Offset3D { + x: 0, + y: 0, + z: 0, + }, + vk::Offset3D { + x: source_size.width as i32, + y: source_size.height as i32, + z: 1, + }, + ]; + + let dst_offsets = [ + vk::Offset3D { + x: 0, + y: 0, + z: 0, + }, + vk::Offset3D { + x: target_size.width as i32, + y: target_size.height as i32, + z: 1, + }, + ]; + let src_subresource = ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(level - 1) + .base_array_layer(0) + .layer_count(1) + .build(); + + let dst_subresource = ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(level) + .base_array_layer(0) + .layer_count(1) + .build(); + + let image_blit = [vk::ImageBlit::builder() + .src_subresource(src_subresource) + .src_offsets(src_offsets) + .dst_subresource(dst_subresource) + .dst_offsets(dst_offsets) + .build()]; + + unsafe { + util::vulkan_image_layout_transition_levels(&vulkan.device, *cmd, texture, + vk::REMAINING_MIP_LEVELS, + vk::ImageLayout::GENERAL, + vk::ImageLayout::GENERAL, + vk::AccessFlags::TRANSFER_WRITE, + vk::AccessFlags::TRANSFER_READ, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::TRANSFER, + vk::QUEUE_FAMILY_IGNORED, + vk::QUEUE_FAMILY_IGNORED + ); + + // todo: respect mipmap filter? + vulkan.device.cmd_blit_image(*cmd, texture, vk::ImageLayout::GENERAL, texture, vk::ImageLayout::GENERAL, &image_blit, vk::Filter::LINEAR); + } + } + + unsafe { + util::vulkan_image_layout_transition_levels(&vulkan.device, *cmd, texture, + vk::REMAINING_MIP_LEVELS, + if config.mipmap { vk::ImageLayout::GENERAL } else { vk::ImageLayout::TRANSFER_DST_OPTIMAL }, + vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, + vk::AccessFlags::TRANSFER_WRITE, + vk::AccessFlags::TRANSFER_READ, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::FRAGMENT_SHADER, + vk::QUEUE_FAMILY_IGNORED, + vk::QUEUE_FAMILY_IGNORED + ); + } + + Ok(LutTexture { + texture, + texture_view, + memory, + staging, + filter_mode: config.filter_mode, + wrap_mode: config.wrap_mode, + mipmap: config.mipmap + }) + } +} \ No newline at end of file diff --git a/librashader-runtime-vk/src/samplers.rs b/librashader-runtime-vk/src/samplers.rs new file mode 100644 index 0000000..e69de29 diff --git a/librashader-runtime-vk/src/util.rs b/librashader-runtime-vk/src/util.rs index 2833723..31e053b 100644 --- a/librashader-runtime-vk/src/util.rs +++ b/librashader-runtime-vk/src/util.rs @@ -1,5 +1,8 @@ use ash::vk; +use ash::vk::{AccessFlags, Extent3D, ImageAspectFlags}; +use librashader_common::Size; use librashader_reflect::reflect::semantics::BindingStage; +use crate::error; pub fn binding_stage_to_vulkan_stage(stage_mask: BindingStage) -> vk::ShaderStageFlags { let mut mask = vk::ShaderStageFlags::default(); @@ -27,4 +30,38 @@ pub fn find_vulkan_memory_type(props: &vk::PhysicalDeviceMemoryProperties, devic } else { find_vulkan_memory_type(props, device_reqs, vk::MemoryPropertyFlags::empty()) } -} \ No newline at end of file +} + +#[inline(always)] +pub unsafe fn vulkan_image_layout_transition_levels(device: &ash::Device, + cmd: vk::CommandBuffer, + image: vk::Image, + levels: u32, old_layout: vk::ImageLayout, + new_layout: vk::ImageLayout, + src_access: vk::AccessFlags, + dst_access: vk::AccessFlags, + src_stage: vk::PipelineStageFlags, + dst_stage: vk::PipelineStageFlags, + + + src_queue_family_index: u32, + dst_queue_family_index: u32, + + ) { + let mut barrier = vk::ImageMemoryBarrier::default(); + barrier.s_type = vk::StructureType::IMAGE_MEMORY_BARRIER; + barrier.p_next = std::ptr::null(); + barrier.src_access_mask = src_access; + barrier.dst_access_mask = dst_access; + barrier.old_layout = old_layout; + barrier.new_layout = new_layout; + barrier.src_queue_family_index = src_queue_family_index; + barrier.dst_queue_family_index = dst_queue_family_index; + barrier.image = image.clone(); + barrier.subresource_range.aspect_mask = vk::ImageAspectFlags::COLOR; + barrier.subresource_range.base_array_layer = 0; + barrier.subresource_range.level_count = levels; + barrier.subresource_range.layer_count = vk::REMAINING_ARRAY_LAYERS; + device.cmd_pipeline_barrier(cmd, src_stage, dst_stage, vk::DependencyFlags::empty(), + &[], &[], &[barrier]) +} diff --git a/librashader-runtime/src/image.rs b/librashader-runtime/src/image.rs index af20495..6882d18 100644 --- a/librashader-runtime/src/image.rs +++ b/librashader-runtime/src/image.rs @@ -1,12 +1,36 @@ +use std::marker::PhantomData; use librashader_common::Size; pub use image::ImageError; use std::path::Path; -pub struct Image { +pub struct Image { pub bytes: Vec, pub size: Size, pub pitch: usize, + _pd: PhantomData

+} + +pub struct RGBA8; +pub struct BGRA8; + +pub trait PixelFormat { + #[doc(hidden)] + fn convert(pixels: &mut Vec); +} + +impl PixelFormat for RGBA8 { + fn convert(_pixels: &mut Vec) { + + } +} + +impl PixelFormat for BGRA8 { + fn convert(pixels: &mut Vec) { + for [r, _g, b, _a] in pixels.array_chunks_mut::<4>() { + std::mem::swap(b, r) + } + } } /// The direction of UV coordinates to load the image for. @@ -18,7 +42,7 @@ pub enum UVDirection { BottomLeft, } -impl Image { +impl Image

{ /// Load the image from the path as RGBA8. pub fn load(path: impl AsRef, direction: UVDirection) -> Result { let mut image = image::open(path.as_ref())?; @@ -36,10 +60,13 @@ impl Image { .height_stride .max(image.sample_layout().width_stride); + let mut bytes = image.into_raw(); + P::convert(&mut bytes); Ok(Image { - bytes: image.into_raw(), + bytes, pitch, size: Size { height, width }, + _pd: Default::default(), }) } } diff --git a/librashader-runtime/src/lib.rs b/librashader-runtime/src/lib.rs index e06b4b5..9497d8b 100644 --- a/librashader-runtime/src/lib.rs +++ b/librashader-runtime/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(array_chunks)] //! Helpers and shared logic for librashader runtime implementations. /// Scaling helpers. diff --git a/librashader-runtime/src/scaling.rs b/librashader-runtime/src/scaling.rs index 5ed6a91..5715e81 100644 --- a/librashader-runtime/src/scaling.rs +++ b/librashader-runtime/src/scaling.rs @@ -2,9 +2,53 @@ use librashader_common::Size; use librashader_presets::{Scale2D, ScaleFactor, ScaleType, Scaling}; use num_traits::AsPrimitive; use std::ops::Mul; +use crate::scaling; -/// Produce a `Size` scaled with the input scaling options. -pub fn scale(scaling: Scale2D, source: Size, viewport: Size) -> Size +pub trait ViewportSize + where + T: Mul + Copy + 'static, + f32: AsPrimitive +{ + /// Produce a `Size` scaled with the input scaling options. + fn scale_viewport(self, scaling: Scale2D, viewport: Size) -> Size; +} + +impl ViewportSize for Size + where + T: Mul + Copy + 'static, + f32: AsPrimitive{ + fn scale_viewport(self, scaling: Scale2D, viewport: Size) -> Size where T: Mul + Copy + 'static, f32: AsPrimitive { + scaling::scale(scaling, self, viewport) + } +} + +pub trait MipmapSize { + /// Calculate the number of mipmap levels for a given size. + fn calculate_miplevels(self) -> T; + + fn scale_mipmap(self, miplevel: T) -> Size; +} + +impl MipmapSize for Size { + fn calculate_miplevels(self) -> u32 { + let mut size = std::cmp::max(self.width, self.height); + let mut levels = 0; + while size != 0 { + levels += 1; + size >>= 1; + } + + levels + } + + fn scale_mipmap(self, miplevel: u32) -> Size { + let scaled_width = std::cmp::max(self.width >> miplevel, 1); + let scaled_height = std::cmp::max(self.height >> miplevel, 1); + Size::new(scaled_width, scaled_height) + } +} + +fn scale(scaling: Scale2D, source: Size, viewport: Size) -> Size where T: Mul + Copy + 'static, f32: AsPrimitive, @@ -45,8 +89,7 @@ where } } -/// Calculate the number of mipmap levels for a given size. -pub fn calc_miplevel(size: Size) -> u32 { +pub fn calculate_miplevels(size: Size) -> u32 { let mut size = std::cmp::max(size.width, size.height); let mut levels = 0; while size != 0 {