From 2b995539f23618dcf83a4c1e5a232c6bb6942779 Mon Sep 17 00:00:00 2001 From: chyyran Date: Sat, 16 Dec 2023 20:28:41 +0900 Subject: [PATCH] rt(wgpu): add structure to wgpu backend --- .idea/workspace.xml | 93 ++++---- Cargo.lock | 1 + librashader-common/src/wgpu.rs | 10 + librashader-runtime-vk/src/filter_pass.rs | 4 +- .../src/graphics_pipeline.rs | 13 +- librashader-runtime-wgpu/Cargo.toml | 2 +- librashader-runtime-wgpu/src/draw_quad.rs | 52 +++++ librashader-runtime-wgpu/src/error.rs | 2 +- librashader-runtime-wgpu/src/filter_chain.rs | 197 ++++++++--------- librashader-runtime-wgpu/src/filter_pass.rs | 8 +- librashader-runtime-wgpu/src/framebuffer.rs | 7 + .../src/graphics_pipeline.rs | 202 +++++++++++------- librashader-runtime-wgpu/src/lib.rs | 12 +- librashader-runtime-wgpu/src/luts.rs | 60 ++++++ librashader-runtime-wgpu/src/samplers.rs | 20 +- librashader-runtime-wgpu/src/texture.rs | 95 ++++++++ librashader-runtime-wgpu/src/util.rs | 4 +- .../tests/hello_triangle.rs | 61 +++--- librashader-runtime-wgpu/tests/triangle.rs | 5 +- 19 files changed, 566 insertions(+), 282 deletions(-) create mode 100644 librashader-runtime-wgpu/src/draw_quad.rs create mode 100644 librashader-runtime-wgpu/src/framebuffer.rs create mode 100644 librashader-runtime-wgpu/src/luts.rs diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 1a39b46..78bde67 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -12,10 +12,8 @@ - - + - - { - "keyToString": { - "Cargo.Build `Test back::wgsl::test::test_into`.executor": "Run", - "Cargo.Build `Test reflect::cross::test::test_into`.executor": "Run", - "Cargo.Test back::wgsl::test::test_into.executor": "Run", - "Cargo.Test front::naga::test::naga_playground (1).executor": "Run", - "Cargo.Test front::naga::test::naga_playground.executor": "Run", - "Cargo.Test reflect::cross::test::test_into.executor": "Run", - "Cargo.Test triangle_d3d11.executor": "Run", - "Cargo.Test triangle_d3d12.executor": "Run", - "Cargo.Test triangle_gl.executor": "Run", - "Cargo.Test triangle_gl46.executor": "Run", - "Cargo.Test triangle_vk.executor": "Run", - "Cargo.Test triangle_wgpu.executor": "Run", - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.RadMigrateCodeStyle": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "RunOnceActivity.cidr.known.project.marker": "true", - "RunOnceActivity.readMode.enableVisualFormatting": "true", - "cf.first.check.clang-format": "false", - "cidr.known.project.marker": "true", - "git-widget-placeholder": "feat-wgpu-runtime", - "last_opened_file_path": "C:/coding/librashader", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_package_manager_path": "npm", - "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", - "settings.editor.selected.configurable": "language.rust", - "vue.rearranger.settings.migration": "true" + +}]]> @@ -266,7 +264,8 @@ - + + - @@ -330,7 +345,9 @@ - diff --git a/Cargo.lock b/Cargo.lock index a03c047..f4f32a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1830,6 +1830,7 @@ dependencies = [ name = "librashader-runtime-wgpu" version = "0.1.0" dependencies = [ + "array-concat", "bytemuck", "config", "env_logger", diff --git a/librashader-common/src/wgpu.rs b/librashader-common/src/wgpu.rs index fa44238..e5a8272 100644 --- a/librashader-common/src/wgpu.rs +++ b/librashader-common/src/wgpu.rs @@ -175,3 +175,13 @@ impl From for wgpu_types::AddressMode { } } } + +impl From> for wgpu_types::Extent3d { + fn from(value: Size) -> Self { + wgpu_types::Extent3d { + width: value.width, + height: value.height, + depth_or_array_layers: 1, + } + } +} \ No newline at end of file diff --git a/librashader-runtime-vk/src/filter_pass.rs b/librashader-runtime-vk/src/filter_pass.rs index 67dcb92..50a085b 100644 --- a/librashader-runtime-vk/src/filter_pass.rs +++ b/librashader-runtime-vk/src/filter_pass.rs @@ -126,7 +126,7 @@ impl FilterPass { let residual = self .graphics_pipeline - .begin_rendering(&parent.device, output, cmd)?; + .begin_rendering(output, cmd)?; unsafe { parent.device.cmd_bind_pipeline( @@ -178,7 +178,7 @@ impl FilterPass { .device .cmd_set_viewport(cmd, 0, &[output.output.size.into()]); parent.draw_quad.draw_quad(cmd, vbo_type); - self.graphics_pipeline.end_rendering(&parent.device, cmd); + self.graphics_pipeline.end_rendering(cmd); } Ok(residual) } diff --git a/librashader-runtime-vk/src/graphics_pipeline.rs b/librashader-runtime-vk/src/graphics_pipeline.rs index ceac83e..6ffb54a 100644 --- a/librashader-runtime-vk/src/graphics_pipeline.rs +++ b/librashader-runtime-vk/src/graphics_pipeline.rs @@ -384,14 +384,13 @@ impl VulkanGraphicsPipeline { #[inline(always)] pub(crate) fn begin_rendering( &self, - device: &ash::Device, output: &RenderTarget, cmd: vk::CommandBuffer, ) -> error::Result> { if let Some(render_pass) = &self.render_pass { let attachments = [output.output.image_view]; let framebuffer = unsafe { - device.create_framebuffer( + self.device.create_framebuffer( &vk::FramebufferCreateInfo::builder() .render_pass(render_pass.handle) .attachments(&attachments) @@ -418,7 +417,7 @@ impl VulkanGraphicsPipeline { extent: output.output.size.into(), }); unsafe { - device.cmd_begin_render_pass(cmd, &render_pass_info, vk::SubpassContents::INLINE); + self.device.cmd_begin_render_pass(cmd, &render_pass_info, vk::SubpassContents::INLINE); } Ok(Some(framebuffer)) } else { @@ -438,18 +437,18 @@ impl VulkanGraphicsPipeline { .color_attachments(&attachments); unsafe { - device.cmd_begin_rendering(cmd, &rendering_info); + self.device.cmd_begin_rendering(cmd, &rendering_info); } Ok(None) } } - pub(crate) fn end_rendering(&self, device: &ash::Device, cmd: vk::CommandBuffer) { + pub(crate) fn end_rendering(&self, cmd: vk::CommandBuffer) { unsafe { if self.render_pass.is_none() { - device.cmd_end_rendering(cmd); + self.device.cmd_end_rendering(cmd); } else { - device.cmd_end_render_pass(cmd) + self.device.cmd_end_render_pass(cmd) } } } diff --git a/librashader-runtime-wgpu/Cargo.toml b/librashader-runtime-wgpu/Cargo.toml index 3d2cf1c..cf9eecd 100644 --- a/librashader-runtime-wgpu/Cargo.toml +++ b/librashader-runtime-wgpu/Cargo.toml @@ -21,7 +21,7 @@ parking_lot = "0.12.1" rayon = "1.8.0" bytemuck = { version = "1.14.0", features = ["derive"] } - +array-concat = "0.5.2" [dev-dependencies] config = { version = "0.13.4", features = [] } diff --git a/librashader-runtime-wgpu/src/draw_quad.rs b/librashader-runtime-wgpu/src/draw_quad.rs new file mode 100644 index 0000000..401795c --- /dev/null +++ b/librashader-runtime-wgpu/src/draw_quad.rs @@ -0,0 +1,52 @@ +use array_concat::concat_arrays; +use librashader_runtime::quad::QuadType; +use wgpu::util::{BufferInitDescriptor, DeviceExt}; +use wgpu::{Buffer, BufferDescriptor, Device, RenderPass}; + +#[rustfmt::skip] +const VBO_OFFSCREEN: [f32; 16] = [ + // Offscreen + -1.0f32, -1.0, 0.0, 0.0, + -1.0, 1.0, 0.0, 1.0, + 1.0, -1.0, 1.0, 0.0, + 1.0, 1.0, 1.0, 1.0, +]; + +#[rustfmt::skip] +const VBO_DEFAULT_FINAL: [f32; 16] = [ + // Final + 0.0f32, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, 0.0, + 1.0, 1.0, 1.0, 1.0, +]; + +static VBO_DATA: &[f32; 32] = &concat_arrays!(VBO_OFFSCREEN, VBO_DEFAULT_FINAL); +pub struct DrawQuad { + buffer: Buffer, +} + +impl DrawQuad { + pub fn new(device: &Device) -> DrawQuad { + let buffer = device.create_buffer_init(&BufferInitDescriptor { + label: Some("librashader vbo"), + contents: bytemuck::cast_slice(VBO_DATA), + usage: wgpu::BufferUsages::VERTEX, + }); + + DrawQuad { buffer } + } + + pub fn bind_vbo_for_frame<'a, 'b: 'a>(&'b self, cmd: &mut RenderPass<'a>) { + cmd.set_vertex_buffer(0, self.buffer.slice(0..)); + } + + pub fn draw_quad<'a, 'b: 'a>(&'b self, cmd: &mut RenderPass<'a>, vbo: QuadType) { + let offset = match vbo { + QuadType::Offscreen => 0..3, + QuadType::Final => 1..4, + }; + + cmd.draw(offset, 0..1) + } +} diff --git a/librashader-runtime-wgpu/src/error.rs b/librashader-runtime-wgpu/src/error.rs index adcd985..79b6e75 100644 --- a/librashader-runtime-wgpu/src/error.rs +++ b/librashader-runtime-wgpu/src/error.rs @@ -6,7 +6,7 @@ use librashader_runtime::image::ImageError; use std::convert::Infallible; use thiserror::Error; -/// Cumulative error type for Vulkan filter chains. +/// Cumulative error type for WGPU filter chains. #[derive(Error, Debug)] pub enum FilterChainError { #[error("SPIRV reflection error")] diff --git a/librashader-runtime-wgpu/src/filter_chain.rs b/librashader-runtime-wgpu/src/filter_chain.rs index d5a2a22..5f1809b 100644 --- a/librashader-runtime-wgpu/src/filter_chain.rs +++ b/librashader-runtime-wgpu/src/filter_chain.rs @@ -6,7 +6,7 @@ use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtif use librashader_reflect::reflect::semantics::ShaderSemantics; use librashader_reflect::reflect::ReflectShader; use librashader_runtime::binding::BindingUtil; -use librashader_runtime::image::{Image, ImageError, UVDirection, BGRA8}; +use librashader_runtime::image::{Image, ImageError, UVDirection, BGRA8, RGBA8}; use librashader_runtime::quad::QuadType; use librashader_runtime::uniforms::UniformStorage; use parking_lot::RwLock; @@ -16,22 +16,25 @@ use std::convert::Infallible; use std::path::Path; use std::sync::Arc; -use librashader_cache::CachedCompilation; +use librashader_common::{ImageFormat, Size}; +use librashader_reflect::back::wgsl::WgslCompileOptions; use librashader_runtime::framebuffer::FramebufferInit; use librashader_runtime::render_target::RenderTarget; use librashader_runtime::scaling::ScaleFramebuffer; use rayon::prelude::*; use wgpu::{CommandBuffer, CommandEncoder, Device, Queue, TextureFormat}; -use librashader_common::ImageFormat; -use librashader_reflect::back::wgsl::WgslCompileOptions; +use crate::draw_quad::DrawQuad; use crate::error; use crate::error::FilterChainError; use crate::filter_pass::FilterPass; use crate::graphics_pipeline::WgpuGraphicsPipeline; +use crate::luts::LutTexture; +use crate::samplers::SamplerSet; +use crate::texture::{InputImage, OwnedImage}; type ShaderPassMeta = -ShaderPassArtifact + Send>; + ShaderPassArtifact + Send>; fn compile_passes( shaders: Vec, textures: &[TextureConfig], @@ -45,29 +48,35 @@ fn compile_passes( pub struct FilterChainWGPU { pub(crate) common: FilterCommon, passes: Box<[FilterPass]>, - // vulkan: VulkanObjects, - // output_framebuffers: Box<[OwnedImage]>, - // feedback_framebuffers: Box<[OwnedImage]>, - // history_framebuffers: VecDeque, - // disable_mipmaps: bool, + output_framebuffers: Box<[OwnedImage]>, + feedback_framebuffers: Box<[OwnedImage]>, + history_framebuffers: VecDeque, + disable_mipmaps: bool, // residuals: Box<[FrameResiduals]>, } pub struct FilterMutable { - // pub(crate) passes_enabled: usize, - // pub(crate) parameters: FxHashMap, + pub passes_enabled: usize, + pub(crate) parameters: FxHashMap, + } pub(crate) struct FilterCommon { // pub(crate) luts: FxHashMap, // pub samplers: SamplerSet, // pub(crate) draw_quad: DrawQuad, - // pub output_textures: Box<[Option]>, - // pub feedback_textures: Box<[Option]>, - // pub history_textures: Box<[Option]>, + pub output_textures: Box<[Option]>, + pub feedback_textures: Box<[Option]>, + pub history_textures: Box<[Option]>, // pub config: FilterMutable, // pub device: Arc, // pub(crate) internal_frame_count: usize, + luts: FxHashMap, + samplers: SamplerSet, + config: FilterMutable, + internal_frame_count: i32, + draw_quad: DrawQuad, + device: Arc, } impl FilterChainWGPU { @@ -79,92 +88,94 @@ impl FilterChainWGPU { /// The caller is responsible for ending the command buffer and immediately submitting it to a /// graphics queue. The command buffer must be completely executed before calling [`frame`](Self::frame). pub fn load_from_preset_deferred( - device: &Device, - // cmd: &mut CommandEncoder, + device: Arc, + queue: &mut wgpu::Queue, + cmd: &mut wgpu::CommandEncoder, preset: ShaderPreset, - - ) -> error::Result - - { + ) -> error::Result { let (passes, semantics) = compile_passes(preset.shaders, &preset.textures)?; - // let device = vulkan.try_into().map_err(From::from)?; - // - // let mut frames_in_flight = options.map_or(0, |o| o.frames_in_flight); - // if frames_in_flight == 0 { - // frames_in_flight = 3; - // } - // // // initialize passes - let filters = Self::init_passes( - &device, - passes, - &semantics, - )?; + let filters = Self::init_passes(Arc::clone(&device), passes, &semantics)?; // - // let luts = FilterChainVulkan::load_luts(&device, cmd, &preset.textures)?; - // let samplers = SamplerSet::new(&device.device)?; + let luts = FilterChainWGPU::load_luts(&device, queue, cmd, &preset.textures)?; + let samplers = SamplerSet::new(&device); // - // let framebuffer_gen = - // || OwnedImage::new(&device, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, 1); - // let input_gen = || None; - // let framebuffer_init = FramebufferInit::new( - // filters.iter().map(|f| &f.reflection.meta), - // &framebuffer_gen, - // &input_gen, - // ); + let framebuffer_gen = + || Ok::<_, error::FilterChainError>(OwnedImage::new(Arc::clone(&device), Size::new(1, 1), 1, ImageFormat::R8G8B8A8Unorm)); + let input_gen = || None; + let framebuffer_init = FramebufferInit::new( + filters.iter().map(|f| &f.reflection.meta), + &framebuffer_gen, + &input_gen, + ); + // // // initialize output framebuffers - // let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?; + let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?; // - // // initialize feedback framebuffers - // let (feedback_framebuffers, feedback_textures) = - // framebuffer_init.init_output_framebuffers()?; + // initialize feedback framebuffers + let (feedback_framebuffers, feedback_textures) = + framebuffer_init.init_output_framebuffers()?; // - // // initialize history - // let (history_framebuffers, history_textures) = framebuffer_init.init_history()?; + // initialize history + let (history_framebuffers, history_textures) = framebuffer_init.init_history()?; // // let mut intermediates = Vec::new(); // intermediates.resize_with(frames_in_flight as usize, || { // FrameResiduals::new(&device.device) // }); - // Ok(FilterChainVulkan { - // common: FilterCommon { - // luts, - // samplers, - // config: FilterMutable { - // passes_enabled: preset.shader_count as usize, - // parameters: preset - // .parameters - // .into_iter() - // .map(|param| (param.name, param.value)) - // .collect(), - // }, - // draw_quad: DrawQuad::new(&device.device, &device.alloc)?, - // device: device.device.clone(), - // output_textures, - // feedback_textures, - // history_textures, - // internal_frame_count: 0, - // }, - // passes: filters, - // vulkan: device, - // output_framebuffers, - // feedback_framebuffers, - // history_framebuffers, - // residuals: intermediates.into_boxed_slice(), - // disable_mipmaps: options.map_or(false, |o| o.force_no_mipmaps), - // }) + let draw_quad = DrawQuad::new(&device); Ok(FilterChainWGPU { - common: FilterCommon {}, + common: FilterCommon { + luts, + samplers, + config: FilterMutable { + passes_enabled: preset.shader_count as usize, + parameters: preset + .parameters + .into_iter() + .map(|param| (param.name, param.value)) + .collect(), + }, + draw_quad, + device, + output_textures, + feedback_textures, + history_textures, + internal_frame_count: 0, + + }, passes: filters, + output_framebuffers, + feedback_framebuffers, + history_framebuffers, + disable_mipmaps: false // todo: force no mipmaps, }) } + fn load_luts( + device: &wgpu::Device, + queue: &mut wgpu::Queue, + cmd: &mut wgpu::CommandEncoder, + textures: &[TextureConfig], + ) -> error::Result> { + let mut luts = FxHashMap::default(); + let images = textures + .par_iter() + .map(|texture| Image::load(&texture.path, UVDirection::TopLeft)) + .collect::, ImageError>>()?; + for (index, (texture, image)) in textures.iter().zip(images).enumerate() { + let texture = LutTexture::new(device, queue, cmd, image, texture); + luts.insert(index, texture); + } + Ok(luts) + } + fn init_passes( - device: &Device, + device: Arc, passes: Vec, semantics: &ShaderSemantics, ) -> error::Result> { @@ -192,29 +203,20 @@ impl FilterChainWGPU { let uniform_bindings = reflection.meta.create_binding_map(|param| param.offset()); // - let render_pass_format: Option = if let Some(format) = config.get_format_override() { - format.into() - } else { - source.format.into() - }; - + let render_pass_format: Option = + if let Some(format) = config.get_format_override() { + format.into() + } else { + source.format.into() + }; let graphics_pipeline = WgpuGraphicsPipeline::new( - device, + Arc::clone(&device), &wgsl, &reflection, - render_pass_format.unwrap_or(TextureFormat::R8Unorm) + render_pass_format.unwrap_or(TextureFormat::R8Unorm), ); - // let graphics_pipeline = VulkanGraphicsPipeline::new( - // &vulkan.device, - // &spirv_words, - // &reflection, - // frames_in_flight, - // render_pass_format, - // disable_cache, - // )?; - Ok(FilterPass { // device: vulkan.device.clone(), reflection, @@ -233,6 +235,7 @@ impl FilterChainWGPU { let filters: error::Result> = filters.into_iter().collect(); let filters = filters?; Ok(filters.into_boxed_slice()) - } -} \ No newline at end of file + + +} diff --git a/librashader-runtime-wgpu/src/filter_pass.rs b/librashader-runtime-wgpu/src/filter_pass.rs index 169d1ef..99a5f55 100644 --- a/librashader-runtime-wgpu/src/filter_pass.rs +++ b/librashader-runtime-wgpu/src/filter_pass.rs @@ -1,13 +1,13 @@ -use std::sync::Arc; -use rustc_hash::FxHashMap; +use crate::graphics_pipeline::WgpuGraphicsPipeline; use librashader_preprocess::ShaderSource; use librashader_presets::ShaderPassConfig; -use librashader_reflect::back::ShaderCompilerOutput; use librashader_reflect::back::wgsl::NagaWgslContext; +use librashader_reflect::back::ShaderCompilerOutput; use librashader_reflect::reflect::semantics::{MemberOffset, UniformBinding}; use librashader_reflect::reflect::ShaderReflection; use librashader_runtime::uniforms::{NoUniformBinder, UniformStorage}; -use crate::graphics_pipeline::WgpuGraphicsPipeline; +use rustc_hash::FxHashMap; +use std::sync::Arc; pub struct FilterPass { pub reflection: ShaderReflection, diff --git a/librashader-runtime-wgpu/src/framebuffer.rs b/librashader-runtime-wgpu/src/framebuffer.rs new file mode 100644 index 0000000..19b09fb --- /dev/null +++ b/librashader-runtime-wgpu/src/framebuffer.rs @@ -0,0 +1,7 @@ +use wgpu::TextureView; +use librashader_common::Size; + +pub(crate) struct OutputImage { + pub size: Size, + pub view: TextureView +} \ No newline at end of file diff --git a/librashader-runtime-wgpu/src/graphics_pipeline.rs b/librashader-runtime-wgpu/src/graphics_pipeline.rs index a855eff..2de96d3 100644 --- a/librashader-runtime-wgpu/src/graphics_pipeline.rs +++ b/librashader-runtime-wgpu/src/graphics_pipeline.rs @@ -1,28 +1,43 @@ -use std::borrow::Cow; -use std::num::NonZeroU32; -use std::sync::Arc; -use wgpu::{BindGroup, BindGroupDescriptor, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType, BufferSize, Device, PipelineLayout, PushConstantRange, SamplerBindingType, ShaderModule, ShaderSource, ShaderStages, TextureFormat, TextureSampleType, TextureViewDimension, VertexAttribute, VertexBufferLayout}; -use librashader_reflect::back::ShaderCompilerOutput; +use crate::{error, util}; use librashader_reflect::back::wgsl::NagaWgslContext; -use librashader_reflect::reflect::semantics::BufferReflection; +use librashader_reflect::back::ShaderCompilerOutput; use librashader_reflect::reflect::ShaderReflection; -use crate::util; +use std::borrow::Cow; +use std::sync::Arc; +use wgpu::{BindGroup, BindGroupDescriptor, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType, BufferSize, CommandEncoder, Device, Operations, PipelineLayout, PushConstantRange, RenderPass, RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, SamplerBindingType, ShaderModule, ShaderSource, ShaderStages, TextureFormat, TextureSampleType, TextureViewDimension, VertexAttribute, VertexBufferLayout}; +use librashader_runtime::render_target::RenderTarget; +use crate::framebuffer::OutputImage; pub struct WgpuGraphicsPipeline { - vertex: ShaderModule, - fragment: ShaderModule + layout: PipelineLayoutObjects, + render_pipeline: wgpu::RenderPipeline, } pub struct PipelineLayoutObjects { - pub layout: PipelineLayout, - pub bind_group_layouts: Vec + layout: PipelineLayout, + bind_group_layouts: Vec, + fragment_entry_name: String, + vertex_entry_name: String, + vertex: ShaderModule, + fragment: ShaderModule, + device: Arc } impl PipelineLayoutObjects { pub fn new( reflection: &ShaderReflection, - device: &Device + shader_assembly: &ShaderCompilerOutput, + device: Arc, ) -> Self { + let vertex = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("vertex"), + source: ShaderSource::Wgsl(Cow::from(&shader_assembly.vertex)), + }); + + let fragment = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("fragment"), + source: ShaderSource::Wgsl(Cow::from(&shader_assembly.fragment)), + }); let mut bind_group_layouts = Vec::new(); @@ -31,7 +46,9 @@ impl PipelineLayoutObjects { let mut push_constant_range = Vec::new(); - if let Some(push_meta) = reflection.push_constant.as_ref() && !push_meta.stage_mask.is_empty() { + if let Some(push_meta) = reflection.push_constant.as_ref() + && !push_meta.stage_mask.is_empty() + { let push_mask = util::binding_stage_to_wgpu_stage(push_meta.stage_mask); if let Some(binding) = push_meta.binding { @@ -53,7 +70,9 @@ impl PipelineLayoutObjects { } } - if let Some(ubo_meta) = reflection.ubo.as_ref() && !ubo_meta.stage_mask.is_empty() { + if let Some(ubo_meta) = reflection.ubo.as_ref() + && !ubo_meta.stage_mask.is_empty() + { let ubo_mask = util::binding_stage_to_wgpu_stage(ubo_meta.stage_mask); main_bindings.push(BindGroupLayoutEntry { binding: ubo_meta.binding, @@ -99,80 +118,56 @@ impl PipelineLayoutObjects { bind_group_layouts.push(main_bind_group); bind_group_layouts.push(sampler_bind_group); + let bind_group_layout_refs = bind_group_layouts.iter().collect::>(); + let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("shader pipeline layout"), - bind_group_layouts: &bind_group_layouts.as_ref(), + bind_group_layouts: &bind_group_layout_refs, push_constant_ranges: &push_constant_range.as_ref(), }); Self { layout, - bind_group_layouts + bind_group_layouts, + fragment_entry_name: shader_assembly.context.fragment.entry_points[0] + .name + .clone(), + vertex_entry_name: shader_assembly.context.vertex.entry_points[0].name.clone(), + vertex, + fragment, + device, } } -} - - -impl WgpuGraphicsPipeline { - pub fn new( - device: &Device, - shader_assembly: &ShaderCompilerOutput, - reflection: &ShaderReflection, - render_pass_format: TextureFormat, - ) -> Self { - let vertex = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("vertex"), - source: ShaderSource::Wgsl(Cow::from(&shader_assembly.vertex)) - }); - - let fragment = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("fragment"), - source: ShaderSource::Wgsl(Cow::from(&shader_assembly.fragment)) - }); - - let layout = PipelineLayoutObjects::new(reflection, device); - - let vao_layout = VertexBufferLayout { - array_stride: 4 * std::mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[ - wgpu::VertexAttribute { - format: wgpu::VertexFormat::Float32x2, - offset: 0, - shader_location: 0, - }, - wgpu::VertexAttribute { - format: wgpu::VertexFormat::Float32x2, - offset: (2 * std::mem::size_of::()) as wgpu::BufferAddress, - shader_location: 1, - }, - ], - }; - - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + pub fn create_pipeline(&self, framebuffer_format: TextureFormat) -> wgpu::RenderPipeline { + self.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Render Pipeline"), - layout: Some(&layout.layout), + layout: Some(&self.layout), vertex: wgpu::VertexState { - module: &vertex, - entry_point: &shader_assembly - .context - .vertex - .entry_points[0] - .name, - buffers: &[ - vao_layout - ], + module: &self.vertex, + entry_point: &self.vertex_entry_name, + buffers: &[VertexBufferLayout { + array_stride: 4 * std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x2, + offset: 0, + shader_location: 0, + }, + wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x2, + offset: (2 * std::mem::size_of::()) as wgpu::BufferAddress, + shader_location: 1, + }, + ], + }], }, fragment: Some(wgpu::FragmentState { - module: &fragment, - entry_point: &shader_assembly - .context - .fragment - .entry_points[0] - .name, + module: &self.fragment, + entry_point: &self.fragment_entry_name, targets: &[Some(wgpu::ColorTargetState { - format: render_pass_format, + format: framebuffer_format, blend: Some(wgpu::BlendState::REPLACE), write_mask: wgpu::ColorWrites::ALL, })], @@ -193,11 +188,64 @@ impl WgpuGraphicsPipeline { alpha_to_coverage_enabled: false, }, multiview: None, - }); + }) + } +} +impl WgpuGraphicsPipeline { + pub fn new( + device: Arc, + shader_assembly: &ShaderCompilerOutput, + reflection: &ShaderReflection, + render_pass_format: TextureFormat, + ) -> Self { + let layout = PipelineLayoutObjects::new(reflection, shader_assembly, device); + let render_pipeline = layout.create_pipeline(render_pass_format); Self { - vertex, - fragment, + layout, + render_pipeline, } } -} \ No newline at end of file + + pub fn recompile(&mut self, format: TextureFormat) { + let render_pipeline = self.layout.create_pipeline(format); + self.render_pipeline = render_pipeline; + } + + pub(crate) fn begin_rendering<'pass>( + &'pass self, + output: &RenderTarget<'pass, OutputImage>, + cmd: &'pass mut CommandEncoder + ) -> RenderPass<'pass> { + let mut render_pass = cmd.begin_render_pass(&RenderPassDescriptor { + label: Some("librashader"), + color_attachments: &[Some(RenderPassColorAttachment { + view: &output.output.view, + resolve_target: None, + ops: Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + render_pass.set_viewport( + output.x, + output.y, + output.output.size.width as f32, + output.output.size.height as f32, + 1.0, + 1.0 + ); + + render_pass + } +} diff --git a/librashader-runtime-wgpu/src/lib.rs b/librashader-runtime-wgpu/src/lib.rs index dee60f9..b15da91 100644 --- a/librashader-runtime-wgpu/src/lib.rs +++ b/librashader-runtime-wgpu/src/lib.rs @@ -7,14 +7,16 @@ #![feature(let_chains)] #![feature(strict_provenance)] - -mod filter_chain; +mod draw_quad; mod error; -mod texture; +mod filter_chain; mod filter_pass; mod graphics_pipeline; -mod util; mod samplers; +mod texture; +mod util; +mod framebuffer; +mod luts; pub use filter_chain::FilterChainWGPU; -pub use filter_pass::FilterPass; \ No newline at end of file +pub use filter_pass::FilterPass; diff --git a/librashader-runtime-wgpu/src/luts.rs b/librashader-runtime-wgpu/src/luts.rs new file mode 100644 index 0000000..fe05857 --- /dev/null +++ b/librashader-runtime-wgpu/src/luts.rs @@ -0,0 +1,60 @@ +use wgpu::{ImageDataLayout, Label, TextureDescriptor}; +use wgpu::util::DeviceExt; +use librashader_presets::TextureConfig; +use librashader_runtime::image::{BGRA8, Image}; +use librashader_runtime::scaling::MipmapSize; + +pub(crate) struct LutTexture { + texture: wgpu::Texture, +} + +impl LutTexture { + pub fn new( + device: &wgpu::Device, + queue: &mut wgpu::Queue, + _cmd: &mut wgpu::CommandEncoder, + image: Image, + config: &TextureConfig + ) -> LutTexture { + let texture = device.create_texture(&TextureDescriptor { + label: Some(&config.name), + size: image.size.into(), + mip_level_count: if config.mipmap { + image.size.calculate_miplevels() + } else { + 1 + }, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::COPY_DST + // need render attachment for mipmaps... + | wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[wgpu::TextureFormat::Rgba8Unorm], + }); + + + queue.write_texture( + wgpu::ImageCopyTexture { + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + &image.bytes, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * image.size.width), + rows_per_image: None, + }, + image.size.into() + ); + + // todo: mipmaps + + Self { + texture + } + } +} \ No newline at end of file diff --git a/librashader-runtime-wgpu/src/samplers.rs b/librashader-runtime-wgpu/src/samplers.rs index b6f1a5a..bd056a0 100644 --- a/librashader-runtime-wgpu/src/samplers.rs +++ b/librashader-runtime-wgpu/src/samplers.rs @@ -1,5 +1,5 @@ -use librashader_common::{FilterMode, WrapMode}; use crate::error; +use librashader_common::{FilterMode, WrapMode}; use rustc_hash::FxHashMap; use std::sync::Arc; use wgpu::{Sampler, SamplerBorderColor, SamplerDescriptor}; @@ -22,7 +22,7 @@ impl SamplerSet { } } - pub fn new(device: &wgpu::Device) -> error::Result { + pub fn new(device: &wgpu::Device) -> SamplerSet { let mut samplers = FxHashMap::default(); let wrap_modes = &[ WrapMode::ClampToBorder, @@ -37,18 +37,18 @@ impl SamplerSet { (*wrap_mode, *filter_mode, *mipmap_filter), device.create_sampler(&SamplerDescriptor { label: None, - address_mode_u: wrap_mode.into(), - address_mode_v: wrap_mode.into(), - address_mode_w: wrap_mode.into(), - mag_filter: filter_mode.into(), - min_filter: filter_mode.into(), - mipmap_filter: mipmap_filter.into(), + address_mode_u: (*wrap_mode).into(), + address_mode_v: (*wrap_mode).into(), + address_mode_w: (*wrap_mode).into(), + mag_filter: (*filter_mode).into(), + min_filter: (*filter_mode).into(), + mipmap_filter: (*mipmap_filter).into(), lod_min_clamp: 0.0, lod_max_clamp: 1000.0, compare: None, anisotropy_clamp: 1, border_color: Some(SamplerBorderColor::TransparentBlack), - }) + }), ); } } @@ -56,6 +56,6 @@ impl SamplerSet { // assert all samplers were created. assert_eq!(samplers.len(), wrap_modes.len() * 2 * 2); - Ok(SamplerSet { samplers }) + SamplerSet { samplers } } } diff --git a/librashader-runtime-wgpu/src/texture.rs b/librashader-runtime-wgpu/src/texture.rs index e69de29..d48db73 100644 --- a/librashader-runtime-wgpu/src/texture.rs +++ b/librashader-runtime-wgpu/src/texture.rs @@ -0,0 +1,95 @@ +use std::sync::Arc; +use wgpu::TextureFormat; +use librashader_common::{FilterMode, ImageFormat, Size, WrapMode}; +use librashader_presets::Scale2D; +use librashader_runtime::scaling::{MipmapSize, ViewportSize}; + +pub struct OwnedImage { + device: Arc, + pub image: wgpu::Texture, + pub view: wgpu::TextureView, + pub max_miplevels: u32, + pub levels: u32, + pub size: Size, + pub format: wgpu::TextureFormat, +} + +pub struct InputImage { + /// A handle to the `VkImage`. + pub image: wgpu::Texture, + pub image_view: wgpu::TextureView, + pub wrap_mode: WrapMode, + pub filter_mode: FilterMode, + pub mip_filter: FilterMode, +} + + +impl OwnedImage { + pub fn new(device: Arc, + size: Size, + max_miplevels: u32, + format: ImageFormat, + ) -> Self { + + let format: Option = format.into(); + let format = format.unwrap_or(wgpu::TextureFormat::Rgba8Unorm); + + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: size.into(), + mip_level_count: std::cmp::min(max_miplevels, size.calculate_miplevels()), + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::COPY_SRC, + view_formats: &[format.into()], + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor { + label: None, + format: Some(format), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }); + + Self { + device, + image: texture, + view, + max_miplevels, + levels: std::cmp::min(max_miplevels, size.calculate_miplevels()), + size, + format, + } + } + + pub fn scale( + &mut self, + scaling: Scale2D, + format: ImageFormat, + viewport_size: &Size, + source_size: &Size, + mipmap: bool, + ) -> Size { + let size = source_size.scale_viewport(scaling, *viewport_size); + let format: Option = format.into(); + let format = format.unwrap_or(wgpu::TextureFormat::Rgba8Unorm); + + if self.size != size + || (mipmap && self.max_miplevels == 1) + || (!mipmap && self.max_miplevels != 1) + || format != self.format + { + let mut new = OwnedImage::new(Arc::clone(&self.device), size, self.max_miplevels, format.into()); + std::mem::swap(self, &mut new); + } + size + } +} \ No newline at end of file diff --git a/librashader-runtime-wgpu/src/util.rs b/librashader-runtime-wgpu/src/util.rs index aae2164..6a48a80 100644 --- a/librashader-runtime-wgpu/src/util.rs +++ b/librashader-runtime-wgpu/src/util.rs @@ -1,5 +1,5 @@ -use wgpu::ShaderStages; use librashader_reflect::reflect::semantics::BindingStage; +use wgpu::ShaderStages; pub fn binding_stage_to_wgpu_stage(stage_mask: BindingStage) -> ShaderStages { let mut mask = ShaderStages::empty(); @@ -12,4 +12,4 @@ pub fn binding_stage_to_wgpu_stage(stage_mask: BindingStage) -> ShaderStages { } mask -} \ No newline at end of file +} diff --git a/librashader-runtime-wgpu/tests/hello_triangle.rs b/librashader-runtime-wgpu/tests/hello_triangle.rs index e9c7c42..414107e 100644 --- a/librashader-runtime-wgpu/tests/hello_triangle.rs +++ b/librashader-runtime-wgpu/tests/hello_triangle.rs @@ -4,11 +4,11 @@ use winit::{ window::{Window, WindowBuilder}, }; +use librashader_presets::ShaderPreset; +use librashader_runtime_wgpu::FilterChainWGPU; use wgpu::util::DeviceExt; use winit::event_loop::EventLoopBuilder; use winit::platform::windows::EventLoopBuilderExtWindows; -use librashader_presets::ShaderPreset; -use librashader_runtime_wgpu::FilterChainWGPU; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -35,30 +35,29 @@ impl Vertex { shader_location: 1, format: wgpu::VertexFormat::Float32x3, }, - ] + ], } } } - - - const VERTICES: &[Vertex] = &[ - Vertex { // top + Vertex { + // top position: [0.0, 0.5, 0.0], color: [1.0, 0.0, 0.0], }, - Vertex { // bottom left + Vertex { + // bottom left position: [-0.5, -0.5, 0.0], color: [0.0, 1.0, 0.0], }, - Vertex { // bottom right + Vertex { + // bottom right position: [0.5, -0.5, 0.0], color: [0.0, 0.0, 1.0], }, ]; - struct State { surface: wgpu::Surface, device: wgpu::Device, @@ -71,7 +70,7 @@ struct State { vertex_buffer: wgpu::Buffer, num_vertices: u32, - chain: FilterChainWGPU + chain: FilterChainWGPU, } impl State { async fn new(window: &Window) -> Self { @@ -113,7 +112,8 @@ impl State { view_formats: vec![], }; - let preset = ShaderPreset::try_parse("../test/shaders_slang/crt/crt-royale.slangp").unwrap(); + let preset = + ShaderPreset::try_parse("../test/shaders_slang/crt/crt-royale.slangp").unwrap(); let chain = FilterChainWGPU::load_from_preset_deferred(&device, preset).unwrap(); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { @@ -132,9 +132,7 @@ impl State { vertex: wgpu::VertexState { module: &shader, entry_point: "vs_main", - buffers: &[ - Vertex::desc(), - ], + buffers: &[Vertex::desc()], }, fragment: Some(wgpu::FragmentState { module: &shader, @@ -163,14 +161,11 @@ impl State { multiview: None, }); - - let vertex_buffer = device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(VERTICES), - usage: wgpu::BufferUsages::VERTEX, - }, - ); + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(VERTICES), + usage: wgpu::BufferUsages::VERTEX, + }); let clear_color = wgpu::Color { r: 0.1, @@ -189,7 +184,7 @@ impl State { render_pipeline, vertex_buffer, num_vertices, - chain + chain, } } fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { @@ -223,7 +218,7 @@ impl State { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(self.clear_color), - store: wgpu::StoreOp::Store, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, @@ -251,8 +246,6 @@ pub fn run() { .build(); let window = WindowBuilder::new().build(&event_loop).unwrap(); - - pollster::block_on(async { let mut state = State::new(&window).await; event_loop.run(move |event, _, control_flow| { @@ -267,11 +260,11 @@ pub fn run() { WindowEvent::CloseRequested | WindowEvent::KeyboardInput { input: - KeyboardInput { - state: ElementState::Pressed, - virtual_keycode: Some(VirtualKeyCode::Escape), - .. - }, + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + }, .. } => *control_flow = ControlFlow::Exit, WindowEvent::Resized(physical_size) => { @@ -302,6 +295,4 @@ pub fn run() { } }); }); - - -} \ No newline at end of file +} diff --git a/librashader-runtime-wgpu/tests/triangle.rs b/librashader-runtime-wgpu/tests/triangle.rs index 9c21fb6..b13c037 100644 --- a/librashader-runtime-wgpu/tests/triangle.rs +++ b/librashader-runtime-wgpu/tests/triangle.rs @@ -1,7 +1,6 @@ mod hello_triangle; #[test] -fn triangle_wgpu() -{ +fn triangle_wgpu() { hello_triangle::run() -} \ No newline at end of file +}