diff --git a/Cargo.lock b/Cargo.lock index e8fe12e..0d51b40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1645,6 +1645,24 @@ dependencies = [ "windows", ] +[[package]] +name = "librashader-runtime-d3d9" +version = "0.2.4" +dependencies = [ + "array-concat", + "bytemuck", + "gfx-maths", + "librashader-cache", + "librashader-common", + "librashader-preprocess", + "librashader-presets", + "librashader-reflect", + "librashader-runtime", + "num-traits", + "thiserror", + "windows", +] + [[package]] name = "librashader-runtime-gl" version = "0.2.6" diff --git a/Cargo.toml b/Cargo.toml index 908ebcf..d3bee7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ members = [ "librashader-runtime-wgpu", "librashader-cache", "librashader-capi", - "librashader-build-script"] + "librashader-build-script", "librashader-runtime-d3d9"] resolver = "2" [workspace.dependencies] diff --git a/librashader-common/Cargo.toml b/librashader-common/Cargo.toml index d2eada6..0e2839e 100644 --- a/librashader-common/Cargo.toml +++ b/librashader-common/Cargo.toml @@ -14,6 +14,7 @@ description = "RetroArch shaders for all." [features] default = [] opengl = ["gl"] +d3d9 = ["windows"] d3d11 = ["windows", "dxgi"] d3d12 = ["windows", "dxgi"] dxgi = ["windows"] @@ -36,6 +37,7 @@ features = [ "Win32_Foundation", "Win32_Graphics_Dxgi_Common", "Win32_Graphics_Direct3D", + "Win32_Graphics_Direct3D9", "Win32_Graphics_Direct3D11", "Win32_Graphics_Direct3D12", ] diff --git a/librashader-common/src/d3d9.rs b/librashader-common/src/d3d9.rs new file mode 100644 index 0000000..fdf38aa --- /dev/null +++ b/librashader-common/src/d3d9.rs @@ -0,0 +1,78 @@ +use crate::{FilterMode, ImageFormat, WrapMode}; +use windows::Win32::Graphics::Direct3D9; +// +impl From for Direct3D9::D3DFORMAT { + fn from(format: ImageFormat) -> Self { + match format { + ImageFormat::Unknown => Direct3D9::D3DFMT_UNKNOWN, + ImageFormat::R8Unorm => Direct3D9::D3DFMT_R8G8B8, + ImageFormat::R8Uint => Direct3D9::D3DFMT_R8G8B8, + ImageFormat::R8Sint => Direct3D9::D3DFMT_R8G8B8, + ImageFormat::R8G8B8A8Unorm => Direct3D9::D3DFMT_A8R8G8B8, + ImageFormat::R8G8B8A8Uint => Direct3D9::D3DFMT_A8R8G8B8, + ImageFormat::R8G8B8A8Sint => Direct3D9::D3DFMT_A8R8G8B8, + ImageFormat::R8G8B8A8Srgb => Direct3D9::D3DFMT_A8R8G8B8, + ImageFormat::A2B10G10R10UnormPack32 => Direct3D9::D3DFMT_A2B10G10R10, + ImageFormat::A2B10G10R10UintPack32 => Direct3D9::D3DFMT_A2B10G10R10, + ImageFormat::R16Sfloat => Direct3D9::D3DFMT_R16F, + ImageFormat::R16G16Uint => Direct3D9::D3DFMT_G16R16, + ImageFormat::R16G16Sint => Direct3D9::D3DFMT_G16R16, + ImageFormat::R16G16Sfloat => Direct3D9::D3DFMT_G16R16F, + ImageFormat::R16G16B16A16Uint => Direct3D9::D3DFMT_A16B16G16R16, + ImageFormat::R16G16B16A16Sint => Direct3D9::D3DFMT_A16B16G16R16, + ImageFormat::R16G16B16A16Sfloat => Direct3D9::D3DFMT_A16B16G16R16F, + ImageFormat::R32Sfloat => Direct3D9::D3DFMT_R32F, + _ => Direct3D9::D3DFMT_UNKNOWN, + } + } +} +// +impl From for ImageFormat { + fn from(format: Direct3D9::D3DFORMAT) -> Self { + match format { + Direct3D9::D3DFMT_R8G8B8 => ImageFormat::R8Unorm, + Direct3D9::D3DFMT_A8R8G8B8 => ImageFormat::R8G8B8A8Unorm, + Direct3D9::D3DFMT_A2B10G10R10 => ImageFormat::A2B10G10R10UnormPack32, + Direct3D9::D3DFMT_R16F => ImageFormat::R16Sfloat, + Direct3D9::D3DFMT_G16R16 => ImageFormat::R16G16Uint, + Direct3D9::D3DFMT_G16R16F => ImageFormat::R16G16Sfloat, + Direct3D9::D3DFMT_A16B16G16R16 => ImageFormat::R16G16B16A16Uint, + Direct3D9::D3DFMT_A16B16G16R16F => ImageFormat::R16G16B16A16Sfloat, + Direct3D9::D3DFMT_R32F => ImageFormat::R32Sfloat, + _ => ImageFormat::Unknown, + } + } +} + + +impl From for Direct3D9::D3DTEXTUREADDRESS { + fn from(value: WrapMode) -> Self { + match value { + WrapMode::ClampToBorder => Direct3D9::D3DTADDRESS_BORDER, + WrapMode::ClampToEdge => Direct3D9::D3DTADDRESS_CLAMP, + WrapMode::Repeat => Direct3D9::D3DTADDRESS_WRAP, + WrapMode::MirroredRepeat => Direct3D9::D3DTADDRESS_MIRROR, + } + } +} + +impl From for Direct3D9::D3DTEXTUREFILTER { + fn from(value: FilterMode) -> Self { + match value { + FilterMode::Linear => Direct3D9::D3DFILTER_LINEAR, + FilterMode::Nearest => Direct3D9::D3DFILTER_NEAREST, + } + } +} + +// impl FilterMode { +// /// Get the mipmap filtering mode for the given combination. +// pub fn d3d9_mip(&self, mip: FilterMode) -> Direct3D9::D3DTEXTUREFILTER { +// match (self, mip) { +// (FilterMode::Linear, FilterMode::Linear) => Direct3D9::D3DFILTER_MIPLINEAR, +// (FilterMode::Linear, FilterMode::Nearest) => Direct3D9::D3DFILTER_LINEARMIPNEAREST, +// (FilterMode::Nearest, FilterMode::Linear) => Direct3D9::D3DFILTER_MIPNEAREST, +// _ => Direct3D9::D3DFILTER_MIPNEAREST +// } +// } +// } diff --git a/librashader-common/src/lib.rs b/librashader-common/src/lib.rs index b7a4dd9..b78abef 100644 --- a/librashader-common/src/lib.rs +++ b/librashader-common/src/lib.rs @@ -16,6 +16,10 @@ pub mod wgpu; #[cfg(all(target_os = "windows", feature = "dxgi"))] pub mod dxgi; +/// Direct3D 9 common conversions. +#[cfg(all(target_os = "windows", feature = "d3d9"))] +pub mod d3d9; + /// Direct3D 11 common conversions. #[cfg(all(target_os = "windows", feature = "d3d11"))] pub mod d3d11; diff --git a/librashader-reflect/src/back/hlsl.rs b/librashader-reflect/src/back/hlsl.rs index 5b6a2d8..c5c7e0a 100644 --- a/librashader-reflect/src/back/hlsl.rs +++ b/librashader-reflect/src/back/hlsl.rs @@ -9,10 +9,94 @@ use crate::reflect::ReflectShader; /// The HLSL shader model version to target. pub use spirv_cross::hlsl::ShaderModel as HlslShaderModel; +/// Buffer assignment information +#[derive(Debug, Clone)] +pub struct HlslBufferAssignment { + /// The name of the buffer + pub name: String, + /// The id of the buffer + pub id: u32, +} + +/// Buffer assignment information +#[derive(Debug, Clone, Default)] +pub struct HlslBufferAssignments { + /// Buffer assignment information for UBO + pub ubo: Option, + /// Buffer assignment information for Push + pub push: Option, +} + +impl HlslBufferAssignments { + fn find_mangled_id(mangled_name: &str) -> Option { + if !mangled_name.starts_with("_") { + return None; + } + + let Some(next_underscore) = mangled_name[1..].find("_") else { + return None; + }; + + mangled_name[1..next_underscore + 1].parse().ok() + } + + fn find_mangled_name(buffer_name: &str, uniform_name: &str, mangled_name: &str) -> bool { + // name prependded + if mangled_name[buffer_name.len()..].starts_with("_") + && &mangled_name[buffer_name.len() + 1..] == uniform_name + { + return true; + } + return false; + } + + // Check if the mangled name matches. + pub fn contains_uniform(&self, uniform_name: &str, mangled_name: &str) -> bool { + let is_likely_id_mangled = mangled_name.starts_with("_"); + if !mangled_name.ends_with(uniform_name) { + return false; + } + + if let Some(ubo) = &self.ubo { + if is_likely_id_mangled { + if let Some(id) = Self::find_mangled_id(mangled_name) { + if id == ubo.id { + return true; + } + } + } + + // name prependded + if Self::find_mangled_name(&ubo.name, uniform_name, mangled_name) { + return true; + } + } + + if let Some(push) = &self.push { + if is_likely_id_mangled { + if let Some(id) = Self::find_mangled_id(mangled_name) { + if id == push.id { + return true; + } + } + } + + // name prependded + if Self::find_mangled_name(&push.name, uniform_name, mangled_name) { + return true; + } + } + + return false; + } +} + /// The context for a HLSL compilation via spirv-cross. pub struct CrossHlslContext { /// The compiled HLSL program. pub artifact: CompiledProgram, + pub vertex_buffers: HlslBufferAssignments, + pub fragment_buffers: HlslBufferAssignments, } impl FromCompilation for HLSL { @@ -30,3 +114,30 @@ impl FromCompilation for HLSL { }) } } + +#[cfg(test)] +mod test { + use crate::back::hlsl::HlslBufferAssignments; + + #[test] + pub fn mangled_id_test() { + assert_eq!(HlslBufferAssignments::find_mangled_id("_19_MVP"), Some(19)); + assert_eq!(HlslBufferAssignments::find_mangled_id("_19"), None); + assert_eq!(HlslBufferAssignments::find_mangled_id("_19_"), Some(19)); + assert_eq!(HlslBufferAssignments::find_mangled_id("19_"), None); + assert_eq!(HlslBufferAssignments::find_mangled_id("_19MVP"), None); + assert_eq!( + HlslBufferAssignments::find_mangled_id("_19_29_MVP"), + Some(19) + ); + } + + #[test] + pub fn mangled_name_test() { + assert!(HlslBufferAssignments::find_mangled_name( + "params", + "MVP", + "params_MVP" + )); + } +} diff --git a/librashader-reflect/src/reflect/cross/hlsl.rs b/librashader-reflect/src/reflect/cross/hlsl.rs index 4788c0f..f170ed3 100644 --- a/librashader-reflect/src/reflect/cross/hlsl.rs +++ b/librashader-reflect/src/reflect/cross/hlsl.rs @@ -1,9 +1,11 @@ -use crate::back::hlsl::CrossHlslContext; +use crate::back::hlsl::{CrossHlslContext, HlslBufferAssignment, HlslBufferAssignments}; use crate::back::targets::HLSL; use crate::back::{CompileShader, ShaderCompilerOutput}; use crate::error::ShaderCompileError; use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect}; use spirv_cross::hlsl::ShaderModel as HlslShaderModel; +use spirv_cross::spirv::Decoration; +use spirv_cross::ErrorCode; pub(crate) type HlslReflect = CrossReflect; @@ -22,6 +24,84 @@ impl CompileShader for CrossReflect { self.vertex.set_compiler_options(&options)?; self.fragment.set_compiler_options(&options)?; + // todo: options + + let vertex_resources = self.vertex.get_shader_resources()?; + let fragment_resources = self.fragment.get_shader_resources()?; + + let mut vertex_buffer_assignment = HlslBufferAssignments::default(); + let mut fragment_buffer_assignment = HlslBufferAssignments::default(); + + if vertex_resources.uniform_buffers.len() > 1 { + return Err(ShaderCompileError::SpirvCrossCompileError( + ErrorCode::CompilationError(String::from( + "Cannot have more than one uniform buffer", + )), + )); + } + + if let Some(buf) = vertex_resources.uniform_buffers.first() { + vertex_buffer_assignment.ubo = Some(HlslBufferAssignment { + name: buf.name.clone(), + id: buf.id, + }) + } + + if vertex_resources.push_constant_buffers.len() > 1 { + return Err(ShaderCompileError::SpirvCrossCompileError( + ErrorCode::CompilationError(String::from( + "Cannot have more than one push constant buffer", + )), + )); + } + + if let Some(buf) = vertex_resources.push_constant_buffers.first() { + vertex_buffer_assignment.push = Some(HlslBufferAssignment { + name: buf.name.clone(), + id: buf.id, + }) + } + + if fragment_resources.uniform_buffers.len() > 1 { + return Err(ShaderCompileError::SpirvCrossCompileError( + ErrorCode::CompilationError(String::from( + "Cannot have more than one uniform buffer", + )), + )); + } + + if let Some(buf) = fragment_resources.uniform_buffers.first() { + fragment_buffer_assignment.ubo = Some(HlslBufferAssignment { + name: buf.name.clone(), + id: buf.id, + }) + } + + if fragment_resources.push_constant_buffers.len() > 1 { + return Err(ShaderCompileError::SpirvCrossCompileError( + ErrorCode::CompilationError(String::from( + "Cannot have more than one push constant buffer", + )), + )); + } + + if let Some(buf) = fragment_resources.push_constant_buffers.first() { + fragment_buffer_assignment.push = Some(HlslBufferAssignment { + name: buf.name.clone(), + id: buf.id, + }) + } + + if sm == HlslShaderModel::V3_0 { + for res in &fragment_resources.sampled_images { + let binding = self.fragment.get_decoration(res.id, Decoration::Binding)?; + self.fragment + .set_name(res.id, &format!("LIBRA_SAMPLER2D_{binding}"))?; + // self.fragment + // .unset_decoration(res.id, Decoration::Binding)?; + } + } + Ok(ShaderCompilerOutput { vertex: self.vertex.compile()?, fragment: self.fragment.compile()?, @@ -30,6 +110,9 @@ impl CompileShader for CrossReflect { vertex: CompiledAst(self.vertex), fragment: CompiledAst(self.fragment), }, + + vertex_buffers: vertex_buffer_assignment, + fragment_buffers: fragment_buffer_assignment, }, }) } diff --git a/librashader-reflect/src/reflect/cross/mod.rs b/librashader-reflect/src/reflect/cross/mod.rs index c7ec563..d49cbdc 100644 --- a/librashader-reflect/src/reflect/cross/mod.rs +++ b/librashader-reflect/src/reflect/cross/mod.rs @@ -741,12 +741,16 @@ mod test { use crate::reflect::ReflectShader; use rustc_hash::FxHashMap; + use crate::back::hlsl::CrossHlslContext; + use crate::back::targets::HLSL; + use crate::back::{CompileShader, ShaderCompilerOutput}; use crate::front::{Glslang, ShaderInputCompiler}; use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics}; use librashader_common::map::FastHashMap; use librashader_preprocess::ShaderSource; - use spirv_cross::glsl; use spirv_cross::glsl::{CompilerOptions, Version}; + use spirv_cross::hlsl::ShaderModel; + use spirv_cross::{glsl, hlsl}; #[test] pub fn test_into() { @@ -763,8 +767,8 @@ mod test { ); } let spirv = Glslang::compile(&result).unwrap(); - let mut reflect = CrossReflect::::try_from(&spirv).unwrap(); - let _shader_reflection = reflect + let mut reflect = CrossReflect::::try_from(&spirv).unwrap(); + let shader_reflection = reflect .reflect( 0, &ShaderSemantics { @@ -773,10 +777,20 @@ mod test { }, ) .unwrap(); - let mut opts = CompilerOptions::default(); - opts.version = Version::V4_60; - opts.enable_420_pack_extension = false; - // let compiled: ShaderCompilerOutput = as CompileShader>::compile(reflect, Version::V3_30).unwrap(); + let mut opts = hlsl::CompilerOptions::default(); + opts.shader_model = ShaderModel::V3_0; + + let compiled: ShaderCompilerOutput = + as CompileShader>::compile( + reflect, + Some(ShaderModel::V3_0), + ) + .unwrap(); + + println!("{:?}", shader_reflection.meta); + println!("{}", compiled.fragment); + println!("{}", compiled.vertex); + // // eprintln!("{shader_reflection:#?}"); // eprintln!("{}", compiled.fragment) // let mut loader = rspirv::dr::Loader::new(); diff --git a/librashader-runtime-d3d12/tests/triangle.rs b/librashader-runtime-d3d12/tests/triangle.rs index 23933e5..c48e4bc 100644 --- a/librashader-runtime-d3d12/tests/triangle.rs +++ b/librashader-runtime-d3d12/tests/triangle.rs @@ -5,13 +5,13 @@ use crate::hello_triangle::{DXSample, SampleCommandLine}; #[test] fn triangle_d3d12() { let sample = hello_triangle::d3d12_hello_triangle::Sample::new( - "../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp", + // "../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp", // "../test/shaders_slang/crt/crt-lottes.slangp", // "../test/basic.slangp", // "../test/shaders_slang/handheld/console-border/gbc-lcd-grid-v2.slangp", // "../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_GBA_SP/GBA_SP-[ADV]-[LCD-GRID]-[Night].slangp", // "../test/slang-shaders/test/feedback.slangp", - // "../test/slang-shaders/test/history.slangp", + "../test/shaders_slang/test/history.slangp", // "../test/shaders_slang/crt/crt-royale.slangp", // "../test/slang-shaders/vhs/VHSPro.slangp", &SampleCommandLine { diff --git a/librashader-runtime-d3d9/Cargo.toml b/librashader-runtime-d3d9/Cargo.toml new file mode 100644 index 0000000..158114e --- /dev/null +++ b/librashader-runtime-d3d9/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "librashader-runtime-d3d9" +edition = "2021" + +license = "MPL-2.0 OR GPL-3.0-only" +version = "0.2.4" +authors = ["Ronny Chan "] +repository = "https://github.com/SnowflakePowered/librashader" +readme = "../README.md" +categories = ["emulators", "compilers", "graphics"] +keywords = ["shader", "retroarch", "SPIR-V"] +description = "RetroArch shaders for all." + +[dependencies] +librashader-common = { path = "../librashader-common", features = ["d3d9", "d3d11"], version = "0.2.4" } +librashader-presets = { path = "../librashader-presets", version = "0.2.4" } +librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.4" } +librashader-reflect = { path = "../librashader-reflect", version = "0.2.4" } +librashader-runtime = { path = "../librashader-runtime", version = "0.2.4" } +librashader-cache = { path = "../librashader-cache", version = "0.2.4", features = ["d3d"] } + +thiserror = "1.0.37" +bytemuck = "1.12.3" +array-concat = "0.5.2" +num-traits = "0.2.18" + +[target.'cfg(windows)'.dependencies.windows] +workspace = true +features = [ + "Win32_Foundation", + "Win32_Graphics_Direct3D", + "Win32_Graphics_Direct3D9", + "Win32_Graphics_Direct3D_Fxc", + "Win32_System_Threading", + "Win32_Security", + "Foundation_Numerics" +] + +[target.'cfg(windows)'.dev-dependencies.windows] +workspace = true +features = [ + "Win32_Foundation", + "Win32_Graphics_Direct3D", + "Win32_Graphics_Direct3D9", + "Win32_Graphics_Direct3D9on12", + "Win32_Graphics_Direct3D_Fxc", + "Win32_Graphics_Gdi", + "Win32_Security", + "Win32_System_LibraryLoader", + "Win32_System_Threading", + "Win32_UI_WindowsAndMessaging", + "Win32_UI", + "Foundation_Numerics" +] + +[[test]] +name = "triangle" + +[dev-dependencies] +gfx-maths = "0.2.8" + +[package.metadata.docs.rs] +features = ["librashader-cache/docsrs"] diff --git a/librashader-runtime-d3d9/build.rs b/librashader-runtime-d3d9/build.rs new file mode 100644 index 0000000..bec72c1 --- /dev/null +++ b/librashader-runtime-d3d9/build.rs @@ -0,0 +1,6 @@ +pub fn main() { + #[cfg(all(target_os = "windows"))] + { + // println!("cargo:rustc-link-arg=/DELAYLOAD:D3DX9_43.dll"); + } +} diff --git a/librashader-runtime-d3d9/src/binding.rs b/librashader-runtime-d3d9/src/binding.rs new file mode 100644 index 0000000..cf43f39 --- /dev/null +++ b/librashader-runtime-d3d9/src/binding.rs @@ -0,0 +1,236 @@ +use librashader_common::map::FastHashMap; +use librashader_reflect::back::hlsl::CrossHlslContext; +use librashader_reflect::reflect::semantics::{ + BindingMeta, MemberOffset, UniformMemberBlock, UniformMeta, +}; +use librashader_runtime::binding::ContextOffset; +use librashader_runtime::uniforms::{BindUniform, UniformScalar, UniformStorage}; +use num_traits::AsPrimitive; +use std::fmt::Debug; +use windows::Win32::Graphics::Direct3D9::IDirect3DDevice9; + +// only cN (float) or sN (sampler) registers allowed. +#[derive(Debug, Copy, Clone)] +pub enum RegisterSet { + Float, + Sampler, +} + +#[derive(Debug)] +pub struct ConstantDescriptor { + pub assignment: RegisterAssignment, + pub set: RegisterSet, +} + +#[derive(Debug, Copy, Clone)] +pub struct RegisterAssignment { + pub index: u32, + pub count: u32, +} + +#[derive(Debug, Copy, Clone)] +pub struct ConstantRegister { + pub register: VariableRegister, + pub _offset: MemberOffset, +} + +impl ConstantRegister { + pub fn reflect_register_assignment( + meta: &dyn UniformMeta, + ps_constants: &FastHashMap, + vs_constants: &FastHashMap, + context: &CrossHlslContext, + ) -> Self { + let uniform_name = meta.id(); + + // Yeah this is n^2 but ah well. + let ps = ps_constants + .iter() + .find_map(|(mangled_name, register)| { + if context + .fragment_buffers + .contains_uniform(uniform_name, mangled_name) + { + Some(register) + } else { + None + } + }) + .map(|c| c.assignment); + + let vs = vs_constants + .iter() + .find_map(|(mangled_name, register)| { + if context + .fragment_buffers + .contains_uniform(uniform_name, mangled_name) + { + Some(register) + } else { + None + } + }) + .map(|c| c.assignment); + + ConstantRegister { + register: VariableRegister { ps, vs }, + _offset: meta.offset(), + } + } +} + +#[derive(Debug, Copy, Clone)] +pub struct VariableRegister { + pub(crate) ps: Option, + pub(crate) vs: Option, +} + +impl ContextOffset for ConstantRegister { + fn offset(&self) -> MemberOffset { + self._offset + } + + fn context(&self) -> ConstantRegister { + *self + } +} + +pub(crate) type D3D9UniformStorage = + UniformStorage, Box<[u8]>, IDirect3DDevice9>; + +trait D3D9UniformScalar: UniformScalar + AsPrimitive + Copy {} +impl D3D9UniformScalar for u32 {} +impl D3D9UniformScalar for i32 {} +impl D3D9UniformScalar for f32 {} + +pub(crate) struct D3D9UniformBinder; +impl BindUniform for D3D9UniformBinder +where + T: D3D9UniformScalar, +{ + fn bind_uniform( + _block: UniformMemberBlock, + value: T, + context: ConstantRegister, + device: &IDirect3DDevice9, + ) -> Option<()> { + let zeroed = T::zeroed().as_(); + let value = [value.as_(), zeroed, zeroed, zeroed]; + let location = &context.register; + unsafe { + if let Some(location) = location.vs { + if let Err(err) = + device.SetVertexShaderConstantF(location.index, value.as_ptr(), location.count) + { + println!( + "[librashader-runtime-d3d9] unable to bind vertex {}: {err}", + location.index + ); + } + } + } + unsafe { + if let Some(location) = location.ps { + if let Err(err) = + device.SetPixelShaderConstantF(location.index, value.as_ptr(), location.count) + { + println!( + "[librashader-runtime-d3d9] unable to bind vertex {}: {err}", + location.index + ); + } + } + } + Some(()) + } +} + +impl BindUniform for D3D9UniformBinder { + fn bind_uniform( + _block: UniformMemberBlock, + vec4: &[f32; 4], + context: ConstantRegister, + device: &IDirect3DDevice9, + ) -> Option<()> { + let location = &context.register; + unsafe { + if let Some(location) = location.vs { + if let Err(err) = + device.SetVertexShaderConstantF(location.index, vec4.as_ptr(), location.count) + { + println!( + "[librashader-runtime-d3d9] unable to bind vertex {}: {err}", + location.index + ); + } + } + } + unsafe { + if let Some(location) = location.ps { + if let Err(err) = + device.SetPixelShaderConstantF(location.index, vec4.as_ptr(), location.count) + { + println!( + "[librashader-runtime-d3d9] unable to bind fragment {}: {err}", + location.index + ); + } + } + } + Some(()) + } +} + +impl BindUniform for D3D9UniformBinder { + fn bind_uniform( + _block: UniformMemberBlock, + mat4: &[f32; 16], + context: ConstantRegister, + device: &IDirect3DDevice9, + ) -> Option<()> { + let location = &context.register; + unsafe { + if let Some(location) = location.vs { + if let Err(err) = + device.SetVertexShaderConstantF(location.index, mat4.as_ptr(), location.count) + { + println!( + "[librashader-runtime-d3d9] unable to bind vertex {}: {err}", + location.index + ); + } + } + } + unsafe { + if let Some(location) = location.ps { + if let Err(err) = + device.SetPixelShaderConstantF(location.index, mat4.as_ptr(), location.count) + { + println!( + "[librashader-runtime-d3d9] unable to bind fragment {}: {err}", + location.index + ); + } + } + } + Some(()) + } +} + +pub fn update_sampler_bindings( + meta: &mut BindingMeta, + ps_constants: &FastHashMap, +) { + for (_, binding) in meta.texture_meta.iter_mut() { + let Some(descriptor) = ps_constants.get(&format!("LIBRA_SAMPLER2D_{}", binding.binding)) + else { + continue; + }; + + // eprintln!( + // "updating binding {} to {}", + // binding.binding, descriptor.assignment.index + // ); + binding.binding = descriptor.assignment.index; + } +} diff --git a/librashader-runtime-d3d9/src/d3dx.rs b/librashader-runtime-d3d9/src/d3dx.rs new file mode 100644 index 0000000..728b8ec --- /dev/null +++ b/librashader-runtime-d3d9/src/d3dx.rs @@ -0,0 +1,430 @@ +use std::ffi::c_void; +use windows::core::imp::BOOL; +use windows::core::{IntoParam, HRESULT, PCSTR}; +use windows::Win32::Graphics::Direct3D::{ID3DBlob, ID3DInclude, D3D_SHADER_MACRO}; + +const D3DERR_INVALIDCALL: u32 = 0x8876086c; + +#[allow(dead_code)] +pub mod raw { + use super::*; + + #[link(name = "D3DX9_43", kind = "raw-dylib")] + extern "system" { + pub fn D3DXGetShaderVersion(p_function: *const c_void) -> u32; + pub fn D3DXGetShaderSize(p_function: *const c_void) -> u32; + pub fn D3DXGetShaderConstantTable( + p_function: *const c_void, + ppconstanttable: *mut *mut c_void, + ) -> windows::core::HRESULT; + + pub(super) fn D3DXCompileShader( + psrcdata: *const c_void, + srcdatasize: usize, + pdefines: *const D3D_SHADER_MACRO, + pinclude: *mut c_void, + pfunctionname: PCSTR, + pprofile: PCSTR, + flags: u32, + ppcode: *mut *mut c_void, + pperrormsgs: *mut *mut c_void, + ppconstantable: *mut *mut c_void, + ) -> windows::core::HRESULT; + } +} + +#[inline] +pub unsafe fn D3DXCompileShader( + psrcdata: *const c_void, + srcdatasize: usize, + pdefines: Option<*const D3D_SHADER_MACRO>, + pinclude: P1, + pfunctioname: P2, + pprofile: P3, + flags: u32, + ppcode: *mut Option, + pperrormsgs: Option<*mut Option>, + ppconstanttable: Option<*mut Option>, +) -> ::windows::core::Result<()> +where + P1: IntoParam, + P2: IntoParam, + P3: IntoParam, +{ + raw::D3DXCompileShader( + psrcdata, + srcdatasize, + ::core::mem::transmute(pdefines.unwrap_or(::std::ptr::null())), + pinclude.into_param().abi(), + pfunctioname.into_param().abi(), + pprofile.into_param().abi(), + flags, + ::core::mem::transmute(ppcode), + ::core::mem::transmute(pperrormsgs.unwrap_or(::std::ptr::null_mut())), + ::core::mem::transmute(ppconstanttable.unwrap_or(::std::ptr::null_mut())), + ) + .ok() +} + +#[repr(transparent)] +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct ID3DXConstantTable(windows::core::IUnknown); +impl ID3DXConstantTable { + #[allow(non_snake_case)] + pub unsafe fn GetShaderConstantTable( + p_function: *const c_void, + ) -> windows::core::Result { + let mut result__ = ::std::mem::zeroed(); + raw::D3DXGetShaderConstantTable(p_function, &mut result__).from_abi(result__) + } + + #[allow(non_snake_case)] + pub unsafe fn GetBufferPointer(&self) -> *mut c_void { + (windows::core::Interface::vtable(self).GetBufferPointer)(windows::core::Interface::as_raw( + self, + )) + } + + #[allow(non_snake_case)] + pub unsafe fn GetBufferSize(&self) -> usize { + (windows::core::Interface::vtable(self).GetBufferSize)(windows::core::Interface::as_raw( + self, + )) + } + + #[allow(non_snake_case)] + pub unsafe fn GetDesc(&self) -> windows::core::Result { + let mut result__ = ::std::mem::MaybeUninit::::zeroed(); + (windows::core::Interface::vtable(self).GetDesc)( + windows::core::Interface::as_raw(self), + result__.as_mut_ptr(), + ) + .ok()?; + + Ok(result__.assume_init()) + } + + #[allow(non_snake_case)] + pub unsafe fn GetConstant( + &self, + hconstant: Option, + index: u32, + ) -> windows::core::Result { + let handle = (windows::core::Interface::vtable(self).GetConstant)( + windows::core::Interface::as_raw(self), + hconstant.unwrap_or(D3DXHANDLE(std::ptr::null())), + index, + ); + + if handle.0 as u32 == D3DERR_INVALIDCALL { + return Err(HRESULT(D3DERR_INVALIDCALL as i32).into()); + } + + Ok(handle) + } + + #[allow(non_snake_case)] + pub unsafe fn GetConstantDesc( + &self, + handle: D3DXHANDLE, + pdesc: *mut D3DXCONSTANT_DESC, + pcount: *mut u32, + ) -> windows::core::Result<()> { + (windows::core::Interface::vtable(self).GetConstantDesc)( + windows::core::Interface::as_raw(self), + handle, + pdesc, + pcount, + ) + .ok() + } + + #[allow(non_snake_case)] + pub unsafe fn GetConstantByName

( + &self, + hconstant: Option, + pname: P, + ) -> windows::core::Result + where + P: IntoParam, + { + let handle = (windows::core::Interface::vtable(self).GetConstantByName)( + windows::core::Interface::as_raw(self), + hconstant.unwrap_or(D3DXHANDLE(std::ptr::null())), + pname.into_param().abi(), + ); + + if handle.0 as u32 == D3DERR_INVALIDCALL { + return Err(HRESULT(D3DERR_INVALIDCALL as i32).into()); + } + + Ok(handle) + } + + #[allow(non_snake_case)] + pub unsafe fn GetConstantElement( + &self, + hconstant: D3DXHANDLE, + index: u32, + ) -> windows::core::Result { + let handle = (windows::core::Interface::vtable(self).GetConstantElement)( + windows::core::Interface::as_raw(self), + hconstant, + index, + ); + + if handle.0 as u32 == D3DERR_INVALIDCALL { + return Err(HRESULT(D3DERR_INVALIDCALL as i32).into()); + } + + Ok(handle) + } + + #[allow(non_snake_case)] + pub unsafe fn GetSamplerIndex(&self, hconstant: Option) -> u32 { + (windows::core::Interface::vtable(self).GetSamplerIndex)( + windows::core::Interface::as_raw(self), + hconstant.unwrap_or(D3DXHANDLE(std::ptr::null())), + ) + } +} + +impl windows::core::CanInto for ID3DXConstantTable {} +unsafe impl windows::core::Interface for ID3DXConstantTable { + type Vtable = ID3DXConstantTable_Vtbl; +} + +#[repr(C)] +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[allow(non_snake_case)] +pub struct ID3DXConstantTable_Vtbl { + pub base__: windows::core::IUnknown_Vtbl, + #[allow(non_snake_case)] + pub GetBufferPointer: unsafe extern "system" fn(this: *mut c_void) -> *mut c_void, + #[allow(non_snake_case)] + pub GetBufferSize: unsafe extern "system" fn(this: *mut c_void) -> usize, + #[allow(non_snake_case)] + pub GetDesc: unsafe extern "system" fn( + this: *mut c_void, + pdesc: *mut D3DXCONSTANTTABLE_DESC, + ) -> windows::core::HRESULT, + #[allow(non_snake_case)] + pub GetConstantDesc: unsafe extern "system" fn( + this: *mut c_void, + hconstant: D3DXHANDLE, + pdesc: *mut D3DXCONSTANT_DESC, + pcount: *mut u32, + ) -> windows::core::HRESULT, + #[allow(non_snake_case)] + pub GetSamplerIndex: unsafe extern "system" fn(this: *mut c_void, hconstant: D3DXHANDLE) -> u32, + #[allow(non_snake_case)] + pub GetConstant: unsafe extern "system" fn( + this: *mut c_void, + hconstant: D3DXHANDLE, + index: u32, + ) -> D3DXHANDLE, + #[allow(non_snake_case)] + pub GetConstantByName: unsafe extern "system" fn( + this: *mut c_void, + hconstant: D3DXHANDLE, + pname: PCSTR, + ) -> D3DXHANDLE, + #[allow(non_snake_case)] + pub GetConstantElement: unsafe extern "system" fn( + this: *mut c_void, + hconstant: D3DXHANDLE, + index: u32, + ) -> D3DXHANDLE, + #[allow(non_snake_case)] + pub SetDefaults: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + ) -> windows::core::HRESULT, + #[allow(non_snake_case)] + pub SetValue: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + hconstant: D3DXHANDLE, + pdata: *mut c_void, + bytes: u32, + ) -> HRESULT, + #[allow(non_snake_case)] + pub SetBool: + unsafe extern "system" fn(this: *mut c_void, pdevice: *mut c_void, b: BOOL) -> HRESULT, + #[allow(non_snake_case)] + pub SetBoolArray: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + b: *const BOOL, + count: u32, + ) -> HRESULT, + #[allow(non_snake_case)] + pub SetInt: + unsafe extern "system" fn(this: *mut c_void, pdevice: *mut c_void, n: i32) -> HRESULT, + #[allow(non_snake_case)] + pub SetIntArray: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + n: *const i32, + count: u32, + ) -> HRESULT, + #[allow(non_snake_case)] + pub SetFloat: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + f: f32, + ) -> windows::core::HRESULT, + #[allow(non_snake_case)] + pub SetFloatArray: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + b: *const f32, + count: u32, + ) -> windows::core::HRESULT, + #[allow(non_snake_case)] + pub SetVector: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + pvector: *const ::windows::Foundation::Numerics::Vector4, + ) -> windows::core::HRESULT, + #[allow(non_snake_case)] + pub SetVectorArray: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + pvector: *const ::windows::Foundation::Numerics::Vector4, + count: u32, + ) -> HRESULT, + #[allow(non_snake_case)] + pub SetMatrix: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + pmatrix: *const ::windows::Foundation::Numerics::Matrix4x4, + ) -> HRESULT, + #[allow(non_snake_case)] + pub SetMatrixArray: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + pmatrix: *const ::windows::Foundation::Numerics::Matrix4x4, + count: u32, + ) -> HRESULT, + #[allow(non_snake_case)] + pub SetMatrixPointerArray: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + ppmatrix: *const *const ::windows::Foundation::Numerics::Matrix4x4, + count: u32, + ) -> HRESULT, + #[allow(non_snake_case)] + pub SetMatrixTranspose: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + pmatrix: *const ::windows::Foundation::Numerics::Matrix4x4, + ) -> HRESULT, + #[allow(non_snake_case)] + pub SetMatrixTransposeArray: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + pmatrix: *const ::windows::Foundation::Numerics::Matrix4x4, + count: u32, + ) -> HRESULT, + #[allow(non_snake_case)] + pub SetMatrixTransposePointerArray: unsafe extern "system" fn( + this: *mut c_void, + pdevice: *mut c_void, + ppmatrix: *const *const ::windows::Foundation::Numerics::Matrix4x4, + count: u32, + ) -> HRESULT, +} + +#[repr(C)] +#[derive(Clone, Debug)] +#[allow(non_camel_case_types)] +#[allow(non_snake_case)] +pub struct D3DXCONSTANTTABLE_DESC { + pub Creator: PCSTR, + pub Version: u32, + pub Constants: u32, +} + +#[repr(C)] +#[allow(non_camel_case_types)] +#[allow(non_snake_case)] +#[derive(Debug)] +pub struct D3DXCONSTANT_DESC { + pub Name: PCSTR, + pub RegisterSet: D3DXREGISTER_SET, + pub RegisterIndex: u32, + pub RegisterCount: u32, + pub Class: D3DXPARAMETER_CLASS, + pub Type: D3DXPARAMETER_TYPE, + pub Rows: u32, + pub Columns: u32, + pub Elements: u32, + pub StructMembers: u32, + pub Bytes: u32, + pub DefaultValue: *const c_void, +} + +#[repr(u32)] +#[derive(PartialEq, Eq, Debug)] +#[allow(non_camel_case_types)] +#[allow(non_snake_case)] +pub enum D3DXREGISTER_SET { + D3DXRS_BOOL = 0, + D3DXRS_INT4 = 1, + D3DXRS_FLOAT4 = 2, + D3DXRS_SAMPLER = 3, + D3DXRS_FORCE_DWORD = 0x7fffffff, +} + +#[repr(u32)] +#[derive(PartialEq, Eq, Debug)] +#[allow(non_camel_case_types)] +#[allow(non_snake_case)] +pub enum D3DXPARAMETER_CLASS { + D3DXPC_SCALAR = 0, + D3DXPC_VECTOR = 1, + D3DXPC_MATRIX_ROWS = 2, + D3DXPC_MATRIX_COLUMNS = 3, + D3DXPC_OBJECT = 4, + D3DXPC_STRUCT = 5, + D3DXPC_FORCE_DWORD = 0x7fffffff, +} + +#[repr(u32)] +#[derive(Debug)] +#[allow(non_camel_case_types)] +#[allow(non_snake_case)] +pub enum D3DXPARAMETER_TYPE { + D3DXPT_VOID = 0, + D3DXPT_BOOL = 1, + D3DXPT_INT = 2, + D3DXPT_FLOAT = 3, + D3DXPT_STRING = 4, + D3DXPT_TEXTURE = 5, + D3DXPT_TEXTURE1D = 6, + D3DXPT_TEXTURE2D = 7, + D3DXPT_TEXTURE3D = 8, + D3DXPT_TEXTURECUBE = 9, + D3DXPT_SAMPLER = 10, + D3DXPT_SAMPLER1D = 11, + D3DXPT_SAMPLER2D = 12, + D3DXPT_SAMPLER3D = 13, + D3DXPT_SAMPLERCUBE = 14, + D3DXPT_PIXELSHADER = 15, + D3DXPT_VERTEXSHADER = 16, + D3DXPT_PIXELFRAGMENT = 17, + D3DXPT_VERTEXFRAGMENT = 18, + D3DXPT_UNSUPPORTED = 19, + D3DXPT_FORCE_DWORD = 0x7fffffff, +} + +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct D3DXHANDLE(pub *const c_void); + +unsafe impl windows::core::ComInterface for ID3DXConstantTable { + const IID: windows::core::GUID = + windows::core::GUID::from_u128(0xab3c758f_93e_4356_b7_62_4d_b1_8f_1b_3a1); +} diff --git a/librashader-runtime-d3d9/src/draw_quad.rs b/librashader-runtime-d3d9/src/draw_quad.rs new file mode 100644 index 0000000..450b616 --- /dev/null +++ b/librashader-runtime-d3d9/src/draw_quad.rs @@ -0,0 +1,154 @@ +use crate::error; +use crate::error::{assume_d3d_init, Result}; +use array_concat::concat_arrays; +use bytemuck::offset_of; +use librashader_runtime::quad::{QuadType, VertexInput}; + +use windows::Win32::Foundation::FALSE; + +use windows::Win32::Graphics::Direct3D9::{ + IDirect3DDevice9, IDirect3DVertexBuffer9, IDirect3DVertexDeclaration9, D3DCMP_ALWAYS, + D3DCULL_NONE, D3DDECLMETHOD_DEFAULT, D3DDECLTYPE_FLOAT2, D3DDECLTYPE_FLOAT3, + D3DDECLTYPE_UNUSED, D3DDECLUSAGE_TEXCOORD, D3DPOOL_DEFAULT, D3DPT_TRIANGLESTRIP, + D3DRS_CLIPPING, D3DRS_CULLMODE, D3DRS_LIGHTING, D3DRS_ZENABLE, D3DRS_ZFUNC, + D3DTRANSFORMSTATETYPE, D3DTS_PROJECTION, D3DTS_VIEW, D3DVERTEXELEMENT9, +}; + +const OFFSCREEN_VBO_DATA: [VertexInput; 4] = [ + VertexInput { + position: [-1.0, -1.0, 0.0, 1.0], + texcoord: [0.0, 1.0], + }, + VertexInput { + position: [1.0, -1.0, 0.0, 1.0], + texcoord: [1.0, 1.0], + }, + VertexInput { + position: [-1.0, 1.0, 0.0, 1.0], + texcoord: [0.0, 0.0], + }, + VertexInput { + position: [1.0, 1.0, 0.0, 1.0], + texcoord: [1.0, 0.0], + }, +]; + +const FINAL_VBO_DATA: [VertexInput; 4] = [ + VertexInput { + position: [0.0, 0.0, 0.0, 1.0], + texcoord: [0.0, 1.0], + }, + VertexInput { + position: [1.0, 0.0, 0.0, 1.0], + texcoord: [1.0, 1.0], + }, + VertexInput { + position: [0.0, 1.0, 0.0, 1.0], + texcoord: [0.0, 0.0], + }, + VertexInput { + position: [1.0, 1.0, 0.0, 1.0], + texcoord: [1.0, 0.0], + }, +]; + +static VBO_DATA: &[VertexInput; 8] = &concat_arrays!(OFFSCREEN_VBO_DATA, FINAL_VBO_DATA); + +pub(crate) struct DrawQuad { + vbo: IDirect3DVertexBuffer9, + vao: IDirect3DVertexDeclaration9, +} + +impl DrawQuad { + pub fn new(device: &IDirect3DDevice9) -> error::Result { + unsafe { + let mut vbo = None; + device.CreateVertexBuffer( + 2 * std::mem::size_of::<[VertexInput; 4]>() as u32, + 0, + 0, + D3DPOOL_DEFAULT, + &mut vbo, + std::ptr::null_mut(), + )?; + + assume_d3d_init!(vbo, "CreateVertexBuffer"); + + let mut ptr = std::ptr::null_mut(); + vbo.Lock( + 0, + 2 * std::mem::size_of::<[VertexInput; 4]>() as u32, + &mut ptr, + 0, + )?; + std::ptr::copy_nonoverlapping(VBO_DATA.as_ptr(), ptr.cast::(), 8); + vbo.Unlock()?; + + let vao = device.CreateVertexDeclaration(Self::get_spirv_cross_vbo_desc().as_ptr())?; + Ok(DrawQuad { vbo, vao }) + } + } + + pub fn draw_quad( + &self, + device: &IDirect3DDevice9, + vbo_type: QuadType, + mvp: &[f32; 16], + ) -> Result<()> { + let offset = match vbo_type { + QuadType::Offscreen => 0, + QuadType::Final => 4, + }; + + unsafe { + device.SetTransform(D3DTS_PROJECTION, mvp.as_ptr().cast())?; + device.SetTransform(D3DTS_VIEW, mvp.as_ptr().cast())?; + device.SetTransform(D3DTRANSFORMSTATETYPE(256), mvp.as_ptr().cast())?; + + device.SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE.0 as u32)?; + device.SetRenderState(D3DRS_CLIPPING, FALSE.0 as u32)?; + device.SetRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS.0 as u32)?; + device.SetRenderState(D3DRS_ZENABLE, FALSE.0 as u32)?; + device.SetRenderState(D3DRS_LIGHTING, FALSE.0 as u32)?; + + device.BeginScene()?; + device.SetStreamSource(0, &self.vbo, 0, std::mem::size_of::() as u32)?; + // device.SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1)?; + device.SetVertexDeclaration(&self.vao)?; + + device.DrawPrimitive(D3DPT_TRIANGLESTRIP, offset, 2)?; + device.EndScene()?; + } + + Ok(()) + } + + pub fn get_spirv_cross_vbo_desc() -> [D3DVERTEXELEMENT9; 3] { + [ + D3DVERTEXELEMENT9 { + Stream: 0, + Offset: offset_of!(VertexInput, position) as u16, + Type: D3DDECLTYPE_FLOAT3.0 as u8, + Method: D3DDECLMETHOD_DEFAULT.0 as u8, + Usage: D3DDECLUSAGE_TEXCOORD.0 as u8, + UsageIndex: 0, + }, + D3DVERTEXELEMENT9 { + Stream: 0, + Offset: offset_of!(VertexInput, texcoord) as u16, + Type: D3DDECLTYPE_FLOAT2.0 as u8, + Method: D3DDECLMETHOD_DEFAULT.0 as u8, + Usage: D3DDECLUSAGE_TEXCOORD.0 as u8, + UsageIndex: 1, + }, + D3DVERTEXELEMENT9 { + Stream: 0xFF, + Offset: 0, + Type: D3DDECLTYPE_UNUSED.0 as u8, + Method: 0, + Usage: 0, + UsageIndex: 0, + }, + ] + } +} diff --git a/librashader-runtime-d3d9/src/error.rs b/librashader-runtime-d3d9/src/error.rs new file mode 100644 index 0000000..865ac83 --- /dev/null +++ b/librashader-runtime-d3d9/src/error.rs @@ -0,0 +1,53 @@ +//! Direct3D 11 shader runtime errors. +//! +use librashader_preprocess::PreprocessError; +use librashader_presets::ParsePresetError; +use librashader_reflect::error::{ShaderCompileError, ShaderReflectError}; +use librashader_runtime::image::ImageError; +use std::backtrace::Backtrace; +use std::string::FromUtf8Error; +use thiserror::Error; + +/// Cumulative error type for Direct3D11 filter chains. +#[derive(Error, Debug)] +pub enum FilterChainError { + #[error("invariant assumption about d3d11 did not hold. report this as an issue.")] + Direct3DOperationError(&'static str), + #[error("direct3d driver error")] + Direct3DError { + #[from] + error: windows::core::Error, + backtrace: Backtrace, + }, + #[error("shader preset parse error")] + ShaderPresetError(#[from] ParsePresetError), + #[error("shader preprocess error")] + ShaderPreprocessError(#[from] PreprocessError), + #[error("shader compile error")] + ShaderCompileError(#[from] ShaderCompileError), + #[error("shader reflect error")] + ShaderReflectError(#[from] ShaderReflectError), + #[error("lut loading error")] + LutLoadError(#[from] ImageError), + #[error("invalid hlsl uniform name")] + UniformNameError(#[from] FromUtf8Error), +} + +macro_rules! assume_d3d_init { + ($value:ident, $call:literal) => { + let $value = $value.ok_or($crate::error::FilterChainError::Direct3DOperationError( + $call, + ))?; + }; + (mut $value:ident, $call:literal) => { + let mut $value = $value.ok_or($crate::error::FilterChainError::Direct3DOperationError( + $call, + ))?; + }; +} + +/// Macro for unwrapping result of a D3D function. +pub(crate) use assume_d3d_init; + +/// Result type for Direct3D 11 filter chains. +pub type Result = std::result::Result; diff --git a/librashader-runtime-d3d9/src/filter_chain.rs b/librashader-runtime-d3d9/src/filter_chain.rs new file mode 100644 index 0000000..f17bd68 --- /dev/null +++ b/librashader-runtime-d3d9/src/filter_chain.rs @@ -0,0 +1,422 @@ +use crate::binding::{update_sampler_bindings, ConstantRegister, RegisterSet}; +use crate::draw_quad::DrawQuad; +use crate::error::FilterChainError; +use crate::filter_pass::FilterPass; +use crate::graphics_pipeline::D3D9State; +use crate::luts::LutTexture; +use crate::options::{FilterChainOptionsD3D9, FrameOptionsD3D9}; +use crate::samplers::SamplerSet; +use crate::texture::{D3D9InputTexture, D3D9Texture}; +use crate::{error, util}; +use librashader_cache::{cache_shader_object, CachedCompilation}; +use librashader_common::map::FastHashMap; +use librashader_common::{ImageFormat, Size, Viewport}; +use librashader_presets::context::VideoDriver; +use librashader_presets::{ShaderPassConfig, ShaderPreset, TextureConfig}; +use librashader_reflect::back::hlsl::HlslShaderModel; +use librashader_reflect::back::targets::HLSL; +use librashader_reflect::back::{CompileReflectShader, CompileShader}; +use librashader_reflect::front::SpirvCompilation; +use librashader_reflect::reflect::cross::SpirvCross; +use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact}; +use librashader_reflect::reflect::semantics::ShaderSemantics; +use librashader_reflect::reflect::ReflectShader; +use librashader_runtime::binding::{BindingUtil, TextureInput}; +use librashader_runtime::framebuffer::FramebufferInit; +use librashader_runtime::image::{Image, ImageError, UVDirection, ARGB8}; +use librashader_runtime::quad::QuadType; +use librashader_runtime::render_target::RenderTarget; +use librashader_runtime::scaling::ScaleFramebuffer; +use librashader_runtime::uniforms::UniformStorage; +use std::collections::VecDeque; + +use std::path::Path; + +use crate::util::GetSize; + +use windows::Win32::Graphics::Direct3D9::{IDirect3DDevice9, IDirect3DSurface9, IDirect3DTexture9}; + +pub struct FilterMutable { + pub(crate) passes_enabled: usize, + pub(crate) parameters: FastHashMap, +} + +pub(crate) struct FilterCommon { + pub(crate) d3d9: IDirect3DDevice9, + pub(crate) luts: FastHashMap, + pub samplers: SamplerSet, + pub output_textures: Box<[Option]>, + pub feedback_textures: Box<[Option]>, + pub history_textures: Box<[Option]>, + pub config: FilterMutable, + pub disable_mipmaps: bool, + pub(crate) draw_quad: DrawQuad, +} + +/// A Direct3D 9 filter chain. +pub struct FilterChainD3D9 { + pub(crate) common: FilterCommon, + passes: Vec, + output_framebuffers: Box<[D3D9Texture]>, + feedback_framebuffers: Box<[D3D9Texture]>, + history_framebuffers: VecDeque, + default_options: FrameOptionsD3D9, +} + +type ShaderPassMeta = + ShaderPassArtifact + Send>; + +fn compile_passes( + shaders: Vec, + textures: &[TextureConfig], + disable_cache: bool, +) -> Result<(Vec, ShaderSemantics), FilterChainError> { + let (passes, semantics) = if !disable_cache { + HLSL::compile_preset_passes::< + CachedCompilation, + SpirvCross, + FilterChainError, + >(shaders, &textures)? + } else { + HLSL::compile_preset_passes::( + shaders, &textures, + )? + }; + + Ok((passes, semantics)) +} + +impl FilterChainD3D9 { + fn init_passes( + device: &IDirect3DDevice9, + passes: Vec, + semantics: &ShaderSemantics, + disable_cache: bool, + ) -> error::Result> { + let builder_fn = |(index, (config, source, mut reflect)): (usize, ShaderPassMeta)| { + let mut reflection = reflect.reflect(index, semantics)?; + let hlsl = reflect.compile(Some(HlslShaderModel::V3_0))?; + + // eprintln!("===vs===\n{}", hlsl.vertex); + + let (vs, vs_blob) = cache_shader_object( + "d3d9_sm3", + &[hlsl.vertex.as_bytes()], + |&[bytes]| util::d3d_compile_shader(bytes, b"main\0", b"vs_3_0\0"), + |blob| unsafe { + Ok(( + device.CreateVertexShader(blob.GetBufferPointer().cast())?, + blob, + )) + }, + disable_cache, + )?; + + // eprintln!("===ps===\n{}", hlsl.fragment); + + let (ps, ps_blob) = cache_shader_object( + "d3d9_sm3", + &[hlsl.fragment.as_bytes()], + |&[bytes]| util::d3d_compile_shader(bytes, b"main\0", b"ps_3_0\0"), + |blob| unsafe { + Ok(( + device.CreatePixelShader(blob.GetBufferPointer().cast())?, + blob, + )) + }, + disable_cache, + )?; + + let uniform_storage = UniformStorage::new( + reflection.ubo.as_ref().map_or(0, |ubo| ubo.size as usize), + reflection + .push_constant + .as_ref() + .map_or(0, |push| push.size as usize), + ); + + let mut ps_constants = util::d3d_reflect_shader(ps_blob)?; + let vs_constants = util::d3d_reflect_shader(vs_blob)?; + + let uniform_bindings = reflection.meta.create_binding_map(|param| { + ConstantRegister::reflect_register_assignment( + param, + &ps_constants, + &vs_constants, + &hlsl.context, + ) + }); + + let gl_halfpixel = vs_constants.get("gl_HalfPixel").map(|o| o.assignment); + + ps_constants.retain(|_, v| matches!(v.set, RegisterSet::Sampler)); + + update_sampler_bindings(&mut reflection.meta, &ps_constants); + // eprintln!("{:?}", ps_constants); + Ok(FilterPass { + reflection, + vertex_shader: vs, + pixel_shader: ps, + uniform_bindings, + uniform_storage, + gl_halfpixel, + source, + config, + }) + }; + + let filters: Vec> = + passes.into_iter().enumerate().map(builder_fn).collect(); + + let filters: error::Result> = filters.into_iter().collect(); + let filters = filters?; + Ok(filters) + } + + fn load_luts( + device: &IDirect3DDevice9, + textures: &[TextureConfig], + ) -> error::Result> { + let mut luts = FastHashMap::default(); + let images = textures + .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, &image, &texture)?; + luts.insert(index, texture); + } + Ok(luts) + } +} + +impl FilterChainD3D9 { + /// Load the shader preset at the given path into a filter chain. + pub unsafe fn load_from_path( + path: impl AsRef, + device: &IDirect3DDevice9, + options: Option<&FilterChainOptionsD3D9>, + ) -> error::Result { + // load passes from preset + let preset = ShaderPreset::try_parse_with_driver_context(path, VideoDriver::Direct3D11)?; + + unsafe { Self::load_from_preset(preset, device, options) } + } + + /// Load a filter chain from a pre-parsed `ShaderPreset`. + pub unsafe fn load_from_preset( + preset: ShaderPreset, + device: &IDirect3DDevice9, + options: Option<&FilterChainOptionsD3D9>, + ) -> error::Result { + let disable_cache = options.map_or(false, |o| o.disable_cache); + + let (passes, semantics) = compile_passes(preset.shaders, &preset.textures, disable_cache)?; + + let samplers = SamplerSet::new()?; + + // initialize passes + let filters = FilterChainD3D9::init_passes(device, passes, &semantics, disable_cache)?; + + // load luts + let luts = FilterChainD3D9::load_luts(device, &preset.textures)?; + + let framebuffer_gen = + || D3D9Texture::new(device, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, false); + 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()?; + + // initialize feedback framebuffers + let (feedback_framebuffers, feedback_textures) = + framebuffer_init.init_output_framebuffers()?; + + // initialize history + let (history_framebuffers, history_textures) = framebuffer_init.init_history()?; + + let draw_quad = DrawQuad::new(device)?; + + Ok(FilterChainD3D9 { + passes: filters, + output_framebuffers, + feedback_framebuffers, + history_framebuffers, + common: FilterCommon { + d3d9: device.clone(), + config: FilterMutable { + passes_enabled: preset.shader_count as usize, + parameters: preset + .parameters + .into_iter() + .map(|param| (param.name, param.value)) + .collect(), + }, + disable_mipmaps: options.map_or(false, |o| o.force_no_mipmaps), + luts, + samplers, + output_textures, + feedback_textures, + history_textures, + draw_quad, + }, + default_options: Default::default(), + }) + } + + fn push_history(&mut self, input: &IDirect3DTexture9) -> error::Result<()> { + if let Some(mut back) = self.history_framebuffers.pop_back() { + back.copy_from(&self.common.d3d9, input)?; + self.history_framebuffers.push_front(back) + } + + Ok(()) + } + + /// Process a frame with the input image. + /// + /// ## Safety: + /// * `input` must be in `D3DPOOL_DEFAULT`. + pub unsafe fn frame( + &mut self, + input: IDirect3DTexture9, + viewport: &Viewport, + frame_count: usize, + options: Option<&FrameOptionsD3D9>, + ) -> error::Result<()> { + let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled); + let passes = &mut self.passes[0..max]; + if let Some(options) = options { + if options.clear_history { + for framebuffer in &mut self.history_framebuffers { + framebuffer.clear(&self.common.d3d9)?; + } + } + } + + if passes.is_empty() { + return Ok(()); + } + + let options = options.unwrap_or(&self.default_options); + let filter = passes[0].config.filter; + let wrap_mode = passes[0].config.wrap_mode; + + for (texture, fbo) in self + .common + .history_textures + .iter_mut() + .zip(self.history_framebuffers.iter()) + { + *texture = Some(fbo.as_input(filter, filter, wrap_mode)); + } + + let original = D3D9InputTexture { + handle: input.clone(), + filter, + wrap: wrap_mode, + mipmode: filter, + is_srgb: false, + }; + + let mut source = original.clone(); + + // rescale render buffers to ensure all bindings are valid. + D3D9Texture::scale_framebuffers( + source.size(), + viewport.output.size()?, + original.size(), + &mut self.output_framebuffers, + &mut self.feedback_framebuffers, + passes, + None, + )?; + + // Refresh inputs for feedback textures. + // Don't need to do this for outputs because they are yet to be bound. + for ((texture, fbo), pass) in self + .common + .feedback_textures + .iter_mut() + .zip(self.feedback_framebuffers.iter()) + .zip(passes.iter()) + { + *texture = Some(fbo.as_input( + pass.config.filter, + pass.config.filter, + pass.config.wrap_mode, + )); + } + + let passes_len = passes.len(); + let (pass, last) = passes.split_at_mut(passes_len - 1); + let state_guard = D3D9State::new(&self.common.d3d9)?; + + for (index, pass) in pass.iter_mut().enumerate() { + source.filter = pass.config.filter; + source.wrap = pass.config.wrap_mode; + source.is_srgb = pass.config.srgb_framebuffer; + let target = &self.output_framebuffers[index]; + let target_rtv = target.as_output()?; + pass.draw( + &self.common.d3d9, + index, + &self.common, + pass.config.get_frame_count(frame_count), + options, + viewport, + &original, + &source, + RenderTarget::identity(&target_rtv), + QuadType::Offscreen, + )?; + + source = D3D9InputTexture { + handle: target.handle.clone(), + filter: pass.config.filter, + wrap: pass.config.wrap_mode, + mipmode: pass.config.filter, + is_srgb: pass.config.srgb_framebuffer, + }; + self.common.output_textures[index] = Some(source.clone()); + } + + // try to hint the optimizer + assert_eq!(last.len(), 1); + if let Some(pass) = last.iter_mut().next() { + source.filter = pass.config.filter; + source.wrap = pass.config.wrap_mode; + source.is_srgb = pass.config.srgb_framebuffer; + + pass.draw( + &self.common.d3d9, + passes_len - 1, + &self.common, + pass.config.get_frame_count(frame_count), + options, + viewport, + &original, + &source, + RenderTarget::viewport(viewport), + QuadType::Final, + )?; + } + + std::mem::swap( + &mut self.output_framebuffers, + &mut self.feedback_framebuffers, + ); + + drop(state_guard); + + self.push_history(&input)?; + + Ok(()) + } +} diff --git a/librashader-runtime-d3d9/src/filter_pass.rs b/librashader-runtime-d3d9/src/filter_pass.rs new file mode 100644 index 0000000..b141cae --- /dev/null +++ b/librashader-runtime-d3d9/src/filter_pass.rs @@ -0,0 +1,229 @@ +use crate::binding::{ConstantRegister, D3D9UniformBinder, D3D9UniformStorage, RegisterAssignment}; +use crate::error; +use crate::filter_chain::FilterCommon; +use crate::options::FrameOptionsD3D9; +use crate::samplers::SamplerSet; +use crate::texture::D3D9InputTexture; +use librashader_common::map::FastHashMap; +use librashader_common::{ImageFormat, Size, Viewport}; +use librashader_preprocess::ShaderSource; +use librashader_presets::ShaderPassConfig; +use librashader_reflect::reflect::semantics::{TextureBinding, UniformBinding}; +use librashader_reflect::reflect::ShaderReflection; +use librashader_runtime::binding::{BindSemantics, UniformInputs}; +use librashader_runtime::filter_pass::FilterPassMeta; +use librashader_runtime::quad::QuadType; +use librashader_runtime::render_target::RenderTarget; +use windows::Win32::Foundation::{FALSE, TRUE}; + +use crate::util::GetSize; +use windows::Win32::Graphics::Direct3D9::{ + IDirect3DDevice9, IDirect3DPixelShader9, IDirect3DSurface9, IDirect3DVertexShader9, + D3DCLEAR_TARGET, D3DRS_SRGBWRITEENABLE, D3DSAMP_SRGBTEXTURE, D3DVIEWPORT9, +}; + +pub struct FilterPass { + pub reflection: ShaderReflection, + pub vertex_shader: IDirect3DVertexShader9, + pub pixel_shader: IDirect3DPixelShader9, + pub uniform_bindings: FastHashMap, + pub source: ShaderSource, + pub config: ShaderPassConfig, + pub uniform_storage: D3D9UniformStorage, + pub gl_halfpixel: Option, +} + +impl FilterPassMeta for FilterPass { + fn framebuffer_format(&self) -> ImageFormat { + self.source.format + } + + fn config(&self) -> &ShaderPassConfig { + &self.config + } +} + +impl BindSemantics for FilterPass { + type InputTexture = D3D9InputTexture; + type SamplerSet = SamplerSet; + type DescriptorSet<'a> = (); + type DeviceContext = IDirect3DDevice9; + type UniformOffset = ConstantRegister; + + fn bind_texture<'a>( + _: &mut Self::DescriptorSet<'a>, + samplers: &Self::SamplerSet, + binding: &TextureBinding, + texture: &Self::InputTexture, + device: &Self::DeviceContext, + ) { + // eprintln!("binding s{}", binding.binding); + unsafe { + if let Err(e) = device.SetTexture(binding.binding, &texture.handle) { + println!( + "[librashader-runtime-d3d9] failed to texture at {}: {e}", + binding.binding + ); + } + + let setter = samplers.get(texture.wrap, texture.filter, texture.mipmode); + if let Err(e) = setter(&device, binding.binding) { + println!( + "[librashader-runtime-d3d9] failed to set sampler at {}: {e}", + binding.binding + ); + } + + if texture.is_srgb { + if let Err(e) = device.SetSamplerState(binding.binding, D3DSAMP_SRGBTEXTURE, 1u32) { + println!( + "[librashader-runtime-d3d9] failed to set srgb at {}: {e}", + binding.binding + ); + } + } else { + if let Err(e) = device.SetSamplerState(binding.binding, D3DSAMP_SRGBTEXTURE, 0u32) { + println!( + "[librashader-runtime-d3d9] failed to set srgb at {}: {e}", + binding.binding + ); + } + } + } + } +} + +impl FilterPass { + // framecount should be pre-modded + fn build_semantics<'a>( + &mut self, + pass_index: usize, + parent: &FilterCommon, + mvp: &[f32; 16], + frame_count: u32, + options: &FrameOptionsD3D9, + fb_size: Size, + viewport_size: Size, + original: &D3D9InputTexture, + source: &D3D9InputTexture, + ) { + Self::bind_semantics( + &parent.d3d9, + &parent.samplers, + &mut self.uniform_storage, + &mut (), + UniformInputs { + mvp, + frame_count, + rotation: options.rotation, + total_subframes: options.total_subframes, + current_subframe: options.current_subframe, + frame_direction: options.frame_direction, + framebuffer_size: fb_size, + viewport_size, + }, + original, + source, + &self.uniform_bindings, + &self.reflection.meta.texture_meta, + parent.output_textures[0..pass_index] + .iter() + .map(|o| o.as_ref()), + parent.feedback_textures.iter().map(|o| o.as_ref()), + parent.history_textures.iter().map(|o| o.as_ref()), + parent.luts.iter().map(|(u, i)| (*u, i.as_ref())), + &self.source.parameters, + &parent.config.parameters, + ); + } + + pub(crate) fn draw( + &mut self, + device: &IDirect3DDevice9, + pass_index: usize, + parent: &FilterCommon, + frame_count: u32, + options: &FrameOptionsD3D9, + viewport: &Viewport, + original: &D3D9InputTexture, + source: &D3D9InputTexture, + output: RenderTarget, + vbo_type: QuadType, + ) -> error::Result<()> { + if self.config.mipmap_input && !parent.disable_mipmaps { + unsafe { + source.handle.GenerateMipSubLevels(); + } + } + + let output_size = output.output.size()?; + // let viewport_size = viewport.output.size()?; + unsafe { + device.SetVertexShader(&self.vertex_shader)?; + device.SetPixelShader(&self.pixel_shader)?; + } + + self.build_semantics( + pass_index, + parent, + output.mvp, + frame_count, + options, + output_size, + viewport.output.size()?, + original, + source, + ); + + unsafe { + if let Some(gl_halfpixel) = &self.gl_halfpixel { + let data = [ + 1.0 / output_size.width as f32, + 1.0 / output_size.height as f32, + 0.0, + 0.0, + ]; + device.SetVertexShaderConstantF( + gl_halfpixel.index, + data.as_ptr(), + gl_halfpixel.count, + )?; + } + + device.SetViewport(&D3DVIEWPORT9 { + X: output.x as u32, + Y: output.y as u32, + Width: output_size.width, + Height: output_size.height, + MinZ: 0.0, + MaxZ: 1.0, + })?; + + device.SetRenderTarget(0, &*output.output)?; + + device.Clear( + 0, + std::ptr::null_mut(), + D3DCLEAR_TARGET as u32, + if cfg!(debug_assertions) { + 0xFFFF00FF + } else { + 0x0 + }, + 0.0, + 0, + )?; + } + + if self.framebuffer_format() == ImageFormat::R8G8B8A8Srgb { + unsafe { + device.SetRenderState(D3DRS_SRGBWRITEENABLE, TRUE.0 as u32)?; + } + } + parent.draw_quad.draw_quad(device, vbo_type, output.mvp)?; + unsafe { + device.SetRenderState(D3DRS_SRGBWRITEENABLE, FALSE.0 as u32)?; + } + Ok(()) + } +} diff --git a/librashader-runtime-d3d9/src/graphics_pipeline.rs b/librashader-runtime-d3d9/src/graphics_pipeline.rs new file mode 100644 index 0000000..82ff11d --- /dev/null +++ b/librashader-runtime-d3d9/src/graphics_pipeline.rs @@ -0,0 +1,27 @@ +use crate::error; +use windows::Win32::Graphics::Direct3D9::{IDirect3DDevice9, IDirect3DStateBlock9, D3DSBT_ALL}; + +pub struct D3D9State { + state: IDirect3DStateBlock9, +} + +impl D3D9State { + pub fn new(device: &IDirect3DDevice9) -> error::Result { + let block = unsafe { + let block = device.CreateStateBlock(D3DSBT_ALL)?; + block.Capture()?; + + block + }; + + Ok(D3D9State { state: block }) + } +} + +impl Drop for D3D9State { + fn drop(&mut self) { + if let Err(e) = unsafe { self.state.Apply() } { + println!("librashader-runtime-d3d9: [warn] failed to restore state {e:?}") + } + } +} diff --git a/librashader-runtime-d3d9/src/lib.rs b/librashader-runtime-d3d9/src/lib.rs new file mode 100644 index 0000000..2c58db3 --- /dev/null +++ b/librashader-runtime-d3d9/src/lib.rs @@ -0,0 +1,20 @@ +#![cfg(target_os = "windows")] +#![feature(type_alias_impl_trait)] +#![feature(error_generic_member_access)] +mod binding; +mod draw_quad; +mod filter_chain; +mod filter_pass; +mod graphics_pipeline; +mod luts; +mod samplers; +mod texture; +mod util; +mod d3dx; +pub mod error; +pub mod options; + +use librashader_runtime::impl_filter_chain_parameters; +impl_filter_chain_parameters!(FilterChainD3D9); + +pub use crate::filter_chain::FilterChainD3D9; diff --git a/librashader-runtime-d3d9/src/luts.rs b/librashader-runtime-d3d9/src/luts.rs new file mode 100644 index 0000000..d9e9f11 --- /dev/null +++ b/librashader-runtime-d3d9/src/luts.rs @@ -0,0 +1,65 @@ +use crate::error; +use crate::error::assume_d3d_init; +use crate::texture::D3D9InputTexture; + +use librashader_presets::TextureConfig; +use librashader_runtime::image::{Image, ARGB8}; + +use windows::Win32::Graphics::Direct3D9::{ + IDirect3DDevice9, D3DFMT_A8R8G8B8, D3DLOCKED_RECT, D3DPOOL_MANAGED, +}; + +#[derive(Debug, Clone)] +pub(crate) struct LutTexture(D3D9InputTexture); + +impl AsRef for LutTexture { + fn as_ref(&self) -> &D3D9InputTexture { + &self.0 + } +} + +impl LutTexture { + pub fn new( + device: &IDirect3DDevice9, + source: &Image, + config: &TextureConfig, + ) -> error::Result { + let mut texture = None; + unsafe { + device.CreateTexture( + source.size.width, + source.size.height, + if config.mipmap { 0 } else { 1 }, + 0, + D3DFMT_A8R8G8B8, + D3DPOOL_MANAGED, + &mut texture, + std::ptr::null_mut(), + )?; + } + assume_d3d_init!(texture, "CreateTexture"); + + unsafe { + let mut lock = D3DLOCKED_RECT::default(); + texture.LockRect(0, &mut lock, std::ptr::null_mut(), 0)?; + std::ptr::copy_nonoverlapping( + source.bytes.as_ptr(), + lock.pBits.cast(), + source.bytes.len(), + ); + texture.UnlockRect(0)?; + + if config.mipmap { + texture.GenerateMipSubLevels(); + } + } + + Ok(LutTexture(D3D9InputTexture { + handle: texture, + filter: config.filter_mode, + wrap: config.wrap_mode, + mipmode: config.filter_mode, + is_srgb: false, + })) + } +} diff --git a/librashader-runtime-d3d9/src/options.rs b/librashader-runtime-d3d9/src/options.rs new file mode 100644 index 0000000..e73d42a --- /dev/null +++ b/librashader-runtime-d3d9/src/options.rs @@ -0,0 +1,16 @@ +//! Direct3D 9 shader runtime options. + +use librashader_runtime::impl_default_frame_options; +impl_default_frame_options!(FrameOptionsD3D9); + +/// Options for Direct3D 11 filter chain creation. +#[repr(C)] +#[derive(Default, Debug, Clone)] +pub struct FilterChainOptionsD3D9 { + /// Whether or not to explicitly disable mipmap + /// generation regardless of shader preset settings. + pub force_no_mipmaps: bool, + /// Disable the shader object cache. Shaders will be + /// recompiled rather than loaded from the cache. + pub disable_cache: bool, +} diff --git a/librashader-runtime-d3d9/src/samplers.rs b/librashader-runtime-d3d9/src/samplers.rs new file mode 100644 index 0000000..ab75aa7 --- /dev/null +++ b/librashader-runtime-d3d9/src/samplers.rs @@ -0,0 +1,97 @@ +use crate::error::Result; +use librashader_common::map::FastHashMap; +use librashader_common::{FilterMode, WrapMode}; + +use windows::Win32::Graphics::Direct3D9::{ + IDirect3DDevice9, D3DSAMP_ADDRESSU, D3DSAMP_ADDRESSV, D3DSAMP_ADDRESSW, D3DSAMP_MAGFILTER, + D3DSAMP_MINFILTER, D3DSAMP_MIPFILTER, D3DTEXTUREADDRESS, D3DTEXTUREFILTER, +}; + +pub struct SamplerSet { + samplers: FastHashMap< + (WrapMode, FilterMode, FilterMode), + Box Result<()>>, + >, +} + +impl SamplerSet { + #[inline(always)] + pub fn get( + &self, + wrap: WrapMode, + filter: FilterMode, + mip_filter: FilterMode, + ) -> &dyn Fn(&IDirect3DDevice9, u32) -> Result<()> { + // SAFETY: the sampler set is complete for the matrix + // wrap x filter x mipfilter + unsafe { + &*self + .samplers + .get(&(wrap, filter, mip_filter)) + .unwrap_unchecked() + } + } + + pub fn new() -> Result { + let mut samplers = FastHashMap::default(); + let wrap_modes = &[ + WrapMode::ClampToBorder, + WrapMode::ClampToEdge, + WrapMode::Repeat, + WrapMode::MirroredRepeat, + ]; + for wrap_mode in wrap_modes { + for filter_mode in &[FilterMode::Linear, FilterMode::Nearest] { + for mip_filter in &[FilterMode::Linear, FilterMode::Nearest] { + let sampler: Box Result<()>> = + Box::new(|device: &IDirect3DDevice9, index| { + unsafe { + let wrap_mode = *wrap_mode; + let filter_mode = *filter_mode; + let mip_filter = *mip_filter; + + device.SetSamplerState( + index, + D3DSAMP_ADDRESSU, + D3DTEXTUREADDRESS::from(wrap_mode).0 as u32, + )?; + device.SetSamplerState( + index, + D3DSAMP_ADDRESSV, + D3DTEXTUREADDRESS::from(wrap_mode).0 as u32, + )?; + device.SetSamplerState( + index, + D3DSAMP_ADDRESSW, + D3DTEXTUREADDRESS::from(wrap_mode).0 as u32, + )?; + + device.SetSamplerState( + index, + D3DSAMP_MAGFILTER, + D3DTEXTUREFILTER::from(filter_mode).0 as u32, + )?; + device.SetSamplerState( + index, + D3DSAMP_MINFILTER, + D3DTEXTUREFILTER::from(mip_filter).0 as u32, + )?; + device.SetSamplerState( + index, + D3DSAMP_MIPFILTER, + D3DTEXTUREFILTER::from(mip_filter).0 as u32, + )?; + } + + Ok(()) + }); + + samplers.insert((*wrap_mode, *filter_mode, *mip_filter), sampler); + } + } + } + + assert_eq!(samplers.len(), wrap_modes.len() * 2 * 2); + Ok(SamplerSet { samplers }) + } +} diff --git a/librashader-runtime-d3d9/src/texture.rs b/librashader-runtime-d3d9/src/texture.rs new file mode 100644 index 0000000..8f05a56 --- /dev/null +++ b/librashader-runtime-d3d9/src/texture.rs @@ -0,0 +1,228 @@ +use crate::error; +use crate::error::{assume_d3d_init, FilterChainError}; + +use crate::util::GetSize; +use librashader_common::{FilterMode, ImageFormat, Size, WrapMode}; +use librashader_presets::Scale2D; +use librashader_runtime::binding::TextureInput; +use librashader_runtime::scaling::{ScaleFramebuffer, ViewportSize}; +use windows::Win32::Graphics::Direct3D9::{ + IDirect3DDevice9, IDirect3DSurface9, IDirect3DTexture9, D3DCLEAR_TARGET, D3DFORMAT, + D3DPOOL_DEFAULT, D3DTEXF_LINEAR, D3DUSAGE_DYNAMIC, D3DUSAGE_RENDERTARGET, +}; + +/// An image view for use as a shader resource. +/// +/// Contains an `ID3D11ShaderResourceView`, and a size. +#[derive(Debug, Clone)] +pub struct D3D9Texture { + /// A handle to the shader resource view. + pub handle: IDirect3DTexture9, + pub mipmap: bool, + pub original_format: ImageFormat, +} + +impl TextureInput for D3D9InputTexture { + fn size(&self) -> Size { + GetSize::size(&self.handle).unwrap_or_else(|_| Size::new(0, 0)) + } +} + +impl AsRef for D3D9InputTexture { + fn as_ref(&self) -> &D3D9InputTexture { + &self + } +} + +#[derive(Debug, Clone)] +pub struct D3D9InputTexture { + /// A handle to the shader resource view. + pub handle: IDirect3DTexture9, + pub filter: FilterMode, + pub wrap: WrapMode, + pub mipmode: FilterMode, + pub is_srgb: bool, +} + +impl D3D9Texture { + pub fn new( + device: &IDirect3DDevice9, + size: Size, + format: ImageFormat, + mipmap: bool, + ) -> error::Result { + let mut texture = None; + // eprintln!("creating texture"); + unsafe { + device.CreateTexture( + size.width, + size.height, + if mipmap { 0 } else { 1 }, + D3DUSAGE_RENDERTARGET as u32, + format.into(), + D3DPOOL_DEFAULT, + &mut texture, + std::ptr::null_mut(), + )?; + } + + // eprintln!("creating texture ok"); + + assume_d3d_init!(texture, "CreateTexture"); + + Ok(Self { + handle: texture, + mipmap, + original_format: format, + }) + } + + pub fn init(&mut self, size: Size, format: ImageFormat) -> error::Result<()> { + // let format = format.into(); + + unsafe { + let mut texture = None; + self.handle.GetDevice()?.CreateTexture( + size.width, + size.height, + if self.mipmap { 0 } else { 1 }, + D3DUSAGE_RENDERTARGET as u32, + format.into(), + D3DPOOL_DEFAULT, + &mut texture, + std::ptr::null_mut(), + )?; + assume_d3d_init!(mut texture, "CreateTexture2D"); + std::mem::swap(&mut self.handle, &mut texture); + drop(texture) + } + + Ok(()) + } + + pub fn size(&self) -> error::Result> { + let mut desc = Default::default(); + unsafe { + self.handle.GetLevelDesc(0, &mut desc)?; + } + + Ok(Size { + height: desc.Height, + width: desc.Width, + }) + } + + pub fn as_output(&self) -> error::Result { + unsafe { Ok(self.handle.GetSurfaceLevel(0)?) } + } + + pub(crate) fn scale( + &mut self, + scaling: Scale2D, + format: ImageFormat, + viewport_size: &Size, + source_size: &Size, + original_size: &Size, + should_mipmap: bool, + ) -> error::Result> { + let size = source_size.scale_viewport(scaling, *viewport_size, *original_size); + + if self.size()? != size || should_mipmap != self.mipmap { + self.mipmap = should_mipmap; + self.init( + size, + if format == ImageFormat::Unknown { + ImageFormat::R8G8B8A8Unorm + } else { + format + }, + )?; + } + Ok(size) + } + + pub fn clear(&mut self, device: &IDirect3DDevice9) -> error::Result<()> { + unsafe { + let surface = self.handle.GetSurfaceLevel(0)?; + device.SetRenderTarget(0, &surface)?; + device.Clear(0, std::ptr::null_mut(), D3DCLEAR_TARGET as u32, 0x0, 0.0, 0)?; + } + Ok(()) + } + + pub fn copy_from( + &mut self, + device: &IDirect3DDevice9, + input: &IDirect3DTexture9, + ) -> error::Result<()> { + let mut desc = Default::default(); + unsafe { + input.GetLevelDesc(0, &mut desc)?; + } + + let size = Size { + width: desc.Width, + height: desc.Height, + }; + + if self.size()? != size || D3DFORMAT::from(self.original_format) != desc.Format { + eprintln!("[history] resizing"); + self.init(size, ImageFormat::from(desc.Format))?; + } + + unsafe { + let dest = self.handle.GetSurfaceLevel(0)?; + let source = input.GetSurfaceLevel(0)?; + + device.StretchRect( + &source, + std::ptr::null(), + &dest, + std::ptr::null(), + D3DTEXF_LINEAR, + )?; + } + + Ok(()) + } + + pub fn as_input( + &self, + filter: FilterMode, + mipmode: FilterMode, + wrap: WrapMode, + ) -> D3D9InputTexture { + D3D9InputTexture { + handle: self.handle.clone(), + filter, + wrap, + mipmode, + is_srgb: self.original_format == ImageFormat::R8G8B8A8Srgb, + } + } +} + +impl ScaleFramebuffer for D3D9Texture { + type Error = FilterChainError; + type Context = (); + + fn scale( + &mut self, + scaling: Scale2D, + format: ImageFormat, + viewport_size: &Size, + source_size: &Size, + original_size: &Size, + should_mipmap: bool, + _context: &Self::Context, + ) -> Result, Self::Error> { + self.scale( + scaling, + format, + viewport_size, + source_size, + original_size, + should_mipmap, + ) + } +} diff --git a/librashader-runtime-d3d9/src/util.rs b/librashader-runtime-d3d9/src/util.rs new file mode 100644 index 0000000..6e52438 --- /dev/null +++ b/librashader-runtime-d3d9/src/util.rs @@ -0,0 +1,245 @@ +use crate::error; +use crate::error::assume_d3d_init; + +use std::mem::MaybeUninit; + +use crate::binding::{ConstantDescriptor, RegisterAssignment, RegisterSet}; +use crate::d3dx::{ID3DXConstantTable, D3DXCONSTANT_DESC, D3DXREGISTER_SET}; +use librashader_common::map::FastHashMap; +use librashader_common::Size; +use windows::core::PCSTR; +use windows::Win32::Graphics::Direct3D::Fxc::{D3DCompile, D3DCOMPILE_AVOID_FLOW_CONTROL}; +use windows::Win32::Graphics::Direct3D::ID3DBlob; +use windows::Win32::Graphics::Direct3D9::{IDirect3DSurface9, IDirect3DTexture9}; + +// const fn d3d9_format_fallback_list(format: D3DFORMAT) -> Option<&'static [D3DFORMAT]> { +// match format { +// DXGI_FORMAT_R32G32B32A32_FLOAT => Some(&[ +// windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_R32G32B32A32_FLOAT, +// DXGI_FORMAT_R16G16B16A16_FLOAT, +// DXGI_FORMAT_R32G32B32_FLOAT, +// DXGI_FORMAT_R11G11B10_FLOAT, +// DXGI_FORMAT_UNKNOWN, +// ]), +// DXGI_FORMAT_R16G16B16A16_FLOAT => Some(&[ +// windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_R16G16B16A16_FLOAT, +// DXGI_FORMAT_R32G32B32A32_FLOAT, +// DXGI_FORMAT_R32G32B32_FLOAT, +// DXGI_FORMAT_R11G11B10_FLOAT, +// DXGI_FORMAT_UNKNOWN, +// ]), +// DXGI_FORMAT_R8G8B8A8_UNORM => Some(&[ +// windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM, +// DXGI_FORMAT_B8G8R8A8_UNORM, +// DXGI_FORMAT_B8G8R8X8_UNORM, +// DXGI_FORMAT_UNKNOWN, +// ]), +// DXGI_FORMAT_R8G8B8A8_UNORM_SRGB => Some(&[ +// windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, +// DXGI_FORMAT_R8G8B8A8_UNORM, +// DXGI_FORMAT_B8G8R8A8_UNORM, +// DXGI_FORMAT_B8G8R8X8_UNORM, +// DXGI_FORMAT_UNKNOWN, +// ]), +// DXGI_FORMAT_B8G8R8A8_UNORM => Some(&[ +// windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_B8G8R8A8_UNORM, +// DXGI_FORMAT_R8G8B8A8_UNORM, +// DXGI_FORMAT_UNKNOWN, +// ]), +// DXGI_FORMAT_B8G8R8X8_UNORM => Some(&[ +// windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_B8G8R8X8_UNORM, +// DXGI_FORMAT_B8G8R8A8_UNORM, +// DXGI_FORMAT_R8G8B8A8_UNORM, +// DXGI_FORMAT_UNKNOWN, +// ]), +// DXGI_FORMAT_B5G6R5_UNORM => Some(&[ +// windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_B5G6R5_UNORM, +// DXGI_FORMAT_B8G8R8X8_UNORM, +// DXGI_FORMAT_B8G8R8A8_UNORM, +// DXGI_FORMAT_R8G8B8A8_UNORM, +// DXGI_FORMAT_UNKNOWN, +// ]), +// DXGI_FORMAT_EX_A4R4G4B4_UNORM | DXGI_FORMAT_B4G4R4A4_UNORM => Some(&[ +// windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_B4G4R4A4_UNORM, +// DXGI_FORMAT_B8G8R8A8_UNORM, +// DXGI_FORMAT_R8G8B8A8_UNORM, +// DXGI_FORMAT_UNKNOWN, +// ]), +// // DXGI_FORMAT_A8_UNORM => Some(&[ +// // windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_A8_UNORM, +// // DXGI_FORMAT_R8_UNORM, +// // DXGI_FORMAT_R8G8_UNORM, +// // DXGI_FORMAT_R8G8B8A8_UNORM, +// // DXGI_FORMAT_B8G8R8A8_UNORM, +// // DXGI_FORMAT_UNKNOWN, +// // ]), +// // DXGI_FORMAT_R8_UNORM => Some(&[ +// // windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_R8_UNORM, +// // DXGI_FORMAT_A8_UNORM, +// // DXGI_FORMAT_R8G8_UNORM, +// // DXGI_FORMAT_R8G8B8A8_UNORM, +// // DXGI_FORMAT_B8G8R8A8_UNORM, +// // DXGI_FORMAT_UNKNOWN, +// // ]), +// // _ => None, +// // } +// // } +// +// pub fn d3d9_get_closest_format( +// device: &IDirect3DDevice9, +// format: D3DFORMAT, +// restype: D3DRESOURCETYPE, +// format_support_mask: i32, +// ) -> error::Result { +// let d3d9 = unsafe { device.GetDirect3D()? }; +// let (devtype, ordinal) = unsafe { +// let mut params = Default::default(); +// device.GetCreationParameters(&mut params)?; +// (params.DeviceType, params.AdapterOrdinal) +// }; +// +// let default_list = [format, D3DFMT_UNKNOWN]; +// let format_support_list = d3d9_format_fallback_list(format).unwrap_or(&default_list); +// let format_support_mask = format_support_mask as u32; +// +// for supported in format_support_list { +// unsafe { +// if let Ok(_) = d3d9.CheckDeviceFormat( +// ordinal,devtype, +// format, format_support_mask, +// restype, +// format) +// { +// return Ok(*supported); +// } +// } +// } +// +// Ok(D3DFMT_UNKNOWN) +// } + +pub fn d3d_compile_shader(source: &[u8], entry: &[u8], version: &[u8]) -> error::Result { + unsafe { + let mut blob = None; + let mut errs = None; + let res = D3DCompile( + source.as_ptr().cast(), + source.len(), + None, + None, + None, + PCSTR(entry.as_ptr()), + PCSTR(version.as_ptr()), + D3DCOMPILE_AVOID_FLOW_CONTROL, + 0, + &mut blob, + Some(&mut errs), + ); + + // let res = D3DXCompileShader( + // source.as_ptr().cast(), + // source.len(), + // None, + // None, + // PCSTR(entry.as_ptr()), + // PCSTR(version.as_ptr()), + // 0, + // &mut blob, + // Some(&mut errs), + // None, + // ); + + // if let Some(errs) = errs { + // let str = std::slice::from_raw_parts( + // errs.GetBufferPointer().cast::(), + // errs.GetBufferSize(), + // ); + // let str = std::ffi::CStr::from_bytes_until_nul(str).unwrap(); + // // eprintln!("{}", str.to_str().unwrap()); + // } + + res?; + assume_d3d_init!(blob, "D3DCompile"); + + Ok(blob) + } +} +pub fn d3d_reflect_shader( + shader: ID3DBlob, +) -> error::Result> { + unsafe { + let table = ID3DXConstantTable::GetShaderConstantTable(shader.GetBufferPointer())?; + let desc = table.GetDesc()?; + + let mut scratch = Vec::with_capacity(16); + scratch.resize_with(16, MaybeUninit::zeroed); + let mut assignments = FastHashMap::default(); + for assignment in 0..desc.Constants { + let constant = table.GetConstant(None, assignment)?; + let mut written = scratch.len() as u32; + table.GetConstantDesc(constant, scratch.as_mut_ptr().cast(), &mut written)?; + + for i in 0..written as usize { + let desc: MaybeUninit = + std::mem::replace(&mut scratch[i], MaybeUninit::zeroed()); + + let desc = desc.assume_init(); + + // Only cN and sN allowed. + if desc.RegisterSet != D3DXREGISTER_SET::D3DXRS_FLOAT4 + && desc.RegisterSet != D3DXREGISTER_SET::D3DXRS_SAMPLER + { + continue; + } + let name = desc.Name.to_string()?; + assignments.insert( + name, + ConstantDescriptor { + assignment: RegisterAssignment { + index: desc.RegisterIndex, + count: desc.RegisterCount, + }, + set: if desc.RegisterSet == D3DXREGISTER_SET::D3DXRS_SAMPLER { + RegisterSet::Sampler + } else { + RegisterSet::Float + }, + }, + ); + } + } + Ok(assignments) + } +} + +pub(crate) trait GetSize { + fn size(&self) -> error::Result>; +} + +impl GetSize for IDirect3DSurface9 { + fn size(&self) -> error::Result> { + let mut desc = Default::default(); + unsafe { + self.GetDesc(&mut desc)?; + } + + Ok(Size { + height: desc.Height, + width: desc.Width, + }) + } +} + +impl GetSize for IDirect3DTexture9 { + fn size(&self) -> error::Result> { + let mut desc = Default::default(); + unsafe { + self.GetLevelDesc(0, &mut desc)?; + } + + Ok(Size { + height: desc.Height, + width: desc.Width, + }) + } +} diff --git a/librashader-runtime-d3d9/tests/hello_triangle/mod.rs b/librashader-runtime-d3d9/tests/hello_triangle/mod.rs new file mode 100644 index 0000000..ea4ee31 --- /dev/null +++ b/librashader-runtime-d3d9/tests/hello_triangle/mod.rs @@ -0,0 +1,578 @@ +const WIDTH: i32 = 800; +const HEIGHT: i32 = 600; +const TITLE: &str = "librashader DirectX 9"; + +use windows::{ + core::*, Win32::Foundation::*, Win32::Graphics::Direct3D9::*, Win32::System::LibraryLoader::*, + Win32::UI::WindowsAndMessaging::*, +}; + +use gfx_maths::Mat4; +use std::mem::transmute; + +pub trait DXSample { + fn bind_to_window(&mut self, hwnd: &HWND) -> Result<()>; + + fn update(&mut self) {} + fn render(&mut self) -> Result<()> { + Ok(()) + } + fn on_key_up(&mut self, _key: u8) {} + fn on_key_down(&mut self, _key: u8) {} + + fn title(&self) -> String { + TITLE.into() + } + + fn window_size(&self) -> (i32, i32) { + (WIDTH, HEIGHT) + } + fn resize(&mut self, w: u32, h: u32) -> Result<()>; +} + +#[inline] +pub fn loword(l: usize) -> u32 { + (l & 0xffff) as u32 +} +#[inline] +pub fn hiword(l: usize) -> u32 { + ((l >> 16) & 0xffff) as u32 +} + +fn run_sample(mut sample: S) -> Result<()> +where + S: DXSample, +{ + let instance = unsafe { GetModuleHandleA(None)? }; + + let wc = WNDCLASSEXA { + cbSize: std::mem::size_of::() as u32, + style: CS_HREDRAW | CS_VREDRAW, + lpfnWndProc: Some(wndproc::), + hInstance: HINSTANCE::from(instance), + hCursor: unsafe { LoadCursorW(None, IDC_ARROW)? }, + lpszClassName: s!("RustWindowClass"), + ..Default::default() + }; + + let size = sample.window_size(); + + let atom = unsafe { RegisterClassExA(&wc) }; + debug_assert_ne!(atom, 0); + + let mut window_rect = RECT { + left: 0, + top: 0, + right: size.0, + bottom: size.1, + }; + unsafe { AdjustWindowRect(&mut window_rect, WS_OVERLAPPEDWINDOW, false)? }; + + let mut title = sample.title(); + + title.push('\0'); + + let hwnd = unsafe { + CreateWindowExA( + WINDOW_EX_STYLE::default(), + s!("RustWindowClass"), + PCSTR(title.as_ptr()), + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, + CW_USEDEFAULT, + window_rect.right - window_rect.left, + window_rect.bottom - window_rect.top, + None, // no parent window + None, // no menus + instance, + Some(&sample as *const _ as _), + ) + }; + + sample.bind_to_window(&hwnd).unwrap(); + unsafe { ShowWindow(hwnd, SW_SHOW) }; + + loop { + let mut message = MSG::default(); + + if unsafe { PeekMessageA(&mut message, None, 0, 0, PM_REMOVE) }.into() { + unsafe { + TranslateMessage(&message); + DispatchMessageA(&message); + } + + if message.message == WM_QUIT { + break; + } + } + } + + Ok(()) +} + +fn sample_wndproc(sample: &mut S, message: u32, wparam: WPARAM) -> bool { + match message { + WM_KEYDOWN => { + sample.on_key_down(wparam.0 as u8); + true + } + WM_KEYUP => { + sample.on_key_up(wparam.0 as u8); + true + } + WM_PAINT => { + sample.update(); + sample.render().unwrap(); + true + } + WM_SIZE => { + sample.resize(loword(wparam.0), hiword(wparam.0)).unwrap(); + true + } + _ => false, + } +} + +extern "system" fn wndproc( + window: HWND, + message: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + match message { + WM_CREATE => { + unsafe { + let create_struct: &CREATESTRUCTA = transmute(lparam); + SetWindowLongPtrA(window, GWLP_USERDATA, create_struct.lpCreateParams as _); + } + LRESULT::default() + } + WM_DESTROY => { + unsafe { PostQuitMessage(0) }; + LRESULT::default() + } + + _ => { + let user_data = unsafe { GetWindowLongPtrA(window, GWLP_USERDATA) }; + let sample = std::ptr::NonNull::::new(user_data as _); + let handled = sample.map_or(false, |mut s| { + sample_wndproc(unsafe { s.as_mut() }, message, wparam) + }); + + if handled { + LRESULT::default() + } else { + unsafe { DefWindowProcA(window, message, wparam, lparam) } + } + } + } +} + +// #[repr(C)] +// struct Vertex { +// position: [f32; 3], +// color: [f32; 3], +// } + +#[repr(C)] +struct Vertex { + position: [f32; 4], + color: u32, +} + +#[repr(C)] +#[derive(Default)] +struct TriangleUniforms { + projection_matrix: Mat4, + model_matrix: Mat4, + view_matrix: Mat4, +} + +pub mod d3d9_hello_triangle { + use super::*; + + use std::path::{Path, PathBuf}; + + use librashader_common::Viewport; + use librashader_runtime::quad::IDENTITY_MVP; + use librashader_runtime_d3d9::options::FilterChainOptionsD3D9; + use librashader_runtime_d3d9::FilterChainD3D9; + use std::time::Instant; + + pub struct Sample { + pub direct3d: IDirect3D9, + pub resources: Option, + pub filter: PathBuf, + } + + pub struct Resources { + pub device: IDirect3DDevice9, + pub filter: FilterChainD3D9, + // pub depth_buffer: ID3D11Texture2D, + // pub depth_stencil_view: ID3D11DepthStencilView, + // pub triangle_vertices: ID3D11Buffer, + // pub triangle_indices: ID3D11Buffer, + // pub triangle_uniforms: ID3D11Buffer, + // pub vs: ID3D11VertexShader, + // pub ps: ID3D11PixelShader, + // pub input_layout: ID3D11InputLayout, + pub frame_start: Instant, + pub frame_end: Instant, + pub elapsed: f32, + pub frame_count: usize, + pub renderbuffer: IDirect3DTexture9, + // pub renderbufffer_rtv: ID3D11RenderTargetView, + // pub backbuffer: Option, + // pub backbuffer_rtv: Option, + // pub viewport: D3D11_VIEWPORT, + // pub shader_output: Option, + // pub deferred_context: ID3D11DeviceContext, + pub vbo: IDirect3DVertexBuffer9, + pub vao: IDirect3DVertexDeclaration9, + } + + impl Sample { + pub(crate) fn new(filter: impl AsRef) -> Result { + // unsafe { + // let mut debug: Option = None; + // if let Some(debug) = D3D12GetDebugInterface(&mut debug).ok().and(debug) { + // eprintln!("enabling debug"); + // debug.EnableDebugLayer(); + // } + // } + // let direct3d = unsafe { Direct3DCreate9On12(D3D_SDK_VERSION, std::ptr::null_mut(), 0).unwrap() }; + + let direct3d = unsafe { Direct3DCreate9(D3D_SDK_VERSION).unwrap() }; + + Ok(Sample { + filter: filter.as_ref().to_path_buf(), + direct3d, + resources: None, + }) + } + } + impl DXSample for Sample { + fn bind_to_window(&mut self, hwnd: &HWND) -> Result<()> { + let device = create_device(&self.direct3d, *hwnd)?; + let filter = unsafe { + FilterChainD3D9::load_from_path( + &self.filter, + &device, + Some(&FilterChainOptionsD3D9 { + force_no_mipmaps: false, + disable_cache: true, + }), + ) + .unwrap() + }; + let (vbo, vao) = create_triangle_buffers(&device)?; + + let renderbuffer = unsafe { + let mut tex = None; + device.CreateTexture( + WIDTH as u32, + HEIGHT as u32, + 1, + D3DUSAGE_RENDERTARGET as u32, + D3DFMT_A8R8G8B8, + D3DPOOL_DEFAULT, + &mut tex, + std::ptr::null_mut(), + )?; + + tex.unwrap() + }; + + self.resources = Some(Resources { + device, + filter, + vbo, + vao, + frame_end: Instant::now(), + frame_start: Instant::now(), + elapsed: 0f32, + // renderbuffer, + // renderbufffer_rtv: render_rtv, + // deferred_context: context, + // viewport: D3D11_VIEWPORT { + // TopLeftX: 0.0, + // TopLeftY: 0.0, + // Width: WIDTH as f32, + // Height: HEIGHT as f32, + // MinDepth: D3D11_MIN_DEPTH, + // MaxDepth: D3D11_MAX_DEPTH, + // }, + // shader_output: None, + frame_count: 0usize, + renderbuffer, + }); + + Ok(()) + } + + // fn resize(&mut self, _w: u32, _h: u32) -> Result<()> { + // unsafe { + // if let Some(resources) = self.resources.as_mut() { + // drop(resources.backbuffer_rtv.take()); + // drop(resources.backbuffer.take()); + // resources + // .swapchain + // .ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0) + // .unwrap_or_else(|f| eprintln!("{f:?}")); + // let (rtv, backbuffer) = create_rtv(&self.device, &resources.swapchain)?; + // + // resources.backbuffer = Some(backbuffer); + // resources.backbuffer_rtv = Some(rtv); + // } + // } + // Ok(()) + // } + + fn render(&mut self) -> Result<()> { + let Some(resources) = &mut self.resources else { + return Ok(()); + }; + + resources.frame_end = Instant::now(); + let time = resources.frame_end - resources.frame_start; + let time = time.as_secs() as f32 * 1000.0; + + // framelimit set to 60fps + if time < (1000.0f32 / 60.0f32) { + return Ok(()); + } + + resources.elapsed += 0.0000001 * time; + resources.elapsed %= 6.283_185_5_f32; + + // resources.triangle_uniform_values.model_matrix = Mat4::rotate(Quaternion::axis_angle(Vec3::new(0.0, 0.0, 1.0), resources.elapsed)); + unsafe { + resources + .device + .SetTransform(D3DTS_PROJECTION, IDENTITY_MVP.as_ptr().cast())?; + resources + .device + .SetTransform(D3DTS_VIEW, IDENTITY_MVP.as_ptr().cast())?; + resources + .device + .SetTransform(D3DTRANSFORMSTATETYPE(256), IDENTITY_MVP.as_ptr().cast())?; + + let rendertarget = resources.renderbuffer.GetSurfaceLevel(0).unwrap(); + + let backbuffer = resources + .device + .GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO)?; + + resources.device.SetRenderTarget(0, &rendertarget)?; + + resources.device.Clear( + 0, + std::ptr::null_mut(), + D3DCLEAR_TARGET as u32, + 0xFF4d6699, + 0.0, + 0, + )?; + + resources.device.BeginScene()?; + + resources.device.SetStreamSource( + 0, + &resources.vbo, + 0, + std::mem::size_of::() as u32, + )?; + resources.device.SetVertexDeclaration(&resources.vao)?; + + resources + .device + .SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE.0 as u32)?; + resources + .device + .SetRenderState(D3DRS_CLIPPING, FALSE.0 as u32)?; + resources + .device + .SetRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS.0 as u32)?; + + resources + .device + .SetRenderState(D3DRS_ZENABLE, FALSE.0 as u32)?; + resources + .device + .SetRenderState(D3DRS_LIGHTING, FALSE.0 as u32)?; + + resources.device.DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1)?; + + resources.device.EndScene()?; + + resources + .filter + .frame( + resources.renderbuffer.clone(), + &Viewport { + x: 0.0, + y: 0.0, + mvp: None, + output: backbuffer.clone(), + }, + 0, + None, + ) + .unwrap(); + + // resources.device.StretchRect( + // &rendertarget, + // std::ptr::null_mut(), + // &backbuffer, + // std::ptr::null_mut(), + // D3DTEXF_POINT, + // )?; + } + + unsafe { + resources.device.Present( + std::ptr::null_mut(), + std::ptr::null_mut(), + None, + std::ptr::null_mut(), + )?; + } + + resources.frame_count += 1; + Ok(()) + } + + fn resize(&mut self, _w: u32, _h: u32) -> Result<()> { + Ok(()) + } + } + + pub fn get_vbo_desc() -> [D3DVERTEXELEMENT9; 3] { + [ + D3DVERTEXELEMENT9 { + Stream: 0, + Offset: 0, + Type: D3DDECLTYPE_FLOAT3.0 as u8, + Method: D3DDECLMETHOD_DEFAULT.0 as u8, + Usage: D3DDECLUSAGE_POSITION.0 as u8, + UsageIndex: 0, + }, + D3DVERTEXELEMENT9 { + Stream: 0, + Offset: (std::mem::size_of::() * 4) as u16, + Type: D3DDECLTYPE_D3DCOLOR.0 as u8, + Method: D3DDECLMETHOD_DEFAULT.0 as u8, + Usage: D3DDECLUSAGE_COLOR.0 as u8, + UsageIndex: 0, + }, + D3DVERTEXELEMENT9 { + Stream: 0xFF, + Offset: 0, + Type: D3DDECLTYPE_UNUSED.0 as u8, + Method: 0, + Usage: 0, + UsageIndex: 0, + }, + ] + } + fn create_triangle_buffers( + device: &IDirect3DDevice9, + ) -> Result<(IDirect3DVertexBuffer9, IDirect3DVertexDeclaration9)> { + // let vertices = [ + // Vertex { + // position: [0.5f32, -0.5, 0.0], + // color: [1.0, 0.0, 0.0], + // }, + // Vertex { + // position: [-0.5, -0.5, 0.0], + // color: [0.0, 1.0, 0.0], + // }, + // Vertex { + // position: [0.0, 0.5, 0.0], + // color: [0.0, 0.0, 1.0], + // }, + // ]; + + const TRIANGLE_VERTICES: [Vertex; 3] = [ + Vertex { + position: [0.5, -0.5, 0.0, 1.0], + color: 0xFFFF0000, + }, // Red + Vertex { + position: [-0.5, -0.5, 0.0, 1.0], + color: 0xFF00FF00, + }, // Green + Vertex { + position: [0.0, 0.5, 0.0, 1.0], + color: 0xFF0000FF, + }, // Blue + ]; + + unsafe { + let mut vb = None; + device.CreateVertexBuffer( + (std::mem::size_of::() * 3) as u32, + 0, + D3DFVF_XYZW | D3DFVF_DIFFUSE, + D3DPOOL_DEFAULT, + &mut vb, + std::ptr::null_mut(), + )?; + + let vb = vb.unwrap(); + + let mut vertices = std::ptr::null_mut(); + vb.Lock(0, 0, &mut vertices, 0)?; + std::ptr::copy_nonoverlapping( + TRIANGLE_VERTICES.as_ptr() as *const std::ffi::c_void, + vertices, + std::mem::size_of_val(&TRIANGLE_VERTICES), + ); + vb.Unlock()?; + + let vao = device.CreateVertexDeclaration(get_vbo_desc().as_ptr())?; + Ok((vb, vao)) + } + } + + fn create_device(d3d9: &IDirect3D9, hwnd: HWND) -> Result { + let mut present_params: D3DPRESENT_PARAMETERS = Default::default(); + present_params.BackBufferWidth = WIDTH as u32; + present_params.BackBufferHeight = HEIGHT as u32; + present_params.Windowed = TRUE; + present_params.SwapEffect = D3DSWAPEFFECT_DISCARD; + present_params.hDeviceWindow = hwnd; + present_params.BackBufferFormat = D3DFMT_UNKNOWN; + present_params.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE as u32; + + let mut device = None; + + unsafe { + // d3d9.CreateDevice( + // D3DADAPTER_DEFAULT, + // D3DDEVTYPE_HAL, + // present_params.hDeviceWindow, + // D3DCREATE_HARDWARE_VERTEXPROCESSING as u32, + // &mut present_params, + // &mut device, + // )?; + + d3d9.CreateDevice( + D3DADAPTER_DEFAULT, + D3DDEVTYPE_HAL, + present_params.hDeviceWindow, + D3DCREATE_HARDWARE_VERTEXPROCESSING as u32, + &mut present_params, + &mut device, + )?; + } + + Ok(device.unwrap()) + } +} + +pub fn main(sample: S) -> Result<()> { + run_sample(sample)?; + + Ok(()) +} diff --git a/librashader-runtime-d3d9/tests/triangle.rs b/librashader-runtime-d3d9/tests/triangle.rs new file mode 100644 index 0000000..ac9fd93 --- /dev/null +++ b/librashader-runtime-d3d9/tests/triangle.rs @@ -0,0 +1,28 @@ +mod hello_triangle; + +const FILTER_PATH: &str = "../test/shaders_slang/test/feedback.slangp"; + +#[test] +fn triangle_d3d9() { + let sample = hello_triangle::d3d9_hello_triangle::Sample::new( + FILTER_PATH, + // Some(&FilterChainOptionsD3D9 { + // force_no_mipmaps: false, + // disable_cache: false, + // }), + // replace below with 'None' for the triangle + // None, + ) + .unwrap(); + // let sample = hello_triangle_old::d3d11_hello_triangle::Sample::new( + // "../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp", + // Some(&FilterChainOptions { + // use_deferred_context: true, + // }) + // ) + // .unwrap(); + + // let sample = hello_triangle_old::d3d11_hello_triangle::Sample::new("../test/basic.slangp").unwrap(); + + hello_triangle::main(sample).unwrap(); +} diff --git a/librashader-runtime-gl/src/binding.rs b/librashader-runtime-gl/src/binding.rs index 3cb84f4..4045ac2 100644 --- a/librashader-runtime-gl/src/binding.rs +++ b/librashader-runtime-gl/src/binding.rs @@ -63,7 +63,12 @@ impl BindUniform for GlUniformBinder where T: GlUniformScalar, { - fn bind_uniform(block: UniformMemberBlock, value: T, location: VariableLocation, _: &()) -> Option<()> { + fn bind_uniform( + block: UniformMemberBlock, + value: T, + location: VariableLocation, + _: &(), + ) -> Option<()> { if let Some(location) = location.location(block) && location.bindable() { @@ -89,7 +94,7 @@ impl BindUniform for GlUniformBinder { block: UniformMemberBlock, vec4: &[f32; 4], location: VariableLocation, - _: &() + _: &(), ) -> Option<()> { if let Some(location) = location.location(block) && location.bindable() @@ -114,7 +119,7 @@ impl BindUniform for GlUniformBinder { block: UniformMemberBlock, mat4: &[f32; 16], location: VariableLocation, - _: &() + _: &(), ) -> Option<()> { if let Some(location) = location.location(block) && location.bindable() diff --git a/librashader-runtime-vk/src/filter_pass.rs b/librashader-runtime-vk/src/filter_pass.rs index 70079af..2af61c0 100644 --- a/librashader-runtime-vk/src/filter_pass.rs +++ b/librashader-runtime-vk/src/filter_pass.rs @@ -24,7 +24,8 @@ use std::sync::Arc; pub struct FilterPass { pub reflection: ShaderReflection, - pub(crate) uniform_storage: UniformStorage, RawVulkanBuffer, Box<[u8]>, Arc>, + pub(crate) uniform_storage: + UniformStorage, RawVulkanBuffer, Box<[u8]>, Arc>, pub uniform_bindings: FastHashMap, pub source: ShaderSource, pub config: ShaderPassConfig, diff --git a/librashader-runtime-wgpu/src/filter_pass.rs b/librashader-runtime-wgpu/src/filter_pass.rs index bb7b855..9778973 100644 --- a/librashader-runtime-wgpu/src/filter_pass.rs +++ b/librashader-runtime-wgpu/src/filter_pass.rs @@ -25,8 +25,13 @@ use wgpu::{BindGroupDescriptor, BindGroupEntry, BindingResource, BufferBinding, pub struct FilterPass { pub device: Arc, pub reflection: ShaderReflection, - pub(crate) uniform_storage: - UniformStorage, WgpuStagedBuffer, WgpuStagedBuffer, Arc>, + pub(crate) uniform_storage: UniformStorage< + NoUniformBinder, + Option<()>, + WgpuStagedBuffer, + WgpuStagedBuffer, + Arc, + >, pub uniform_bindings: FastHashMap, pub source: ShaderSource, pub config: ShaderPassConfig, diff --git a/librashader-runtime/src/binding.rs b/librashader-runtime/src/binding.rs index d218c28..ee31e32 100644 --- a/librashader-runtime/src/binding.rs +++ b/librashader-runtime/src/binding.rs @@ -15,7 +15,7 @@ pub trait TextureInput { } /// A uniform member offset with context that needs to be resolved. -pub trait ContextOffset +pub trait ContextOffset where H: BindUniform, H: BindUniform, @@ -124,7 +124,12 @@ where ) { // Bind MVP if let Some(offset) = uniform_bindings.get(&UniqueSemantics::MVP.into()) { - uniform_storage.bind_mat4(offset.offset(), uniform_inputs.mvp, offset.context(), device); + uniform_storage.bind_mat4( + offset.offset(), + uniform_inputs.mvp, + offset.context(), + device, + ); } // Bind OutputSize @@ -133,7 +138,7 @@ where offset.offset(), uniform_inputs.framebuffer_size, offset.context(), - device + device, ); } @@ -143,7 +148,7 @@ where offset.offset(), uniform_inputs.viewport_size, offset.context(), - device + device, ); } @@ -153,7 +158,7 @@ where offset.offset(), uniform_inputs.frame_count, offset.context(), - device + device, ); } @@ -163,13 +168,18 @@ where offset.offset(), uniform_inputs.frame_direction, offset.context(), - device + device, ); } // bind Rotation if let Some(offset) = uniform_bindings.get(&UniqueSemantics::Rotation.into()) { - uniform_storage.bind_scalar(offset.offset(), uniform_inputs.rotation, offset.context(), device); + uniform_storage.bind_scalar( + offset.offset(), + uniform_inputs.rotation, + offset.context(), + device, + ); } // bind TotalSubFrames @@ -178,7 +188,7 @@ where offset.offset(), uniform_inputs.total_subframes, offset.context(), - device + device, ); } @@ -188,7 +198,7 @@ where offset.offset(), uniform_inputs.current_subframe, offset.context(), - device + device, ); } @@ -246,7 +256,12 @@ where .semantics(index + 1) .into(), ) { - uniform_storage.bind_vec4(offset.offset(), history.size(), offset.context(), device); + uniform_storage.bind_vec4( + offset.offset(), + history.size(), + offset.context(), + device, + ); } } @@ -289,7 +304,12 @@ where if let Some(offset) = uniform_bindings.get(&TextureSemantics::PassFeedback.semantics(index).into()) { - uniform_storage.bind_vec4(offset.offset(), feedback.size(), offset.context(), device); + uniform_storage.bind_vec4( + offset.offset(), + feedback.size(), + offset.context(), + device, + ); } } diff --git a/librashader-runtime/src/uniforms.rs b/librashader-runtime/src/uniforms.rs index 95a3588..fe4a378 100644 --- a/librashader-runtime/src/uniforms.rs +++ b/librashader-runtime/src/uniforms.rs @@ -69,7 +69,7 @@ impl BindUniform, T, D> for NoUniformBinder { } /// A helper to bind uniform variables to UBO or Push Constant Buffers. -pub struct UniformStorage, U = Box<[u8]>, P = Box<[u8]>, D=()> +pub struct UniformStorage, U = Box<[u8]>, P = Box<[u8]>, D = ()> where U: Deref + DerefMut, P: Deref + DerefMut, @@ -78,7 +78,7 @@ where push: P, _h: PhantomData, _c: PhantomData, - _d: PhantomData + _d: PhantomData, } impl UniformStorage @@ -118,8 +118,13 @@ where /// Bind a scalar to the given offset. #[inline(always)] - pub fn bind_scalar(&mut self, offset: MemberOffset, value: T, ctx: C, device: &D) - where + pub fn bind_scalar( + &mut self, + offset: MemberOffset, + value: T, + ctx: C, + device: &D, + ) where H: BindUniform, { for ty in UniformMemberBlock::TYPES { @@ -193,7 +198,13 @@ where } /// Bind a `vec4` to the given offset. #[inline(always)] - pub fn bind_vec4(&mut self, offset: MemberOffset, value: impl Into<[f32; 4]>, ctx: C, device: &D) { + pub fn bind_vec4( + &mut self, + offset: MemberOffset, + value: impl Into<[f32; 4]>, + ctx: C, + device: &D, + ) { let vec4 = value.into(); for ty in UniformMemberBlock::TYPES { diff --git a/test/basic.slang b/test/basic.slang index 7c5aa62..721b88a 100644 --- a/test/basic.slang +++ b/test/basic.slang @@ -4,11 +4,12 @@ layout(set = 0, binding = 0, std140) uniform UBO { mat4 MVP; - float ColorMod2; + float ColorMod; + uint FrameCount; }; layout(push_constant) uniform Push { - float ColorMod; + float ColorMod2; } params; #pragma name StockShader @@ -32,5 +33,5 @@ layout(location = 0) out vec4 FragColor; layout(binding = 1) uniform sampler2D Source; void main() { - FragColor = texture(Source, vTexCoord) * params.ColorMod; + FragColor = texture(Source, vTexCoord) * ColorMod * params.ColorMod2; } \ No newline at end of file diff --git a/test/basic.slangp b/test/basic.slangp index 8edd75d..59778bc 100644 --- a/test/basic.slangp +++ b/test/basic.slangp @@ -7,10 +7,3 @@ float_framebuffer0 = "false" srgb_framebuffer0 = "false" ColorMod = "1.700000" -shader1 = "basic.slang" -wrap_mode1 = "clamp_to_border" -mipmap_input1 = "true" -alias1 = "" -float_framebuffer0 = "false" -srgb_framebuffer0 = "false" -ColorMod = "1.700000"