From 5fba24d3d0370a1e92d645b46fad22c5da1334ba Mon Sep 17 00:00:00 2001 From: nicehack Date: Sat, 27 Jun 2026 16:26:11 +0500 Subject: [PATCH 01/34] starter wgpu init --- Cargo.toml | 3 +- fyrox-graphics-wgpu/Cargo.toml | 22 +++ fyrox-graphics-wgpu/src/lib.rs | 21 +++ fyrox-graphics-wgpu/src/server.rs | 277 ++++++++++++++++++++++++++++++ fyrox-impl/Cargo.toml | 1 + fyrox-impl/src/engine/mod.rs | 19 +- 6 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 fyrox-graphics-wgpu/Cargo.toml create mode 100644 fyrox-graphics-wgpu/src/lib.rs create mode 100644 fyrox-graphics-wgpu/src/server.rs diff --git a/Cargo.toml b/Cargo.toml index 8b554598d4..7036a87971 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,8 @@ members = [ "fyrox-texture", "fyrox-autotile", "fyrox-material", - "fyrox-graphics-gl"] + "fyrox-graphics-gl", + "fyrox-graphics-wgpu"] resolver = "2" [profile.editor-standalone] diff --git a/fyrox-graphics-wgpu/Cargo.toml b/fyrox-graphics-wgpu/Cargo.toml new file mode 100644 index 0000000000..29e989fe98 --- /dev/null +++ b/fyrox-graphics-wgpu/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "fyrox-graphics-wgpu" +version = "2.0.0-rc.1" +edition = "2021" +license = "MIT" +description = "Wgpu-based graphics server for Fyrox Game Engine" +keywords = ["graphics", "gapi"] +categories = ["graphics", "rendering::graphics-api"] +include = ["/src/**/*", "/Cargo.toml", "/LICENSE", "/README.md"] +homepage = "https://fyrox.rs" +documentation = "https://docs.rs/fyrox-graphics-wgpu" +repository = "https://github.com/FyroxEngine/Fyrox" +rust-version = "1.87" + +[dependencies] +winit = { version = "0.30" } +fyrox-graphics = { version = "2.0.0-rc.1", path = "../fyrox-graphics" } +fyrox-core = { version = "2.0.0-rc.1", path = "../fyrox-core" } +wgpu = "29.0.3" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +raw-window-handle = "0.6" \ No newline at end of file diff --git a/fyrox-graphics-wgpu/src/lib.rs b/fyrox-graphics-wgpu/src/lib.rs new file mode 100644 index 0000000000..e6f30f703a --- /dev/null +++ b/fyrox-graphics-wgpu/src/lib.rs @@ -0,0 +1,21 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +pub mod server; \ No newline at end of file diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs new file mode 100644 index 0000000000..6080d85425 --- /dev/null +++ b/fyrox-graphics-wgpu/src/server.rs @@ -0,0 +1,277 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use std::cell::{Cell, RefCell}; +use fyrox_core::futures::executor::block_on; +use fyrox_graphics::buffer::{GpuBuffer, GpuBufferDescriptor}; +use fyrox_graphics::error::FrameworkError; +use fyrox_graphics::framebuffer::{Attachment, GpuFrameBuffer}; +use fyrox_graphics::geometry_buffer::{GpuGeometryBuffer, GpuGeometryBufferDescriptor}; +use fyrox_graphics::gpu_program::{GpuProgram, GpuShader, ShaderKind, ShaderResourceDefinition}; +use fyrox_graphics::gpu_texture::{GpuTexture, GpuTextureDescriptor}; +use fyrox_graphics::query::GpuQuery; +use fyrox_graphics::read_buffer::GpuAsyncReadBuffer; +use fyrox_graphics::sampler::{GpuSampler, GpuSamplerDescriptor}; +use fyrox_graphics::server::{ + GraphicsServer, ServerCapabilities, ServerMemoryUsage, SharedGraphicsServer, +}; +use fyrox_graphics::stats::PipelineStatistics; +use fyrox_graphics::{PolygonFace, PolygonFillMode}; +use std::rc::{Rc, Weak}; +use std::sync::{Arc, RwLock}; +use raw_window_handle::HasDisplayHandle; +use winit::event_loop::ActiveEventLoop; +use winit::window::{Window, WindowAttributes}; + +pub struct WgpuState { + pub instance: wgpu::Instance, + pub adapter: wgpu::Adapter, + pub device: wgpu::Device, + pub queue: wgpu::Queue, +} + +pub struct WgpuGraphicsServer { + pub state: Arc, + pub surface: wgpu::Surface<'static>, + pub surface_config: RwLock, +} + +impl WgpuGraphicsServer { + pub fn new( + vsync: bool, + msaa_sample_count: Option, + window_target: &ActiveEventLoop, + mut window_attributes: WindowAttributes, + named_objects: bool, + ) -> Result<(Window, SharedGraphicsServer), FrameworkError> { + let window = window_target + .create_window(window_attributes) + .map_err(|e| FrameworkError::Custom(format!("Failed to create window: {e}")))?; + let size = window.inner_size(); + + let display_handle = window_target + .display_handle() + .map_err(|e| FrameworkError::Custom(format!("Failed to get display handle: {e}")))?; + + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_with_display_handle(Box::new(window_target.owned_display_handle()))); + + // Создаем поверхность отрисовки (Surface) + let surface = unsafe { + let target = wgpu::SurfaceTargetUnsafe::from_window(&window) + .map_err(|e| FrameworkError::Custom(format!("Failed to get window handle: {e}")))?; + instance + .create_surface_unsafe(target) + .map_err(|e| FrameworkError::Custom(format!("Failed to create surface: {e}")))? + }; + + let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + })) + .map_err(|e| FrameworkError::Custom(format!("No suitable WGPU adapter found: {e}")))?; + + // Инициализируем логическое устройство и очередь команд + let (device, queue) = block_on(adapter.request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + // WebGL doesn't support all of wgpu's features, so if + // we're building for the web we'll have to disable some. + required_limits: if cfg!(target_arch = "wasm32") { + wgpu::Limits::downlevel_webgl2_defaults() + } else { + wgpu::Limits::default() + }, + experimental_features: wgpu::ExperimentalFeatures::disabled(), + memory_hints: wgpu::MemoryHints::Performance, + trace: wgpu::Trace::Off, + }, + )) + .map_err(|e| FrameworkError::Custom(format!("Failed to request device: {e}")))?; + + let surface_caps = surface.get_capabilities(&adapter); + + let Some(surface_format) = surface_caps.formats.first().copied() else { + return Err(FrameworkError::Custom( + "Surface has no supported formats".into(), + )); + }; + + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + + surface.configure(&device, &surface_config); + + let state = Self { + state: Arc::new(WgpuState { + instance, + adapter, + device, + queue, + }), + surface, + surface_config: RwLock::new(surface_config), + }; + + let shared = Rc::new(state); + + Ok((window, shared)) + } +} + +impl GraphicsServer for WgpuGraphicsServer { + fn create_buffer(&self, desc: GpuBufferDescriptor) -> Result { + todo!() + } + + fn create_texture(&self, desc: GpuTextureDescriptor) -> Result { + todo!() + } + + fn create_sampler(&self, desc: GpuSamplerDescriptor) -> Result { + todo!() + } + + fn create_frame_buffer( + &self, + depth_attachment: Option, + color_attachments: Vec, + ) -> Result { + todo!() + } + + fn back_buffer(&self) -> GpuFrameBuffer { + todo!() + } + + fn create_query(&self) -> Result { + todo!() + } + + fn create_shader( + &self, + name: String, + kind: ShaderKind, + source: String, + resources: &[ShaderResourceDefinition], + line_offset: isize, + ) -> Result { + todo!() + } + + fn create_program( + &self, + name: &str, + vertex_source: String, + vertex_source_line_offset: isize, + fragment_source: String, + fragment_source_line_offset: isize, + resources: &[ShaderResourceDefinition], + ) -> Result { + todo!() + } + + fn create_program_from_shaders( + &self, + name: &str, + vertex_shader: &GpuShader, + fragment_shader: &GpuShader, + resources: &[ShaderResourceDefinition], + ) -> Result { + todo!() + } + + fn create_async_read_buffer( + &self, + name: &str, + pixel_size: usize, + pixel_count: usize, + ) -> Result { + todo!() + } + + fn create_geometry_buffer( + &self, + desc: GpuGeometryBufferDescriptor, + ) -> Result { + todo!() + } + + fn weak(&self) -> Weak { + todo!() + } + + fn flush(&self) { + todo!() + } + + fn finish(&self) { + todo!() + } + + fn invalidate_resource_bindings_cache(&self) { + todo!() + } + + fn pipeline_statistics(&self) -> PipelineStatistics { + todo!() + } + + fn swap_buffers(&self) -> Result<(), FrameworkError> { + todo!() + } + + fn set_frame_size(&self, new_size: (u32, u32)) { + todo!() + } + + fn capabilities(&self) -> ServerCapabilities { + todo!() + } + + fn set_polygon_fill_mode(&self, polygon_face: PolygonFace, polygon_fill_mode: PolygonFillMode) { + todo!() + } + + fn generate_mipmap(&self, texture: &GpuTexture) { + todo!() + } + + fn memory_usage(&self) -> ServerMemoryUsage { + todo!() + } + + fn push_debug_group(&self, name: &str) { + todo!() + } + + fn pop_debug_group(&self) { + todo!() + } +} diff --git a/fyrox-impl/Cargo.toml b/fyrox-impl/Cargo.toml index 034de50bee..bcde398f94 100644 --- a/fyrox-impl/Cargo.toml +++ b/fyrox-impl/Cargo.toml @@ -24,6 +24,7 @@ fyrox-animation = { path = "../fyrox-animation", version = "2.0.0-rc.1" } fyrox-graph = { path = "../fyrox-graph", version = "2.0.0-rc.1" } fyrox-graphics = { path = "../fyrox-graphics", version = "2.0.0-rc.1" } fyrox-graphics-gl = { path = "../fyrox-graphics-gl", version = "2.0.0-rc.1" } +fyrox-graphics-wgpu = { path = "../fyrox-graphics-wgpu", version = "2.0.0-rc.1" } fyrox-texture = { path = "../fyrox-texture", version = "2.0.0-rc.1" } fyrox-autotile = { path = "../fyrox-autotile", version = "2.0.0-rc.1" } fyrox-material = { path = "../fyrox-material", version = "2.0.0-rc.1" } diff --git a/fyrox-impl/src/engine/mod.rs b/fyrox-impl/src/engine/mod.rs index 9cc6e2617a..934ff84a3b 100644 --- a/fyrox-impl/src/engine/mod.rs +++ b/fyrox-impl/src/engine/mod.rs @@ -142,6 +142,7 @@ use winit::{ window::Icon, window::WindowAttributes, }; +use fyrox_graphics_wgpu::server::WgpuGraphicsServer; /// Serialization context holds runtime type information that allows to create unknown types using /// their UUIDs and a respective constructors. @@ -1022,11 +1023,27 @@ pub type GraphicsServerConstructorCallback = dyn Fn( #[derive(Clone)] pub struct GraphicsServerConstructor(Rc); +impl GraphicsServerConstructor { + pub fn wgpu() -> Self { + Self(Rc::new( + |params, window_target, window_builder, named_objects| { + WgpuGraphicsServer::new( + params.vsync, + params.msaa_sample_count, + window_target, + window_builder, + named_objects, + ) + }, + )) + } +} + impl Default for GraphicsServerConstructor { fn default() -> Self { Self(Rc::new( |params, window_target, window_builder, named_objects| { - GlGraphicsServer::new( + WgpuGraphicsServer::new( params.vsync, params.msaa_sample_count, window_target, From 20fa4d56684b6fb96b924e52bb485054e949e139 Mon Sep 17 00:00:00 2001 From: nice0hack Date: Wed, 1 Jul 2026 16:06:05 +0500 Subject: [PATCH 02/34] full implementation of fyrox-graphics-wgpu module --- fyrox-graphics-wgpu/Cargo.toml | 7 +- fyrox-graphics-wgpu/src/buffer.rs | 122 ++++++ fyrox-graphics-wgpu/src/framebuffer.rs | 291 +++++++++++++ fyrox-graphics-wgpu/src/geometry_buffer.rs | 135 ++++++ fyrox-graphics-wgpu/src/lib.rs | 10 +- fyrox-graphics-wgpu/src/program.rs | 445 ++++++++++++++++++++ fyrox-graphics-wgpu/src/query.rs | 48 +++ fyrox-graphics-wgpu/src/read_buffer.rs | 96 +++++ fyrox-graphics-wgpu/src/sampler.rs | 84 ++++ fyrox-graphics-wgpu/src/server.rs | 243 +++++------ fyrox-graphics-wgpu/src/shaders/shared.glsl | 366 ++++++++++++++++ fyrox-graphics-wgpu/src/texture.rs | 210 +++++++++ 12 files changed, 1918 insertions(+), 139 deletions(-) create mode 100644 fyrox-graphics-wgpu/src/buffer.rs create mode 100644 fyrox-graphics-wgpu/src/framebuffer.rs create mode 100644 fyrox-graphics-wgpu/src/geometry_buffer.rs create mode 100644 fyrox-graphics-wgpu/src/program.rs create mode 100644 fyrox-graphics-wgpu/src/query.rs create mode 100644 fyrox-graphics-wgpu/src/read_buffer.rs create mode 100644 fyrox-graphics-wgpu/src/sampler.rs create mode 100644 fyrox-graphics-wgpu/src/shaders/shared.glsl create mode 100644 fyrox-graphics-wgpu/src/texture.rs diff --git a/fyrox-graphics-wgpu/Cargo.toml b/fyrox-graphics-wgpu/Cargo.toml index 29e989fe98..a021a2059b 100644 --- a/fyrox-graphics-wgpu/Cargo.toml +++ b/fyrox-graphics-wgpu/Cargo.toml @@ -16,7 +16,10 @@ rust-version = "1.87" winit = { version = "0.30" } fyrox-graphics = { version = "2.0.0-rc.1", path = "../fyrox-graphics" } fyrox-core = { version = "2.0.0-rc.1", path = "../fyrox-core" } -wgpu = "29.0.3" +wgpu = { version = "29.0.3", features = ["spirv"] } +naga = { version = "29.0.3", features = ["glsl-in", "spv-out"] } +bytemuck = { version = "1", features = ["derive"] } +log = "0.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -raw-window-handle = "0.6" \ No newline at end of file +raw-window-handle = "0.6" diff --git a/fyrox-graphics-wgpu/src/buffer.rs b/fyrox-graphics-wgpu/src/buffer.rs new file mode 100644 index 0000000000..1b074f58e7 --- /dev/null +++ b/fyrox-graphics-wgpu/src/buffer.rs @@ -0,0 +1,122 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::server::WgpuGraphicsServer; +use fyrox_graphics::{ + buffer::{BufferKind, BufferUsage, GpuBufferDescriptor, GpuBufferTrait}, + error::FrameworkError, +}; +use std::cell::Cell; +use std::rc::Weak; + +fn buffer_usage_to_wgpu(kind: BufferKind) -> wgpu::BufferUsages { + let mut flags = match kind { + BufferKind::Vertex => wgpu::BufferUsages::VERTEX, + BufferKind::Index => wgpu::BufferUsages::INDEX, + BufferKind::Uniform => wgpu::BufferUsages::UNIFORM, + BufferKind::PixelRead => wgpu::BufferUsages::MAP_READ, + BufferKind::PixelWrite => wgpu::BufferUsages::MAP_WRITE, + }; + flags |= wgpu::BufferUsages::COPY_DST; + if kind == BufferKind::PixelWrite { + flags |= wgpu::BufferUsages::COPY_SRC; + } + flags +} + +pub struct WgpuBuffer { + server: Weak, + buffer: wgpu::Buffer, + size: Cell, + kind: BufferKind, + usage: BufferUsage, +} + +impl WgpuBuffer { + pub fn new( + server: &WgpuGraphicsServer, + desc: GpuBufferDescriptor, + ) -> Result { + let wgpu_usage = buffer_usage_to_wgpu(desc.kind); + let buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor { + label: if server.named_objects { Some(desc.name) } else { None }, + size: desc.size.max(1) as u64, + usage: wgpu_usage, + mapped_at_creation: false, + }); + server.memory_usage.borrow_mut().buffers += desc.size; + Ok(Self { + server: server.weak_ref(), + buffer, + size: Cell::new(desc.size), + kind: desc.kind, + usage: desc.usage, + }) + } + + pub fn wgpu_buffer(&self) -> &wgpu::Buffer { + &self.buffer + } +} + +impl Drop for WgpuBuffer { + fn drop(&mut self) { + if let Some(server) = self.server.upgrade() { + server.memory_usage.borrow_mut().buffers -= self.size.get(); + } + } +} + +impl GpuBufferTrait for WgpuBuffer { + fn usage(&self) -> BufferUsage { self.usage } + fn kind(&self) -> BufferKind { self.kind } + fn size(&self) -> usize { self.size.get() } + + fn write_data(&self, data: &[u8]) -> Result<(), FrameworkError> { + if data.is_empty() { return Ok(()); } + let Some(server) = self.server.upgrade() else { + return Err(FrameworkError::GraphicsServerUnavailable); + }; + if data.len() <= self.size.get() { + server.state.queue.write_buffer(&self.buffer, 0, data); + } else { + log::warn!("WgpuBuffer::write_data: data ({} bytes) exceeds buffer ({} bytes)", data.len(), self.size.get()); + server.state.queue.write_buffer(&self.buffer, 0, &data[..self.size.get()]); + } + Ok(()) + } + + fn read_data(&self, data: &mut [u8]) -> Result<(), FrameworkError> { + let Some(server) = self.server.upgrade() else { + return Err(FrameworkError::GraphicsServerUnavailable); + }; + let buffer_slice = self.buffer.slice(..data.len() as u64); + let (tx, rx) = std::sync::mpsc::channel(); + buffer_slice.map_async(wgpu::MapMode::Read, move |result| { tx.send(result).ok(); }); + server.state.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None }).ok(); + rx.recv().map_err(|_| FrameworkError::Custom("Channel closed".into()))? + .map_err(|e| FrameworkError::Custom(format!("Buffer map failed: {e}")))?; + let mapped = buffer_slice.get_mapped_range(); + data.copy_from_slice(&mapped); + drop(mapped); + self.buffer.unmap(); + Ok(()) + } +} diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs new file mode 100644 index 0000000000..663f4acac6 --- /dev/null +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -0,0 +1,291 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::buffer::WgpuBuffer; +use crate::geometry_buffer::WgpuGeometryBuffer; +use crate::program::WgpuProgram; +use crate::sampler::WgpuSampler; +use crate::server::WgpuGraphicsServer; +use crate::texture::WgpuTexture; +use fyrox_graphics::{ + core::{color::Color, math::Rect}, + error::FrameworkError, + framebuffer::{Attachment, BufferDataUsage, DrawCallStatistics, GpuFrameBuffer, GpuFrameBufferTrait, ReadTarget, ResourceBindGroup, ResourceBinding}, + geometry_buffer::GpuGeometryBuffer, + gpu_program::GpuProgram, + gpu_texture::{image_2d_size_bytes, CubeMapFace, GpuTexture, GpuTextureKind}, + CompareFunc, CullFace, DrawParameters, ElementRange, BlendMode, +}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use std::rc::Weak; + +fn compare_func_to_wgpu(f: CompareFunc) -> wgpu::CompareFunction { + match f { + CompareFunc::Never => wgpu::CompareFunction::Never, + CompareFunc::Less => wgpu::CompareFunction::Less, + CompareFunc::Equal => wgpu::CompareFunction::Equal, + CompareFunc::LessOrEqual => wgpu::CompareFunction::LessEqual, + CompareFunc::Greater => wgpu::CompareFunction::Greater, + CompareFunc::NotEqual => wgpu::CompareFunction::NotEqual, + CompareFunc::GreaterOrEqual => wgpu::CompareFunction::GreaterEqual, + CompareFunc::Always => wgpu::CompareFunction::Always, + } +} + +fn blend_mode_to_wgpu(m: BlendMode) -> wgpu::BlendOperation { + match m { + BlendMode::Add => wgpu::BlendOperation::Add, + BlendMode::Subtract => wgpu::BlendOperation::Subtract, + BlendMode::ReverseSubtract => wgpu::BlendOperation::ReverseSubtract, + BlendMode::Min => wgpu::BlendOperation::Min, + BlendMode::Max => wgpu::BlendOperation::Max, + } +} + +fn blend_factor_to_wgpu(f: fyrox_graphics::BlendFactor) -> wgpu::BlendFactor { + use fyrox_graphics::BlendFactor; + match f { + BlendFactor::Zero => wgpu::BlendFactor::Zero, + BlendFactor::One => wgpu::BlendFactor::One, + BlendFactor::SrcColor => wgpu::BlendFactor::Src, + BlendFactor::OneMinusSrcColor => wgpu::BlendFactor::OneMinusSrc, + BlendFactor::DstColor => wgpu::BlendFactor::Dst, + BlendFactor::OneMinusDstColor => wgpu::BlendFactor::OneMinusDst, + BlendFactor::SrcAlpha => wgpu::BlendFactor::SrcAlpha, + BlendFactor::OneMinusSrcAlpha => wgpu::BlendFactor::OneMinusSrcAlpha, + BlendFactor::DstAlpha => wgpu::BlendFactor::DstAlpha, + BlendFactor::OneMinusDstAlpha => wgpu::BlendFactor::OneMinusDstAlpha, + BlendFactor::ConstantColor | BlendFactor::ConstantAlpha => wgpu::BlendFactor::Constant, + BlendFactor::OneMinusConstantColor | BlendFactor::OneMinusConstantAlpha => wgpu::BlendFactor::OneMinusConstant, + BlendFactor::SrcAlphaSaturate => wgpu::BlendFactor::SrcAlphaSaturated, + BlendFactor::Src1Color => wgpu::BlendFactor::Src, + BlendFactor::OneMinusSrc1Color => wgpu::BlendFactor::OneMinusSrc, + BlendFactor::Src1Alpha => wgpu::BlendFactor::SrcAlpha, + BlendFactor::OneMinusSrc1Alpha => wgpu::BlendFactor::OneMinusSrcAlpha, + } +} + +fn texture_format_for_attachment(tex: &GpuTexture) -> wgpu::TextureFormat { + tex.as_any().downcast_ref::().unwrap().format() +} + +#[derive(Hash, PartialEq, Eq, Clone)] +pub struct PipelineKey { + program_ptr: usize, + color_format: wgpu::TextureFormat, + depth_format: Option, + sample_count: u32, + blend: bool, + depth_test: bool, + depth_write: bool, + cull: u8, +} + +pub struct WgpuFrameBuffer { + server: Weak, + depth_attachment: Option, + color_attachments: Vec, + is_backbuffer: bool, +} + +impl WgpuFrameBuffer { + pub fn new(server: &WgpuGraphicsServer, depth: Option, colors: Vec) -> Result { + Ok(Self { server: server.weak_ref(), depth_attachment: depth, color_attachments: colors, is_backbuffer: false }) + } + + pub fn backbuffer(server: &WgpuGraphicsServer) -> Self { + Self { server: server.weak_ref(), depth_attachment: None, color_attachments: Default::default(), is_backbuffer: true } + } + + fn get_or_create_pipeline(&self, server: &WgpuGraphicsServer, program: &WgpuProgram, params: &DrawParameters, geo: &WgpuGeometryBuffer, cf: wgpu::TextureFormat, df: Option) -> wgpu::RenderPipeline { + let key = PipelineKey { + program_ptr: program as *const WgpuProgram as usize, color_format: cf, depth_format: df, sample_count: server.msaa_sample_count, + blend: params.blend.is_some(), depth_test: params.depth_test.is_some(), depth_write: params.depth_write, + cull: match params.cull_face { Some(CullFace::Back) => 2, Some(CullFace::Front) => 1, None => 0 }, + }; + let key_hash = { let mut h = DefaultHasher::new(); key.hash(&mut h); h.finish() }; + { let cache = server.pipeline_cache.borrow(); if let Some(p) = cache.get(&key_hash) { return p.clone(); } } + + let blend_state = params.blend.as_ref().map(|bp| wgpu::BlendState { + color: wgpu::BlendComponent { src_factor: blend_factor_to_wgpu(bp.func.sfactor), dst_factor: blend_factor_to_wgpu(bp.func.dfactor), operation: blend_mode_to_wgpu(bp.equation.rgb) }, + alpha: wgpu::BlendComponent { src_factor: blend_factor_to_wgpu(bp.func.alpha_sfactor), dst_factor: blend_factor_to_wgpu(bp.func.alpha_dfactor), operation: blend_mode_to_wgpu(bp.equation.alpha) }, + }); + + let depth_stencil = if params.depth_test.is_some() || params.depth_write { + Some(wgpu::DepthStencilState { + format: df.unwrap_or(wgpu::TextureFormat::Depth32Float), + depth_write_enabled: Some(params.depth_write), + depth_compare: Some(params.depth_test.map(compare_func_to_wgpu).unwrap_or(wgpu::CompareFunction::Always)), + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }) + } else { df.map(|f| wgpu::DepthStencilState { format: f, depth_write_enabled: Some(false), depth_compare: Some(wgpu::CompareFunction::Always), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default() }) }; + + let cull = match params.cull_face { Some(CullFace::Back) => Some(wgpu::Face::Back), Some(CullFace::Front) => Some(wgpu::Face::Front), None => None }; + + let topo = match geo.element_kind() { + fyrox_graphics::ElementKind::Triangle => wgpu::PrimitiveTopology::TriangleList, + fyrox_graphics::ElementKind::Line => wgpu::PrimitiveTopology::LineList, + fyrox_graphics::ElementKind::Point => wgpu::PrimitiveTopology::PointList, + }; + + let pipeline = server.state.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("RP"), layout: Some(program.pipeline_layout()), + vertex: wgpu::VertexState { module: program.vertex_module(), entry_point: Some("main"), buffers: geo.vertex_buffer_layouts(), compilation_options: Default::default() }, + fragment: Some(wgpu::FragmentState { module: program.fragment_module(), entry_point: Some("main"), targets: &[Some(wgpu::ColorTargetState { format: cf, blend: blend_state, write_mask: wgpu::ColorWrites::ALL })], compilation_options: Default::default() }), + primitive: wgpu::PrimitiveState { topology: topo, strip_index_format: None, front_face: wgpu::FrontFace::Ccw, cull_mode: cull, ..Default::default() }, + depth_stencil, + multisample: wgpu::MultisampleState { count: server.msaa_sample_count, mask: !0, alpha_to_coverage_enabled: false }, + multiview_mask: None, + cache: None, + }); + + server.pipeline_cache.borrow_mut().insert(key_hash, pipeline.clone()); + pipeline + } + + fn do_draw(&self, instance_count: u32, geometry: &GpuGeometryBuffer, viewport: Rect, program: &GpuProgram, params: &DrawParameters, resources: &[ResourceBindGroup], element_range: ElementRange) -> Result { + let server = self.server.upgrade().unwrap(); + let geo = geometry.as_any().downcast_ref::().unwrap(); + let prog = program.as_any().downcast_ref::().unwrap(); + + let (offset, count) = match element_range { ElementRange::Full => (0, geo.element_count()), ElementRange::Specific { offset, count } => (offset, count) }; + if offset + count > geo.element_count() { return Err(FrameworkError::InvalidElementRange { start: offset, end: offset + count, total: geo.element_count() }); } + if count == 0 { return Ok(DrawCallStatistics { triangles: 0 }); } + + let surface_tex = if self.is_backbuffer { + match server.surface.get_current_texture() { + wgpu::CurrentSurfaceTexture::Success(t) | wgpu::CurrentSurfaceTexture::Suboptimal(t) => Some(t), + other => return Err(FrameworkError::Custom(format!("Surface texture error: {other:?}"))), + } + } else { None }; + + let cf = if self.is_backbuffer { server.surface_config.read().unwrap().format } + else if let Some(fc) = self.color_attachments.first() { texture_format_for_attachment(&fc.texture) } + else { wgpu::TextureFormat::Rgba8Unorm }; + + let df = self.depth_attachment.as_ref().map(|a| texture_format_for_attachment(&a.texture)); + let pipeline = self.get_or_create_pipeline(&server, prog, params, geo, cf, df); + + let bind_group = create_bind_group(&server, prog, resources); + let mut encoder = server.state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("DrawEnc") }); + + let color_view = if self.is_backbuffer { surface_tex.as_ref().unwrap().texture.create_view(&wgpu::TextureViewDescriptor::default()) } + else { self.color_attachments.first().unwrap().texture.as_any().downcast_ref::().unwrap().wgpu_view().clone() }; + + let depth_view = self.depth_attachment.as_ref().map(|a| a.texture.as_any().downcast_ref::().unwrap().wgpu_view().clone()); + + { + let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("DrawRP"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &color_view, resolve_target: None, depth_slice: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store } })], + depth_stencil_attachment: depth_view.as_ref().map(|v| wgpu::RenderPassDepthStencilAttachment { view: v, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store }), stencil_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store }) }), + ..Default::default() + }); + + rp.set_viewport(viewport.x() as f32, viewport.y() as f32, viewport.w() as f32, viewport.h() as f32, 0.0, 1.0); + rp.set_pipeline(&pipeline); + if let Some(bg) = &bind_group { rp.set_bind_group(0, bg, &[]); } + for (i, vb) in geo.vertex_buffers().iter().enumerate() { rp.set_vertex_buffer(i as u32, vb.slice(..)); } + rp.set_index_buffer(geo.element_buffer().slice(..), wgpu::IndexFormat::Uint32); + + let ipe = geo.element_kind().index_per_element(); + let idx_count = (count * ipe) as u32; + let base_vert = (offset * ipe) as i32; + rp.draw_indexed(0..idx_count, base_vert, 0..instance_count); + } + + server.state.queue.submit(std::iter::once(encoder.finish())); + Ok(DrawCallStatistics { triangles: count * instance_count as usize }) + } +} + +impl GpuFrameBufferTrait for WgpuFrameBuffer { + fn color_attachments(&self) -> &[Attachment] { &self.color_attachments } + fn depth_attachment(&self) -> Option<&Attachment> { self.depth_attachment.as_ref() } + fn set_cubemap_face(&self, i: usize, face: CubeMapFace, level: usize) { + if let Some(a) = self.color_attachments.get(i) { a.set_cube_map_face(Some(face)); a.set_level(level); } + } + fn blit_to(&self, _dest: &GpuFrameBuffer, _sx0: i32, _sy0: i32, _sx1: i32, _sy1: i32, _dx0: i32, _dy0: i32, _dx1: i32, _dy1: i32, _c: bool, _d: bool, _s: bool) { + log::warn!("blit_to not yet implemented for wgpu"); + } + fn read_pixels(&self, read_target: ReadTarget) -> Option> { + let server = self.server.upgrade()?; + let texture = match read_target { + ReadTarget::Depth | ReadTarget::Stencil => &self.depth_attachment.as_ref()?.texture, + ReadTarget::Color(i) => &self.color_attachments.get(i)?.texture, + }; + let wtex = texture.as_any().downcast_ref::()?; + if let GpuTextureKind::Rectangle { width, height } = texture.kind() { + let pk = texture.pixel_kind(); + let total = image_2d_size_bytes(pk, width, height); + let buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { label: Some("ReadPx"), size: total as u64, usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false }); + let mut enc = server.state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("ReadPxEnc") }); + let fmt = wtex.format(); + let bps = fmt.block_copy_size(None).unwrap_or(4); + enc.copy_texture_to_buffer(wgpu::TexelCopyTextureInfo { texture: wtex.wgpu_texture(), mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, wgpu::TexelCopyBufferInfo { buffer: &buf, layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((width as u32 * bps).max(256)), rows_per_image: Some(height as u32) } }, wgpu::Extent3d { width: width as u32, height: height as u32, depth_or_array_layers: 1 }); + server.state.queue.submit(std::iter::once(enc.finish())); + let slice = buf.slice(..); + let (tx, rx) = std::sync::mpsc::channel(); + slice.map_async(wgpu::MapMode::Read, move |r| { tx.send(r).ok(); }); + server.state.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None }).ok(); + rx.recv().ok()?.ok()?; + let mapped = slice.get_mapped_range(); + let mut result = vec![0u8; total]; + result.copy_from_slice(&mapped); + drop(mapped); + buf.unmap(); + Some(result) + } else { None } + } + fn clear(&self, _viewport: Rect, _color: Option, _depth: Option, _stencil: Option) {} + fn draw(&self, geometry: &GpuGeometryBuffer, viewport: Rect, program: &GpuProgram, params: &DrawParameters, resources: &[ResourceBindGroup], element_range: ElementRange) -> Result { + self.do_draw(1, geometry, viewport, program, params, resources, element_range) + } + fn draw_instances(&self, instance_count: usize, geometry: &GpuGeometryBuffer, viewport: Rect, program: &GpuProgram, params: &DrawParameters, resources: &[ResourceBindGroup], element_range: ElementRange) -> Result { + self.do_draw(instance_count as u32, geometry, viewport, program, params, resources, element_range) + } +} + +fn create_bind_group(server: &WgpuGraphicsServer, program: &WgpuProgram, groups: &[ResourceBindGroup]) -> Option { + let mut entries = Vec::new(); + for group in groups { + for binding in group.bindings { + match binding { + ResourceBinding::Texture { texture, sampler, binding: loc } => { + let wt = texture.as_any().downcast_ref::()?; + let ws = sampler.as_any().downcast_ref::()?; + entries.push(wgpu::BindGroupEntry { binding: *loc as u32, resource: wgpu::BindingResource::TextureView(wt.wgpu_view()) }); + entries.push(wgpu::BindGroupEntry { binding: (*loc + 100) as u32, resource: wgpu::BindingResource::Sampler(ws.wgpu_sampler()) }); + } + ResourceBinding::Buffer { buffer, binding: loc, data_usage } => { + let wb = buffer.as_any().downcast_ref::()?; + match data_usage { + BufferDataUsage::UseEverything => entries.push(wgpu::BindGroupEntry { binding: (*loc + 200) as u32, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: wb.wgpu_buffer(), offset: 0, size: None }) }), + BufferDataUsage::UseSegment { offset, size } => entries.push(wgpu::BindGroupEntry { binding: (*loc + 200) as u32, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: wb.wgpu_buffer(), offset: *offset as u64, size: Some(std::num::NonZeroU64::new(*size as u64).unwrap()) }) }), + } + } + } + } + } + if entries.is_empty() { return None; } + Some(server.state.device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("BG"), layout: program.bind_group_layout(), entries: &entries })) +} diff --git a/fyrox-graphics-wgpu/src/geometry_buffer.rs b/fyrox-graphics-wgpu/src/geometry_buffer.rs new file mode 100644 index 0000000000..cb9338457f --- /dev/null +++ b/fyrox-graphics-wgpu/src/geometry_buffer.rs @@ -0,0 +1,135 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::server::WgpuGraphicsServer; +use fyrox_graphics::{ + core::{array_as_u8_slice, math::TriangleDefinition}, + error::FrameworkError, + geometry_buffer::{AttributeKind, ElementsDescriptor, GpuGeometryBufferDescriptor, GpuGeometryBufferTrait}, + ElementKind, +}; +use std::cell::Cell; +use std::rc::Weak; + +fn attribute_format(kind: AttributeKind, cc: usize) -> wgpu::VertexFormat { + match (kind, cc) { + (AttributeKind::Float, 1) => wgpu::VertexFormat::Float32, + (AttributeKind::Float, 2) => wgpu::VertexFormat::Float32x2, + (AttributeKind::Float, 3) => wgpu::VertexFormat::Float32x3, + (AttributeKind::Float, 4) => wgpu::VertexFormat::Float32x4, + (AttributeKind::UnsignedByte, 4) => wgpu::VertexFormat::Uint8x4, + (AttributeKind::UnsignedShort, 2) => wgpu::VertexFormat::Uint16x2, + (AttributeKind::UnsignedShort, 4) => wgpu::VertexFormat::Uint16x4, + (AttributeKind::UnsignedInt, 1) => wgpu::VertexFormat::Uint32, + (AttributeKind::UnsignedInt, 2) => wgpu::VertexFormat::Uint32x2, + (AttributeKind::UnsignedInt, 3) => wgpu::VertexFormat::Uint32x3, + (AttributeKind::UnsignedInt, 4) => wgpu::VertexFormat::Uint32x4, + _ => wgpu::VertexFormat::Float32x4, + } +} + +pub struct WgpuGeometryBuffer { + _server: Weak, + vertex_buffers: Vec, + vertex_buffer_layouts: Vec>, + element_buffer: wgpu::Buffer, + element_count: Cell, + element_kind: ElementKind, +} + +impl WgpuGeometryBuffer { + pub fn new(server: &WgpuGraphicsServer, desc: GpuGeometryBufferDescriptor) -> Result { + let mut vertex_buffers = Vec::new(); + let mut vertex_buffer_layouts = Vec::new(); + + for (i, buf) in desc.buffers.iter().enumerate() { + let data_size = buf.data.bytes.map(|b| b.len()).unwrap_or(0); + let label_str = format!("{}VB{i}", desc.name); + let buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor { + label: if server.named_objects { Some(&label_str) } else { None }, + size: data_size.max(1) as u64, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + if let Some(data) = buf.data.bytes { if !data.is_empty() { server.state.queue.write_buffer(&buffer, 0, data); } } + + let mut attributes = Vec::new(); + let mut offset = 0u64; + for attr in buf.attributes { + attributes.push(wgpu::VertexAttribute { format: attribute_format(attr.kind, attr.component_count), offset, shader_location: attr.location }); + offset += (attr.kind.size() * attr.component_count) as u64; + } + + let step_mode = if buf.attributes.iter().any(|a| a.divisor > 0) { wgpu::VertexStepMode::Instance } else { wgpu::VertexStepMode::Vertex }; + vertex_buffer_layouts.push(wgpu::VertexBufferLayout { array_stride: buf.data.element_size as u64, step_mode, attributes: attributes.leak() }); + vertex_buffers.push(buffer); + } + + let (element_count, element_data) = match desc.elements { + ElementsDescriptor::Triangles(t) => (t.len(), array_as_u8_slice(t)), + ElementsDescriptor::Lines(l) => (l.len(), array_as_u8_slice(l)), + ElementsDescriptor::Points(p) => (p.len(), array_as_u8_slice(p)), + }; + + let ib_label = format!("{}IB", desc.name); + let element_buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor { + label: if server.named_objects { Some(&ib_label) } else { None }, + size: element_data.len().max(1) as u64, + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + if !element_data.is_empty() { server.state.queue.write_buffer(&element_buffer, 0, element_data); } + + Ok(Self { _server: server.weak_ref(), vertex_buffers, vertex_buffer_layouts, element_buffer, element_count: Cell::new(element_count), element_kind: desc.elements.element_kind() }) + } + + pub fn element_count(&self) -> usize { self.element_count.get() } + pub fn element_kind(&self) -> ElementKind { self.element_kind } + pub fn vertex_buffers(&self) -> &[wgpu::Buffer] { &self.vertex_buffers } + pub fn vertex_buffer_layouts(&self) -> &[wgpu::VertexBufferLayout<'static>] { &self.vertex_buffer_layouts } + pub fn element_buffer(&self) -> &wgpu::Buffer { &self.element_buffer } +} + +impl GpuGeometryBufferTrait for WgpuGeometryBuffer { + fn set_buffer_data(&self, buffer: usize, data: &[u8]) { + if let Some(server) = self._server.upgrade() { + if let Some(buf) = self.vertex_buffers.get(buffer) { server.state.queue.write_buffer(buf, 0, data); } + } + } + fn element_count(&self) -> usize { self.element_count.get() } + fn set_triangles(&self, triangles: &[TriangleDefinition]) { + if let Some(server) = self._server.upgrade() { + self.element_count.set(triangles.len()); + server.state.queue.write_buffer(&self.element_buffer, 0, array_as_u8_slice(triangles)); + } + } + fn set_lines(&self, lines: &[[u32; 2]]) { + if let Some(server) = self._server.upgrade() { + self.element_count.set(lines.len()); + server.state.queue.write_buffer(&self.element_buffer, 0, array_as_u8_slice(lines)); + } + } + fn set_points(&self, points: &[u32]) { + if let Some(server) = self._server.upgrade() { + self.element_count.set(points.len()); + server.state.queue.write_buffer(&self.element_buffer, 0, array_as_u8_slice(points)); + } + } +} diff --git a/fyrox-graphics-wgpu/src/lib.rs b/fyrox-graphics-wgpu/src/lib.rs index e6f30f703a..cc2f8ec070 100644 --- a/fyrox-graphics-wgpu/src/lib.rs +++ b/fyrox-graphics-wgpu/src/lib.rs @@ -18,4 +18,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -pub mod server; \ No newline at end of file +pub mod buffer; +pub mod framebuffer; +pub mod geometry_buffer; +pub mod program; +pub mod query; +pub mod read_buffer; +pub mod sampler; +pub mod server; +pub mod texture; diff --git a/fyrox-graphics-wgpu/src/program.rs b/fyrox-graphics-wgpu/src/program.rs new file mode 100644 index 0000000000..383744adca --- /dev/null +++ b/fyrox-graphics-wgpu/src/program.rs @@ -0,0 +1,445 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::server::WgpuGraphicsServer; +use fyrox_graphics::{ + core::log::{Log, MessageKind}, + error::FrameworkError, + gpu_program::{GpuProgramTrait, GpuShaderTrait, SamplerKind, ShaderKind, ShaderPropertyKind, ShaderResourceDefinition, ShaderResourceKind}, +}; +use std::rc::Weak; + +fn count_lines(src: &str) -> isize { src.bytes().filter(|b| *b == b'\n').count() as isize } + +/// Returns the Vulkan GLSL 450 separate texture type name. +fn vulkan_texture_type(kind: SamplerKind) -> &'static str { + match kind { + SamplerKind::Sampler1D => "texture1D", + SamplerKind::Sampler2D => "texture2D", + SamplerKind::Sampler3D => "texture3D", + SamplerKind::SamplerCube => "textureCube", + SamplerKind::USampler1D => "utexture1D", + SamplerKind::USampler2D => "utexture2D", + SamplerKind::USampler3D => "utexture3D", + SamplerKind::USamplerCube => "utextureCube", + } +} + +/// Returns the combined sampler constructor name (e.g. "sampler2D", "samplerCube"). +fn sampler_constructor_name(kind: SamplerKind) -> &'static str { + match kind { + SamplerKind::Sampler1D => "sampler1D", + SamplerKind::Sampler2D => "sampler2D", + SamplerKind::Sampler3D => "sampler3D", + SamplerKind::SamplerCube => "samplerCube", + SamplerKind::USampler1D => "usampler1D", + SamplerKind::USampler2D => "usampler2D", + SamplerKind::USampler3D => "usampler3D", + SamplerKind::USamplerCube => "usamplerCube", + } +} + +/// Generates separate texture + sampler uniform declarations for wgpu bindings, +/// plus uniform block declarations with `layout(std140, binding=...)`. +fn generate_resource_declarations(resources: &[ShaderResourceDefinition], source: &mut String, line_offset: &mut isize) { + let mut tex_decls = String::new(); + for res in resources { + match res.kind { + ShaderResourceKind::Texture { kind, .. } => { + let tex_type = vulkan_texture_type(kind); + tex_decls += &format!("layout(binding={}) uniform {} {}_tex;\n", res.binding, tex_type, res.name); + tex_decls += &format!("layout(binding={}) uniform sampler {}_samp;\n", res.binding + 100, res.name); + } + ShaderResourceKind::PropertyGroup(ref fields) => { + if fields.is_empty() { continue; } + let mut block = format!("struct T{}{{\n", res.name); + for f in fields { + let n = &f.name; + match f.kind { + ShaderPropertyKind::Float { .. } => block += &format!("\tfloat {n};\n"), + ShaderPropertyKind::FloatArray { max_len, .. } => block += &format!("\tfloat {n}[{max_len}];\n"), + ShaderPropertyKind::Int { .. } => block += &format!("\tint {n};\n"), + ShaderPropertyKind::IntArray { max_len, .. } => block += &format!("\tint {n}[{max_len}];\n"), + ShaderPropertyKind::UInt { .. } => block += &format!("\tuint {n};\n"), + ShaderPropertyKind::UIntArray { max_len, .. } => block += &format!("\tuint {n}[{max_len}];\n"), + ShaderPropertyKind::Bool { .. } => block += &format!("\tuint {n};\n"), + ShaderPropertyKind::Vector2 { .. } => block += &format!("\tvec2 {n};\n"), + ShaderPropertyKind::Vector2Array { max_len, .. } => block += &format!("\tvec2 {n}[{max_len}];\n"), + ShaderPropertyKind::Vector3 { .. } => block += &format!("\tvec3 {n};\n"), + ShaderPropertyKind::Vector3Array { max_len, .. } => block += &format!("\tvec3 {n}[{max_len}];\n"), + ShaderPropertyKind::Vector4 { .. } => block += &format!("\tvec4 {n};\n"), + ShaderPropertyKind::Vector4Array { max_len, .. } => block += &format!("\tvec4 {n}[{max_len}];\n"), + ShaderPropertyKind::Matrix2 { .. } => block += &format!("\tmat2 {n};\n"), + ShaderPropertyKind::Matrix2Array { max_len, .. } => block += &format!("\tmat2 {n}[{max_len}];\n"), + ShaderPropertyKind::Matrix3 { .. } => block += &format!("\tmat3 {n};\n"), + ShaderPropertyKind::Matrix3Array { max_len, .. } => block += &format!("\tmat3 {n}[{max_len}];\n"), + ShaderPropertyKind::Matrix4 { .. } => block += &format!("\tmat4 {n};\n"), + ShaderPropertyKind::Matrix4Array { max_len, .. } => block += &format!("\tmat4 {n}[{max_len}];\n"), + ShaderPropertyKind::Color { .. } => block += &format!("\tvec4 {n};\n"), + } + } + block += "};\n"; + block += &format!("layout(std140, binding={}) uniform U{} {{ T{} {}; }};\n", res.binding + 200, res.name, res.name, res.name); + source.insert_str(0, &block); + *line_offset -= count_lines(&block); + } + } + } + source.insert_str(0, &tex_decls); + *line_offset -= count_lines(&tex_decls); +} + +/// Preprocesses shader source for naga/wgpu compatibility. +/// +/// Transformations: +/// 1. `texture(name, uv)` → `texture(samplerXxx(name_tex, name_samp), uv)` for all texture functions +/// 2. `textureSize(name, ...)` → `textureSize(name_tex, ...)` +/// 3. `gl_InstanceID` → `gl_InstanceIndex`, `gl_VertexID` → `gl_VertexIndex` +/// 4. `properties.boolField` → `bool(properties.boolField)` (uint→bool cast) +/// 5. Shared function calls with texture args: `S_PointShadow(..., name)` → `S_PointShadow(..., name_tex, name_samp)` +fn preprocess_shader(source: &mut String, resources: &[ShaderResourceDefinition]) { + // Collect texture resources (longest first to avoid partial matches) + let mut tex_resources: Vec<(&str, SamplerKind)> = resources + .iter() + .filter_map(|r| match r.kind { + ShaderResourceKind::Texture { kind, .. } => Some((r.name.as_str(), kind)), + _ => None, + }) + .collect(); + tex_resources.sort_by(|a, b| b.0.len().cmp(&a.0.len())); + + for (name, kind) in &tex_resources { + let constructor = sampler_constructor_name(*kind); + let sampler_expr = format!("{constructor}({name}_tex, {name}_samp)"); + + // Transform texture functions: texture(name, ...) → texture(samplerXxx(name_tex, name_samp), ...) + for func_name in &["texture", "textureLod", "textureGrad", "textureOffset", "textureLodOffset", + "texelFetch", "texelFetchOffset", "textureProj", "textureProjLod"] { + for pattern in &[ + format!("{func_name}({name},"), + format!("{func_name}({name} ,"), + format!("{func_name}( {name},"), + ] { + *source = source.replace(pattern, &format!("{func_name}({sampler_expr},")); + } + } + + // textureSize takes the raw texture (not sampler) + for pattern in &[ + format!("textureSize({name},"), + format!("textureSize({name} ,"), + ] { + *source = source.replace(pattern, &format!("textureSize({name}_tex,")); + } + + // Replace shared function calls that pass texture resource names as arguments. + // These functions (in shared.glsl) take separate texture2D + sampler parameters. + // The call site passes the combined name; we split it into _tex and _samp. + for func_name in &[ + "S_PointShadow", "S_SpotShadowFactor", + "S_ComputeParallaxTextureCoordinates", "S_FetchMatrix", "S_FetchBlendShapeOffsets", + "Internal_FetchHeight", "CsmGetShadow", + ] { + replace_texture_arg_in_calls(source, func_name, name); + } + } + + // Replace OpenGL built-in names with Vulkan equivalents + *source = source.replace("gl_InstanceID", "gl_InstanceIndex"); + *source = source.replace("gl_VertexID", "gl_VertexIndex"); + + // Wrap bool uniform property accesses with bool() cast (uint→bool) + for res in resources { + let ShaderResourceKind::PropertyGroup(ref fields) = res.kind else { continue }; + let block_name = &*res.name; + for f in fields { + if !matches!(f.kind, ShaderPropertyKind::Bool { .. }) { continue; } + let field_name = &*f.name; + let search = format!("{block_name}.{field_name}"); + let replacement = format!("bool({block_name}.{field_name})"); + *source = source.replace(&search, &replacement); + } + } +} + +/// Finds calls to `funcName(...)` in the source where `texture_name` appears as an argument, +/// and replaces it with `texture_name_tex, texture_name_samp`. +/// Handles multiple cases: +/// 1. Last argument: `funcName(..., name)` → `funcName(..., name_tex, name_samp)` +/// 2. Two consecutive args: `funcName(..., name, name, ...)` → `funcName(..., name_tex, name_samp, ...)` +/// 3. First/middle argument: `funcName(name, ...)` → `funcName(name_tex, name_samp, ...)` +fn replace_texture_arg_in_calls(source: &mut String, func_name: &str, texture_name: &str) { + let search = format!("{func_name}("); + // Process from end to start to avoid position shifts + let mut positions: Vec = Vec::new(); + let mut pos = 0; + while let Some(start) = source[pos..].find(&search) { + positions.push(pos + start); + pos = pos + start + search.len(); + } + + for &call_start in positions.iter().rev() { + let paren_start = call_start + search.len() - 1; + let Some((args_str, call_end)) = extract_call_args(source, paren_start) else { continue }; + + // Case 1: last argument is the texture name + let trimmed = args_str.trim_end(); + if trimmed.ends_with(texture_name) { + let name_start = trimmed.len() - texture_name.len(); + let preceded_ok = name_start == 0 || { + let prev = trimmed.as_bytes()[name_start - 1]; + matches!(prev, b',' | b' ' | b'\t' | b'\n') + }; + if preceded_ok { + let before = &source[..paren_start + 1 + name_start]; + let after = &source[call_end - 1..]; + *source = format!("{before}{texture_name}_tex, {texture_name}_samp{after}"); + continue; + } + } + + // Case 2: two consecutive args are the texture name (e.g., CsmGetShadow(name, name, ...)) + for sep in &[",", ", ", ",\n", ",\t"] { + let double_pattern = format!("{texture_name}{sep}{texture_name}"); + if let Some(found) = args_str.find(&double_pattern) { + let abs_in_args = found; + let preceded_ok = abs_in_args == 0 || { + let prev = args_str.as_bytes()[abs_in_args - 1]; + matches!(prev, b',' | b' ' | b'\t' | b'\n') + }; + let after_end = abs_in_args + double_pattern.len(); + let followed_ok = after_end >= args_str.len() || { + let next = args_str.as_bytes()[after_end]; + matches!(next, b',' | b' ' | b'\t' | b'\n') + }; + if preceded_ok && followed_ok { + let args_start = paren_start + 1; + if let Some(found_in_source) = source[args_start..call_end - 1].find(&double_pattern) { + let abs_pos = args_start + found_in_source; + let before = &source[..abs_pos]; + let after = &source[abs_pos + double_pattern.len()..]; + *source = format!("{before}{texture_name}_tex,{texture_name}_samp{after}"); + } + break; + } + } + } + + // Case 3: texture name is the first/middle argument (not last, not doubled) + // Find the texture name as a standalone identifier followed by a comma + for sep in &[",", ", "] { + let pattern = format!("{texture_name}{sep}"); + if let Some(found) = args_str.find(&pattern) { + let abs_in_args = found; + let preceded_ok = abs_in_args == 0 || { + let prev = args_str.as_bytes()[abs_in_args - 1]; + matches!(prev, b',' | b' ' | b'\t' | b'\n') + }; + // Make sure this isn't already handled by case 2 (double pattern) + let after_sep = abs_in_args + pattern.len(); + let is_double = after_sep + texture_name.len() <= args_str.len() + && args_str[after_sep..].starts_with(texture_name); + if preceded_ok && !is_double { + let args_start = paren_start + 1; + if let Some(found_in_source) = source[args_start..call_end - 1].find(&pattern) { + let abs_pos = args_start + found_in_source; + let before = &source[..abs_pos]; + let after = &source[abs_pos + pattern.len()..]; + *source = format!("{before}{texture_name}_tex,{texture_name}_samp{sep}{after}"); + } + break; + } + } + } + } +} + +/// Finds the arguments of a function call starting at the opening paren. +/// Returns (args_string, end_index) where end_index is the index AFTER the closing paren. +fn extract_call_args(source: &str, paren_start: usize) -> Option<(String, usize)> { + let bytes = source.as_bytes(); + if bytes.get(paren_start) != Some(&b'(') { return None; } + let mut depth = 1; + let mut i = paren_start + 1; + while i < bytes.len() && depth > 0 { + match bytes[i] { + b'(' => depth += 1, + b')' => depth -= 1, + _ => {} + } + i += 1; + } + if depth == 0 { + Some((source[paren_start + 1..i - 1].to_string(), i)) + } else { + None + } +} + +fn naga_stage(kind: &ShaderKind) -> naga::ShaderStage { + match kind { ShaderKind::Vertex => naga::ShaderStage::Vertex, ShaderKind::Fragment => naga::ShaderStage::Fragment } +} + +fn prepare_source(code: &str) -> String { + let mut src = String::from("#version 450\n// include 'shared.glsl'\n"); + src += include_str!("shaders/shared.glsl"); + src += "\n// end of include\n"; + src += code; + src +} + +fn compile_glsl(device: &wgpu::Device, name: &str, kind: &ShaderKind, glsl: &str) -> Result { + let mut frontend = naga::front::glsl::Frontend::default(); + let module = frontend.parse(&naga::front::glsl::Options { stage: naga_stage(kind), defines: Default::default() }, glsl).map_err(|errors| { + let msg = format!("Failed to parse GLSL for {name}:\n{errors}"); + Log::writeln(MessageKind::Error, msg.clone()); + FrameworkError::ShaderCompilationFailed { shader_name: name.to_owned(), error_message: msg } + })?; + + // Use relaxed validation — naga's GLSL frontend may produce types that don't + // pass strict WGSL-oriented validation (e.g., bool in uniform blocks). + let info = naga::valid::Validator::new(naga::valid::ValidationFlags::empty(), naga::valid::Capabilities::all()) + .validate(&module) + .map_err(|e| { + let msg = format!("Naga validation failed for {name}: {e}"); + Log::writeln(MessageKind::Error, msg.clone()); + FrameworkError::ShaderCompilationFailed { shader_name: name.to_owned(), error_message: msg } + })?; + + // Convert naga module to SPIR-V for wgpu + let spv = naga::back::spv::write_vec(&module, &info, &naga::back::spv::Options { + flags: naga::back::spv::WriterFlags::empty(), + ..Default::default() + }, None).map_err(|e| { + let msg = format!("SPIR-V generation failed for {name}: {e}"); + Log::writeln(MessageKind::Error, msg.clone()); + FrameworkError::ShaderCompilationFailed { shader_name: name.to_owned(), error_message: msg } + })?; + + let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some(name), + source: wgpu::ShaderSource::SpirV(std::borrow::Cow::Owned(spv)), + }); + + Log::writeln(MessageKind::Information, format!("Shader {name} compiled successfully!")); + Ok(shader_module) +} + +fn create_bind_group_layout(device: &wgpu::Device, resources: &[ShaderResourceDefinition]) -> wgpu::BindGroupLayout { + let mut entries = Vec::new(); + for res in resources { + match res.kind { + ShaderResourceKind::Texture { kind, .. } => { + let vd = match kind { + SamplerKind::Sampler1D | SamplerKind::USampler1D => wgpu::TextureViewDimension::D1, + SamplerKind::Sampler2D | SamplerKind::USampler2D => wgpu::TextureViewDimension::D2, + SamplerKind::Sampler3D | SamplerKind::USampler3D => wgpu::TextureViewDimension::D3, + SamplerKind::SamplerCube | SamplerKind::USamplerCube => wgpu::TextureViewDimension::Cube, + }; + let st = match kind { + SamplerKind::USampler1D | SamplerKind::USampler2D | SamplerKind::USampler3D | SamplerKind::USamplerCube => wgpu::TextureSampleType::Uint, + _ => wgpu::TextureSampleType::Float { filterable: true }, + }; + entries.push(wgpu::BindGroupLayoutEntry { binding: res.binding as u32, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: st, view_dimension: vd, multisampled: false }, count: None }); + entries.push(wgpu::BindGroupLayoutEntry { binding: (res.binding + 100) as u32, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None }); + } + ShaderResourceKind::PropertyGroup { .. } => { + entries.push(wgpu::BindGroupLayoutEntry { binding: (res.binding + 200) as u32, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None }, count: None }); + } + } + } + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("ShaderBindGroupLayout"), entries: &entries }) +} + +pub struct WgpuShader { + _server: Weak, + module: wgpu::ShaderModule, + _kind: ShaderKind, +} + +impl GpuShaderTrait for WgpuShader {} + +impl WgpuShader { + pub fn new(server: &WgpuGraphicsServer, name: String, kind: ShaderKind, mut source: String, resources: &[ShaderResourceDefinition], mut line_offset: isize) -> Result { + for r in resources { + for o in resources { + if std::ptr::eq(r, o) { continue; } + if std::mem::discriminant(&r.kind) == std::mem::discriminant(&o.kind) { + if r.binding == o.binding { return Err(FrameworkError::Custom(format!("Resource {} and {} same binding {}", r.name, o.name, r.binding))); } + if r.name == o.name { return Err(FrameworkError::Custom(format!("Duplicate resource name {}", r.name))); } + } + } + } + generate_resource_declarations(resources, &mut source, &mut line_offset); + preprocess_shader(&mut source, resources); + let full = prepare_source(&source); + let module = compile_glsl(&server.state.device, &name, &kind, &full)?; + Ok(Self { _server: server.weak_ref(), module, _kind: kind }) + } + + pub fn wgpu_module(&self) -> &wgpu::ShaderModule { &self.module } +} + +pub struct WgpuProgram { + _server: Weak, + name: String, + vertex_module: wgpu::ShaderModule, + fragment_module: wgpu::ShaderModule, + bind_group_layout: wgpu::BindGroupLayout, + pipeline_layout: wgpu::PipelineLayout, + resources: Vec, +} + +impl GpuProgramTrait for WgpuProgram {} + +impl WgpuProgram { + pub fn from_source(server: &WgpuGraphicsServer, name: &str, vs: String, vs_off: isize, fs: String, fs_off: isize, resources: &[ShaderResourceDefinition]) -> Result { + let vert = WgpuShader::new(server, format!("{name}_VS"), ShaderKind::Vertex, vs, resources, vs_off)?; + let frag = WgpuShader::new(server, format!("{name}_FS"), ShaderKind::Fragment, fs, resources, fs_off)?; + Self::from_modules(server, name, &vert, &frag, resources) + } + + pub fn from_shaders(server: &WgpuGraphicsServer, name: &str, vs: &fyrox_graphics::gpu_program::GpuShader, fs: &fyrox_graphics::gpu_program::GpuShader, resources: &[ShaderResourceDefinition]) -> Result { + let vert = vs.as_any().downcast_ref::().ok_or_else(|| FrameworkError::Custom("Expected WgpuShader".into()))?; + let frag = fs.as_any().downcast_ref::().ok_or_else(|| FrameworkError::Custom("Expected WgpuShader".into()))?; + Self::from_modules(server, name, vert, frag, resources) + } + + fn from_modules(server: &WgpuGraphicsServer, name: &str, vert: &WgpuShader, frag: &WgpuShader, resources: &[ShaderResourceDefinition]) -> Result { + let bgl = create_bind_group_layout(&server.state.device, resources); + let pl = server.state.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some(&format!("{name}_PL")), + bind_group_layouts: &[Some(&bgl)], + ..Default::default() + }); + Ok(Self { + _server: server.weak_ref(), name: name.to_owned(), + vertex_module: vert.module.clone(), fragment_module: frag.module.clone(), + bind_group_layout: bgl, pipeline_layout: pl, resources: resources.to_vec(), + }) + } + + pub fn vertex_module(&self) -> &wgpu::ShaderModule { &self.vertex_module } + pub fn fragment_module(&self) -> &wgpu::ShaderModule { &self.fragment_module } + pub fn pipeline_layout(&self) -> &wgpu::PipelineLayout { &self.pipeline_layout } + pub fn bind_group_layout(&self) -> &wgpu::BindGroupLayout { &self.bind_group_layout } + pub fn resources(&self) -> &[ShaderResourceDefinition] { &self.resources } + pub fn name(&self) -> &str { &self.name } +} diff --git a/fyrox-graphics-wgpu/src/query.rs b/fyrox-graphics-wgpu/src/query.rs new file mode 100644 index 0000000000..5f46849caf --- /dev/null +++ b/fyrox-graphics-wgpu/src/query.rs @@ -0,0 +1,48 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::server::WgpuGraphicsServer; +use fyrox_graphics::{ + error::FrameworkError, + query::{GpuQueryTrait, QueryKind, QueryResult}, +}; +use std::cell::Cell; +use std::fmt::Debug; +use std::rc::Weak; + +pub struct WgpuQuery { + _server: Weak, + active: Cell, +} + +impl Debug for WgpuQuery { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("WgpuQuery").field("active", &self.active.get()).finish() } } + +impl WgpuQuery { + pub fn new(server: &WgpuGraphicsServer) -> Result { + Ok(Self { _server: server.weak_ref(), active: Cell::new(false) }) + } +} + +impl GpuQueryTrait for WgpuQuery { + fn begin(&self, _kind: QueryKind) { self.active.set(true); } + fn end(&self) { self.active.set(false); } + fn is_started(&self) -> bool { self.active.get() } + fn try_get_result(&self) -> Option { Some(QueryResult::SamplesPassed(u32::MAX)) } +} diff --git a/fyrox-graphics-wgpu/src/read_buffer.rs b/fyrox-graphics-wgpu/src/read_buffer.rs new file mode 100644 index 0000000000..a93513b566 --- /dev/null +++ b/fyrox-graphics-wgpu/src/read_buffer.rs @@ -0,0 +1,96 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::server::WgpuGraphicsServer; +use fyrox_graphics::{ + error::FrameworkError, + gpu_texture::GpuTextureKind, + read_buffer::GpuAsyncReadBufferTrait, + core::math::Rect, + framebuffer::GpuFrameBufferTrait, +}; +use std::cell::Cell; +use std::rc::Weak; + +pub struct WgpuAsyncReadBuffer { + server: Weak, + buffer: wgpu::Buffer, + _pixel_count: usize, + pixel_size: usize, + request_pending: Cell, + size_bytes: usize, +} + +impl WgpuAsyncReadBuffer { + pub fn new(server: &WgpuGraphicsServer, name: &str, pixel_size: usize, pixel_count: usize) -> Result { + let size_bytes = pixel_count * pixel_size; + let buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor { + label: if server.named_objects { Some(name) } else { None }, + size: size_bytes.max(1) as u64, + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + Ok(Self { server: server.weak_ref(), buffer, _pixel_count: pixel_count, pixel_size, request_pending: Cell::new(false), size_bytes }) + } +} + +impl GpuAsyncReadBufferTrait for WgpuAsyncReadBuffer { + fn schedule_pixels_transfer(&self, framebuffer: &dyn GpuFrameBufferTrait, color_buffer_index: u32, _rect: Option>) -> Result<(), FrameworkError> { + if self.request_pending.get() { return Ok(()); } + let Some(server) = self.server.upgrade() else { return Err(FrameworkError::GraphicsServerUnavailable); }; + let color_attachment = framebuffer.color_attachments().get(color_buffer_index as usize).ok_or_else(|| FrameworkError::Custom("No color attachment".into()))?; + let wgpu_tex = color_attachment.texture.as_any().downcast_ref::().ok_or_else(|| FrameworkError::Custom("Expected WgpuTexture".into()))?; + let (w, h) = match color_attachment.texture.kind() { GpuTextureKind::Rectangle { width, height } => (width, height), _ => return Err(FrameworkError::Custom("Only rectangular textures".into())) }; + let bpp = self.pixel_size; + + let mut encoder = server.state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("AsyncReadEnc") }); + encoder.copy_texture_to_buffer( + wgpu::TexelCopyTextureInfo { texture: wgpu_tex.wgpu_texture(), mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, + wgpu::TexelCopyBufferInfo { buffer: &self.buffer, layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((w as u32 * bpp as u32).max(256)), rows_per_image: Some(h as u32) } }, + wgpu::Extent3d { width: w as u32, height: h as u32, depth_or_array_layers: 1 }, + ); + server.state.queue.submit(std::iter::once(encoder.finish())); + self.request_pending.set(true); + Ok(()) + } + + fn is_request_running(&self) -> bool { self.request_pending.get() } + + fn try_read(&self) -> Option> { + if !self.request_pending.get() { return None; } + let server = self.server.upgrade()?; + let slice = self.buffer.slice(..self.size_bytes as u64); + let (tx, rx) = std::sync::mpsc::channel(); + slice.map_async(wgpu::MapMode::Read, move |r| { tx.send(r).ok(); }); + server.state.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None }).ok(); + match rx.recv() { + Ok(Ok(())) => { + let mapped = slice.get_mapped_range(); + let mut result = vec![0u8; self.size_bytes]; + result.copy_from_slice(&mapped); + drop(mapped); + self.buffer.unmap(); + self.request_pending.set(false); + Some(result) + } + _ => { self.request_pending.set(false); None } + } + } +} diff --git a/fyrox-graphics-wgpu/src/sampler.rs b/fyrox-graphics-wgpu/src/sampler.rs new file mode 100644 index 0000000000..4d36f956b6 --- /dev/null +++ b/fyrox-graphics-wgpu/src/sampler.rs @@ -0,0 +1,84 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::server::WgpuGraphicsServer; +use fyrox_graphics::{ + error::FrameworkError, + sampler::{GpuSamplerDescriptor, GpuSamplerTrait, MagnificationFilter, MinificationFilter, WrapMode}, +}; +use std::fmt::Debug; +use std::rc::Weak; + +fn min_filter_to_wgpu(f: MinificationFilter) -> wgpu::FilterMode { + match f { + MinificationFilter::Nearest | MinificationFilter::NearestMipMapNearest | MinificationFilter::NearestMipMapLinear => wgpu::FilterMode::Nearest, + _ => wgpu::FilterMode::Linear, + } +} + +fn mag_filter_to_wgpu(f: MagnificationFilter) -> wgpu::FilterMode { + match f { MagnificationFilter::Nearest => wgpu::FilterMode::Nearest, _ => wgpu::FilterMode::Linear } +} + +fn mipmap_filter_to_wgpu(f: MinificationFilter) -> wgpu::MipmapFilterMode { + match f { + MinificationFilter::NearestMipMapLinear | MinificationFilter::LinearMipMapLinear => wgpu::MipmapFilterMode::Linear, + _ => wgpu::MipmapFilterMode::Nearest, + } +} + +fn wrap_mode_to_wgpu(m: WrapMode) -> wgpu::AddressMode { + match m { + WrapMode::Repeat => wgpu::AddressMode::Repeat, + WrapMode::ClampToEdge => wgpu::AddressMode::ClampToEdge, + WrapMode::ClampToBorder => wgpu::AddressMode::ClampToBorder, + WrapMode::MirroredRepeat | WrapMode::MirrorClampToEdge => wgpu::AddressMode::MirrorRepeat, + } +} + +pub struct WgpuSampler { + _server: Weak, + sampler: wgpu::Sampler, +} + +impl Debug for WgpuSampler { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("WgpuSampler").finish() } } +impl GpuSamplerTrait for WgpuSampler {} + +impl WgpuSampler { + pub fn new(server: &WgpuGraphicsServer, desc: GpuSamplerDescriptor) -> Result { + let sampler = server.state.device.create_sampler(&wgpu::SamplerDescriptor { + label: if server.named_objects { Some("Sampler") } else { None }, + address_mode_u: wrap_mode_to_wgpu(desc.s_wrap_mode), + address_mode_v: wrap_mode_to_wgpu(desc.t_wrap_mode), + address_mode_w: wrap_mode_to_wgpu(desc.r_wrap_mode), + mag_filter: mag_filter_to_wgpu(desc.mag_filter), + min_filter: min_filter_to_wgpu(desc.min_filter), + mipmap_filter: mipmap_filter_to_wgpu(desc.min_filter), + lod_min_clamp: desc.min_lod.max(0.0), + lod_max_clamp: desc.max_lod.max(0.0), + anisotropy_clamp: desc.anisotropy.clamp(1.0, 16.0) as u16, + compare: None, + border_color: None, + }); + Ok(Self { _server: server.weak_ref(), sampler }) + } + + pub fn wgpu_sampler(&self) -> &wgpu::Sampler { &self.sampler } +} diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index 6080d85425..bf8efdda9c 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -18,7 +18,14 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -use std::cell::{Cell, RefCell}; +use crate::buffer::WgpuBuffer; +use crate::framebuffer::WgpuFrameBuffer; +use crate::geometry_buffer::WgpuGeometryBuffer; +use crate::program::{WgpuProgram, WgpuShader}; +use crate::query::WgpuQuery; +use crate::read_buffer::WgpuAsyncReadBuffer; +use crate::sampler::WgpuSampler; +use crate::texture::WgpuTexture; use fyrox_core::futures::executor::block_on; use fyrox_graphics::buffer::{GpuBuffer, GpuBufferDescriptor}; use fyrox_graphics::error::FrameworkError; @@ -34,9 +41,10 @@ use fyrox_graphics::server::{ }; use fyrox_graphics::stats::PipelineStatistics; use fyrox_graphics::{PolygonFace, PolygonFillMode}; +use std::cell::RefCell; +use std::collections::HashMap; use std::rc::{Rc, Weak}; use std::sync::{Arc, RwLock}; -use raw_window_handle::HasDisplayHandle; use winit::event_loop::ActiveEventLoop; use winit::window::{Window, WindowAttributes}; @@ -51,6 +59,12 @@ pub struct WgpuGraphicsServer { pub state: Arc, pub surface: wgpu::Surface<'static>, pub surface_config: RwLock, + pub named_objects: bool, + pub msaa_sample_count: u32, + pub pipeline_cache: RefCell>, + weak_self: RefCell>>, + pub memory_usage: RefCell, + pipeline_statistics: RefCell, } impl WgpuGraphicsServer { @@ -58,7 +72,7 @@ impl WgpuGraphicsServer { vsync: bool, msaa_sample_count: Option, window_target: &ActiveEventLoop, - mut window_attributes: WindowAttributes, + window_attributes: WindowAttributes, named_objects: bool, ) -> Result<(Window, SharedGraphicsServer), FrameworkError> { let window = window_target @@ -66,13 +80,18 @@ impl WgpuGraphicsServer { .map_err(|e| FrameworkError::Custom(format!("Failed to create window: {e}")))?; let size = window.inner_size(); - let display_handle = window_target - .display_handle() - .map_err(|e| FrameworkError::Custom(format!("Failed to get display handle: {e}")))?; + #[cfg(not(target_arch = "wasm32"))] + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_with_display_handle( + Box::new(window_target.owned_display_handle()), + )); - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_with_display_handle(Box::new(window_target.owned_display_handle()))); + #[cfg(target_arch = "wasm32")] + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::GL, + ..Default::default() + }); - // Создаем поверхность отрисовки (Surface) + #[cfg(not(target_arch = "wasm32"))] let surface = unsafe { let target = wgpu::SurfaceTargetUnsafe::from_window(&window) .map_err(|e| FrameworkError::Custom(format!("Failed to get window handle: {e}")))?; @@ -81,6 +100,19 @@ impl WgpuGraphicsServer { .map_err(|e| FrameworkError::Custom(format!("Failed to create surface: {e}")))? }; + #[cfg(target_arch = "wasm32")] + let surface = { + use fyrox_core::wasm_bindgen::JsCast; + use winit::platform::web::WindowExtWebSys; + let canvas = window.canvas().unwrap(); + let web_window = fyrox_core::web_sys::window().unwrap(); + let document = web_window.document().unwrap(); + let body = document.body().unwrap(); + body.append_child(&canvas).expect("Append canvas to HTML body"); + instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas)) + .map_err(|e| FrameworkError::Custom(format!("Failed to create surface: {e}")))? + }; + let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface), @@ -88,190 +120,129 @@ impl WgpuGraphicsServer { })) .map_err(|e| FrameworkError::Custom(format!("No suitable WGPU adapter found: {e}")))?; - // Инициализируем логическое устройство и очередь команд let (device, queue) = block_on(adapter.request_device( &wgpu::DeviceDescriptor { label: None, required_features: wgpu::Features::empty(), - // WebGL doesn't support all of wgpu's features, so if - // we're building for the web we'll have to disable some. required_limits: if cfg!(target_arch = "wasm32") { wgpu::Limits::downlevel_webgl2_defaults() } else { wgpu::Limits::default() }, - experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::Performance, - trace: wgpu::Trace::Off, + ..Default::default() }, )) .map_err(|e| FrameworkError::Custom(format!("Failed to request device: {e}")))?; let surface_caps = surface.get_capabilities(&adapter); - let Some(surface_format) = surface_caps.formats.first().copied() else { - return Err(FrameworkError::Custom( - "Surface has no supported formats".into(), - )); + return Err(FrameworkError::Custom("Surface has no supported formats".into())); }; + let present_mode = if vsync { wgpu::PresentMode::AutoVsync } else { wgpu::PresentMode::AutoNoVsync }; + let surface_config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: surface_format, width: size.width, height: size.height, - present_mode: wgpu::PresentMode::AutoVsync, + present_mode, alpha_mode: surface_caps.alpha_modes[0], view_formats: vec![], desired_maximum_frame_latency: 2, }; - surface.configure(&device, &surface_config); - let state = Self { - state: Arc::new(WgpuState { - instance, - adapter, - device, - queue, - }), + let msaa = msaa_sample_count.unwrap_or(1).max(1) as u32; + + let server = Rc::new(Self { + state: Arc::new(WgpuState { instance, adapter, device, queue }), surface, surface_config: RwLock::new(surface_config), - }; + named_objects, + msaa_sample_count: msaa, + pipeline_cache: RefCell::new(HashMap::new()), + weak_self: RefCell::new(None), + memory_usage: RefCell::new(ServerMemoryUsage::default()), + pipeline_statistics: RefCell::new(PipelineStatistics::default()), + }); - let shared = Rc::new(state); + *server.weak_self.borrow_mut() = Some(Rc::downgrade(&server)); - Ok((window, shared)) + Ok((window, server)) + } + + pub fn weak_ref(&self) -> Weak { + self.weak_self.borrow().clone().unwrap() } } impl GraphicsServer for WgpuGraphicsServer { fn create_buffer(&self, desc: GpuBufferDescriptor) -> Result { - todo!() + Ok(GpuBuffer(Rc::new(WgpuBuffer::new(self, desc)?))) } - fn create_texture(&self, desc: GpuTextureDescriptor) -> Result { - todo!() + Ok(GpuTexture(Rc::new(WgpuTexture::new(self, desc)?))) } - fn create_sampler(&self, desc: GpuSamplerDescriptor) -> Result { - todo!() + Ok(GpuSampler(Rc::new(WgpuSampler::new(self, desc)?))) } - - fn create_frame_buffer( - &self, - depth_attachment: Option, - color_attachments: Vec, - ) -> Result { - todo!() + fn create_frame_buffer(&self, depth: Option, colors: Vec) -> Result { + Ok(GpuFrameBuffer(Rc::new(WgpuFrameBuffer::new(self, depth, colors)?))) } - fn back_buffer(&self) -> GpuFrameBuffer { - todo!() + GpuFrameBuffer(Rc::new(WgpuFrameBuffer::backbuffer(self))) } - fn create_query(&self) -> Result { - todo!() + Ok(GpuQuery(Rc::new(WgpuQuery::new(self)?))) } - - fn create_shader( - &self, - name: String, - kind: ShaderKind, - source: String, - resources: &[ShaderResourceDefinition], - line_offset: isize, - ) -> Result { - todo!() + fn create_shader(&self, name: String, kind: ShaderKind, source: String, resources: &[ShaderResourceDefinition], line_offset: isize) -> Result { + Ok(GpuShader(Rc::new(WgpuShader::new(self, name, kind, source, resources, line_offset)?))) } - - fn create_program( - &self, - name: &str, - vertex_source: String, - vertex_source_line_offset: isize, - fragment_source: String, - fragment_source_line_offset: isize, - resources: &[ShaderResourceDefinition], - ) -> Result { - todo!() + fn create_program(&self, name: &str, vs: String, vs_offset: isize, fs: String, fs_offset: isize, resources: &[ShaderResourceDefinition]) -> Result { + Ok(GpuProgram(Rc::new(WgpuProgram::from_source(self, name, vs, vs_offset, fs, fs_offset, resources)?))) } - - fn create_program_from_shaders( - &self, - name: &str, - vertex_shader: &GpuShader, - fragment_shader: &GpuShader, - resources: &[ShaderResourceDefinition], - ) -> Result { - todo!() + fn create_program_from_shaders(&self, name: &str, vs: &GpuShader, fs: &GpuShader, resources: &[ShaderResourceDefinition]) -> Result { + Ok(GpuProgram(Rc::new(WgpuProgram::from_shaders(self, name, vs, fs, resources)?))) } - - fn create_async_read_buffer( - &self, - name: &str, - pixel_size: usize, - pixel_count: usize, - ) -> Result { - todo!() + fn create_async_read_buffer(&self, name: &str, pixel_size: usize, pixel_count: usize) -> Result { + Ok(GpuAsyncReadBuffer(Rc::new(WgpuAsyncReadBuffer::new(self, name, pixel_size, pixel_count)?))) } - - fn create_geometry_buffer( - &self, - desc: GpuGeometryBufferDescriptor, - ) -> Result { - todo!() + fn create_geometry_buffer(&self, desc: GpuGeometryBufferDescriptor) -> Result { + Ok(GpuGeometryBuffer(Rc::new(WgpuGeometryBuffer::new(self, desc)?))) } - fn weak(&self) -> Weak { - todo!() - } - - fn flush(&self) { - todo!() - } - - fn finish(&self) { - todo!() + self.weak_ref() as Weak } - - fn invalidate_resource_bindings_cache(&self) { - todo!() - } - - fn pipeline_statistics(&self) -> PipelineStatistics { - todo!() - } - - fn swap_buffers(&self) -> Result<(), FrameworkError> { - todo!() - } - + fn flush(&self) { self.state.queue.submit(std::iter::empty()); } + fn finish(&self) { self.state.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None }).ok(); } + fn invalidate_resource_bindings_cache(&self) { *self.pipeline_statistics.borrow_mut() = Default::default(); } + fn pipeline_statistics(&self) -> PipelineStatistics { *self.pipeline_statistics.borrow() } + fn swap_buffers(&self) -> Result<(), FrameworkError> { Ok(()) } fn set_frame_size(&self, new_size: (u32, u32)) { - todo!() + if new_size.0 > 0 && new_size.1 > 0 { + let mut config = self.surface_config.write().unwrap(); + config.width = new_size.0; + config.height = new_size.1; + self.surface.configure(&self.state.device, &config); + } } - fn capabilities(&self) -> ServerCapabilities { - todo!() - } - - fn set_polygon_fill_mode(&self, polygon_face: PolygonFace, polygon_fill_mode: PolygonFillMode) { - todo!() - } - - fn generate_mipmap(&self, texture: &GpuTexture) { - todo!() - } - - fn memory_usage(&self) -> ServerMemoryUsage { - todo!() - } - - fn push_debug_group(&self, name: &str) { - todo!() - } - - fn pop_debug_group(&self) { - todo!() - } + let limits = self.state.device.limits(); + ServerCapabilities { + max_uniform_block_size: limits.max_uniform_buffer_binding_size as usize, + uniform_buffer_offset_alignment: limits.min_uniform_buffer_offset_alignment as usize, + max_lod_bias: 16.0, + } + } + fn set_polygon_fill_mode(&self, _face: PolygonFace, _mode: PolygonFillMode) { + log::warn!("set_polygon_fill_mode: wgpu requires pipeline recreation"); + } + fn generate_mipmap(&self, _texture: &GpuTexture) { + log::warn!("generate_mipmap: not yet fully implemented"); + } + fn memory_usage(&self) -> ServerMemoryUsage { self.memory_usage.borrow().clone() } + fn push_debug_group(&self, _name: &str) {} + fn pop_debug_group(&self) {} } diff --git a/fyrox-graphics-wgpu/src/shaders/shared.glsl b/fyrox-graphics-wgpu/src/shaders/shared.glsl new file mode 100644 index 0000000000..03414c194c --- /dev/null +++ b/fyrox-graphics-wgpu/src/shaders/shared.glsl @@ -0,0 +1,366 @@ +// Shared functions for all shaders in the engine. +// Naga-compatible version: uses texture2D/texture3D/textureCube + sampler +// as separate parameters instead of combined sampler2D/sampler3D/samplerCube. + +const float PI = 3.14159; + +bool S_SolveQuadraticEq(float a, float b, float c, out float minT, out float maxT) +{ + float twoA = 2.0 * a; + float det = b * b - 2.0 * twoA * c; + if (det < 0.0) { minT = 0.0; maxT = 0.0; return false; } + float sqrtDet = sqrt(det); + float root1 = (-b - sqrtDet) / twoA; + float root2 = (-b + sqrtDet) / twoA; + minT = min(root1, root2); + maxT = max(root1, root2); + return true; +} + +float S_LightDistanceAttenuation(float distance, float radius) +{ + return clamp(1.0 - distance * distance / (radius * radius), 0.0, 1.0); +} + +vec3 S_Project(vec3 worldPosition, mat4 matrix) +{ + vec4 screenPos = matrix * vec4(worldPosition, 1); + screenPos.xyz /= screenPos.w; + return screenPos.xyz * 0.5 + 0.5; +} + +vec3 S_UnProject(vec3 screenPos, mat4 matrix) +{ + vec4 clipSpacePos = vec4(screenPos * 2.0 - 1.0, 1.0); + vec4 position = matrix * clipSpacePos; + return position.xyz / position.w; +} + +float S_DistributionGGX(vec3 N, vec3 H, float roughness) +{ + float a = roughness * roughness; + float a2 = a * a; + float NdotH = max(dot(N, H), 0.0); + float NdotH2 = NdotH * NdotH; + float nom = a2; + float denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + return nom / denom; +} + +float S_GeometrySchlickGGX(float NdotV, float roughness) +{ + float r = (roughness + 1.0); + float k = (r * r) / 8.0; + float nom = NdotV; + float denom = NdotV * (1.0 - k) + k; + return nom / denom; +} + +float S_GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) +{ + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = S_GeometrySchlickGGX(NdotV, roughness); + float ggx1 = S_GeometrySchlickGGX(NdotL, roughness); + return ggx1 * ggx2; +} + +vec3 S_FresnelSchlick(float cosTheta, vec3 F0) +{ + return F0 + (1.0 - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0); +} + +vec3 S_FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) +{ + return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0); +} + +struct TPBRContext { + vec3 lightColor; + vec3 viewVector; + vec3 fragmentToLight; + vec3 fragmentNormal; + float metallic; + float roughness; + vec3 albedo; +}; + +vec3 S_PBR_CalculateLight(TPBRContext ctx) { + vec3 F0 = mix(vec3(0.04), ctx.albedo, ctx.metallic); + vec3 L = ctx.fragmentToLight; + vec3 H = normalize(ctx.viewVector + L); + float NDF = S_DistributionGGX(ctx.fragmentNormal, H, ctx.roughness); + float G = S_GeometrySmith(ctx.fragmentNormal, ctx.viewVector, L, ctx.roughness); + vec3 F = S_FresnelSchlick(max(dot(H, ctx.viewVector), 0.0), F0); + vec3 numerator = NDF * G * F; + float denominator = 4.0 * max(dot(ctx.fragmentNormal, ctx.viewVector), 0.0) * max(dot(ctx.fragmentNormal, L), 0.0) + 0.001; + vec3 specular = numerator / denominator; + vec3 kS = F; + vec3 kD = vec3(1.0) - kS; + kD *= 1.0 - ctx.metallic; + float NdotL = max(dot(ctx.fragmentNormal, L), 0.0); + return (kD * ctx.albedo / PI + specular) * ctx.lightColor * NdotL; +} + +float S_InScatter(vec3 start, vec3 dir, vec3 lightPos, float d) +{ + vec3 q = start - lightPos; + float b = dot(dir, q); + float c = dot(q, q); + float s = 1.0 / sqrt(c - b * b); + float l = s * (atan((d + b) * s) - atan(b * s)); + return l; +} + +vec3 S_RayleighScatter(vec3 start, vec3 dir, vec3 lightPos, float d) +{ + float scatter = S_InScatter(start, dir, lightPos, d); + return vec3(0.55, 0.75, 1.0) * scatter; +} + +bool S_RaySphereIntersection(vec3 origin, vec3 dir, vec3 center, float radius, out float minT, out float maxT) +{ + vec3 d = origin - center; + float a = dot(dir, dir); + float b = 2.0 * dot(dir, d); + float c = dot(d, d) - radius * radius; + return S_SolveQuadraticEq(a, b, c, minT, maxT); +} + +float S_PointShadow( + bool shadowsEnabled, + bool softShadows, + float fragmentDistance, + float shadowBias, + vec3 toLight, + textureCube shadowMap_tex, + sampler shadowMap_samp) +{ + if (shadowsEnabled) + { + float biasedFragmentDistance = fragmentDistance - shadowBias; + + if (softShadows) + { + const vec3 directions[20] = vec3[20]( + vec3(1, 1, 1), vec3(1, -1, 1), vec3(-1, -1, 1), vec3(-1, 1, 1), + vec3(1, 1, -1), vec3(1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1), + vec3(1, 1, 0), vec3(1, -1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0), + vec3(1, 0, 1), vec3(-1, 0, 1), vec3(1, 0, -1), vec3(-1, 0, -1), + vec3(0, 1, 1), vec3(0, -1, 1), vec3(0, -1, -1), vec3(0, 1, -1) + ); + + const float diskRadius = 0.0025; + + float accumulator = 0.0; + + for (int i = 0; i < 20; ++i) + { + vec3 fetchDirection = -toLight + directions[i] * diskRadius; + float shadowDistanceToLight = texture(samplerCube(shadowMap_tex, shadowMap_samp), fetchDirection).r; + if (biasedFragmentDistance > shadowDistanceToLight) + { + accumulator += 1.0; + } + } + + return clamp(1.0 - accumulator / 20.0, 0.0, 1.0); + } + else + { + float shadowDistanceToLight = texture(samplerCube(shadowMap_tex, shadowMap_samp), -toLight).r; + return biasedFragmentDistance > shadowDistanceToLight ? 0.0 : 1.0; + } + } else { + return 1.0; + } +} + +float S_SpotShadowFactor( + bool shadowsEnabled, + bool softShadows, + float shadowBias, + vec3 fragmentPosition, + mat4 lightViewProjMatrix, + float shadowMapInvSize, + texture2D spotShadowTexture_tex, + sampler spotShadowTexture_samp) +{ + if (shadowsEnabled) + { + vec3 lightSpacePosition = S_Project(fragmentPosition, lightViewProjMatrix); + + float biasedLightSpaceFragmentDepth = lightSpacePosition.z - shadowBias; + + if (softShadows) + { + float accumulator = 0.0; + + float step = 0.5; + float kernelHalfSize = 2.0; + float kernelSize = 2.0 * kernelHalfSize; + float totalSamples = pow(kernelSize / step, 2.0); + + for (float y = -kernelHalfSize; y <= kernelHalfSize; y += step) + { + for (float x = -kernelHalfSize; x <= kernelHalfSize; x += step) + { + vec2 fetchTexCoord = lightSpacePosition.xy + vec2(x, y) * shadowMapInvSize; + if (biasedLightSpaceFragmentDepth > texture(sampler2D(spotShadowTexture_tex, spotShadowTexture_samp), fetchTexCoord).r) + { + accumulator += 1.0; + } + } + } + + return clamp(1.0 - accumulator / totalSamples, 0.0, 1.0); + } + else + { + return biasedLightSpaceFragmentDepth > texture(sampler2D(spotShadowTexture_tex, spotShadowTexture_samp), lightSpacePosition.xy).r ? 0.0 : 1.0; + } + } else { + return 1.0; + } +} + +float Internal_FetchHeight(texture2D heightTexture_tex, sampler heightTexture_samp, vec2 texCoords, float center) { + return clamp(texture(sampler2D(heightTexture_tex, heightTexture_samp), texCoords).r - center, 0.0, 1.0); +} + +vec2 S_ComputeParallaxTextureCoordinates(texture2D heightTexture_tex, sampler heightTexture_samp, vec3 eyeVec, vec2 texCoords, float center, float scale) { + const float minLayers = 8.0; + const float maxLayers = 15.0; + const int maxIterations = 15; + + float t = max(0.0, abs(dot(vec3(0.0, 0.0, 1.0), eyeVec))); + float numLayers = mix(maxLayers, minLayers, t); + float layerDepth = 1.0 / numLayers; + float currentLayerDepth = 0.0; + + vec2 deltaTexCoords = scale * eyeVec.xy / numLayers; + + vec2 currentTexCoords = texCoords; + float currentDepthMapValue = Internal_FetchHeight(heightTexture_tex, heightTexture_samp, currentTexCoords, center); + + for (int i = 0; i < maxIterations; i++) { + if (currentLayerDepth < currentDepthMapValue) { + currentTexCoords -= deltaTexCoords; + currentDepthMapValue = Internal_FetchHeight(heightTexture_tex, heightTexture_samp, currentTexCoords, center); + currentLayerDepth += layerDepth; + } else { + break; + } + } + + vec2 prev = currentTexCoords + deltaTexCoords; + float nextH = currentDepthMapValue - currentLayerDepth; + float prevH = Internal_FetchHeight(heightTexture_tex, heightTexture_samp, prev, center) - currentLayerDepth + layerDepth; + + float weight = nextH / (nextH - prevH); + + return prev * weight + currentTexCoords * (1.0 - weight); +} + +ivec2 S_LinearIndexToPosition(int index, int textureWidth) { + int y = index / textureWidth; + int x = index - textureWidth * y; + return ivec2(x, y); +} + +mat4 S_FetchMatrix(texture2D storage_tex, sampler storage_samp, int index) { + int textureWidth = textureSize(storage_tex, 0).x; + ivec2 pos = S_LinearIndexToPosition(4 * index, textureWidth); + + vec4 col1 = texelFetch(sampler2D(storage_tex, storage_samp), pos, 0); + vec4 col2 = texelFetch(sampler2D(storage_tex, storage_samp), ivec2(pos.x + 1, pos.y), 0); + vec4 col3 = texelFetch(sampler2D(storage_tex, storage_samp), ivec2(pos.x + 2, pos.y), 0); + vec4 col4 = texelFetch(sampler2D(storage_tex, storage_samp), ivec2(pos.x + 3, pos.y), 0); + + return mat4(col1, col2, col3, col4); +} + +struct TBlendShapeOffsets { + vec3 position; + vec3 normal; + vec3 tangent; +}; + +TBlendShapeOffsets S_FetchBlendShapeOffsets(texture3D storage_tex, sampler storage_samp, int vertexIndex, int blendShapeIndex) { + int textureWidth = textureSize(storage_tex, 0).x; + ivec3 pos = ivec3(S_LinearIndexToPosition(3 * vertexIndex, textureWidth), blendShapeIndex); + vec3 position = texelFetch(sampler3D(storage_tex, storage_samp), pos, 0).xyz; + vec3 normal = texelFetch(sampler3D(storage_tex, storage_samp), ivec3(pos.x + 1, pos.y, pos.z), 0).xyz; + vec3 tangent = texelFetch(sampler3D(storage_tex, storage_samp), ivec3(pos.x + 2, pos.y, pos.z), 0).xyz; + return TBlendShapeOffsets(position, normal, tangent); +} + +vec4 S_LinearToSRGB(vec4 color) { + vec3 a = 12.92 * color.rgb; + vec3 b = 1.055 * pow(color.rgb, vec3(1.0 / 2.4)) - 0.055; + vec3 c = step(vec3(0.0031308), color.rgb); + return vec4(mix(a, b, c), color.a); +} + +vec4 S_SRGBToLinear(vec4 color) { + vec3 a = color.rgb / 12.92; + vec3 b = pow((color.rgb + 0.055) / 1.055, vec3(2.4)); + vec3 c = step(vec3(0.04045), color.rgb); + return vec4(mix(a, b, c), color.a); +} + +float S_Luminance(vec3 x) { + return dot(x, vec3(0.2125, 0.7154, 0.0721)); +} + +vec2 S_RotateVec2(vec2 v, float angle) +{ + float c = cos(angle); + float s = sin(angle); + mat2 m = mat2(c, -s, s, c); + return m * v; +} + +vec3 S_ConvertRgbToXyz(vec3 rgb) +{ + vec3 xyz; + xyz.x = dot(vec3(0.4124564, 0.3575761, 0.1804375), rgb); + xyz.y = dot(vec3(0.2126729, 0.7151522, 0.0721750), rgb); + xyz.z = dot(vec3(0.0193339, 0.1191920, 0.9503041), rgb); + return xyz; +} + +vec3 S_ConvertXyzToRgb(vec3 xyz) +{ + vec3 rgb; + rgb.x = dot(vec3(3.2404542, -1.5371385, -0.4985314), xyz); + rgb.y = dot(vec3(-0.9692660, 1.8760108, 0.0415560), xyz); + rgb.z = dot(vec3(0.0556434, -0.2040259, 1.0572252), xyz); + return rgb; +} + +vec3 S_ConvertXyzToYxy(vec3 xyz) +{ + float inv = 1.0 / dot(xyz, vec3(1.0, 1.0, 1.0)); + return vec3(xyz.y, xyz.x * inv, xyz.y * inv); +} + +vec3 S_ConvertYxyToXyz(vec3 Yxy) +{ + vec3 xyz; + xyz.x = Yxy.x * Yxy.y / Yxy.z; + xyz.y = Yxy.x; + xyz.z = Yxy.x * (1.0 - Yxy.y - Yxy.z) / Yxy.z; + return xyz; +} + +vec3 S_ConvertRgbToYxy(vec3 rgb) +{ + return S_ConvertXyzToYxy(S_ConvertRgbToXyz(rgb)); +} + +vec3 S_ConvertYxyToRgb(vec3 Yxy) +{ + return S_ConvertXyzToRgb(S_ConvertYxyToXyz(Yxy)); +} diff --git a/fyrox-graphics-wgpu/src/texture.rs b/fyrox-graphics-wgpu/src/texture.rs new file mode 100644 index 0000000000..7d60865e33 --- /dev/null +++ b/fyrox-graphics-wgpu/src/texture.rs @@ -0,0 +1,210 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::server::WgpuGraphicsServer; +use fyrox_graphics::{ + error::FrameworkError, + gpu_texture::{image_1d_size_bytes, image_2d_size_bytes, image_3d_size_bytes, GpuTextureDescriptor, GpuTextureKind, GpuTextureTrait, PixelKind}, +}; +use std::cell::Cell; +use std::rc::Weak; + +fn pixel_kind_to_wgpu_format(kind: PixelKind) -> wgpu::TextureFormat { + match kind { + PixelKind::R32F => wgpu::TextureFormat::R32Float, + PixelKind::R32UI => wgpu::TextureFormat::R32Uint, + PixelKind::R16F => wgpu::TextureFormat::R16Float, + PixelKind::RG16F => wgpu::TextureFormat::Rg16Float, + PixelKind::D32F => wgpu::TextureFormat::Depth32Float, + PixelKind::D16 => wgpu::TextureFormat::Depth16Unorm, + PixelKind::D24S8 => wgpu::TextureFormat::Depth24PlusStencil8, + PixelKind::RGBA8 | PixelKind::RGB8 => wgpu::TextureFormat::Rgba8Unorm, + PixelKind::SRGBA8 | PixelKind::SRGB8 => wgpu::TextureFormat::Rgba8UnormSrgb, + PixelKind::BGRA8 | PixelKind::BGR8 => wgpu::TextureFormat::Bgra8Unorm, + PixelKind::RG8 | PixelKind::LA8 => wgpu::TextureFormat::Rg8Unorm, + PixelKind::R8 | PixelKind::L8 => wgpu::TextureFormat::R8Unorm, + PixelKind::R8UI => wgpu::TextureFormat::R8Uint, + PixelKind::RGBA16F => wgpu::TextureFormat::Rgba16Float, + PixelKind::RGB16F => wgpu::TextureFormat::Rgba16Float, + PixelKind::RGBA32F | PixelKind::RGB32F => wgpu::TextureFormat::Rgba32Float, + PixelKind::R11G11B10F => wgpu::TextureFormat::Rg11b10Ufloat, + PixelKind::RGB10A2 => wgpu::TextureFormat::Rgb10a2Unorm, + _ => wgpu::TextureFormat::Rgba8Unorm, + } +} + +fn texture_dimension(kind: GpuTextureKind) -> wgpu::TextureDimension { + match kind { + GpuTextureKind::Line { .. } => wgpu::TextureDimension::D1, + GpuTextureKind::Rectangle { .. } | GpuTextureKind::Cube { .. } => wgpu::TextureDimension::D2, + GpuTextureKind::Volume { .. } => wgpu::TextureDimension::D3, + } +} + +fn texture_size(kind: GpuTextureKind) -> (u32, u32, u32) { + match kind { + GpuTextureKind::Line { length } => (length as u32, 1, 1), + GpuTextureKind::Rectangle { width, height } => (width as u32, height as u32, 1), + GpuTextureKind::Cube { size } => (size as u32, size as u32, 6), + GpuTextureKind::Volume { width, height, depth } => (width as u32, height as u32, depth as u32), + } +} + +fn texture_view_dimension(kind: GpuTextureKind) -> wgpu::TextureViewDimension { + match kind { + GpuTextureKind::Line { .. } => wgpu::TextureViewDimension::D1, + GpuTextureKind::Rectangle { .. } => wgpu::TextureViewDimension::D2, + GpuTextureKind::Cube { .. } => wgpu::TextureViewDimension::Cube, + GpuTextureKind::Volume { .. } => wgpu::TextureViewDimension::D3, + } +} + +pub struct WgpuTexture { + server: Weak, + texture: wgpu::Texture, + view: wgpu::TextureView, + kind: Cell, + pixel_kind: Cell, + size_bytes: Cell, +} + +impl WgpuTexture { + pub fn new(server: &WgpuGraphicsServer, desc: GpuTextureDescriptor) -> Result { + let format = pixel_kind_to_wgpu_format(desc.pixel_kind); + let dimension = texture_dimension(desc.kind); + let (width, height, depth_or_layers) = texture_size(desc.kind); + let mip_count = desc.mip_count.max(1) as u32; + + let texture = server.state.device.create_texture(&wgpu::TextureDescriptor { + label: if server.named_objects { Some(desc.name) } else { None }, + size: wgpu::Extent3d { width, height, depth_or_array_layers: depth_or_layers }, + mip_level_count: mip_count, + sample_count: 1, + dimension, + format, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor { + label: None, format: Some(format), dimension: Some(texture_view_dimension(desc.kind)), ..Default::default() + }); + + let size_bytes = calc_size_bytes(desc.kind, desc.pixel_kind, desc.mip_count); + if let Some(data) = desc.data { Self::upload(&server.state.queue, &texture, desc.kind, desc.pixel_kind, data, desc.mip_count)?; } + server.memory_usage.borrow_mut().textures += size_bytes; + + Ok(Self { server: server.weak_ref(), texture, view, kind: Cell::new(desc.kind), pixel_kind: Cell::new(desc.pixel_kind), size_bytes: Cell::new(size_bytes) }) + } + + fn upload(queue: &wgpu::Queue, texture: &wgpu::Texture, kind: GpuTextureKind, pk: PixelKind, data: &[u8], mip_count: usize) -> Result<(), FrameworkError> { + let mip_count = mip_count.max(1); + let mut offset = 0; + for mip in 0..mip_count { + match kind { + GpuTextureKind::Line { length } => { + if let Some(l) = length.checked_shr(mip as u32) { + let sz = image_1d_size_bytes(pk, l); + if offset + sz > data.len() { break; } + queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, &data[offset..offset+sz], wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(sz as u32), rows_per_image: Some(1) }, wgpu::Extent3d { width: l as u32, height: 1, depth_or_array_layers: 1 }); + offset += sz; + } + } + GpuTextureKind::Rectangle { width, height } => { + if let (Some(w), Some(h)) = (width.checked_shr(mip as u32), height.checked_shr(mip as u32)) { + let sz = image_2d_size_bytes(pk, w, h); + if offset + sz > data.len() { break; } + let fmt = pixel_kind_to_wgpu_format(pk); + let bps = fmt.block_copy_size(None).unwrap_or(4); + queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, &data[offset..offset+sz], wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((w as u32 * bps).max(1)), rows_per_image: Some(h as u32) }, wgpu::Extent3d { width: w as u32, height: h as u32, depth_or_array_layers: 1 }); + offset += sz; + } + } + GpuTextureKind::Cube { size } => { + if let Some(s) = size.checked_shr(mip as u32) { + let bpf = image_2d_size_bytes(pk, s, s); + for face in 0..6u32 { + let fo = offset + (face as usize) * bpf; + if fo + bpf > data.len() { break; } + let fmt = pixel_kind_to_wgpu_format(pk); + let bps = fmt.block_copy_size(None).unwrap_or(4); + queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d { x: 0, y: 0, z: face }, aspect: wgpu::TextureAspect::All }, &data[fo..fo+bpf], wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((s as u32 * bps).max(1)), rows_per_image: Some(s as u32) }, wgpu::Extent3d { width: s as u32, height: s as u32, depth_or_array_layers: 1 }); + } + offset += 6 * bpf; + } + } + GpuTextureKind::Volume { width, height, depth } => { + if let (Some(w), Some(h), Some(d)) = (width.checked_shr(mip as u32), height.checked_shr(mip as u32), depth.checked_shr(mip as u32)) { + let sz = image_3d_size_bytes(pk, w, h, d); + if offset + sz > data.len() { break; } + let fmt = pixel_kind_to_wgpu_format(pk); + let bps = fmt.block_copy_size(None).unwrap_or(4); + queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, &data[offset..offset+sz], wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((w as u32 * bps).max(1)), rows_per_image: Some(h as u32) }, wgpu::Extent3d { width: w as u32, height: h as u32, depth_or_array_layers: d as u32 }); + offset += sz; + } + } + } + } + Ok(()) + } + + pub fn wgpu_texture(&self) -> &wgpu::Texture { &self.texture } + pub fn wgpu_view(&self) -> &wgpu::TextureView { &self.view } + pub fn format(&self) -> wgpu::TextureFormat { self.texture.format() } +} + +fn calc_size_bytes(kind: GpuTextureKind, pk: PixelKind, mip_count: usize) -> usize { + let mip_count = mip_count.max(1); + let mut total = 0; + for mip in 0..mip_count { + match kind { + GpuTextureKind::Line { length } => { if let Some(l) = length.checked_shr(mip as u32) { total += image_1d_size_bytes(pk, l); } } + GpuTextureKind::Rectangle { width, height } => { if let (Some(w), Some(h)) = (width.checked_shr(mip as u32), height.checked_shr(mip as u32)) { total += image_2d_size_bytes(pk, w, h); } } + GpuTextureKind::Cube { size } => { if let Some(s) = size.checked_shr(mip as u32) { total += 6 * image_2d_size_bytes(pk, s, s); } } + GpuTextureKind::Volume { width, height, depth } => { if let (Some(w), Some(h), Some(d)) = (width.checked_shr(mip as u32), height.checked_shr(mip as u32), depth.checked_shr(mip as u32)) { total += image_3d_size_bytes(pk, w, h, d); } } + } + } + total +} + +impl Drop for WgpuTexture { + fn drop(&mut self) { + if let Some(server) = self.server.upgrade() { server.memory_usage.borrow_mut().textures -= self.size_bytes.get(); } + self.texture.destroy(); + } +} + +impl GpuTextureTrait for WgpuTexture { + fn set_data(&self, kind: GpuTextureKind, pk: PixelKind, mip_count: usize, data: Option<&[u8]>) -> Result { + let Some(server) = self.server.upgrade() else { return Err(FrameworkError::GraphicsServerUnavailable); }; + let new_size = calc_size_bytes(kind, pk, mip_count); + let mut mu = server.memory_usage.borrow_mut(); + mu.textures -= self.size_bytes.get(); + mu.textures += new_size; + drop(mu); + self.size_bytes.set(new_size); + self.kind.set(kind); + self.pixel_kind.set(pk); + if let Some(data) = data { Self::upload(&server.state.queue, &self.texture, kind, pk, data, mip_count)?; } + Ok(new_size) + } + fn kind(&self) -> GpuTextureKind { self.kind.get() } + fn pixel_kind(&self) -> PixelKind { self.pixel_kind.get() } +} From 1df67064b0e8e9169e6f2977df2d7d267f0fd082 Mon Sep 17 00:00:00 2001 From: nice0hack Date: Wed, 1 Jul 2026 17:29:00 +0500 Subject: [PATCH 03/34] add GLSL transpiler to extend naga library compatibility --- fyrox-graphics-wgpu/src/program.rs | 345 +++++++++++++++++++++++++---- 1 file changed, 304 insertions(+), 41 deletions(-) diff --git a/fyrox-graphics-wgpu/src/program.rs b/fyrox-graphics-wgpu/src/program.rs index 383744adca..a9cb8754f2 100644 --- a/fyrox-graphics-wgpu/src/program.rs +++ b/fyrox-graphics-wgpu/src/program.rs @@ -56,6 +56,200 @@ fn sampler_constructor_name(kind: SamplerKind) -> &'static str { } } +/// Maps a combined sampler type name to SamplerKind. +fn sampler_kind_from_type_name(type_name: &str) -> Option { + match type_name { + "sampler1D" => Some(SamplerKind::Sampler1D), + "sampler2D" => Some(SamplerKind::Sampler2D), + "sampler3D" => Some(SamplerKind::Sampler3D), + "samplerCube" => Some(SamplerKind::SamplerCube), + "usampler1D" => Some(SamplerKind::USampler1D), + "usampler2D" => Some(SamplerKind::USampler2D), + "usampler3D" => Some(SamplerKind::USampler3D), + "usamplerCube" => Some(SamplerKind::USamplerCube), + _ => None, + } +} + +/// Rewrites texture function calls, textureSize calls, and shared function argument +/// splitting for a given sampler name. Used for both global texture resources and +/// local function parameters. +fn rewrite_sampler_usages(source: &mut String, name: &str, kind: SamplerKind) { + let constructor = sampler_constructor_name(kind); + let sampler_expr = format!("{constructor}({name}_tex, {name}_samp)"); + + // Transform texture functions: texture(name, ...) → texture(samplerXxx(name_tex, name_samp), ...) + for func_name in &["texture", "textureLod", "textureGrad", "textureOffset", "textureLodOffset", + "texelFetch", "texelFetchOffset", "textureProj", "textureProjLod"] { + for pattern in &[ + format!("{func_name}({name},"), + format!("{func_name}({name} ,"), + format!("{func_name}( {name},"), + ] { + *source = source.replace(pattern, &format!("{func_name}({sampler_expr},")); + } + } + + // textureSize takes the raw texture (not sampler) + for pattern in &[ + format!("textureSize({name},"), + format!("textureSize({name} ,"), + ] { + *source = source.replace(pattern, &format!("textureSize({name}_tex,")); + } + + // Replace shared function calls that pass texture names as arguments. + // These functions (in shared.glsl) take separate texture2D + sampler parameters. + for func_name in &[ + "S_PointShadow", "S_SpotShadowFactor", + "S_ComputeParallaxTextureCoordinates", "S_FetchMatrix", "S_FetchBlendShapeOffsets", + "Internal_FetchHeight", "CsmGetShadow", + ] { + replace_texture_arg_in_calls(source, func_name, name); + } +} + +/// Rewrites local function definitions that have combined sampler parameters +/// (e.g. `in sampler2D name`) into Vulkan-compatible signatures with separate +/// `texture2D` + `sampler` parameters. Also rewrites the function body to use +/// the new parameter names. +fn rewrite_sampler_function_definitions(source: &mut String) { + let sampler_type_keywords = [ + "sampler1D", "sampler2D", "sampler3D", "samplerCube", + "usampler1D", "usampler2D", "usampler3D", "usamplerCube", + ]; + + // Phase 1: Find all function definitions with sampler params. + // Store (paren_pos, body_close_brace_pos, vec<(param_name, SamplerKind)>). + let mut regions: Vec<(usize, usize, Vec<(String, SamplerKind)>)> = Vec::new(); + + let bytes = source.as_bytes(); + let mut i = 0; + while i < bytes.len() { + if bytes[i] == b'(' { + // Check there's an identifier immediately before '(' (function name). + let paren_pos = i; + let mut j = paren_pos; + while j > 0 && (bytes[j - 1].is_ascii_alphanumeric() || bytes[j - 1] == b'_') { + j -= 1; + } + if j == paren_pos { + // No identifier before '(' + i += 1; + continue; + } + + // Extract parameter list. + let Some((args_str, after_paren)) = extract_call_args(source, paren_pos) else { + i += 1; + continue; + }; + + // Check if this looks like a function definition (followed by '{', possibly with whitespace). + let rest = source[after_paren..].trim_start(); + if !rest.starts_with('{') { + i += 1; + continue; + } + + // Parse parameters and find sampler types. + let mut sampler_params: Vec<(String, SamplerKind)> = Vec::new(); + for param in args_str.split(',') { + let param = param.trim(); + let tokens: Vec<&str> = param.split_whitespace().collect(); + if tokens.len() < 2 { + continue; + } + let mut type_idx = None; + for (idx, tok) in tokens.iter().enumerate() { + if sampler_type_keywords.contains(tok) { + type_idx = Some(idx); + break; + } + } + let Some(ti) = type_idx else { continue }; + let kind = sampler_kind_from_type_name(tokens[ti]).unwrap(); + let param_name = tokens.last().unwrap().to_string(); + sampler_params.push((param_name, kind)); + } + + if !sampler_params.is_empty() { + // Find the function body via brace matching. + let brace_start = source[after_paren..].find('{').unwrap() + after_paren; + let mut depth = 0i32; + let mut k = brace_start; + while k < bytes.len() { + match bytes[k] { + b'{' => depth += 1, + b'}' => { + depth -= 1; + if depth == 0 { break; } + } + _ => {} + } + k += 1; + } + if depth == 0 { + regions.push((paren_pos, k + 1, sampler_params)); + } + } + } + i += 1; + } + + // Phase 2: Apply rewrites from end to start to avoid position shifts. + for (paren_pos, body_end, sampler_params) in regions.iter().rev() { + // Extract parameter list text. + let Some((args_str, after_paren)) = extract_call_args(source, *paren_pos) else { continue }; + + // Rewrite parameter list: split each sampler param into _tex + _samp. + let mut new_params: Vec = Vec::new(); + for param in args_str.split(',') { + let trimmed = param.trim(); + let tokens: Vec<&str> = trimmed.split_whitespace().collect(); + if tokens.len() < 2 { + new_params.push(param.to_string()); + continue; + } + let mut type_idx = None; + for (idx, tok) in tokens.iter().enumerate() { + if sampler_type_keywords.contains(tok) { + type_idx = Some(idx); + break; + } + } + if let Some(ti) = type_idx { + let kind = sampler_kind_from_type_name(tokens[ti]).unwrap(); + let param_name = tokens.last().unwrap(); + // Collect qualifiers (everything before the type keyword). + let qualifiers: Vec<&str> = tokens[..ti].to_vec(); + let qual_prefix = if qualifiers.is_empty() { String::new() } else { qualifiers.join(" ") + " " }; + let tex_type = vulkan_texture_type(kind); + new_params.push(format!("{qual_prefix}{tex_type} {param_name}_tex")); + new_params.push(format!("{qual_prefix}sampler {param_name}_samp")); + } else { + new_params.push(param.to_string()); + } + } + let new_args = new_params.join(", "); + + // Find the body text (from '{' to '}'). + let brace_start = source[after_paren..].find('{').unwrap() + after_paren; + let body_text = &source[brace_start..*body_end].to_string(); + + // Rewrite body for each sampler param. + let mut new_body = body_text.clone(); + for (param_name, kind) in sampler_params { + rewrite_sampler_usages(&mut new_body, param_name, *kind); + } + + // Replace: from paren_pos to body_end. + let before = &source[..*paren_pos].to_string(); + let after = &source[*body_end..].to_string(); + *source = format!("{before}({new_args}){new_body}{after}"); + } +} + /// Generates separate texture + sampler uniform declarations for wgpu bindings, /// plus uniform block declarations with `layout(std140, binding=...)`. fn generate_resource_declarations(resources: &[ShaderResourceDefinition], source: &mut String, line_offset: &mut isize) { @@ -109,12 +303,16 @@ fn generate_resource_declarations(resources: &[ShaderResourceDefinition], source /// Preprocesses shader source for naga/wgpu compatibility. /// /// Transformations: -/// 1. `texture(name, uv)` → `texture(samplerXxx(name_tex, name_samp), uv)` for all texture functions -/// 2. `textureSize(name, ...)` → `textureSize(name_tex, ...)` -/// 3. `gl_InstanceID` → `gl_InstanceIndex`, `gl_VertexID` → `gl_VertexIndex` -/// 4. `properties.boolField` → `bool(properties.boolField)` (uint→bool cast) -/// 5. Shared function calls with texture args: `S_PointShadow(..., name)` → `S_PointShadow(..., name_tex, name_samp)` +/// 1. Local function definitions with sampler params → separate texture + sampler params +/// 2. `texture(name, uv)` → `texture(samplerXxx(name_tex, name_samp), uv)` for all texture functions +/// 3. `textureSize(name, ...)` → `textureSize(name_tex, ...)` +/// 4. Shared function calls with texture args: `S_PointShadow(..., name)` → `S_PointShadow(..., name_tex, name_samp)` +/// 5. `gl_InstanceID` → `gl_InstanceIndex`, `gl_VertexID` → `gl_VertexIndex` +/// 6. `properties.boolField` → `bool(properties.boolField)` (uint→bool cast) fn preprocess_shader(source: &mut String, resources: &[ShaderResourceDefinition]) { + // Rewrite local function definitions with sampler params first. + rewrite_sampler_function_definitions(source); + // Collect texture resources (longest first to avoid partial matches) let mut tex_resources: Vec<(&str, SamplerKind)> = resources .iter() @@ -126,44 +324,15 @@ fn preprocess_shader(source: &mut String, resources: &[ShaderResourceDefinition] tex_resources.sort_by(|a, b| b.0.len().cmp(&a.0.len())); for (name, kind) in &tex_resources { - let constructor = sampler_constructor_name(*kind); - let sampler_expr = format!("{constructor}({name}_tex, {name}_samp)"); - - // Transform texture functions: texture(name, ...) → texture(samplerXxx(name_tex, name_samp), ...) - for func_name in &["texture", "textureLod", "textureGrad", "textureOffset", "textureLodOffset", - "texelFetch", "texelFetchOffset", "textureProj", "textureProjLod"] { - for pattern in &[ - format!("{func_name}({name},"), - format!("{func_name}({name} ,"), - format!("{func_name}( {name},"), - ] { - *source = source.replace(pattern, &format!("{func_name}({sampler_expr},")); - } - } - - // textureSize takes the raw texture (not sampler) - for pattern in &[ - format!("textureSize({name},"), - format!("textureSize({name} ,"), - ] { - *source = source.replace(pattern, &format!("textureSize({name}_tex,")); - } - - // Replace shared function calls that pass texture resource names as arguments. - // These functions (in shared.glsl) take separate texture2D + sampler parameters. - // The call site passes the combined name; we split it into _tex and _samp. - for func_name in &[ - "S_PointShadow", "S_SpotShadowFactor", - "S_ComputeParallaxTextureCoordinates", "S_FetchMatrix", "S_FetchBlendShapeOffsets", - "Internal_FetchHeight", "CsmGetShadow", - ] { - replace_texture_arg_in_calls(source, func_name, name); - } + rewrite_sampler_usages(source, name, *kind); } - // Replace OpenGL built-in names with Vulkan equivalents - *source = source.replace("gl_InstanceID", "gl_InstanceIndex"); - *source = source.replace("gl_VertexID", "gl_VertexIndex"); + // Replace OpenGL built-in names with Vulkan equivalents. + // Use `int(builtin + 0)` wrapper to work around a Naga bug where passing + // gl_InstanceIndex/gl_VertexIndex directly as function arguments to functions + // with texture2D/sampler parameters causes "Unknown function" errors. + *source = source.replace("gl_InstanceID", "int(gl_InstanceIndex + 0)"); + *source = source.replace("gl_VertexID", "int(gl_VertexIndex + 0)"); // Wrap bool uniform property accesses with bool() cast (uint→bool) for res in resources { @@ -443,3 +612,97 @@ impl WgpuProgram { pub fn resources(&self) -> &[ShaderResourceDefinition] { &self.resources } pub fn name(&self) -> &str { &self.name } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rewrite_sampler_function_definitions() { + let mut source = r#"float CsmGetShadow(in sampler2D shadowSampler, in vec3 fragmentPosition, in mat4 lightViewProjMatrix) +{ + float invSize = 1.0 / float(textureSize(shadowSampler, 0).x); + return S_SpotShadowFactor(properties.shadowsEnabled, properties.softShadows, + properties.shadowBias, fragmentPosition, lightViewProjMatrix, invSize, shadowSampler); +}"#.to_string(); + + rewrite_sampler_function_definitions(&mut source); + + // Parameter list should be split + assert!(source.contains("in texture2D shadowSampler_tex, in sampler shadowSampler_samp")); + // textureSize should use _tex + assert!(source.contains("textureSize(shadowSampler_tex, 0)")); + // S_SpotShadowFactor call should have split args + assert!(source.contains("shadowSampler_tex, shadowSampler_samp")); + // Original combined sampler type should be gone + assert!(!source.contains("sampler2D shadowSampler")); + } + + #[test] + fn test_rewrite_sampler_function_definitions_no_sampler() { + let mut source = r#"float plainFunc(float a, float b) { + return a + b; +}"#.to_string(); + + let original = source.clone(); + rewrite_sampler_function_definitions(&mut source); + assert_eq!(source, original); + } + + #[test] + fn test_rewrite_sampler_usages_global_resource() { + let mut source = r#"vec3 c = texture(materialTexture, texCoord).rgb; +float d = textureSize(materialTexture, 0).x;"#.to_string(); + + rewrite_sampler_usages(&mut source, "materialTexture", SamplerKind::Sampler2D); + + assert!(source.contains("texture(sampler2D(materialTexture_tex, materialTexture_samp),")); + assert!(source.contains("textureSize(materialTexture_tex,")); + } + + #[test] + fn test_naga_shared_glsl_std140_with_gl_instance_index() { + // Verify that the gl_InstanceIndex workaround (int(gl_InstanceIndex + 0)) + // works around the Naga bug where gl_InstanceIndex as a function argument + // to functions with texture2D/sampler params causes "Unknown function" errors. + let shared = include_str!("shaders/shared.glsl"); + let glsl = format!(r#"#version 450 +{shared} + +// end of include +layout(binding=0) uniform texture2D matrices_tex; +layout(binding=100) uniform sampler matrices_samp; + +struct Tproperties{{ + mat4 viewProjection; + int tileSize; + float frameBufferHeight; +}}; +layout(std140, binding=200) uniform Uproperties {{ Tproperties properties; }}; + +void main() +{{ + gl_Position = S_FetchMatrix(matrices_tex, matrices_samp, int(gl_InstanceIndex + 0)) * vec4(1.0); +}} +"#); + + let mut frontend = naga::front::glsl::Frontend::default(); + let result = frontend.parse(&naga::front::glsl::Options { + stage: naga::ShaderStage::Vertex, + defines: Default::default(), + }, &glsl); + + match result { + Ok(module) => { + let info = naga::valid::Validator::new( + naga::valid::ValidationFlags::empty(), + naga::valid::Capabilities::all(), + ).validate(&module); + assert!(info.is_ok(), "Naga validation failed: {:?}", info.err()); + } + Err(errors) => { + panic!("Naga GLSL parse failed:\n{errors}"); + } + } + } +} From d77b8716107cbb8ba3c04c7cd9b00dd89f071603 Mon Sep 17 00:00:00 2001 From: nice0hack Date: Thu, 2 Jul 2026 10:04:27 +0500 Subject: [PATCH 04/34] compilation of all shaders has been fixed --- fyrox-graphics-wgpu/src/program.rs | 216 +++++++++++++++++++++++++++++ fyrox-graphics-wgpu/src/texture.rs | 67 ++++++++- 2 files changed, 279 insertions(+), 4 deletions(-) diff --git a/fyrox-graphics-wgpu/src/program.rs b/fyrox-graphics-wgpu/src/program.rs index a9cb8754f2..82aa2f0efb 100644 --- a/fyrox-graphics-wgpu/src/program.rs +++ b/fyrox-graphics-wgpu/src/program.rs @@ -474,6 +474,103 @@ fn prepare_source(code: &str) -> String { src } +/// Assigns explicit `layout(location = N)` to inter-stage `in`/`out` variables +/// that lack them. Naga requires explicit locations for all inter-stage variables; +/// without them, all default to location 0 causing "Multiple bindings" errors. +/// +/// For vertex shaders: assigns locations to `out` variables (inter-stage outputs). +/// For fragment shaders: assigns locations to `in` variables (inter-stage inputs). +/// Fragment `out` variables (render targets) are NOT touched — they keep their defaults. +/// +/// Uses a fixed base location (8) for inter-stage variables to avoid conflicts +/// with vertex input locations (typically 0-7) and fragment output locations (typically 0-7). +/// Both vertex and fragment shaders use the same base, ensuring matching locations. +fn assign_inter_stage_locations(source: &mut String, kind: &ShaderKind) { + /// Base location for inter-stage variables. High enough to avoid conflicts + /// with vertex inputs and fragment outputs. + const INTER_STAGE_BASE: i32 = 8; + + let lines: Vec<&str> = source.lines().collect(); + let mut unqualified_indices: Vec = Vec::new(); + + for (i, line) in lines.iter().enumerate() { + let trimmed = line.trim(); + + match kind { + ShaderKind::Vertex => { + // In vertex shaders, assign locations to `out` variables only + // (skip `in` which are vertex buffer inputs — they should already have locations) + if is_unqualified_out_decl(trimmed) { + unqualified_indices.push(i); + } + } + ShaderKind::Fragment => { + // In fragment shaders, assign locations to `in` variables only + // (skip `out` which are render target outputs — they should keep defaults) + if is_unqualified_in_decl(trimmed) { + unqualified_indices.push(i); + } + } + } + } + + if unqualified_indices.is_empty() { + return; + } + + let mut next_location = INTER_STAGE_BASE; + let mut result = String::new(); + for (i, line) in lines.iter().enumerate() { + if unqualified_indices.contains(&i) { + let indent: String = line.chars().take_while(|c| c.is_whitespace()).collect(); + let trimmed = line.trim(); + result.push_str(&format!("{indent}layout (location = {next_location}) {trimmed}\n")); + next_location += 1; + } else { + result.push_str(line); + result.push('\n'); + } + } + if result.ends_with('\n') { + result.pop(); + } + *source = result; +} + +/// Checks if a line is an unqualified `out` variable declaration +/// (no layout qualifier, starts with `out` followed by a GLSL type). +fn is_unqualified_out_decl(line: &str) -> bool { + let trimmed = line.trim(); + if !trimmed.starts_with("out ") || trimmed.starts_with("out[") { + return false; + } + if trimmed.contains("layout") { + return false; + } + if !trimmed.ends_with(';') { + return false; + } + let tokens: Vec<&str> = trimmed.trim_end_matches(';').split_whitespace().collect(); + tokens.len() >= 3 +} + +/// Checks if a line is an unqualified `in` variable declaration +/// (no layout qualifier, starts with `in` followed by a GLSL type). +fn is_unqualified_in_decl(line: &str) -> bool { + let trimmed = line.trim(); + if !trimmed.starts_with("in ") || trimmed.starts_with("in[") { + return false; + } + if trimmed.contains("layout") { + return false; + } + if !trimmed.ends_with(';') { + return false; + } + let tokens: Vec<&str> = trimmed.trim_end_matches(';').split_whitespace().collect(); + tokens.len() >= 3 +} + fn compile_glsl(device: &wgpu::Device, name: &str, kind: &ShaderKind, glsl: &str) -> Result { let mut frontend = naga::front::glsl::Frontend::default(); let module = frontend.parse(&naga::front::glsl::Options { stage: naga_stage(kind), defines: Default::default() }, glsl).map_err(|errors| { @@ -558,6 +655,7 @@ impl WgpuShader { } generate_resource_declarations(resources, &mut source, &mut line_offset); preprocess_shader(&mut source, resources); + assign_inter_stage_locations(&mut source, &kind); let full = prepare_source(&source); let module = compile_glsl(&server.state.device, &name, &kind, &full)?; Ok(Self { _server: server.weak_ref(), module, _kind: kind }) @@ -705,4 +803,122 @@ void main() } } } + + #[test] + fn test_naga_widget_shader_binding_conflict() { + let glsl = r#"#version 450 + +layout(binding=8) uniform texture2D fyrox_widgetTexture_tex; +layout(binding=108) uniform sampler fyrox_widgetTexture_samp; +struct Tfyrox_widgetData{ + mat4 projectionMatrix; + mat3 worldMatrix; +}; +layout(std140, binding=208) uniform Ufyrox_widgetData { Tfyrox_widgetData fyrox_widgetData; }; + +layout (location = 0) in vec2 vertexPosition; + +void main() +{ + vec3 worldSpaceVertex = fyrox_widgetData.worldMatrix * vec3(vertexPosition, 1.0); + gl_Position = fyrox_widgetData.projectionMatrix * vec4(worldSpaceVertex, 1.0); +} +"#; + + let mut frontend = naga::front::glsl::Frontend::default(); + let module = frontend.parse(&naga::front::glsl::Options { + stage: naga::ShaderStage::Vertex, + defines: Default::default(), + }, glsl).expect("Naga parse failed"); + + let info = naga::valid::Validator::new( + naga::valid::ValidationFlags::empty(), + naga::valid::Capabilities::all(), + ).validate(&module).expect("Naga validation failed"); + + // Dump all global variables and their bindings + for (handle, var) in module.global_variables.iter() { + let name = var.name.as_deref().unwrap_or(""); + let binding = var.binding.as_ref().map(|b| format!("set={}, binding={}", b.group, b.binding)).unwrap_or_else(|| "none".into()); + let space = var.space; + eprintln!("GlobalVar {handle:?}: name={name}, binding={binding}, space={space:?}"); + } + + let spv = naga::back::spv::write_vec(&module, &info, &naga::back::spv::Options { + flags: naga::back::spv::WriterFlags::empty(), + ..Default::default() + }, None).expect("SPIR-V generation failed"); + let _ = spv; + } + + #[test] + fn test_assign_inter_stage_locations_vertex_shader() { + // Simulates the widget vertex shader pattern: unqualified out variables + let mut source = r#"layout (location = 0) in vec2 vertexPosition; +layout (location = 1) in vec2 vertexTexCoord; +layout (location = 2) in vec4 vertexColor; + +out vec2 texCoord; +out vec4 color; +out vec2 localPosition; + +void main() +{ + texCoord = vertexTexCoord; + color = vertexColor; + localPosition = vertexPosition; +} +"#.to_string(); + + assign_inter_stage_locations(&mut source, &ShaderKind::Vertex); + + // Inter-stage outputs start at base location 8 + assert!(source.contains("layout (location = 8) out vec2 texCoord")); + assert!(source.contains("layout (location = 9) out vec4 color")); + assert!(source.contains("layout (location = 10) out vec2 localPosition")); + // Original vertex inputs should be unchanged + assert!(source.contains("layout (location = 0) in vec2 vertexPosition")); + } + + #[test] + fn test_assign_inter_stage_locations_fragment_shader() { + // Simulates the widget fragment shader pattern: unqualified in + out variables + // Only `in` variables should get locations; `out` should be left alone + let mut source = r#"out vec4 fragColor; + +in vec2 texCoord; +in vec4 color; +in vec2 localPosition; + +void main() +{ + fragColor = vec4(1.0); +} +"#.to_string(); + + assign_inter_stage_locations(&mut source, &ShaderKind::Fragment); + + // `out vec4 fragColor` should NOT get a location (fragment output keeps default) + assert!(source.contains("out vec4 fragColor;")); + assert!(!source.contains("location") && source.contains("out vec4 fragColor;") || + source.lines().filter(|l| l.contains("fragColor") && l.contains("location")).count() == 0); + // `in` variables get sequential locations starting at 8 + assert!(source.contains("layout (location = 8) in vec2 texCoord")); + assert!(source.contains("layout (location = 9) in vec4 color")); + assert!(source.contains("layout (location = 10) in vec2 localPosition")); + } + + #[test] + fn test_assign_inter_stage_locations_no_unqualified() { + // No unqualified variables — should be a no-op + let mut source = r#"layout (location = 0) in vec3 vertexPosition; +layout (location = 0) out vec4 fragColor; + +void main() { fragColor = vec4(1.0); } +"#.to_string(); + + let original = source.clone(); + assign_inter_stage_locations(&mut source, &ShaderKind::Vertex); + assert_eq!(source, original); + } } diff --git a/fyrox-graphics-wgpu/src/texture.rs b/fyrox-graphics-wgpu/src/texture.rs index 7d60865e33..aa6a95c9d9 100644 --- a/fyrox-graphics-wgpu/src/texture.rs +++ b/fyrox-graphics-wgpu/src/texture.rs @@ -50,6 +50,50 @@ fn pixel_kind_to_wgpu_format(kind: PixelKind) -> wgpu::TextureFormat { } } +/// Returns true if the pixel kind is 3-component and needs expansion to 4-component +/// for wgpu compatibility (wgpu has no 3-component texture formats). +fn needs_rgba_expansion(pk: PixelKind) -> bool { + matches!(pk, PixelKind::RGB8 | PixelKind::BGR8 | PixelKind::SRGB8 + | PixelKind::RGB16F | PixelKind::RGB32F) +} + +/// Expands 3-component pixel data to 4-component by adding an opaque alpha channel. +fn expand_to_rgba(pk: PixelKind, data: &[u8]) -> Vec { + match pk { + PixelKind::RGB8 | PixelKind::SRGB8 | PixelKind::BGR8 => { + // 3 bytes → 4 bytes per pixel (add 0xFF alpha) + let pixel_count = data.len() / 3; + let mut out = Vec::with_capacity(pixel_count * 4); + for chunk in data.chunks(3) { + out.extend_from_slice(chunk); + out.push(0xFF); + } + out + } + PixelKind::RGB16F => { + // 6 bytes → 8 bytes per pixel (add 1.0f16 = 0x3C00 as alpha) + let pixel_count = data.len() / 6; + let mut out = Vec::with_capacity(pixel_count * 8); + for chunk in data.chunks(6) { + out.extend_from_slice(chunk); + out.extend_from_slice(&[0x00, 0x3C]); + } + out + } + PixelKind::RGB32F => { + // 12 bytes → 16 bytes per pixel (add 1.0f32 = 0x3F800000 as alpha) + let pixel_count = data.len() / 12; + let mut out = Vec::with_capacity(pixel_count * 16); + for chunk in data.chunks(12) { + out.extend_from_slice(chunk); + out.extend_from_slice(&[0x00, 0x00, 0x80, 0x3F]); + } + out + } + _ => data.to_vec(), + } +} + fn texture_dimension(kind: GpuTextureKind) -> wgpu::TextureDimension { match kind { GpuTextureKind::Line { .. } => wgpu::TextureDimension::D1, @@ -116,6 +160,7 @@ impl WgpuTexture { fn upload(queue: &wgpu::Queue, texture: &wgpu::Texture, kind: GpuTextureKind, pk: PixelKind, data: &[u8], mip_count: usize) -> Result<(), FrameworkError> { let mip_count = mip_count.max(1); + let needs_expansion = needs_rgba_expansion(pk); let mut offset = 0; for mip in 0..mip_count { match kind { @@ -123,7 +168,12 @@ impl WgpuTexture { if let Some(l) = length.checked_shr(mip as u32) { let sz = image_1d_size_bytes(pk, l); if offset + sz > data.len() { break; } - queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, &data[offset..offset+sz], wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(sz as u32), rows_per_image: Some(1) }, wgpu::Extent3d { width: l as u32, height: 1, depth_or_array_layers: 1 }); + let slice = &data[offset..offset+sz]; + let expanded; + let upload_data = if needs_expansion { expanded = expand_to_rgba(pk, slice); &expanded } else { slice }; + let fmt = pixel_kind_to_wgpu_format(pk); + let bps = fmt.block_copy_size(None).unwrap_or(4); + queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, upload_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(l as u32 * bps), rows_per_image: Some(1) }, wgpu::Extent3d { width: l as u32, height: 1, depth_or_array_layers: 1 }); offset += sz; } } @@ -131,9 +181,12 @@ impl WgpuTexture { if let (Some(w), Some(h)) = (width.checked_shr(mip as u32), height.checked_shr(mip as u32)) { let sz = image_2d_size_bytes(pk, w, h); if offset + sz > data.len() { break; } + let slice = &data[offset..offset+sz]; + let expanded; + let upload_data = if needs_expansion { expanded = expand_to_rgba(pk, slice); &expanded } else { slice }; let fmt = pixel_kind_to_wgpu_format(pk); let bps = fmt.block_copy_size(None).unwrap_or(4); - queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, &data[offset..offset+sz], wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((w as u32 * bps).max(1)), rows_per_image: Some(h as u32) }, wgpu::Extent3d { width: w as u32, height: h as u32, depth_or_array_layers: 1 }); + queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, upload_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((w as u32 * bps).max(1)), rows_per_image: Some(h as u32) }, wgpu::Extent3d { width: w as u32, height: h as u32, depth_or_array_layers: 1 }); offset += sz; } } @@ -143,9 +196,12 @@ impl WgpuTexture { for face in 0..6u32 { let fo = offset + (face as usize) * bpf; if fo + bpf > data.len() { break; } + let slice = &data[fo..fo+bpf]; + let expanded; + let upload_data = if needs_expansion { expanded = expand_to_rgba(pk, slice); &expanded } else { slice }; let fmt = pixel_kind_to_wgpu_format(pk); let bps = fmt.block_copy_size(None).unwrap_or(4); - queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d { x: 0, y: 0, z: face }, aspect: wgpu::TextureAspect::All }, &data[fo..fo+bpf], wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((s as u32 * bps).max(1)), rows_per_image: Some(s as u32) }, wgpu::Extent3d { width: s as u32, height: s as u32, depth_or_array_layers: 1 }); + queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d { x: 0, y: 0, z: face }, aspect: wgpu::TextureAspect::All }, upload_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((s as u32 * bps).max(1)), rows_per_image: Some(s as u32) }, wgpu::Extent3d { width: s as u32, height: s as u32, depth_or_array_layers: 1 }); } offset += 6 * bpf; } @@ -154,9 +210,12 @@ impl WgpuTexture { if let (Some(w), Some(h), Some(d)) = (width.checked_shr(mip as u32), height.checked_shr(mip as u32), depth.checked_shr(mip as u32)) { let sz = image_3d_size_bytes(pk, w, h, d); if offset + sz > data.len() { break; } + let slice = &data[offset..offset+sz]; + let expanded; + let upload_data = if needs_expansion { expanded = expand_to_rgba(pk, slice); &expanded } else { slice }; let fmt = pixel_kind_to_wgpu_format(pk); let bps = fmt.block_copy_size(None).unwrap_or(4); - queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, &data[offset..offset+sz], wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((w as u32 * bps).max(1)), rows_per_image: Some(h as u32) }, wgpu::Extent3d { width: w as u32, height: h as u32, depth_or_array_layers: d as u32 }); + queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, upload_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((w as u32 * bps).max(1)), rows_per_image: Some(h as u32) }, wgpu::Extent3d { width: w as u32, height: h as u32, depth_or_array_layers: d as u32 }); offset += sz; } } From 124395c36beda1f001181ba607c6e2ba25c38c8d Mon Sep 17 00:00:00 2001 From: nice0hack Date: Thu, 2 Jul 2026 10:28:48 +0500 Subject: [PATCH 05/34] fixed a bug when creating a graphics pipeline --- fyrox-graphics-wgpu/src/program.rs | 175 ++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 1 deletion(-) diff --git a/fyrox-graphics-wgpu/src/program.rs b/fyrox-graphics-wgpu/src/program.rs index 82aa2f0efb..23b4b4734f 100644 --- a/fyrox-graphics-wgpu/src/program.rs +++ b/fyrox-graphics-wgpu/src/program.rs @@ -571,6 +571,129 @@ fn is_unqualified_in_decl(line: &str) -> bool { tokens.len() >= 3 } +/// Handles vertex input declarations that may not be provided by the vertex buffer. +/// +/// For inputs at high locations (4+) that are commonly optional (boneWeights, +/// boneIndices, vertexSecondTexCoord), replaces the `layout(location = N) in TYPE name;` +/// declaration with `const TYPE name = DEFAULT;`. This allows the shader to compile +/// and run correctly even when the vertex buffer doesn't provide these attributes. +/// +/// Inputs at low locations (0-3) that are truly unused are stripped entirely. +fn strip_unused_vertex_inputs(source: &mut String) { + let lines: Vec<&str> = source.lines().collect(); + let mut modifications: Vec<(usize, String)> = Vec::new(); + + for (i, line) in lines.iter().enumerate() { + let trimmed = line.trim(); + + // Match: layout (location = N) in TYPE name; + if !trimmed.contains("layout") || !trimmed.contains(" in ") || !trimmed.ends_with(';') { + continue; + } + if !trimmed.contains("location") { + continue; + } + + // Extract the variable name and type + let without_semi = trimmed.trim_end_matches(';'); + let tokens: Vec<&str> = without_semi.split_whitespace().collect(); + if tokens.len() < 4 { + continue; + } + + let var_name = tokens.last().unwrap(); + + // Extract location number + let loc = extract_location_number(trimmed); + + // Check if the variable name appears anywhere else in the source + let mut used = false; + for (j, other_line) in lines.iter().enumerate() { + if j == i { + continue; + } + if contains_word(other_line, var_name) { + used = true; + break; + } + } + + if !used { + // Truly unused — strip the line entirely + modifications.push((i, String::new())); + } else if loc.map_or(false, |l| l >= 4) { + // Used but at a high location (commonly optional attributes like boneWeights). + // Replace with a constant default so the shader compiles without the vertex attribute. + let type_name = tokens[tokens.len() - 2]; + let default_val = match type_name { + "vec4" => "vec4(0.0)", + "vec3" => "vec3(0.0)", + "vec2" => "vec2(0.0)", + "float" => "0.0", + "int" => "0", + "uint" => "0u", + _ => continue, // Unknown type, don't modify + }; + let indent: String = line.chars().take_while(|c| c.is_whitespace()).collect(); + modifications.push((i, format!("{indent}const {type_name} {var_name} = {default_val};"))); + } + } + + if modifications.is_empty() { + return; + } + + let mut result = String::new(); + for (i, line) in lines.iter().enumerate() { + if let Some((_, replacement)) = modifications.iter().find(|(idx, _)| *idx == i) { + if replacement.is_empty() { + continue; // Strip the line + } + result.push_str(replacement); + result.push('\n'); + } else { + result.push_str(line); + result.push('\n'); + } + } + if result.ends_with('\n') { + result.pop(); + } + *source = result; +} + +/// Extracts the location number from a layout declaration line. +fn extract_location_number(line: &str) -> Option { + let loc_pos = line.find("location")?; + let after_loc = &line[loc_pos + "location".len()..]; + let eq_pos = after_loc.find('=')?; + let after_eq = after_loc[eq_pos + 1..].trim(); + let num_str: String = after_eq.chars().take_while(|c| c.is_ascii_digit() || *c == '-').collect(); + num_str.parse().ok() +} + +/// Checks if a line contains `word` as a standalone identifier (not part of a larger word). +fn contains_word(line: &str, word: &str) -> bool { + let mut pos = 0; + while let Some(found) = line[pos..].find(word) { + let abs_pos = pos + found; + let before_ok = abs_pos == 0 || { + let prev = line.as_bytes()[abs_pos - 1]; + !prev.is_ascii_alphanumeric() && prev != b'_' + }; + let after_pos = abs_pos + word.len(); + let after_ok = after_pos >= line.len() || { + let next = line.as_bytes()[after_pos]; + !next.is_ascii_alphanumeric() && next != b'_' + }; + if before_ok && after_ok { + return true; + } + pos = abs_pos + 1; + } + false +} + fn compile_glsl(device: &wgpu::Device, name: &str, kind: &ShaderKind, glsl: &str) -> Result { let mut frontend = naga::front::glsl::Frontend::default(); let module = frontend.parse(&naga::front::glsl::Options { stage: naga_stage(kind), defines: Default::default() }, glsl).map_err(|errors| { @@ -656,7 +779,10 @@ impl WgpuShader { generate_resource_declarations(resources, &mut source, &mut line_offset); preprocess_shader(&mut source, resources); assign_inter_stage_locations(&mut source, &kind); - let full = prepare_source(&source); + let mut full = prepare_source(&source); + if matches!(kind, ShaderKind::Vertex) { + strip_unused_vertex_inputs(&mut full); + } let module = compile_glsl(&server.state.device, &name, &kind, &full)?; Ok(Self { _server: server.weak_ref(), module, _kind: kind }) } @@ -921,4 +1047,51 @@ void main() { fragColor = vec4(1.0); } assign_inter_stage_locations(&mut source, &ShaderKind::Vertex); assert_eq!(source, original); } + + #[test] + fn test_strip_unused_vertex_inputs() { + let mut source = r#"layout (location = 0) in vec3 vertexPosition; +layout (location = 1) in vec2 vertexTexCoord; +layout (location = 4) in vec4 boneWeights; +layout (location = 5) in vec4 boneIndices; + +void main() +{ + gl_Position = vec4(vertexPosition, 1.0); + vec2 tc = vertexTexCoord; +} +"#.to_string(); + + strip_unused_vertex_inputs(&mut source); + + // vertexPosition and vertexTexCoord are used — keep them + assert!(source.contains("in vec3 vertexPosition")); + assert!(source.contains("in vec2 vertexTexCoord")); + // boneWeights and boneIndices are NOT used — strip them + assert!(!source.contains("boneWeights")); + assert!(!source.contains("boneIndices")); + } + + #[test] + fn test_strip_unused_vertex_inputs_replaces_high_location_used() { + // boneWeights is used (inside an animation block) but at location 4+ + // Should be replaced with a constant default + let mut source = r#"layout (location = 0) in vec3 vertexPosition; +layout (location = 4) in vec4 boneWeights; + +void main() +{ + vec3 pos = vertexPosition + boneWeights.xyz; + gl_Position = vec4(pos, 1.0); +} +"#.to_string(); + + strip_unused_vertex_inputs(&mut source); + + // vertexPosition is used at low location — keep as input + assert!(source.contains("in vec3 vertexPosition")); + // boneWeights is used at location 4 — replace with constant + assert!(source.contains("const vec4 boneWeights = vec4(0.0);")); + assert!(!source.contains("in vec4 boneWeights")); + } } From b32984f5d3164e945bed6bdd3457a0a54dd1e97c Mon Sep 17 00:00:00 2001 From: nice0hack Date: Thu, 2 Jul 2026 11:03:59 +0500 Subject: [PATCH 06/34] fix texture aspect detection --- fyrox-graphics-wgpu/src/framebuffer.rs | 2 +- fyrox-graphics-wgpu/src/server.rs | 5 +++-- fyrox-graphics-wgpu/src/texture.rs | 29 ++++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index 663f4acac6..6a701c64ba 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -273,7 +273,7 @@ fn create_bind_group(server: &WgpuGraphicsServer, program: &WgpuProgram, groups: ResourceBinding::Texture { texture, sampler, binding: loc } => { let wt = texture.as_any().downcast_ref::()?; let ws = sampler.as_any().downcast_ref::()?; - entries.push(wgpu::BindGroupEntry { binding: *loc as u32, resource: wgpu::BindingResource::TextureView(wt.wgpu_view()) }); + entries.push(wgpu::BindGroupEntry { binding: *loc as u32, resource: wgpu::BindingResource::TextureView(wt.wgpu_binding_view()) }); entries.push(wgpu::BindGroupEntry { binding: (*loc + 100) as u32, resource: wgpu::BindingResource::Sampler(ws.wgpu_sampler()) }); } ResourceBinding::Buffer { buffer, binding: loc, data_usage } => { diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index bf8efdda9c..189445397b 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -70,7 +70,7 @@ pub struct WgpuGraphicsServer { impl WgpuGraphicsServer { pub fn new( vsync: bool, - msaa_sample_count: Option, + _msaa_sample_count: Option, window_target: &ActiveEventLoop, window_attributes: WindowAttributes, named_objects: bool, @@ -154,7 +154,8 @@ impl WgpuGraphicsServer { }; surface.configure(&device, &surface_config); - let msaa = msaa_sample_count.unwrap_or(1).max(1) as u32; + // TODO: Force `msaa_sample_count` to 1 in the wgpu backend. Full MSAA support requires creating multisampled render targets and resolve targets, which is a larger feature. + let msaa = 1u32; // msaa_sample_count.unwrap_or(1).max(1) as u32; let server = Rc::new(Self { state: Arc::new(WgpuState { instance, adapter, device, queue }), diff --git a/fyrox-graphics-wgpu/src/texture.rs b/fyrox-graphics-wgpu/src/texture.rs index aa6a95c9d9..e182ecf2c2 100644 --- a/fyrox-graphics-wgpu/src/texture.rs +++ b/fyrox-graphics-wgpu/src/texture.rs @@ -124,6 +124,9 @@ pub struct WgpuTexture { server: Weak, texture: wgpu::Texture, view: wgpu::TextureView, + /// Separate view with DepthOnly aspect for shader bindings of depth-stencil textures. + /// For non-depth-stencil textures, this is the same as `view`. + binding_view: wgpu::TextureView, kind: Cell, pixel_kind: Cell, size_bytes: Cell, @@ -148,14 +151,34 @@ impl WgpuTexture { }); let view = texture.create_view(&wgpu::TextureViewDescriptor { - label: None, format: Some(format), dimension: Some(texture_view_dimension(desc.kind)), ..Default::default() + label: None, + format: None, + dimension: Some(texture_view_dimension(desc.kind)), + aspect: wgpu::TextureAspect::All, + ..Default::default() }); + // For depth-stencil textures, create a separate view with DepthOnly aspect + // for shader bindings. wgpu rejects Depth+Stencil aspect on texture bindings. + let binding_view = match format { + wgpu::TextureFormat::Depth24PlusStencil8 + | wgpu::TextureFormat::Depth32FloatStencil8 => { + texture.create_view(&wgpu::TextureViewDescriptor { + label: None, + format: None, + dimension: Some(texture_view_dimension(desc.kind)), + aspect: wgpu::TextureAspect::DepthOnly, + ..Default::default() + }) + } + _ => view.clone(), + }; + let size_bytes = calc_size_bytes(desc.kind, desc.pixel_kind, desc.mip_count); if let Some(data) = desc.data { Self::upload(&server.state.queue, &texture, desc.kind, desc.pixel_kind, data, desc.mip_count)?; } server.memory_usage.borrow_mut().textures += size_bytes; - Ok(Self { server: server.weak_ref(), texture, view, kind: Cell::new(desc.kind), pixel_kind: Cell::new(desc.pixel_kind), size_bytes: Cell::new(size_bytes) }) + Ok(Self { server: server.weak_ref(), texture, view, binding_view, kind: Cell::new(desc.kind), pixel_kind: Cell::new(desc.pixel_kind), size_bytes: Cell::new(size_bytes) }) } fn upload(queue: &wgpu::Queue, texture: &wgpu::Texture, kind: GpuTextureKind, pk: PixelKind, data: &[u8], mip_count: usize) -> Result<(), FrameworkError> { @@ -226,6 +249,8 @@ impl WgpuTexture { pub fn wgpu_texture(&self) -> &wgpu::Texture { &self.texture } pub fn wgpu_view(&self) -> &wgpu::TextureView { &self.view } + /// Returns a view suitable for shader bindings (DepthOnly for depth-stencil textures). + pub fn wgpu_binding_view(&self) -> &wgpu::TextureView { &self.binding_view } pub fn format(&self) -> wgpu::TextureFormat { self.texture.format() } } From 31c7ceaaa3149b6c12d10be1a6dfd753526e6df2 Mon Sep 17 00:00:00 2001 From: nice0hack Date: Sat, 4 Jul 2026 21:40:10 +0500 Subject: [PATCH 07/34] convert all shaders to wgpu --- Cargo.toml | 1 - editor/resources/shaders/gizmo.shader | 33 +- editor/resources/shaders/grid.shader | 124 +- editor/resources/shaders/highlight.shader | 64 +- editor/resources/shaders/overlay.shader | 39 +- editor/resources/shaders/sprite_gizmo.shader | 65 +- fyrox-graphics-wgpu/Cargo.toml | 3 +- fyrox-graphics-wgpu/src/framebuffer.rs | 130 +- fyrox-graphics-wgpu/src/geometry_buffer.rs | 104 +- fyrox-graphics-wgpu/src/program.rs | 1126 +++-------------- fyrox-graphics-wgpu/src/server.rs | 24 + fyrox-graphics-wgpu/src/shaders/shared.glsl | 366 ------ fyrox-graphics-wgpu/src/shaders/shared.wgsl | 412 ++++++ fyrox-graphics/src/gpu_program.rs | 4 + fyrox-impl/Cargo.toml | 1 - fyrox-impl/src/engine/mod.rs | 2 +- fyrox-impl/src/lib.rs | 3 - .../src/renderer/shaders/ambient_light.shader | 89 +- fyrox-impl/src/renderer/shaders/blit.shader | 31 +- fyrox-impl/src/renderer/shaders/bloom.shader | 35 +- fyrox-impl/src/renderer/shaders/blur.shader | 42 +- fyrox-impl/src/renderer/shaders/debug.shader | 31 +- fyrox-impl/src/renderer/shaders/decal.shader | 79 +- .../shaders/deferred_directional_light.shader | 82 +- .../shaders/deferred_point_light.shader | 62 +- .../shaders/deferred_spot_light.shader | 76 +- fyrox-impl/src/renderer/shaders/fxaa.shader | 273 ++-- .../src/renderer/shaders/gaussian_blur.shader | 55 +- .../renderer/shaders/hdr_adaptation.shader | 32 +- .../src/renderer/shaders/hdr_downscale.shader | 65 +- .../src/renderer/shaders/hdr_luminance.shader | 38 +- .../src/renderer/shaders/hdr_map.shader | 74 +- .../src/renderer/shaders/irradiance.shader | 70 +- .../src/renderer/shaders/pixel_counter.shader | 22 +- .../renderer/shaders/point_volumetric.shader | 46 +- .../src/renderer/shaders/prefilter.shader | 86 +- fyrox-impl/src/renderer/shaders/skybox.shader | 27 +- .../renderer/shaders/spot_volumetric.shader | 62 +- fyrox-impl/src/renderer/shaders/ssao.shader | 67 +- .../src/renderer/shaders/visibility.shader | 59 +- .../shaders/visibility_optimizer.shader | 48 +- .../renderer/shaders/volume_marker_lit.shader | 26 +- .../renderer/shaders/volume_marker_vol.shader | 26 +- .../src/resource/gltf/gltf_standard.shader | 533 ++++---- .../shader/standard/standard-two-sides.shader | 616 ++------- .../src/shader/standard/standard.shader | 536 ++++---- .../src/shader/standard/standard2d.shader | 77 +- .../standard/standard_particle_system.shader | 130 +- .../shader/standard/standard_sprite.shader | 54 +- .../src/shader/standard/terrain.shader | 600 +++------ .../src/shader/standard/tile.shader | 77 +- .../src/shader/standard/widget.shader | 117 +- 52 files changed, 2829 insertions(+), 4015 deletions(-) delete mode 100644 fyrox-graphics-wgpu/src/shaders/shared.glsl create mode 100644 fyrox-graphics-wgpu/src/shaders/shared.wgsl diff --git a/Cargo.toml b/Cargo.toml index 7036a87971..7951a2f67b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ members = [ "fyrox-texture", "fyrox-autotile", "fyrox-material", - "fyrox-graphics-gl", "fyrox-graphics-wgpu"] resolver = "2" diff --git a/editor/resources/shaders/gizmo.shader b/editor/resources/shaders/gizmo.shader index 7023806919..5bff2e2cbb 100644 --- a/editor/resources/shaders/gizmo.shader +++ b/editor/resources/shaders/gizmo.shader @@ -59,27 +59,40 @@ ), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; + struct VertexInput { + @location(0) vertexPosition: vec3f, + }; - void main() - { - gl_Position = fyrox_instanceData.worldViewProjection * vec4(vertexPosition, 1.0); + struct VertexOutput { + @builtin(position) position: vec4f, + }; + + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.position = fyrox_instanceData.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; + struct FragOutput { + @location(0) color: vec4f, + @builtin(frag_depth) depth: f32, + }; - void main() - { - FragColor = properties.diffuseColor; + @fragment + fn fs_main(@builtin(position) fragCoord: vec4f) -> FragOutput { + var output: FragOutput; + output.color = properties.diffuseColor; // Pull depth towards near clipping plane so the gizmo will be drawn on top // of everything, but its parts will be correctly handled by depth test. - gl_FragDepth = gl_FragCoord.z * 0.001; + output.depth = fragCoord.z * 0.001; + return output; } "#, ), ], -) \ No newline at end of file +) diff --git a/editor/resources/shaders/grid.shader b/editor/resources/shaders/grid.shader index 1b21785a3f..590ac2ff97 100644 --- a/editor/resources/shaders/grid.shader +++ b/editor/resources/shaders/grid.shader @@ -83,23 +83,29 @@ ), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - - out vec3 nearPoint; - out vec3 farPoint; - - vec3 Unproject(float x, float y, float z, mat4 matrix) - { - vec4 position = matrix * vec4(x, y, z, 1.0); + struct VertexInput { + @location(0) vertexPosition: vec3f, + }; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) nearPoint: vec3f, + @location(1) farPoint: vec3f, + }; + + fn Unproject(x: f32, y: f32, z: f32, matrix: mat4x4f) -> vec3f { + var position = matrix * vec4f(x, y, z, 1.0); return position.xyz / position.w; } - void main() - { - mat4 invViewProj = inverse(fyrox_cameraData.viewProjectionMatrix); - nearPoint = Unproject(vertexPosition.x, vertexPosition.y, 0.0, invViewProj); - farPoint = Unproject(vertexPosition.x, vertexPosition.y, 1.0, invViewProj); - gl_Position = vec4(vertexPosition, 1.0); + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + var invViewProj = inverse(fyrox_cameraData.viewProjectionMatrix); + output.nearPoint = Unproject(input.vertexPosition.x, input.vertexPosition.y, 0.0, invViewProj); + output.farPoint = Unproject(input.vertexPosition.x, input.vertexPosition.y, 1.0, invViewProj); + output.position = vec4f(input.vertexPosition, 1.0); + return output; } "#, @@ -108,68 +114,68 @@ // Original code: https://asliceofrendering.com/scene%20helper/2020/01/05/InfiniteGrid/ // Fixed and adapted for Fyrox. - out vec4 FragColor; + struct FragOutput { + @location(0) color: vec4f, + @builtin(frag_depth) depth: f32, + }; - in vec3 nearPoint; - in vec3 farPoint; - - vec4 grid(vec3 fragPos3D) { - vec2 projection; - vec3 planeNormal; - vec4 xColor; - vec4 yColor; + fn grid(fragPos3D: vec3f) -> vec4f { + var projection: vec2f; + var planeNormal: vec3f; + var xColor: vec4f; + var yColor: vec4f; if (properties.orientation == 0) { projection = fragPos3D.xz; - planeNormal = vec3(0.0, 1.0, 0.0); + planeNormal = vec3f(0.0, 1.0, 0.0); xColor = properties.xAxisColor; yColor = properties.zAxisColor; } else if (properties.orientation == 1) { projection = fragPos3D.xy; - planeNormal = vec3(0.0, 0.0, 1.0); + planeNormal = vec3f(0.0, 0.0, 1.0); xColor = properties.yAxisColor; yColor = properties.xAxisColor; } else if (properties.orientation == 2) { projection = fragPos3D.zy; - planeNormal = vec3(1.0, 0.0, 0.0); + planeNormal = vec3f(1.0, 0.0, 0.0); xColor = properties.zAxisColor; yColor = properties.yAxisColor; } - - vec2 coord = projection * properties.scale; - vec2 derivative = fwidth(coord); - vec2 grid = abs(fract(coord - 0.5) - 0.5) / derivative; - float line = min(grid.x, grid.y); - float minX = 0.5 * min(derivative.x, 1.0); - float minY = 0.5 * min(derivative.y, 1.0); - - vec4 color = properties.diffuseColor; - float alpha = 1.0 - min(line, 1.0); + + var coord = projection * properties.scale; + var derivative = fwidth(coord); + var gridVal = abs(fract(coord - vec2f(0.5)) - vec2f(0.5)) / derivative; + var lineVal = min(gridVal.x, gridVal.y); + var minX = 0.5 * min(derivative.x, 1.0); + var minY = 0.5 * min(derivative.y, 1.0); + + var color = properties.diffuseColor; + var alpha = 1.0 - min(lineVal, 1.0); // Sharpen lines a bit. - color.a = alpha >= 0.5 ? 1.0 : 0.0; + color.a = select(0.0, 1.0, alpha >= 0.5); if (projection.x > -minX && projection.x < minX) { - color.xyz = xColor.xyz; + color = vec4f(xColor.xyz, color.a); } else if (projection.y > -minY && projection.y < minY) { - color.xyz = yColor.xyz; + color = vec4f(yColor.xyz, color.a); } else { - vec3 viewDir = fragPos3D - fyrox_cameraData.position; + var viewDir = fragPos3D - fyrox_cameraData.position; // This helps to negate moire pattern at large distances. - float cosAngle = abs(dot(planeNormal, normalize(viewDir))); + var cosAngle = abs(dot(planeNormal, normalize(viewDir))); color.a *= cosAngle; } return color; } - float computeDepth(vec3 pos) { - vec4 clip_space_pos = fyrox_cameraData.viewProjectionMatrix * vec4(pos.xyz, 1.0); + fn computeDepth(pos: vec3f) -> f32 { + var clip_space_pos = fyrox_cameraData.viewProjectionMatrix * vec4f(pos.xyz, 1.0); return (clip_space_pos.z / clip_space_pos.w); } - void main() - { - float nearCoord; - float farCoord; + @fragment + fn fs_main(@location(0) nearPoint: vec3f, @location(1) farPoint: vec3f, @builtin(position) fragCoord: vec4f) -> FragOutput { + var nearCoord: f32; + var farCoord: f32; if (properties.orientation == 0) { nearCoord = nearPoint.y; farCoord = farPoint.y; @@ -181,25 +187,29 @@ farCoord = farPoint.x; } - float denominator = farCoord - nearCoord; - float t = denominator != 0.0 ? -nearCoord / denominator : 0.0; + var denominator = farCoord - nearCoord; + var t = select(0.0, -nearCoord / denominator, denominator != 0.0); + + var fragPos3D = nearPoint + t * (farPoint - nearPoint); - vec3 fragPos3D = nearPoint + t * (farPoint - nearPoint); + var depth = computeDepth(fragPos3D); - float depth = computeDepth(fragPos3D); - gl_FragDepth = ((gl_DepthRange.diff * depth) + gl_DepthRange.near + gl_DepthRange.far) / 2.0; + var output: FragOutput; + output.depth = (depth + 1.0) / 2.0; - FragColor = grid(fragPos3D); - if (properties.isPerspective) { - FragColor.a *= float(t > 0.0); + output.color = grid(fragPos3D); + if (properties.isPerspective != 0u) { + output.color.a *= f32(t > 0.0); } // Alpha test to prevent blending issues. - if (FragColor.a < 0.01) { + if (output.color.a < 0.01) { discard; } + + return output; } "#, ), ], -) \ No newline at end of file +) diff --git a/editor/resources/shaders/highlight.shader b/editor/resources/shaders/highlight.shader index 5987561dd8..aec3bd11c4 100644 --- a/editor/resources/shaders/highlight.shader +++ b/editor/resources/shaders/highlight.shader @@ -53,48 +53,52 @@ vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + }; - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.texCoord = input.vertexTexCoord; + output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - layout (location = 0) out vec4 outColor; + @fragment + fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f { + var size = textureDimensions(frameTexture_tex); - in vec2 texCoord; + var w = 1.0 / f32(size.x); + var h = 1.0 / f32(size.y); - void main() { - ivec2 size = textureSize(frameTexture, 0); + var n: array; + n[0] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(-w, -h)).a; + n[1] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(0.0, -h)).a; + n[2] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(w, -h)).a; + n[3] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(-w, 0.0)).a; + n[4] = textureSample(frameTexture_tex, frameTexture_samp, texCoord).a; + n[5] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(w, 0.0)).a; + n[6] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(-w, h)).a; + n[7] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(0.0, h)).a; + n[8] = textureSample(frameTexture_tex, frameTexture_samp, texCoord + vec2f(w, h)).a; - float w = 1.0 / float(size.x); - float h = 1.0 / float(size.y); + var sobel_edge_h = n[2] + (2.0 * n[5]) + n[8] - (n[0] + (2.0 * n[3]) + n[6]); + var sobel_edge_v = n[0] + (2.0 * n[1]) + n[2] - (n[6] + (2.0 * n[7]) + n[8]); + var sobel = sqrt((sobel_edge_h * sobel_edge_h) + (sobel_edge_v * sobel_edge_v)); - float n[9]; - n[0] = texture(frameTexture, texCoord + vec2(-w, -h)).a; - n[1] = texture(frameTexture, texCoord + vec2(0.0, -h)).a; - n[2] = texture(frameTexture, texCoord + vec2(w, -h)).a; - n[3] = texture(frameTexture, texCoord + vec2( -w, 0.0)).a; - n[4] = texture(frameTexture, texCoord).a; - n[5] = texture(frameTexture, texCoord + vec2(w, 0.0)).a; - n[6] = texture(frameTexture, texCoord + vec2(-w, h)).a; - n[7] = texture(frameTexture, texCoord + vec2(0.0, h)).a; - n[8] = texture(frameTexture, texCoord + vec2(w, h)).a; - - float sobel_edge_h = n[2] + (2.0 * n[5]) + n[8] - (n[0] + (2.0 * n[3]) + n[6]); - float sobel_edge_v = n[0] + (2.0 * n[1]) + n[2] - (n[6] + (2.0 * n[7]) + n[8]); - float sobel = sqrt((sobel_edge_h * sobel_edge_h) + (sobel_edge_v * sobel_edge_v)); - - outColor = vec4(properties.color.rgb, properties.color.a * sobel); + return vec4f(properties.color.rgb, properties.color.a * sobel); } "#, ) ] -) \ No newline at end of file +) diff --git a/editor/resources/shaders/overlay.shader b/editor/resources/shaders/overlay.shader index 07c43416f2..2cc92ef6f3 100644 --- a/editor/resources/shaders/overlay.shader +++ b/editor/resources/shaders/overlay.shader @@ -56,32 +56,35 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + }; - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; - void main() - { - texCoord = vertexTexCoord; - vec2 vertexOffset = vertexTexCoord * 2.0 - 1.0; - vec4 worldPosition = properties.worldMatrix * vec4(vertexPosition, 1.0); - vec3 offset = (vertexOffset.x * properties.cameraSideVector + vertexOffset.y * properties.cameraUpVector) * properties.size; - gl_Position = properties.viewProjectionMatrix * (worldPosition + vec4(offset.x, offset.y, offset.z, 0.0)); + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.texCoord = input.vertexTexCoord; + var vertexOffset = input.vertexTexCoord * 2.0 - 1.0; + var worldPosition = properties.worldMatrix * vec4f(input.vertexPosition, 1.0); + var offset = (vertexOffset.x * properties.cameraSideVector + vertexOffset.y * properties.cameraUpVector) * properties.size; + output.position = properties.viewProjectionMatrix * (worldPosition + vec4f(offset.x, offset.y, offset.z, 0.0)); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - - in vec2 texCoord; - - void main() - { - FragColor = texture(diffuseTexture, texCoord); + @fragment + fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f { + return textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord); } "#, ) ] -) \ No newline at end of file +) diff --git a/editor/resources/shaders/sprite_gizmo.shader b/editor/resources/shaders/sprite_gizmo.shader index 442e73c59d..bb8e3feffb 100644 --- a/editor/resources/shaders/sprite_gizmo.shader +++ b/editor/resources/shaders/sprite_gizmo.shader @@ -61,44 +61,53 @@ ), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 2) in vec2 vertexParams; - layout(location = 3) in vec4 vertexColor; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(2) vertexParams: vec2f, + @location(3) vertexColor: vec4f, + }; - out vec2 texCoord; - out vec4 color; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + @location(1) color: vec4f, + }; - void main() - { - float size = vertexParams.x; - float rotation = vertexParams.y; + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + var size = input.vertexParams.x; + var rotation = input.vertexParams.y; - texCoord = vertexTexCoord; - color = vertexColor; - vec2 vertexOffset = S_RotateVec2(vertexTexCoord * 2.0 - 1.0, rotation); - vec4 worldPosition = fyrox_instanceData.worldMatrix * vec4(vertexPosition, 1.0); - vec3 offset = (vertexOffset.x * fyrox_cameraData.sideVector + vertexOffset.y * fyrox_cameraData.upVector) * size; - gl_Position = fyrox_cameraData.viewProjectionMatrix * (worldPosition + vec4(offset.x, offset.y, offset.z, 0.0)); + output.texCoord = input.vertexTexCoord; + output.color = input.vertexColor; + var vertexOffset = S_RotateVec2(input.vertexTexCoord * 2.0 - 1.0, rotation); + var worldPosition = fyrox_instanceData.worldMatrix * vec4f(input.vertexPosition, 1.0); + var offset = (vertexOffset.x * fyrox_cameraData.sideVector + vertexOffset.y * fyrox_cameraData.upVector) * size; + output.position = fyrox_cameraData.viewProjectionMatrix * (worldPosition + vec4f(offset.x, offset.y, offset.z, 0.0)); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; + struct FragOutput { + @location(0) color: vec4f, + @builtin(frag_depth) depth: f32, + }; - in vec2 texCoord; - in vec4 color; + @fragment + fn fs_main(@location(0) texCoord: vec2f, @location(1) color: vec4f, @builtin(position) fragCoord: vec4f) -> FragOutput { + var output: FragOutput; + output.color = color * S_SRGBToLinear(textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord)); - void main() - { - FragColor = color * S_SRGBToLinear(texture(diffuseTexture, texCoord)); - - // Pull depth towards near clipping plane so the gizmo will be drawn on top - // of everything, but its parts will be correctly handled by depth test. - gl_FragDepth = gl_FragCoord.z * 0.001; - } + // Pull depth towards near clipping plane so the gizmo will be drawn on top + // of everything, but its parts will be correctly handled by depth test. + output.depth = fragCoord.z * 0.001; + return output; + } "#, ), ], -) \ No newline at end of file +) diff --git a/fyrox-graphics-wgpu/Cargo.toml b/fyrox-graphics-wgpu/Cargo.toml index a021a2059b..3caa6f806b 100644 --- a/fyrox-graphics-wgpu/Cargo.toml +++ b/fyrox-graphics-wgpu/Cargo.toml @@ -16,8 +16,7 @@ rust-version = "1.87" winit = { version = "0.30" } fyrox-graphics = { version = "2.0.0-rc.1", path = "../fyrox-graphics" } fyrox-core = { version = "2.0.0-rc.1", path = "../fyrox-core" } -wgpu = { version = "29.0.3", features = ["spirv"] } -naga = { version = "29.0.3", features = ["glsl-in", "spv-out"] } +wgpu = { version = "29.0.3" } bytemuck = { version = "1", features = ["derive"] } log = "0.4" diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index 6a701c64ba..ab8a1e790d 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -83,6 +83,17 @@ fn blend_factor_to_wgpu(f: fyrox_graphics::BlendFactor) -> wgpu::BlendFactor { } } +fn cubemap_face_to_layer(face: CubeMapFace) -> u32 { + match face { + CubeMapFace::PositiveX => 0, + CubeMapFace::NegativeX => 1, + CubeMapFace::PositiveY => 2, + CubeMapFace::NegativeY => 3, + CubeMapFace::PositiveZ => 4, + CubeMapFace::NegativeZ => 5, + } +} + fn texture_format_for_attachment(tex: &GpuTexture) -> wgpu::TextureFormat { tex.as_any().downcast_ref::().unwrap().format() } @@ -97,6 +108,7 @@ pub struct PipelineKey { depth_test: bool, depth_write: bool, cull: u8, + extra_vert_count: u8, } pub struct WgpuFrameBuffer { @@ -115,11 +127,12 @@ impl WgpuFrameBuffer { Self { server: server.weak_ref(), depth_attachment: None, color_attachments: Default::default(), is_backbuffer: true } } - fn get_or_create_pipeline(&self, server: &WgpuGraphicsServer, program: &WgpuProgram, params: &DrawParameters, geo: &WgpuGeometryBuffer, cf: wgpu::TextureFormat, df: Option) -> wgpu::RenderPipeline { + fn get_or_create_pipeline(&self, server: &WgpuGraphicsServer, program: &WgpuProgram, params: &DrawParameters, all_layouts: &[wgpu::VertexBufferLayout<'static>], cf: wgpu::TextureFormat, df: Option, pipeline_layout: &wgpu::PipelineLayout, element_kind: fyrox_graphics::ElementKind) -> wgpu::RenderPipeline { let key = PipelineKey { program_ptr: program as *const WgpuProgram as usize, color_format: cf, depth_format: df, sample_count: server.msaa_sample_count, blend: params.blend.is_some(), depth_test: params.depth_test.is_some(), depth_write: params.depth_write, cull: match params.cull_face { Some(CullFace::Back) => 2, Some(CullFace::Front) => 1, None => 0 }, + extra_vert_count: all_layouts.len() as u8, }; let key_hash = { let mut h = DefaultHasher::new(); key.hash(&mut h); h.finish() }; { let cache = server.pipeline_cache.borrow(); if let Some(p) = cache.get(&key_hash) { return p.clone(); } } @@ -141,16 +154,16 @@ impl WgpuFrameBuffer { let cull = match params.cull_face { Some(CullFace::Back) => Some(wgpu::Face::Back), Some(CullFace::Front) => Some(wgpu::Face::Front), None => None }; - let topo = match geo.element_kind() { + let topo = match element_kind { fyrox_graphics::ElementKind::Triangle => wgpu::PrimitiveTopology::TriangleList, fyrox_graphics::ElementKind::Line => wgpu::PrimitiveTopology::LineList, fyrox_graphics::ElementKind::Point => wgpu::PrimitiveTopology::PointList, }; let pipeline = server.state.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("RP"), layout: Some(program.pipeline_layout()), - vertex: wgpu::VertexState { module: program.vertex_module(), entry_point: Some("main"), buffers: geo.vertex_buffer_layouts(), compilation_options: Default::default() }, - fragment: Some(wgpu::FragmentState { module: program.fragment_module(), entry_point: Some("main"), targets: &[Some(wgpu::ColorTargetState { format: cf, blend: blend_state, write_mask: wgpu::ColorWrites::ALL })], compilation_options: Default::default() }), + label: Some("RP"), layout: Some(pipeline_layout), + vertex: wgpu::VertexState { module: program.vertex_module(), entry_point: Some("vs_main"), buffers: all_layouts, compilation_options: Default::default() }, + fragment: Some(wgpu::FragmentState { module: program.fragment_module(), entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format: cf, blend: blend_state, write_mask: wgpu::ColorWrites::ALL })], compilation_options: Default::default() }), primitive: wgpu::PrimitiveState { topology: topo, strip_index_format: None, front_face: wgpu::FrontFace::Ccw, cull_mode: cull, ..Default::default() }, depth_stencil, multisample: wgpu::MultisampleState { count: server.msaa_sample_count, mask: !0, alpha_to_coverage_enabled: false }, @@ -183,15 +196,57 @@ impl WgpuFrameBuffer { else { wgpu::TextureFormat::Rgba8Unorm }; let df = self.depth_attachment.as_ref().map(|a| texture_format_for_attachment(&a.texture)); - let pipeline = self.get_or_create_pipeline(&server, prog, params, geo, cf, df); + + // Collect actual texture formats from resources for layout creation + let mut texture_formats: Vec<(usize, wgpu::TextureFormat)> = Vec::new(); + for group in resources { + for binding in group.bindings { + if let ResourceBinding::Texture { texture, binding: loc, .. } = binding { + let wt = texture.as_any().downcast_ref::().unwrap(); + texture_formats.push((*loc, wt.format())); + } + } + } + let (_, pipeline_layout) = prog.get_or_create_layouts(&texture_formats); + + let (all_layouts, extra_vert_count) = build_vertex_layouts(geo); + let pipeline = self.get_or_create_pipeline(&server, prog, params, &all_layouts, cf, df, &pipeline_layout, geo.element_kind()); let bind_group = create_bind_group(&server, prog, resources); let mut encoder = server.state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("DrawEnc") }); - let color_view = if self.is_backbuffer { surface_tex.as_ref().unwrap().texture.create_view(&wgpu::TextureViewDescriptor::default()) } - else { self.color_attachments.first().unwrap().texture.as_any().downcast_ref::().unwrap().wgpu_view().clone() }; + let color_view = if self.is_backbuffer { + surface_tex.as_ref().unwrap().texture.create_view(&wgpu::TextureViewDescriptor::default()) + } else { + let first_color = self.color_attachments.first().unwrap(); + if let Some(face) = first_color.cube_map_face() { + let wt = first_color.texture.as_any().downcast_ref::().unwrap(); + wt.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { + dimension: Some(wgpu::TextureViewDimension::D2), + base_array_layer: cubemap_face_to_layer(face), + array_layer_count: Some(1), + mip_level_count: Some(1), + ..Default::default() + }) + } else { + first_color.texture.as_any().downcast_ref::().unwrap().wgpu_view().clone() + } + }; - let depth_view = self.depth_attachment.as_ref().map(|a| a.texture.as_any().downcast_ref::().unwrap().wgpu_view().clone()); + let depth_view = self.depth_attachment.as_ref().map(|a| { + let wt = a.texture.as_any().downcast_ref::().unwrap(); + if let Some(face) = a.cube_map_face() { + wt.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { + dimension: Some(wgpu::TextureViewDimension::D2), + base_array_layer: cubemap_face_to_layer(face), + array_layer_count: Some(1), + mip_level_count: Some(1), + ..Default::default() + }) + } else { + wt.wgpu_view().clone() + } + }); { let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { @@ -204,8 +259,12 @@ impl WgpuFrameBuffer { rp.set_viewport(viewport.x() as f32, viewport.y() as f32, viewport.w() as f32, viewport.h() as f32, 0.0, 1.0); rp.set_pipeline(&pipeline); if let Some(bg) = &bind_group { rp.set_bind_group(0, bg, &[]); } - for (i, vb) in geo.vertex_buffers().iter().enumerate() { rp.set_vertex_buffer(i as u32, vb.slice(..)); } - rp.set_index_buffer(geo.element_buffer().slice(..), wgpu::IndexFormat::Uint32); + let vbs = geo.vertex_buffers(); + for (i, vb) in vbs.iter().enumerate() { rp.set_vertex_buffer(i as u32, vb.slice(..)); } + let geo_buf_count = vbs.len() as u32; + for i in 0..extra_vert_count { rp.set_vertex_buffer(geo_buf_count + i, server.dummy_vertex_buffer.slice(..)); } + let eb = geo.element_buffer(); + rp.set_index_buffer(eb.slice(..), wgpu::IndexFormat::Uint32); let ipe = geo.element_kind().index_per_element(); let idx_count = (count * ipe) as u32; @@ -265,16 +324,60 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { } } +/// Expected vertex attribute locations that the standard shaders may need but +/// geometry might not provide (e.g. boneWeights, boneIndices, vertexSecondTexCoord). +const EXTRA_VERTEX_ATTRS: &[(u32, wgpu::VertexFormat)] = &[ + (4, wgpu::VertexFormat::Float32x4), // boneWeights + (5, wgpu::VertexFormat::Float32x4), // boneIndices + (6, wgpu::VertexFormat::Float32x2), // vertexSecondTexCoord +]; + +/// Builds the full vertex buffer layout list, adding dummy entries for attributes +/// the shader expects but the geometry doesn't provide. Returns (layouts, extra_count). +fn build_vertex_layouts(geo: &WgpuGeometryBuffer) -> (Vec>, u32) { + let geo_layouts = geo.vertex_buffer_layouts(); + let mut provided = std::collections::HashSet::new(); + for layout in geo_layouts { + for attr in layout.attributes { provided.insert(attr.shader_location); } + } + let mut all: Vec> = geo_layouts.to_vec(); + let mut extra = 0u32; + for &(loc, fmt) in EXTRA_VERTEX_ATTRS { + if !provided.contains(&loc) { + all.push(wgpu::VertexBufferLayout { + array_stride: 0, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: Box::leak(vec![wgpu::VertexAttribute { format: fmt, offset: 0, shader_location: loc }].into_boxed_slice()), + }); + extra += 1; + } + } + (all, extra) +} + +fn is_filterable_format(fmt: wgpu::TextureFormat) -> bool { + !matches!(fmt, wgpu::TextureFormat::R32Float | wgpu::TextureFormat::Rg32Float | wgpu::TextureFormat::Rgba32Float + | wgpu::TextureFormat::R8Uint | wgpu::TextureFormat::R16Uint | wgpu::TextureFormat::R32Uint + | wgpu::TextureFormat::R8Sint | wgpu::TextureFormat::R16Sint | wgpu::TextureFormat::R32Sint) +} + fn create_bind_group(server: &WgpuGraphicsServer, program: &WgpuProgram, groups: &[ResourceBindGroup]) -> Option { let mut entries = Vec::new(); + let mut texture_formats: Vec<(usize, wgpu::TextureFormat)> = Vec::new(); for group in groups { for binding in group.bindings { match binding { ResourceBinding::Texture { texture, sampler, binding: loc } => { let wt = texture.as_any().downcast_ref::()?; let ws = sampler.as_any().downcast_ref::()?; + texture_formats.push((*loc, wt.format())); entries.push(wgpu::BindGroupEntry { binding: *loc as u32, resource: wgpu::BindingResource::TextureView(wt.wgpu_binding_view()) }); - entries.push(wgpu::BindGroupEntry { binding: (*loc + 100) as u32, resource: wgpu::BindingResource::Sampler(ws.wgpu_sampler()) }); + let sampler_res = if is_filterable_format(wt.format()) { + wgpu::BindingResource::Sampler(ws.wgpu_sampler()) + } else { + wgpu::BindingResource::Sampler(server.non_filtering_sampler()) + }; + entries.push(wgpu::BindGroupEntry { binding: (*loc + 100) as u32, resource: sampler_res }); } ResourceBinding::Buffer { buffer, binding: loc, data_usage } => { let wb = buffer.as_any().downcast_ref::()?; @@ -287,5 +390,6 @@ fn create_bind_group(server: &WgpuGraphicsServer, program: &WgpuProgram, groups: } } if entries.is_empty() { return None; } - Some(server.state.device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("BG"), layout: program.bind_group_layout(), entries: &entries })) + let (bgl, _) = program.get_or_create_layouts(&texture_formats); + Some(server.state.device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("BG"), layout: &bgl, entries: &entries })) } diff --git a/fyrox-graphics-wgpu/src/geometry_buffer.rs b/fyrox-graphics-wgpu/src/geometry_buffer.rs index cb9338457f..8cb2100135 100644 --- a/fyrox-graphics-wgpu/src/geometry_buffer.rs +++ b/fyrox-graphics-wgpu/src/geometry_buffer.rs @@ -25,31 +25,34 @@ use fyrox_graphics::{ geometry_buffer::{AttributeKind, ElementsDescriptor, GpuGeometryBufferDescriptor, GpuGeometryBufferTrait}, ElementKind, }; -use std::cell::Cell; +use std::cell::{Cell, RefCell}; use std::rc::Weak; -fn attribute_format(kind: AttributeKind, cc: usize) -> wgpu::VertexFormat { - match (kind, cc) { - (AttributeKind::Float, 1) => wgpu::VertexFormat::Float32, - (AttributeKind::Float, 2) => wgpu::VertexFormat::Float32x2, - (AttributeKind::Float, 3) => wgpu::VertexFormat::Float32x3, - (AttributeKind::Float, 4) => wgpu::VertexFormat::Float32x4, - (AttributeKind::UnsignedByte, 4) => wgpu::VertexFormat::Uint8x4, - (AttributeKind::UnsignedShort, 2) => wgpu::VertexFormat::Uint16x2, - (AttributeKind::UnsignedShort, 4) => wgpu::VertexFormat::Uint16x4, - (AttributeKind::UnsignedInt, 1) => wgpu::VertexFormat::Uint32, - (AttributeKind::UnsignedInt, 2) => wgpu::VertexFormat::Uint32x2, - (AttributeKind::UnsignedInt, 3) => wgpu::VertexFormat::Uint32x3, - (AttributeKind::UnsignedInt, 4) => wgpu::VertexFormat::Uint32x4, +fn attribute_format(kind: AttributeKind, cc: usize, normalized: bool) -> wgpu::VertexFormat { + match (kind, cc, normalized) { + (AttributeKind::Float, 1, _) => wgpu::VertexFormat::Float32, + (AttributeKind::Float, 2, _) => wgpu::VertexFormat::Float32x2, + (AttributeKind::Float, 3, _) => wgpu::VertexFormat::Float32x3, + (AttributeKind::Float, 4, _) => wgpu::VertexFormat::Float32x4, + (AttributeKind::UnsignedByte, 4, true) => wgpu::VertexFormat::Unorm8x4, + (AttributeKind::UnsignedByte, 4, false) => wgpu::VertexFormat::Uint8x4, + (AttributeKind::UnsignedShort, 2, true) => wgpu::VertexFormat::Unorm16x2, + (AttributeKind::UnsignedShort, 2, false) => wgpu::VertexFormat::Uint16x2, + (AttributeKind::UnsignedShort, 4, true) => wgpu::VertexFormat::Unorm16x4, + (AttributeKind::UnsignedShort, 4, false) => wgpu::VertexFormat::Uint16x4, + (AttributeKind::UnsignedInt, 1, _) => wgpu::VertexFormat::Uint32, + (AttributeKind::UnsignedInt, 2, _) => wgpu::VertexFormat::Uint32x2, + (AttributeKind::UnsignedInt, 3, _) => wgpu::VertexFormat::Uint32x3, + (AttributeKind::UnsignedInt, 4, _) => wgpu::VertexFormat::Uint32x4, _ => wgpu::VertexFormat::Float32x4, } } pub struct WgpuGeometryBuffer { _server: Weak, - vertex_buffers: Vec, + vertex_buffers: RefCell>, vertex_buffer_layouts: Vec>, - element_buffer: wgpu::Buffer, + element_buffer: RefCell, element_count: Cell, element_kind: ElementKind, } @@ -73,7 +76,7 @@ impl WgpuGeometryBuffer { let mut attributes = Vec::new(); let mut offset = 0u64; for attr in buf.attributes { - attributes.push(wgpu::VertexAttribute { format: attribute_format(attr.kind, attr.component_count), offset, shader_location: attr.location }); + attributes.push(wgpu::VertexAttribute { format: attribute_format(attr.kind, attr.component_count, attr.normalized), offset, shader_location: attr.location }); offset += (attr.kind.size() * attr.component_count) as u64; } @@ -97,39 +100,90 @@ impl WgpuGeometryBuffer { }); if !element_data.is_empty() { server.state.queue.write_buffer(&element_buffer, 0, element_data); } - Ok(Self { _server: server.weak_ref(), vertex_buffers, vertex_buffer_layouts, element_buffer, element_count: Cell::new(element_count), element_kind: desc.elements.element_kind() }) + Ok(Self { _server: server.weak_ref(), vertex_buffers: RefCell::new(vertex_buffers), vertex_buffer_layouts, element_buffer: RefCell::new(element_buffer), element_count: Cell::new(element_count), element_kind: desc.elements.element_kind() }) } pub fn element_count(&self) -> usize { self.element_count.get() } pub fn element_kind(&self) -> ElementKind { self.element_kind } - pub fn vertex_buffers(&self) -> &[wgpu::Buffer] { &self.vertex_buffers } + pub fn vertex_buffers(&self) -> std::cell::Ref<'_, Vec> { self.vertex_buffers.borrow() } pub fn vertex_buffer_layouts(&self) -> &[wgpu::VertexBufferLayout<'static>] { &self.vertex_buffer_layouts } - pub fn element_buffer(&self) -> &wgpu::Buffer { &self.element_buffer } + pub fn element_buffer(&self) -> std::cell::Ref<'_, wgpu::Buffer> { self.element_buffer.borrow() } } impl GpuGeometryBufferTrait for WgpuGeometryBuffer { - fn set_buffer_data(&self, buffer: usize, data: &[u8]) { + fn set_buffer_data(&self, buffer_idx: usize, data: &[u8]) { if let Some(server) = self._server.upgrade() { - if let Some(buf) = self.vertex_buffers.get(buffer) { server.state.queue.write_buffer(buf, 0, data); } + let mut bufs = self.vertex_buffers.borrow_mut(); + if let Some(buf) = bufs.get(buffer_idx) { + if (data.len() as u64) <= buf.size() { + server.state.queue.write_buffer(buf, 0, data); + } else { + // Recreate buffer with correct size + let new_buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: data.len() as u64, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + server.state.queue.write_buffer(&new_buf, 0, data); + bufs[buffer_idx] = new_buf; + } + } } } fn element_count(&self) -> usize { self.element_count.get() } fn set_triangles(&self, triangles: &[TriangleDefinition]) { if let Some(server) = self._server.upgrade() { self.element_count.set(triangles.len()); - server.state.queue.write_buffer(&self.element_buffer, 0, array_as_u8_slice(triangles)); + let data = array_as_u8_slice(triangles); + let mut eb = self.element_buffer.borrow_mut(); + if (data.len() as u64) <= eb.size() { + server.state.queue.write_buffer(&eb, 0, data); + } else { + let new_buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { + label: None, size: data.len() as u64, + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + server.state.queue.write_buffer(&new_buf, 0, data); + *eb = new_buf; + } } } fn set_lines(&self, lines: &[[u32; 2]]) { if let Some(server) = self._server.upgrade() { self.element_count.set(lines.len()); - server.state.queue.write_buffer(&self.element_buffer, 0, array_as_u8_slice(lines)); + let data = array_as_u8_slice(lines); + let mut eb = self.element_buffer.borrow_mut(); + if (data.len() as u64) <= eb.size() { + server.state.queue.write_buffer(&eb, 0, data); + } else { + let new_buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { + label: None, size: data.len() as u64, + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + server.state.queue.write_buffer(&new_buf, 0, data); + *eb = new_buf; + } } } fn set_points(&self, points: &[u32]) { if let Some(server) = self._server.upgrade() { self.element_count.set(points.len()); - server.state.queue.write_buffer(&self.element_buffer, 0, array_as_u8_slice(points)); + let data = array_as_u8_slice(points); + let mut eb = self.element_buffer.borrow_mut(); + if (data.len() as u64) <= eb.size() { + server.state.queue.write_buffer(&eb, 0, data); + } else { + let new_buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { + label: None, size: data.len() as u64, + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + server.state.queue.write_buffer(&new_buf, 0, data); + *eb = new_buf; + } } } } diff --git a/fyrox-graphics-wgpu/src/program.rs b/fyrox-graphics-wgpu/src/program.rs index 23b4b4734f..c886a6bfd3 100644 --- a/fyrox-graphics-wgpu/src/program.rs +++ b/fyrox-graphics-wgpu/src/program.rs @@ -24,730 +24,142 @@ use fyrox_graphics::{ error::FrameworkError, gpu_program::{GpuProgramTrait, GpuShaderTrait, SamplerKind, ShaderKind, ShaderPropertyKind, ShaderResourceDefinition, ShaderResourceKind}, }; +use std::borrow::Cow; +use std::cell::RefCell; use std::rc::Weak; -fn count_lines(src: &str) -> isize { src.bytes().filter(|b| *b == b'\n').count() as isize } - -/// Returns the Vulkan GLSL 450 separate texture type name. -fn vulkan_texture_type(kind: SamplerKind) -> &'static str { +/// Returns the WGSL texture type for a given sampler kind. +fn wgsl_texture_type(kind: SamplerKind) -> &'static str { match kind { - SamplerKind::Sampler1D => "texture1D", - SamplerKind::Sampler2D => "texture2D", - SamplerKind::Sampler3D => "texture3D", - SamplerKind::SamplerCube => "textureCube", - SamplerKind::USampler1D => "utexture1D", - SamplerKind::USampler2D => "utexture2D", - SamplerKind::USampler3D => "utexture3D", - SamplerKind::USamplerCube => "utextureCube", + SamplerKind::Sampler1D => "texture_1d", + SamplerKind::Sampler2D => "texture_2d", + SamplerKind::Sampler3D => "texture_3d", + SamplerKind::SamplerCube => "texture_cube", + SamplerKind::USampler1D => "texture_1d", + SamplerKind::USampler2D => "texture_2d", + SamplerKind::USampler3D => "texture_3d", + SamplerKind::USamplerCube => "texture_cube", + SamplerKind::DepthSampler2D => "texture_depth_2d", + SamplerKind::DepthSamplerCube => "texture_depth_cube", } } -/// Returns the combined sampler constructor name (e.g. "sampler2D", "samplerCube"). -fn sampler_constructor_name(kind: SamplerKind) -> &'static str { +/// Returns the WGSL type string for a shader property kind. +fn wgsl_property_type(kind: &ShaderPropertyKind) -> String { match kind { - SamplerKind::Sampler1D => "sampler1D", - SamplerKind::Sampler2D => "sampler2D", - SamplerKind::Sampler3D => "sampler3D", - SamplerKind::SamplerCube => "samplerCube", - SamplerKind::USampler1D => "usampler1D", - SamplerKind::USampler2D => "usampler2D", - SamplerKind::USampler3D => "usampler3D", - SamplerKind::USamplerCube => "usamplerCube", - } -} - -/// Maps a combined sampler type name to SamplerKind. -fn sampler_kind_from_type_name(type_name: &str) -> Option { - match type_name { - "sampler1D" => Some(SamplerKind::Sampler1D), - "sampler2D" => Some(SamplerKind::Sampler2D), - "sampler3D" => Some(SamplerKind::Sampler3D), - "samplerCube" => Some(SamplerKind::SamplerCube), - "usampler1D" => Some(SamplerKind::USampler1D), - "usampler2D" => Some(SamplerKind::USampler2D), - "usampler3D" => Some(SamplerKind::USampler3D), - "usamplerCube" => Some(SamplerKind::USamplerCube), - _ => None, + ShaderPropertyKind::Float { .. } => "f32".into(), + ShaderPropertyKind::FloatArray { max_len, .. } => format!("array"), + ShaderPropertyKind::Int { .. } => "i32".into(), + ShaderPropertyKind::IntArray { max_len, .. } => format!("array"), + ShaderPropertyKind::UInt { .. } => "u32".into(), + ShaderPropertyKind::UIntArray { max_len, .. } => format!("array"), + ShaderPropertyKind::Bool { .. } => "u32".into(), + ShaderPropertyKind::Vector2 { .. } => "vec2f".into(), + ShaderPropertyKind::Vector2Array { max_len, .. } => format!("array"), + ShaderPropertyKind::Vector3 { .. } => "vec3f".into(), + ShaderPropertyKind::Vector3Array { max_len, .. } => format!("array"), + ShaderPropertyKind::Vector4 { .. } => "vec4f".into(), + ShaderPropertyKind::Vector4Array { max_len, .. } => format!("array"), + ShaderPropertyKind::Matrix2 { .. } => "mat2x2f".into(), + ShaderPropertyKind::Matrix2Array { max_len, .. } => format!("array"), + ShaderPropertyKind::Matrix3 { .. } => "mat3x3f".into(), + ShaderPropertyKind::Matrix3Array { max_len, .. } => format!("array"), + ShaderPropertyKind::Matrix4 { .. } => "mat4x4f".into(), + ShaderPropertyKind::Matrix4Array { max_len, .. } => format!("array"), + ShaderPropertyKind::Color { .. } => "vec4f".into(), } } -/// Rewrites texture function calls, textureSize calls, and shared function argument -/// splitting for a given sampler name. Used for both global texture resources and -/// local function parameters. -fn rewrite_sampler_usages(source: &mut String, name: &str, kind: SamplerKind) { - let constructor = sampler_constructor_name(kind); - let sampler_expr = format!("{constructor}({name}_tex, {name}_samp)"); +/// Generates WGSL `@group(0) @binding(N)` declarations for textures, samplers, +/// and uniform buffers from the shader resource definitions. +fn generate_wgsl_declarations(resources: &[ShaderResourceDefinition]) -> String { + let mut decls = String::new(); - // Transform texture functions: texture(name, ...) → texture(samplerXxx(name_tex, name_samp), ...) - for func_name in &["texture", "textureLod", "textureGrad", "textureOffset", "textureLodOffset", - "texelFetch", "texelFetchOffset", "textureProj", "textureProjLod"] { - for pattern in &[ - format!("{func_name}({name},"), - format!("{func_name}({name} ,"), - format!("{func_name}( {name},"), - ] { - *source = source.replace(pattern, &format!("{func_name}({sampler_expr},")); - } - } - - // textureSize takes the raw texture (not sampler) - for pattern in &[ - format!("textureSize({name},"), - format!("textureSize({name} ,"), - ] { - *source = source.replace(pattern, &format!("textureSize({name}_tex,")); - } - - // Replace shared function calls that pass texture names as arguments. - // These functions (in shared.glsl) take separate texture2D + sampler parameters. - for func_name in &[ - "S_PointShadow", "S_SpotShadowFactor", - "S_ComputeParallaxTextureCoordinates", "S_FetchMatrix", "S_FetchBlendShapeOffsets", - "Internal_FetchHeight", "CsmGetShadow", - ] { - replace_texture_arg_in_calls(source, func_name, name); - } -} - -/// Rewrites local function definitions that have combined sampler parameters -/// (e.g. `in sampler2D name`) into Vulkan-compatible signatures with separate -/// `texture2D` + `sampler` parameters. Also rewrites the function body to use -/// the new parameter names. -fn rewrite_sampler_function_definitions(source: &mut String) { - let sampler_type_keywords = [ - "sampler1D", "sampler2D", "sampler3D", "samplerCube", - "usampler1D", "usampler2D", "usampler3D", "usamplerCube", - ]; - - // Phase 1: Find all function definitions with sampler params. - // Store (paren_pos, body_close_brace_pos, vec<(param_name, SamplerKind)>). - let mut regions: Vec<(usize, usize, Vec<(String, SamplerKind)>)> = Vec::new(); - - let bytes = source.as_bytes(); - let mut i = 0; - while i < bytes.len() { - if bytes[i] == b'(' { - // Check there's an identifier immediately before '(' (function name). - let paren_pos = i; - let mut j = paren_pos; - while j > 0 && (bytes[j - 1].is_ascii_alphanumeric() || bytes[j - 1] == b'_') { - j -= 1; - } - if j == paren_pos { - // No identifier before '(' - i += 1; - continue; - } - - // Extract parameter list. - let Some((args_str, after_paren)) = extract_call_args(source, paren_pos) else { - i += 1; - continue; - }; - - // Check if this looks like a function definition (followed by '{', possibly with whitespace). - let rest = source[after_paren..].trim_start(); - if !rest.starts_with('{') { - i += 1; - continue; - } - - // Parse parameters and find sampler types. - let mut sampler_params: Vec<(String, SamplerKind)> = Vec::new(); - for param in args_str.split(',') { - let param = param.trim(); - let tokens: Vec<&str> = param.split_whitespace().collect(); - if tokens.len() < 2 { - continue; - } - let mut type_idx = None; - for (idx, tok) in tokens.iter().enumerate() { - if sampler_type_keywords.contains(tok) { - type_idx = Some(idx); - break; - } - } - let Some(ti) = type_idx else { continue }; - let kind = sampler_kind_from_type_name(tokens[ti]).unwrap(); - let param_name = tokens.last().unwrap().to_string(); - sampler_params.push((param_name, kind)); - } - - if !sampler_params.is_empty() { - // Find the function body via brace matching. - let brace_start = source[after_paren..].find('{').unwrap() + after_paren; - let mut depth = 0i32; - let mut k = brace_start; - while k < bytes.len() { - match bytes[k] { - b'{' => depth += 1, - b'}' => { - depth -= 1; - if depth == 0 { break; } - } - _ => {} - } - k += 1; - } - if depth == 0 { - regions.push((paren_pos, k + 1, sampler_params)); - } - } - } - i += 1; - } - - // Phase 2: Apply rewrites from end to start to avoid position shifts. - for (paren_pos, body_end, sampler_params) in regions.iter().rev() { - // Extract parameter list text. - let Some((args_str, after_paren)) = extract_call_args(source, *paren_pos) else { continue }; - - // Rewrite parameter list: split each sampler param into _tex + _samp. - let mut new_params: Vec = Vec::new(); - for param in args_str.split(',') { - let trimmed = param.trim(); - let tokens: Vec<&str> = trimmed.split_whitespace().collect(); - if tokens.len() < 2 { - new_params.push(param.to_string()); - continue; - } - let mut type_idx = None; - for (idx, tok) in tokens.iter().enumerate() { - if sampler_type_keywords.contains(tok) { - type_idx = Some(idx); - break; - } - } - if let Some(ti) = type_idx { - let kind = sampler_kind_from_type_name(tokens[ti]).unwrap(); - let param_name = tokens.last().unwrap(); - // Collect qualifiers (everything before the type keyword). - let qualifiers: Vec<&str> = tokens[..ti].to_vec(); - let qual_prefix = if qualifiers.is_empty() { String::new() } else { qualifiers.join(" ") + " " }; - let tex_type = vulkan_texture_type(kind); - new_params.push(format!("{qual_prefix}{tex_type} {param_name}_tex")); - new_params.push(format!("{qual_prefix}sampler {param_name}_samp")); - } else { - new_params.push(param.to_string()); - } - } - let new_args = new_params.join(", "); - - // Find the body text (from '{' to '}'). - let brace_start = source[after_paren..].find('{').unwrap() + after_paren; - let body_text = &source[brace_start..*body_end].to_string(); - - // Rewrite body for each sampler param. - let mut new_body = body_text.clone(); - for (param_name, kind) in sampler_params { - rewrite_sampler_usages(&mut new_body, param_name, *kind); - } - - // Replace: from paren_pos to body_end. - let before = &source[..*paren_pos].to_string(); - let after = &source[*body_end..].to_string(); - *source = format!("{before}({new_args}){new_body}{after}"); - } -} - -/// Generates separate texture + sampler uniform declarations for wgpu bindings, -/// plus uniform block declarations with `layout(std140, binding=...)`. -fn generate_resource_declarations(resources: &[ShaderResourceDefinition], source: &mut String, line_offset: &mut isize) { - let mut tex_decls = String::new(); for res in resources { match res.kind { ShaderResourceKind::Texture { kind, .. } => { - let tex_type = vulkan_texture_type(kind); - tex_decls += &format!("layout(binding={}) uniform {} {}_tex;\n", res.binding, tex_type, res.name); - tex_decls += &format!("layout(binding={}) uniform sampler {}_samp;\n", res.binding + 100, res.name); + let tex_type = wgsl_texture_type(kind); + decls += &format!("@group(0) @binding({}) var {}_tex: {};\n", res.binding, res.name, tex_type); + decls += &format!("@group(0) @binding({}) var {}_samp: sampler;\n", res.binding + 100, res.name); } ShaderResourceKind::PropertyGroup(ref fields) => { if fields.is_empty() { continue; } - let mut block = format!("struct T{}{{\n", res.name); + decls += &format!("struct T{} {{\n", res.name); for f in fields { let n = &f.name; - match f.kind { - ShaderPropertyKind::Float { .. } => block += &format!("\tfloat {n};\n"), - ShaderPropertyKind::FloatArray { max_len, .. } => block += &format!("\tfloat {n}[{max_len}];\n"), - ShaderPropertyKind::Int { .. } => block += &format!("\tint {n};\n"), - ShaderPropertyKind::IntArray { max_len, .. } => block += &format!("\tint {n}[{max_len}];\n"), - ShaderPropertyKind::UInt { .. } => block += &format!("\tuint {n};\n"), - ShaderPropertyKind::UIntArray { max_len, .. } => block += &format!("\tuint {n}[{max_len}];\n"), - ShaderPropertyKind::Bool { .. } => block += &format!("\tuint {n};\n"), - ShaderPropertyKind::Vector2 { .. } => block += &format!("\tvec2 {n};\n"), - ShaderPropertyKind::Vector2Array { max_len, .. } => block += &format!("\tvec2 {n}[{max_len}];\n"), - ShaderPropertyKind::Vector3 { .. } => block += &format!("\tvec3 {n};\n"), - ShaderPropertyKind::Vector3Array { max_len, .. } => block += &format!("\tvec3 {n}[{max_len}];\n"), - ShaderPropertyKind::Vector4 { .. } => block += &format!("\tvec4 {n};\n"), - ShaderPropertyKind::Vector4Array { max_len, .. } => block += &format!("\tvec4 {n}[{max_len}];\n"), - ShaderPropertyKind::Matrix2 { .. } => block += &format!("\tmat2 {n};\n"), - ShaderPropertyKind::Matrix2Array { max_len, .. } => block += &format!("\tmat2 {n}[{max_len}];\n"), - ShaderPropertyKind::Matrix3 { .. } => block += &format!("\tmat3 {n};\n"), - ShaderPropertyKind::Matrix3Array { max_len, .. } => block += &format!("\tmat3 {n}[{max_len}];\n"), - ShaderPropertyKind::Matrix4 { .. } => block += &format!("\tmat4 {n};\n"), - ShaderPropertyKind::Matrix4Array { max_len, .. } => block += &format!("\tmat4 {n}[{max_len}];\n"), - ShaderPropertyKind::Color { .. } => block += &format!("\tvec4 {n};\n"), - } - } - block += "};\n"; - block += &format!("layout(std140, binding={}) uniform U{} {{ T{} {}; }};\n", res.binding + 200, res.name, res.name, res.name); - source.insert_str(0, &block); - *line_offset -= count_lines(&block); - } - } - } - source.insert_str(0, &tex_decls); - *line_offset -= count_lines(&tex_decls); -} - -/// Preprocesses shader source for naga/wgpu compatibility. -/// -/// Transformations: -/// 1. Local function definitions with sampler params → separate texture + sampler params -/// 2. `texture(name, uv)` → `texture(samplerXxx(name_tex, name_samp), uv)` for all texture functions -/// 3. `textureSize(name, ...)` → `textureSize(name_tex, ...)` -/// 4. Shared function calls with texture args: `S_PointShadow(..., name)` → `S_PointShadow(..., name_tex, name_samp)` -/// 5. `gl_InstanceID` → `gl_InstanceIndex`, `gl_VertexID` → `gl_VertexIndex` -/// 6. `properties.boolField` → `bool(properties.boolField)` (uint→bool cast) -fn preprocess_shader(source: &mut String, resources: &[ShaderResourceDefinition]) { - // Rewrite local function definitions with sampler params first. - rewrite_sampler_function_definitions(source); - - // Collect texture resources (longest first to avoid partial matches) - let mut tex_resources: Vec<(&str, SamplerKind)> = resources - .iter() - .filter_map(|r| match r.kind { - ShaderResourceKind::Texture { kind, .. } => Some((r.name.as_str(), kind)), - _ => None, - }) - .collect(); - tex_resources.sort_by(|a, b| b.0.len().cmp(&a.0.len())); - - for (name, kind) in &tex_resources { - rewrite_sampler_usages(source, name, *kind); - } - - // Replace OpenGL built-in names with Vulkan equivalents. - // Use `int(builtin + 0)` wrapper to work around a Naga bug where passing - // gl_InstanceIndex/gl_VertexIndex directly as function arguments to functions - // with texture2D/sampler parameters causes "Unknown function" errors. - *source = source.replace("gl_InstanceID", "int(gl_InstanceIndex + 0)"); - *source = source.replace("gl_VertexID", "int(gl_VertexIndex + 0)"); - - // Wrap bool uniform property accesses with bool() cast (uint→bool) - for res in resources { - let ShaderResourceKind::PropertyGroup(ref fields) = res.kind else { continue }; - let block_name = &*res.name; - for f in fields { - if !matches!(f.kind, ShaderPropertyKind::Bool { .. }) { continue; } - let field_name = &*f.name; - let search = format!("{block_name}.{field_name}"); - let replacement = format!("bool({block_name}.{field_name})"); - *source = source.replace(&search, &replacement); - } - } -} - -/// Finds calls to `funcName(...)` in the source where `texture_name` appears as an argument, -/// and replaces it with `texture_name_tex, texture_name_samp`. -/// Handles multiple cases: -/// 1. Last argument: `funcName(..., name)` → `funcName(..., name_tex, name_samp)` -/// 2. Two consecutive args: `funcName(..., name, name, ...)` → `funcName(..., name_tex, name_samp, ...)` -/// 3. First/middle argument: `funcName(name, ...)` → `funcName(name_tex, name_samp, ...)` -fn replace_texture_arg_in_calls(source: &mut String, func_name: &str, texture_name: &str) { - let search = format!("{func_name}("); - // Process from end to start to avoid position shifts - let mut positions: Vec = Vec::new(); - let mut pos = 0; - while let Some(start) = source[pos..].find(&search) { - positions.push(pos + start); - pos = pos + start + search.len(); - } - - for &call_start in positions.iter().rev() { - let paren_start = call_start + search.len() - 1; - let Some((args_str, call_end)) = extract_call_args(source, paren_start) else { continue }; - - // Case 1: last argument is the texture name - let trimmed = args_str.trim_end(); - if trimmed.ends_with(texture_name) { - let name_start = trimmed.len() - texture_name.len(); - let preceded_ok = name_start == 0 || { - let prev = trimmed.as_bytes()[name_start - 1]; - matches!(prev, b',' | b' ' | b'\t' | b'\n') - }; - if preceded_ok { - let before = &source[..paren_start + 1 + name_start]; - let after = &source[call_end - 1..]; - *source = format!("{before}{texture_name}_tex, {texture_name}_samp{after}"); - continue; - } - } - - // Case 2: two consecutive args are the texture name (e.g., CsmGetShadow(name, name, ...)) - for sep in &[",", ", ", ",\n", ",\t"] { - let double_pattern = format!("{texture_name}{sep}{texture_name}"); - if let Some(found) = args_str.find(&double_pattern) { - let abs_in_args = found; - let preceded_ok = abs_in_args == 0 || { - let prev = args_str.as_bytes()[abs_in_args - 1]; - matches!(prev, b',' | b' ' | b'\t' | b'\n') - }; - let after_end = abs_in_args + double_pattern.len(); - let followed_ok = after_end >= args_str.len() || { - let next = args_str.as_bytes()[after_end]; - matches!(next, b',' | b' ' | b'\t' | b'\n') - }; - if preceded_ok && followed_ok { - let args_start = paren_start + 1; - if let Some(found_in_source) = source[args_start..call_end - 1].find(&double_pattern) { - let abs_pos = args_start + found_in_source; - let before = &source[..abs_pos]; - let after = &source[abs_pos + double_pattern.len()..]; - *source = format!("{before}{texture_name}_tex,{texture_name}_samp{after}"); - } - break; + let ty = wgsl_property_type(&f.kind); + decls += &format!(" {n}: {ty},\n"); } + decls += "}\n"; + decls += &format!("@group(0) @binding({}) var {}: T{};\n", res.binding + 200, res.name, res.name); } } - - // Case 3: texture name is the first/middle argument (not last, not doubled) - // Find the texture name as a standalone identifier followed by a comma - for sep in &[",", ", "] { - let pattern = format!("{texture_name}{sep}"); - if let Some(found) = args_str.find(&pattern) { - let abs_in_args = found; - let preceded_ok = abs_in_args == 0 || { - let prev = args_str.as_bytes()[abs_in_args - 1]; - matches!(prev, b',' | b' ' | b'\t' | b'\n') - }; - // Make sure this isn't already handled by case 2 (double pattern) - let after_sep = abs_in_args + pattern.len(); - let is_double = after_sep + texture_name.len() <= args_str.len() - && args_str[after_sep..].starts_with(texture_name); - if preceded_ok && !is_double { - let args_start = paren_start + 1; - if let Some(found_in_source) = source[args_start..call_end - 1].find(&pattern) { - let abs_pos = args_start + found_in_source; - let before = &source[..abs_pos]; - let after = &source[abs_pos + pattern.len()..]; - *source = format!("{before}{texture_name}_tex,{texture_name}_samp{sep}{after}"); - } - break; - } - } - } - } -} - -/// Finds the arguments of a function call starting at the opening paren. -/// Returns (args_string, end_index) where end_index is the index AFTER the closing paren. -fn extract_call_args(source: &str, paren_start: usize) -> Option<(String, usize)> { - let bytes = source.as_bytes(); - if bytes.get(paren_start) != Some(&b'(') { return None; } - let mut depth = 1; - let mut i = paren_start + 1; - while i < bytes.len() && depth > 0 { - match bytes[i] { - b'(' => depth += 1, - b')' => depth -= 1, - _ => {} - } - i += 1; - } - if depth == 0 { - Some((source[paren_start + 1..i - 1].to_string(), i)) - } else { - None - } -} - -fn naga_stage(kind: &ShaderKind) -> naga::ShaderStage { - match kind { ShaderKind::Vertex => naga::ShaderStage::Vertex, ShaderKind::Fragment => naga::ShaderStage::Fragment } -} - -fn prepare_source(code: &str) -> String { - let mut src = String::from("#version 450\n// include 'shared.glsl'\n"); - src += include_str!("shaders/shared.glsl"); - src += "\n// end of include\n"; - src += code; - src -} - -/// Assigns explicit `layout(location = N)` to inter-stage `in`/`out` variables -/// that lack them. Naga requires explicit locations for all inter-stage variables; -/// without them, all default to location 0 causing "Multiple bindings" errors. -/// -/// For vertex shaders: assigns locations to `out` variables (inter-stage outputs). -/// For fragment shaders: assigns locations to `in` variables (inter-stage inputs). -/// Fragment `out` variables (render targets) are NOT touched — they keep their defaults. -/// -/// Uses a fixed base location (8) for inter-stage variables to avoid conflicts -/// with vertex input locations (typically 0-7) and fragment output locations (typically 0-7). -/// Both vertex and fragment shaders use the same base, ensuring matching locations. -fn assign_inter_stage_locations(source: &mut String, kind: &ShaderKind) { - /// Base location for inter-stage variables. High enough to avoid conflicts - /// with vertex inputs and fragment outputs. - const INTER_STAGE_BASE: i32 = 8; - - let lines: Vec<&str> = source.lines().collect(); - let mut unqualified_indices: Vec = Vec::new(); - - for (i, line) in lines.iter().enumerate() { - let trimmed = line.trim(); - - match kind { - ShaderKind::Vertex => { - // In vertex shaders, assign locations to `out` variables only - // (skip `in` which are vertex buffer inputs — they should already have locations) - if is_unqualified_out_decl(trimmed) { - unqualified_indices.push(i); - } - } - ShaderKind::Fragment => { - // In fragment shaders, assign locations to `in` variables only - // (skip `out` which are render target outputs — they should keep defaults) - if is_unqualified_in_decl(trimmed) { - unqualified_indices.push(i); - } - } - } - } - - if unqualified_indices.is_empty() { - return; - } - - let mut next_location = INTER_STAGE_BASE; - let mut result = String::new(); - for (i, line) in lines.iter().enumerate() { - if unqualified_indices.contains(&i) { - let indent: String = line.chars().take_while(|c| c.is_whitespace()).collect(); - let trimmed = line.trim(); - result.push_str(&format!("{indent}layout (location = {next_location}) {trimmed}\n")); - next_location += 1; - } else { - result.push_str(line); - result.push('\n'); - } - } - if result.ends_with('\n') { - result.pop(); - } - *source = result; -} - -/// Checks if a line is an unqualified `out` variable declaration -/// (no layout qualifier, starts with `out` followed by a GLSL type). -fn is_unqualified_out_decl(line: &str) -> bool { - let trimmed = line.trim(); - if !trimmed.starts_with("out ") || trimmed.starts_with("out[") { - return false; - } - if trimmed.contains("layout") { - return false; - } - if !trimmed.ends_with(';') { - return false; - } - let tokens: Vec<&str> = trimmed.trim_end_matches(';').split_whitespace().collect(); - tokens.len() >= 3 -} - -/// Checks if a line is an unqualified `in` variable declaration -/// (no layout qualifier, starts with `in` followed by a GLSL type). -fn is_unqualified_in_decl(line: &str) -> bool { - let trimmed = line.trim(); - if !trimmed.starts_with("in ") || trimmed.starts_with("in[") { - return false; - } - if trimmed.contains("layout") { - return false; - } - if !trimmed.ends_with(';') { - return false; - } - let tokens: Vec<&str> = trimmed.trim_end_matches(';').split_whitespace().collect(); - tokens.len() >= 3 -} - -/// Handles vertex input declarations that may not be provided by the vertex buffer. -/// -/// For inputs at high locations (4+) that are commonly optional (boneWeights, -/// boneIndices, vertexSecondTexCoord), replaces the `layout(location = N) in TYPE name;` -/// declaration with `const TYPE name = DEFAULT;`. This allows the shader to compile -/// and run correctly even when the vertex buffer doesn't provide these attributes. -/// -/// Inputs at low locations (0-3) that are truly unused are stripped entirely. -fn strip_unused_vertex_inputs(source: &mut String) { - let lines: Vec<&str> = source.lines().collect(); - let mut modifications: Vec<(usize, String)> = Vec::new(); - - for (i, line) in lines.iter().enumerate() { - let trimmed = line.trim(); - - // Match: layout (location = N) in TYPE name; - if !trimmed.contains("layout") || !trimmed.contains(" in ") || !trimmed.ends_with(';') { - continue; - } - if !trimmed.contains("location") { - continue; - } - - // Extract the variable name and type - let without_semi = trimmed.trim_end_matches(';'); - let tokens: Vec<&str> = without_semi.split_whitespace().collect(); - if tokens.len() < 4 { - continue; - } - - let var_name = tokens.last().unwrap(); - - // Extract location number - let loc = extract_location_number(trimmed); - - // Check if the variable name appears anywhere else in the source - let mut used = false; - for (j, other_line) in lines.iter().enumerate() { - if j == i { - continue; - } - if contains_word(other_line, var_name) { - used = true; - break; - } - } - - if !used { - // Truly unused — strip the line entirely - modifications.push((i, String::new())); - } else if loc.map_or(false, |l| l >= 4) { - // Used but at a high location (commonly optional attributes like boneWeights). - // Replace with a constant default so the shader compiles without the vertex attribute. - let type_name = tokens[tokens.len() - 2]; - let default_val = match type_name { - "vec4" => "vec4(0.0)", - "vec3" => "vec3(0.0)", - "vec2" => "vec2(0.0)", - "float" => "0.0", - "int" => "0", - "uint" => "0u", - _ => continue, // Unknown type, don't modify - }; - let indent: String = line.chars().take_while(|c| c.is_whitespace()).collect(); - modifications.push((i, format!("{indent}const {type_name} {var_name} = {default_val};"))); - } - } - - if modifications.is_empty() { - return; - } - - let mut result = String::new(); - for (i, line) in lines.iter().enumerate() { - if let Some((_, replacement)) = modifications.iter().find(|(idx, _)| *idx == i) { - if replacement.is_empty() { - continue; // Strip the line - } - result.push_str(replacement); - result.push('\n'); - } else { - result.push_str(line); - result.push('\n'); - } - } - if result.ends_with('\n') { - result.pop(); } - *source = result; -} -/// Extracts the location number from a layout declaration line. -fn extract_location_number(line: &str) -> Option { - let loc_pos = line.find("location")?; - let after_loc = &line[loc_pos + "location".len()..]; - let eq_pos = after_loc.find('=')?; - let after_eq = after_loc[eq_pos + 1..].trim(); - let num_str: String = after_eq.chars().take_while(|c| c.is_ascii_digit() || *c == '-').collect(); - num_str.parse().ok() -} - -/// Checks if a line contains `word` as a standalone identifier (not part of a larger word). -fn contains_word(line: &str, word: &str) -> bool { - let mut pos = 0; - while let Some(found) = line[pos..].find(word) { - let abs_pos = pos + found; - let before_ok = abs_pos == 0 || { - let prev = line.as_bytes()[abs_pos - 1]; - !prev.is_ascii_alphanumeric() && prev != b'_' - }; - let after_pos = abs_pos + word.len(); - let after_ok = after_pos >= line.len() || { - let next = line.as_bytes()[after_pos]; - !next.is_ascii_alphanumeric() && next != b'_' - }; - if before_ok && after_ok { - return true; - } - pos = abs_pos + 1; - } - false + decls } -fn compile_glsl(device: &wgpu::Device, name: &str, kind: &ShaderKind, glsl: &str) -> Result { - let mut frontend = naga::front::glsl::Frontend::default(); - let module = frontend.parse(&naga::front::glsl::Options { stage: naga_stage(kind), defines: Default::default() }, glsl).map_err(|errors| { - let msg = format!("Failed to parse GLSL for {name}:\n{errors}"); - Log::writeln(MessageKind::Error, msg.clone()); - FrameworkError::ShaderCompilationFailed { shader_name: name.to_owned(), error_message: msg } - })?; - - // Use relaxed validation — naga's GLSL frontend may produce types that don't - // pass strict WGSL-oriented validation (e.g., bool in uniform blocks). - let info = naga::valid::Validator::new(naga::valid::ValidationFlags::empty(), naga::valid::Capabilities::all()) - .validate(&module) - .map_err(|e| { - let msg = format!("Naga validation failed for {name}: {e}"); - Log::writeln(MessageKind::Error, msg.clone()); - FrameworkError::ShaderCompilationFailed { shader_name: name.to_owned(), error_message: msg } - })?; - - // Convert naga module to SPIR-V for wgpu - let spv = naga::back::spv::write_vec(&module, &info, &naga::back::spv::Options { - flags: naga::back::spv::WriterFlags::empty(), - ..Default::default() - }, None).map_err(|e| { - let msg = format!("SPIR-V generation failed for {name}: {e}"); - Log::writeln(MessageKind::Error, msg.clone()); - FrameworkError::ShaderCompilationFailed { shader_name: name.to_owned(), error_message: msg } - })?; - +/// Compiles WGSL source into a wgpu shader module. +fn compile_wgsl(device: &wgpu::Device, name: &str, wgsl: &str) -> Result { let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some(name), - source: wgpu::ShaderSource::SpirV(std::borrow::Cow::Owned(spv)), + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(wgsl)), }); - Log::writeln(MessageKind::Information, format!("Shader {name} compiled successfully!")); Ok(shader_module) } -fn create_bind_group_layout(device: &wgpu::Device, resources: &[ShaderResourceDefinition]) -> wgpu::BindGroupLayout { +fn sample_type_for_format(fmt: wgpu::TextureFormat) -> wgpu::TextureSampleType { + use wgpu::TextureFormat as F; + match fmt { + F::Depth16Unorm | F::Depth24Plus | F::Depth24PlusStencil8 + | F::Depth32Float | F::Depth32FloatStencil8 => wgpu::TextureSampleType::Depth, + F::R8Uint | F::R16Uint | F::R32Uint + | F::Rg8Uint | F::Rg16Uint | F::Rg32Uint + | F::Rgba8Uint | F::Rgba16Uint | F::Rgba32Uint => wgpu::TextureSampleType::Uint, + F::R8Sint | F::R16Sint | F::R32Sint + | F::Rg8Sint | F::Rg16Sint | F::Rg32Sint + | F::Rgba8Sint | F::Rgba16Sint | F::Rgba32Sint => wgpu::TextureSampleType::Sint, + F::R32Float | F::Rg32Float | F::Rgba32Float => wgpu::TextureSampleType::Float { filterable: false }, + _ => wgpu::TextureSampleType::Float { filterable: true }, + } +} + +/// Creates bind group layout using actual texture formats when available. +/// `texture_formats` provides the actual wgpu format for specific bindings. +fn create_bind_group_layout_with_formats( + device: &wgpu::Device, + resources: &[ShaderResourceDefinition], + texture_formats: &[(usize, wgpu::TextureFormat)], +) -> wgpu::BindGroupLayout { + let fmt_map: std::collections::HashMap = texture_formats.iter().copied().collect(); let mut entries = Vec::new(); for res in resources { match res.kind { ShaderResourceKind::Texture { kind, .. } => { let vd = match kind { SamplerKind::Sampler1D | SamplerKind::USampler1D => wgpu::TextureViewDimension::D1, - SamplerKind::Sampler2D | SamplerKind::USampler2D => wgpu::TextureViewDimension::D2, + SamplerKind::Sampler2D | SamplerKind::USampler2D | SamplerKind::DepthSampler2D => wgpu::TextureViewDimension::D2, SamplerKind::Sampler3D | SamplerKind::USampler3D => wgpu::TextureViewDimension::D3, - SamplerKind::SamplerCube | SamplerKind::USamplerCube => wgpu::TextureViewDimension::Cube, + SamplerKind::SamplerCube | SamplerKind::USamplerCube | SamplerKind::DepthSamplerCube => wgpu::TextureViewDimension::Cube, }; - let st = match kind { - SamplerKind::USampler1D | SamplerKind::USampler2D | SamplerKind::USampler3D | SamplerKind::USamplerCube => wgpu::TextureSampleType::Uint, - _ => wgpu::TextureSampleType::Float { filterable: true }, + // Use actual texture format if available, otherwise fall back to kind-based inference + let st = if let Some(&fmt) = fmt_map.get(&res.binding) { + sample_type_for_format(fmt) + } else { + match kind { + SamplerKind::DepthSampler2D | SamplerKind::DepthSamplerCube => wgpu::TextureSampleType::Depth, + SamplerKind::USampler1D | SamplerKind::USampler2D | SamplerKind::USampler3D | SamplerKind::USamplerCube => wgpu::TextureSampleType::Uint, + _ => wgpu::TextureSampleType::Float { filterable: true }, + } }; entries.push(wgpu::BindGroupLayoutEntry { binding: res.binding as u32, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: st, view_dimension: vd, multisampled: false }, count: None }); - entries.push(wgpu::BindGroupLayoutEntry { binding: (res.binding + 100) as u32, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None }); + let sampler_binding = if matches!(st, wgpu::TextureSampleType::Float { filterable: false } | wgpu::TextureSampleType::Uint | wgpu::TextureSampleType::Sint) { + wgpu::SamplerBindingType::NonFiltering + } else { + wgpu::SamplerBindingType::Filtering + }; + entries.push(wgpu::BindGroupLayoutEntry { binding: (res.binding + 100) as u32, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(sampler_binding), count: None }); } ShaderResourceKind::PropertyGroup { .. } => { entries.push(wgpu::BindGroupLayoutEntry { binding: (res.binding + 200) as u32, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None }, count: None }); @@ -766,7 +178,7 @@ pub struct WgpuShader { impl GpuShaderTrait for WgpuShader {} impl WgpuShader { - pub fn new(server: &WgpuGraphicsServer, name: String, kind: ShaderKind, mut source: String, resources: &[ShaderResourceDefinition], mut line_offset: isize) -> Result { + pub fn new(server: &WgpuGraphicsServer, name: String, kind: ShaderKind, source: String, resources: &[ShaderResourceDefinition], _line_offset: isize) -> Result { for r in resources { for o in resources { if std::ptr::eq(r, o) { continue; } @@ -776,14 +188,17 @@ impl WgpuShader { } } } - generate_resource_declarations(resources, &mut source, &mut line_offset); - preprocess_shader(&mut source, resources); - assign_inter_stage_locations(&mut source, &kind); - let mut full = prepare_source(&source); - if matches!(kind, ShaderKind::Vertex) { - strip_unused_vertex_inputs(&mut full); - } - let module = compile_glsl(&server.state.device, &name, &kind, &full)?; + + let declarations = generate_wgsl_declarations(resources); + let shared = include_str!("shaders/shared.wgsl"); + + let mut wgsl = String::new(); + wgsl += &declarations; + wgsl += shared; + wgsl += "\n"; + wgsl += &source; + + let module = compile_wgsl(&server.state.device, &name, &wgsl)?; Ok(Self { _server: server.weak_ref(), module, _kind: kind }) } @@ -791,13 +206,12 @@ impl WgpuShader { } pub struct WgpuProgram { - _server: Weak, + server: Weak, name: String, vertex_module: wgpu::ShaderModule, fragment_module: wgpu::ShaderModule, - bind_group_layout: wgpu::BindGroupLayout, - pipeline_layout: wgpu::PipelineLayout, resources: Vec, + cached_layouts: RefCell>, } impl GpuProgramTrait for WgpuProgram {} @@ -816,23 +230,34 @@ impl WgpuProgram { } fn from_modules(server: &WgpuGraphicsServer, name: &str, vert: &WgpuShader, frag: &WgpuShader, resources: &[ShaderResourceDefinition]) -> Result { - let bgl = create_bind_group_layout(&server.state.device, resources); + Ok(Self { + server: server.weak_ref(), name: name.to_owned(), + vertex_module: vert.module.clone(), fragment_module: frag.module.clone(), + resources: resources.to_vec(), + cached_layouts: RefCell::new(None), + }) + } + + /// Lazily create bind group layout + pipeline layout based on actual texture formats. + /// `texture_formats` maps resource binding -> actual wgpu texture format for textures. + pub fn get_or_create_layouts(&self, texture_formats: &[(usize, wgpu::TextureFormat)]) -> (wgpu::BindGroupLayout, wgpu::PipelineLayout) { + if let Some((ref bgl, ref pl)) = *self.cached_layouts.borrow() { + return (bgl.clone(), pl.clone()); + } + let server = self.server.upgrade().unwrap(); + let bgl = create_bind_group_layout_with_formats(&server.state.device, &self.resources, texture_formats); let pl = server.state.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some(&format!("{name}_PL")), + label: Some(&format!("{}_PL", self.name)), bind_group_layouts: &[Some(&bgl)], ..Default::default() }); - Ok(Self { - _server: server.weak_ref(), name: name.to_owned(), - vertex_module: vert.module.clone(), fragment_module: frag.module.clone(), - bind_group_layout: bgl, pipeline_layout: pl, resources: resources.to_vec(), - }) + let result = (bgl.clone(), pl.clone()); + *self.cached_layouts.borrow_mut() = Some(result.clone()); + result } pub fn vertex_module(&self) -> &wgpu::ShaderModule { &self.vertex_module } pub fn fragment_module(&self) -> &wgpu::ShaderModule { &self.fragment_module } - pub fn pipeline_layout(&self) -> &wgpu::PipelineLayout { &self.pipeline_layout } - pub fn bind_group_layout(&self) -> &wgpu::BindGroupLayout { &self.bind_group_layout } pub fn resources(&self) -> &[ShaderResourceDefinition] { &self.resources } pub fn name(&self) -> &str { &self.name } } @@ -842,256 +267,69 @@ mod tests { use super::*; #[test] - fn test_rewrite_sampler_function_definitions() { - let mut source = r#"float CsmGetShadow(in sampler2D shadowSampler, in vec3 fragmentPosition, in mat4 lightViewProjMatrix) -{ - float invSize = 1.0 / float(textureSize(shadowSampler, 0).x); - return S_SpotShadowFactor(properties.shadowsEnabled, properties.softShadows, - properties.shadowBias, fragmentPosition, lightViewProjMatrix, invSize, shadowSampler); -}"#.to_string(); - - rewrite_sampler_function_definitions(&mut source); - - // Parameter list should be split - assert!(source.contains("in texture2D shadowSampler_tex, in sampler shadowSampler_samp")); - // textureSize should use _tex - assert!(source.contains("textureSize(shadowSampler_tex, 0)")); - // S_SpotShadowFactor call should have split args - assert!(source.contains("shadowSampler_tex, shadowSampler_samp")); - // Original combined sampler type should be gone - assert!(!source.contains("sampler2D shadowSampler")); - } - - #[test] - fn test_rewrite_sampler_function_definitions_no_sampler() { - let mut source = r#"float plainFunc(float a, float b) { - return a + b; -}"#.to_string(); - - let original = source.clone(); - rewrite_sampler_function_definitions(&mut source); - assert_eq!(source, original); + fn test_generate_wgsl_declarations_textures() { + let resources = vec![ + ShaderResourceDefinition { + name: "diffuseTexture".into(), + kind: ShaderResourceKind::Texture { kind: SamplerKind::Sampler2D, fallback: Default::default() }, + binding: 0, + }, + ShaderResourceDefinition { + name: "shadowMap".into(), + kind: ShaderResourceKind::Texture { kind: SamplerKind::SamplerCube, fallback: Default::default() }, + binding: 1, + }, + ]; + let decls = generate_wgsl_declarations(&resources); + assert!(decls.contains("@group(0) @binding(0) var diffuseTexture_tex: texture_2d;")); + assert!(decls.contains("@group(0) @binding(100) var diffuseTexture_samp: sampler;")); + assert!(decls.contains("@group(0) @binding(1) var shadowMap_tex: texture_cube;")); + assert!(decls.contains("@group(0) @binding(101) var shadowMap_samp: sampler;")); } #[test] - fn test_rewrite_sampler_usages_global_resource() { - let mut source = r#"vec3 c = texture(materialTexture, texCoord).rgb; -float d = textureSize(materialTexture, 0).x;"#.to_string(); - - rewrite_sampler_usages(&mut source, "materialTexture", SamplerKind::Sampler2D); - - assert!(source.contains("texture(sampler2D(materialTexture_tex, materialTexture_samp),")); - assert!(source.contains("textureSize(materialTexture_tex,")); + fn test_generate_wgsl_declarations_uniforms() { + use fyrox_graphics::gpu_program::ShaderProperty; + let resources = vec![ + ShaderResourceDefinition { + name: "properties".into(), + kind: ShaderResourceKind::PropertyGroup(vec![ + ShaderProperty::new("field0", ShaderPropertyKind::Float { value: 0.0 }), + ShaderProperty::new("field1", ShaderPropertyKind::Vector3 { value: Default::default() }), + ShaderProperty::new("field2", ShaderPropertyKind::Bool { value: false }), + ]), + binding: 0, + }, + ]; + let decls = generate_wgsl_declarations(&resources); + assert!(decls.contains("struct Tproperties {")); + assert!(decls.contains("field0: f32,")); + assert!(decls.contains("field1: vec3f,")); + assert!(decls.contains("field2: u32,")); // Bool → u32 + assert!(decls.contains("@group(0) @binding(200) var properties: Tproperties;")); } #[test] - fn test_naga_shared_glsl_std140_with_gl_instance_index() { - // Verify that the gl_InstanceIndex workaround (int(gl_InstanceIndex + 0)) - // works around the Naga bug where gl_InstanceIndex as a function argument - // to functions with texture2D/sampler params causes "Unknown function" errors. - let shared = include_str!("shaders/shared.glsl"); - let glsl = format!(r#"#version 450 -{shared} - -// end of include -layout(binding=0) uniform texture2D matrices_tex; -layout(binding=100) uniform sampler matrices_samp; - -struct Tproperties{{ - mat4 viewProjection; - int tileSize; - float frameBufferHeight; -}}; -layout(std140, binding=200) uniform Uproperties {{ Tproperties properties; }}; - -void main() -{{ - gl_Position = S_FetchMatrix(matrices_tex, matrices_samp, int(gl_InstanceIndex + 0)) * vec4(1.0); -}} -"#); - - let mut frontend = naga::front::glsl::Frontend::default(); - let result = frontend.parse(&naga::front::glsl::Options { - stage: naga::ShaderStage::Vertex, - defines: Default::default(), - }, &glsl); - - match result { - Ok(module) => { - let info = naga::valid::Validator::new( - naga::valid::ValidationFlags::empty(), - naga::valid::Capabilities::all(), - ).validate(&module); - assert!(info.is_ok(), "Naga validation failed: {:?}", info.err()); - } - Err(errors) => { - panic!("Naga GLSL parse failed:\n{errors}"); - } - } + fn test_wgsl_texture_type_mapping() { + assert_eq!(wgsl_texture_type(SamplerKind::Sampler2D), "texture_2d"); + assert_eq!(wgsl_texture_type(SamplerKind::Sampler3D), "texture_3d"); + assert_eq!(wgsl_texture_type(SamplerKind::SamplerCube), "texture_cube"); + assert_eq!(wgsl_texture_type(SamplerKind::USampler2D), "texture_2d"); } #[test] - fn test_naga_widget_shader_binding_conflict() { - let glsl = r#"#version 450 - -layout(binding=8) uniform texture2D fyrox_widgetTexture_tex; -layout(binding=108) uniform sampler fyrox_widgetTexture_samp; -struct Tfyrox_widgetData{ - mat4 projectionMatrix; - mat3 worldMatrix; -}; -layout(std140, binding=208) uniform Ufyrox_widgetData { Tfyrox_widgetData fyrox_widgetData; }; - -layout (location = 0) in vec2 vertexPosition; - -void main() -{ - vec3 worldSpaceVertex = fyrox_widgetData.worldMatrix * vec3(vertexPosition, 1.0); - gl_Position = fyrox_widgetData.projectionMatrix * vec4(worldSpaceVertex, 1.0); -} -"#; - - let mut frontend = naga::front::glsl::Frontend::default(); - let module = frontend.parse(&naga::front::glsl::Options { - stage: naga::ShaderStage::Vertex, - defines: Default::default(), - }, glsl).expect("Naga parse failed"); - - let info = naga::valid::Validator::new( - naga::valid::ValidationFlags::empty(), - naga::valid::Capabilities::all(), - ).validate(&module).expect("Naga validation failed"); - - // Dump all global variables and their bindings - for (handle, var) in module.global_variables.iter() { - let name = var.name.as_deref().unwrap_or(""); - let binding = var.binding.as_ref().map(|b| format!("set={}, binding={}", b.group, b.binding)).unwrap_or_else(|| "none".into()); - let space = var.space; - eprintln!("GlobalVar {handle:?}: name={name}, binding={binding}, space={space:?}"); - } - - let spv = naga::back::spv::write_vec(&module, &info, &naga::back::spv::Options { - flags: naga::back::spv::WriterFlags::empty(), - ..Default::default() - }, None).expect("SPIR-V generation failed"); - let _ = spv; - } - - #[test] - fn test_assign_inter_stage_locations_vertex_shader() { - // Simulates the widget vertex shader pattern: unqualified out variables - let mut source = r#"layout (location = 0) in vec2 vertexPosition; -layout (location = 1) in vec2 vertexTexCoord; -layout (location = 2) in vec4 vertexColor; - -out vec2 texCoord; -out vec4 color; -out vec2 localPosition; - -void main() -{ - texCoord = vertexTexCoord; - color = vertexColor; - localPosition = vertexPosition; -} -"#.to_string(); - - assign_inter_stage_locations(&mut source, &ShaderKind::Vertex); - - // Inter-stage outputs start at base location 8 - assert!(source.contains("layout (location = 8) out vec2 texCoord")); - assert!(source.contains("layout (location = 9) out vec4 color")); - assert!(source.contains("layout (location = 10) out vec2 localPosition")); - // Original vertex inputs should be unchanged - assert!(source.contains("layout (location = 0) in vec2 vertexPosition")); - } - - #[test] - fn test_assign_inter_stage_locations_fragment_shader() { - // Simulates the widget fragment shader pattern: unqualified in + out variables - // Only `in` variables should get locations; `out` should be left alone - let mut source = r#"out vec4 fragColor; - -in vec2 texCoord; -in vec4 color; -in vec2 localPosition; - -void main() -{ - fragColor = vec4(1.0); -} -"#.to_string(); - - assign_inter_stage_locations(&mut source, &ShaderKind::Fragment); - - // `out vec4 fragColor` should NOT get a location (fragment output keeps default) - assert!(source.contains("out vec4 fragColor;")); - assert!(!source.contains("location") && source.contains("out vec4 fragColor;") || - source.lines().filter(|l| l.contains("fragColor") && l.contains("location")).count() == 0); - // `in` variables get sequential locations starting at 8 - assert!(source.contains("layout (location = 8) in vec2 texCoord")); - assert!(source.contains("layout (location = 9) in vec4 color")); - assert!(source.contains("layout (location = 10) in vec2 localPosition")); - } - - #[test] - fn test_assign_inter_stage_locations_no_unqualified() { - // No unqualified variables — should be a no-op - let mut source = r#"layout (location = 0) in vec3 vertexPosition; -layout (location = 0) out vec4 fragColor; - -void main() { fragColor = vec4(1.0); } -"#.to_string(); - - let original = source.clone(); - assign_inter_stage_locations(&mut source, &ShaderKind::Vertex); - assert_eq!(source, original); - } - - #[test] - fn test_strip_unused_vertex_inputs() { - let mut source = r#"layout (location = 0) in vec3 vertexPosition; -layout (location = 1) in vec2 vertexTexCoord; -layout (location = 4) in vec4 boneWeights; -layout (location = 5) in vec4 boneIndices; - -void main() -{ - gl_Position = vec4(vertexPosition, 1.0); - vec2 tc = vertexTexCoord; -} -"#.to_string(); - - strip_unused_vertex_inputs(&mut source); - - // vertexPosition and vertexTexCoord are used — keep them - assert!(source.contains("in vec3 vertexPosition")); - assert!(source.contains("in vec2 vertexTexCoord")); - // boneWeights and boneIndices are NOT used — strip them - assert!(!source.contains("boneWeights")); - assert!(!source.contains("boneIndices")); - } - - #[test] - fn test_strip_unused_vertex_inputs_replaces_high_location_used() { - // boneWeights is used (inside an animation block) but at location 4+ - // Should be replaced with a constant default - let mut source = r#"layout (location = 0) in vec3 vertexPosition; -layout (location = 4) in vec4 boneWeights; - -void main() -{ - vec3 pos = vertexPosition + boneWeights.xyz; - gl_Position = vec4(pos, 1.0); -} -"#.to_string(); - - strip_unused_vertex_inputs(&mut source); - - // vertexPosition is used at low location — keep as input - assert!(source.contains("in vec3 vertexPosition")); - // boneWeights is used at location 4 — replace with constant - assert!(source.contains("const vec4 boneWeights = vec4(0.0);")); - assert!(!source.contains("in vec4 boneWeights")); + fn test_wgsl_property_type_mapping() { + assert_eq!(wgsl_property_type(&ShaderPropertyKind::Float { value: 0.0 }), "f32"); + assert_eq!(wgsl_property_type(&ShaderPropertyKind::Int { value: 0 }), "i32"); + assert_eq!(wgsl_property_type(&ShaderPropertyKind::UInt { value: 0 }), "u32"); + assert_eq!(wgsl_property_type(&ShaderPropertyKind::Bool { value: false }), "u32"); + assert_eq!(wgsl_property_type(&ShaderPropertyKind::Vector2 { value: Default::default() }), "vec2f"); + assert_eq!(wgsl_property_type(&ShaderPropertyKind::Vector3 { value: Default::default() }), "vec3f"); + assert_eq!(wgsl_property_type(&ShaderPropertyKind::Vector4 { value: Default::default() }), "vec4f"); + assert_eq!(wgsl_property_type(&ShaderPropertyKind::Matrix2 { value: Default::default() }), "mat2x2f"); + assert_eq!(wgsl_property_type(&ShaderPropertyKind::Matrix3 { value: Default::default() }), "mat3x3f"); + assert_eq!(wgsl_property_type(&ShaderPropertyKind::Matrix4 { value: Default::default() }), "mat4x4f"); + assert_eq!(wgsl_property_type(&ShaderPropertyKind::Color { r: 255, g: 255, b: 255, a: 255 }), "vec4f"); + assert_eq!(wgsl_property_type(&ShaderPropertyKind::FloatArray { max_len: 32, value: vec![] }), "array"); } } diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index 189445397b..94d82126bb 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -65,6 +65,10 @@ pub struct WgpuGraphicsServer { weak_self: RefCell>>, pub memory_usage: RefCell, pipeline_statistics: RefCell, + /// Small buffer bound to extra vertex slots when geometry lacks attributes the shader expects. + pub dummy_vertex_buffer: wgpu::Buffer, + /// Non-filtering sampler for textures with non-filterable formats (e.g. R32Float). + non_filtering_sampler: wgpu::Sampler, } impl WgpuGraphicsServer { @@ -157,6 +161,21 @@ impl WgpuGraphicsServer { // TODO: Force `msaa_sample_count` to 1 in the wgpu backend. Full MSAA support requires creating multisampled render targets and resolve targets, which is a larger feature. let msaa = 1u32; // msaa_sample_count.unwrap_or(1).max(1) as u32; + let non_filtering_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("NonFilteringSampler"), + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::MipmapFilterMode::Nearest, + ..Default::default() + }); + + let dummy_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("DummyVB"), + size: 16, // enough for vec4f + usage: wgpu::BufferUsages::VERTEX, + mapped_at_creation: false, + }); + let server = Rc::new(Self { state: Arc::new(WgpuState { instance, adapter, device, queue }), surface, @@ -167,6 +186,8 @@ impl WgpuGraphicsServer { weak_self: RefCell::new(None), memory_usage: RefCell::new(ServerMemoryUsage::default()), pipeline_statistics: RefCell::new(PipelineStatistics::default()), + dummy_vertex_buffer, + non_filtering_sampler, }); *server.weak_self.borrow_mut() = Some(Rc::downgrade(&server)); @@ -177,6 +198,9 @@ impl WgpuGraphicsServer { pub fn weak_ref(&self) -> Weak { self.weak_self.borrow().clone().unwrap() } + pub fn non_filtering_sampler(&self) -> &wgpu::Sampler { + &self.non_filtering_sampler + } } impl GraphicsServer for WgpuGraphicsServer { diff --git a/fyrox-graphics-wgpu/src/shaders/shared.glsl b/fyrox-graphics-wgpu/src/shaders/shared.glsl deleted file mode 100644 index 03414c194c..0000000000 --- a/fyrox-graphics-wgpu/src/shaders/shared.glsl +++ /dev/null @@ -1,366 +0,0 @@ -// Shared functions for all shaders in the engine. -// Naga-compatible version: uses texture2D/texture3D/textureCube + sampler -// as separate parameters instead of combined sampler2D/sampler3D/samplerCube. - -const float PI = 3.14159; - -bool S_SolveQuadraticEq(float a, float b, float c, out float minT, out float maxT) -{ - float twoA = 2.0 * a; - float det = b * b - 2.0 * twoA * c; - if (det < 0.0) { minT = 0.0; maxT = 0.0; return false; } - float sqrtDet = sqrt(det); - float root1 = (-b - sqrtDet) / twoA; - float root2 = (-b + sqrtDet) / twoA; - minT = min(root1, root2); - maxT = max(root1, root2); - return true; -} - -float S_LightDistanceAttenuation(float distance, float radius) -{ - return clamp(1.0 - distance * distance / (radius * radius), 0.0, 1.0); -} - -vec3 S_Project(vec3 worldPosition, mat4 matrix) -{ - vec4 screenPos = matrix * vec4(worldPosition, 1); - screenPos.xyz /= screenPos.w; - return screenPos.xyz * 0.5 + 0.5; -} - -vec3 S_UnProject(vec3 screenPos, mat4 matrix) -{ - vec4 clipSpacePos = vec4(screenPos * 2.0 - 1.0, 1.0); - vec4 position = matrix * clipSpacePos; - return position.xyz / position.w; -} - -float S_DistributionGGX(vec3 N, vec3 H, float roughness) -{ - float a = roughness * roughness; - float a2 = a * a; - float NdotH = max(dot(N, H), 0.0); - float NdotH2 = NdotH * NdotH; - float nom = a2; - float denom = (NdotH2 * (a2 - 1.0) + 1.0); - denom = PI * denom * denom; - return nom / denom; -} - -float S_GeometrySchlickGGX(float NdotV, float roughness) -{ - float r = (roughness + 1.0); - float k = (r * r) / 8.0; - float nom = NdotV; - float denom = NdotV * (1.0 - k) + k; - return nom / denom; -} - -float S_GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) -{ - float NdotV = max(dot(N, V), 0.0); - float NdotL = max(dot(N, L), 0.0); - float ggx2 = S_GeometrySchlickGGX(NdotV, roughness); - float ggx1 = S_GeometrySchlickGGX(NdotL, roughness); - return ggx1 * ggx2; -} - -vec3 S_FresnelSchlick(float cosTheta, vec3 F0) -{ - return F0 + (1.0 - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0); -} - -vec3 S_FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) -{ - return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0); -} - -struct TPBRContext { - vec3 lightColor; - vec3 viewVector; - vec3 fragmentToLight; - vec3 fragmentNormal; - float metallic; - float roughness; - vec3 albedo; -}; - -vec3 S_PBR_CalculateLight(TPBRContext ctx) { - vec3 F0 = mix(vec3(0.04), ctx.albedo, ctx.metallic); - vec3 L = ctx.fragmentToLight; - vec3 H = normalize(ctx.viewVector + L); - float NDF = S_DistributionGGX(ctx.fragmentNormal, H, ctx.roughness); - float G = S_GeometrySmith(ctx.fragmentNormal, ctx.viewVector, L, ctx.roughness); - vec3 F = S_FresnelSchlick(max(dot(H, ctx.viewVector), 0.0), F0); - vec3 numerator = NDF * G * F; - float denominator = 4.0 * max(dot(ctx.fragmentNormal, ctx.viewVector), 0.0) * max(dot(ctx.fragmentNormal, L), 0.0) + 0.001; - vec3 specular = numerator / denominator; - vec3 kS = F; - vec3 kD = vec3(1.0) - kS; - kD *= 1.0 - ctx.metallic; - float NdotL = max(dot(ctx.fragmentNormal, L), 0.0); - return (kD * ctx.albedo / PI + specular) * ctx.lightColor * NdotL; -} - -float S_InScatter(vec3 start, vec3 dir, vec3 lightPos, float d) -{ - vec3 q = start - lightPos; - float b = dot(dir, q); - float c = dot(q, q); - float s = 1.0 / sqrt(c - b * b); - float l = s * (atan((d + b) * s) - atan(b * s)); - return l; -} - -vec3 S_RayleighScatter(vec3 start, vec3 dir, vec3 lightPos, float d) -{ - float scatter = S_InScatter(start, dir, lightPos, d); - return vec3(0.55, 0.75, 1.0) * scatter; -} - -bool S_RaySphereIntersection(vec3 origin, vec3 dir, vec3 center, float radius, out float minT, out float maxT) -{ - vec3 d = origin - center; - float a = dot(dir, dir); - float b = 2.0 * dot(dir, d); - float c = dot(d, d) - radius * radius; - return S_SolveQuadraticEq(a, b, c, minT, maxT); -} - -float S_PointShadow( - bool shadowsEnabled, - bool softShadows, - float fragmentDistance, - float shadowBias, - vec3 toLight, - textureCube shadowMap_tex, - sampler shadowMap_samp) -{ - if (shadowsEnabled) - { - float biasedFragmentDistance = fragmentDistance - shadowBias; - - if (softShadows) - { - const vec3 directions[20] = vec3[20]( - vec3(1, 1, 1), vec3(1, -1, 1), vec3(-1, -1, 1), vec3(-1, 1, 1), - vec3(1, 1, -1), vec3(1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1), - vec3(1, 1, 0), vec3(1, -1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0), - vec3(1, 0, 1), vec3(-1, 0, 1), vec3(1, 0, -1), vec3(-1, 0, -1), - vec3(0, 1, 1), vec3(0, -1, 1), vec3(0, -1, -1), vec3(0, 1, -1) - ); - - const float diskRadius = 0.0025; - - float accumulator = 0.0; - - for (int i = 0; i < 20; ++i) - { - vec3 fetchDirection = -toLight + directions[i] * diskRadius; - float shadowDistanceToLight = texture(samplerCube(shadowMap_tex, shadowMap_samp), fetchDirection).r; - if (biasedFragmentDistance > shadowDistanceToLight) - { - accumulator += 1.0; - } - } - - return clamp(1.0 - accumulator / 20.0, 0.0, 1.0); - } - else - { - float shadowDistanceToLight = texture(samplerCube(shadowMap_tex, shadowMap_samp), -toLight).r; - return biasedFragmentDistance > shadowDistanceToLight ? 0.0 : 1.0; - } - } else { - return 1.0; - } -} - -float S_SpotShadowFactor( - bool shadowsEnabled, - bool softShadows, - float shadowBias, - vec3 fragmentPosition, - mat4 lightViewProjMatrix, - float shadowMapInvSize, - texture2D spotShadowTexture_tex, - sampler spotShadowTexture_samp) -{ - if (shadowsEnabled) - { - vec3 lightSpacePosition = S_Project(fragmentPosition, lightViewProjMatrix); - - float biasedLightSpaceFragmentDepth = lightSpacePosition.z - shadowBias; - - if (softShadows) - { - float accumulator = 0.0; - - float step = 0.5; - float kernelHalfSize = 2.0; - float kernelSize = 2.0 * kernelHalfSize; - float totalSamples = pow(kernelSize / step, 2.0); - - for (float y = -kernelHalfSize; y <= kernelHalfSize; y += step) - { - for (float x = -kernelHalfSize; x <= kernelHalfSize; x += step) - { - vec2 fetchTexCoord = lightSpacePosition.xy + vec2(x, y) * shadowMapInvSize; - if (biasedLightSpaceFragmentDepth > texture(sampler2D(spotShadowTexture_tex, spotShadowTexture_samp), fetchTexCoord).r) - { - accumulator += 1.0; - } - } - } - - return clamp(1.0 - accumulator / totalSamples, 0.0, 1.0); - } - else - { - return biasedLightSpaceFragmentDepth > texture(sampler2D(spotShadowTexture_tex, spotShadowTexture_samp), lightSpacePosition.xy).r ? 0.0 : 1.0; - } - } else { - return 1.0; - } -} - -float Internal_FetchHeight(texture2D heightTexture_tex, sampler heightTexture_samp, vec2 texCoords, float center) { - return clamp(texture(sampler2D(heightTexture_tex, heightTexture_samp), texCoords).r - center, 0.0, 1.0); -} - -vec2 S_ComputeParallaxTextureCoordinates(texture2D heightTexture_tex, sampler heightTexture_samp, vec3 eyeVec, vec2 texCoords, float center, float scale) { - const float minLayers = 8.0; - const float maxLayers = 15.0; - const int maxIterations = 15; - - float t = max(0.0, abs(dot(vec3(0.0, 0.0, 1.0), eyeVec))); - float numLayers = mix(maxLayers, minLayers, t); - float layerDepth = 1.0 / numLayers; - float currentLayerDepth = 0.0; - - vec2 deltaTexCoords = scale * eyeVec.xy / numLayers; - - vec2 currentTexCoords = texCoords; - float currentDepthMapValue = Internal_FetchHeight(heightTexture_tex, heightTexture_samp, currentTexCoords, center); - - for (int i = 0; i < maxIterations; i++) { - if (currentLayerDepth < currentDepthMapValue) { - currentTexCoords -= deltaTexCoords; - currentDepthMapValue = Internal_FetchHeight(heightTexture_tex, heightTexture_samp, currentTexCoords, center); - currentLayerDepth += layerDepth; - } else { - break; - } - } - - vec2 prev = currentTexCoords + deltaTexCoords; - float nextH = currentDepthMapValue - currentLayerDepth; - float prevH = Internal_FetchHeight(heightTexture_tex, heightTexture_samp, prev, center) - currentLayerDepth + layerDepth; - - float weight = nextH / (nextH - prevH); - - return prev * weight + currentTexCoords * (1.0 - weight); -} - -ivec2 S_LinearIndexToPosition(int index, int textureWidth) { - int y = index / textureWidth; - int x = index - textureWidth * y; - return ivec2(x, y); -} - -mat4 S_FetchMatrix(texture2D storage_tex, sampler storage_samp, int index) { - int textureWidth = textureSize(storage_tex, 0).x; - ivec2 pos = S_LinearIndexToPosition(4 * index, textureWidth); - - vec4 col1 = texelFetch(sampler2D(storage_tex, storage_samp), pos, 0); - vec4 col2 = texelFetch(sampler2D(storage_tex, storage_samp), ivec2(pos.x + 1, pos.y), 0); - vec4 col3 = texelFetch(sampler2D(storage_tex, storage_samp), ivec2(pos.x + 2, pos.y), 0); - vec4 col4 = texelFetch(sampler2D(storage_tex, storage_samp), ivec2(pos.x + 3, pos.y), 0); - - return mat4(col1, col2, col3, col4); -} - -struct TBlendShapeOffsets { - vec3 position; - vec3 normal; - vec3 tangent; -}; - -TBlendShapeOffsets S_FetchBlendShapeOffsets(texture3D storage_tex, sampler storage_samp, int vertexIndex, int blendShapeIndex) { - int textureWidth = textureSize(storage_tex, 0).x; - ivec3 pos = ivec3(S_LinearIndexToPosition(3 * vertexIndex, textureWidth), blendShapeIndex); - vec3 position = texelFetch(sampler3D(storage_tex, storage_samp), pos, 0).xyz; - vec3 normal = texelFetch(sampler3D(storage_tex, storage_samp), ivec3(pos.x + 1, pos.y, pos.z), 0).xyz; - vec3 tangent = texelFetch(sampler3D(storage_tex, storage_samp), ivec3(pos.x + 2, pos.y, pos.z), 0).xyz; - return TBlendShapeOffsets(position, normal, tangent); -} - -vec4 S_LinearToSRGB(vec4 color) { - vec3 a = 12.92 * color.rgb; - vec3 b = 1.055 * pow(color.rgb, vec3(1.0 / 2.4)) - 0.055; - vec3 c = step(vec3(0.0031308), color.rgb); - return vec4(mix(a, b, c), color.a); -} - -vec4 S_SRGBToLinear(vec4 color) { - vec3 a = color.rgb / 12.92; - vec3 b = pow((color.rgb + 0.055) / 1.055, vec3(2.4)); - vec3 c = step(vec3(0.04045), color.rgb); - return vec4(mix(a, b, c), color.a); -} - -float S_Luminance(vec3 x) { - return dot(x, vec3(0.2125, 0.7154, 0.0721)); -} - -vec2 S_RotateVec2(vec2 v, float angle) -{ - float c = cos(angle); - float s = sin(angle); - mat2 m = mat2(c, -s, s, c); - return m * v; -} - -vec3 S_ConvertRgbToXyz(vec3 rgb) -{ - vec3 xyz; - xyz.x = dot(vec3(0.4124564, 0.3575761, 0.1804375), rgb); - xyz.y = dot(vec3(0.2126729, 0.7151522, 0.0721750), rgb); - xyz.z = dot(vec3(0.0193339, 0.1191920, 0.9503041), rgb); - return xyz; -} - -vec3 S_ConvertXyzToRgb(vec3 xyz) -{ - vec3 rgb; - rgb.x = dot(vec3(3.2404542, -1.5371385, -0.4985314), xyz); - rgb.y = dot(vec3(-0.9692660, 1.8760108, 0.0415560), xyz); - rgb.z = dot(vec3(0.0556434, -0.2040259, 1.0572252), xyz); - return rgb; -} - -vec3 S_ConvertXyzToYxy(vec3 xyz) -{ - float inv = 1.0 / dot(xyz, vec3(1.0, 1.0, 1.0)); - return vec3(xyz.y, xyz.x * inv, xyz.y * inv); -} - -vec3 S_ConvertYxyToXyz(vec3 Yxy) -{ - vec3 xyz; - xyz.x = Yxy.x * Yxy.y / Yxy.z; - xyz.y = Yxy.x; - xyz.z = Yxy.x * (1.0 - Yxy.y - Yxy.z) / Yxy.z; - return xyz; -} - -vec3 S_ConvertRgbToYxy(vec3 rgb) -{ - return S_ConvertXyzToYxy(S_ConvertRgbToXyz(rgb)); -} - -vec3 S_ConvertYxyToRgb(vec3 Yxy) -{ - return S_ConvertXyzToRgb(S_ConvertYxyToXyz(Yxy)); -} diff --git a/fyrox-graphics-wgpu/src/shaders/shared.wgsl b/fyrox-graphics-wgpu/src/shaders/shared.wgsl new file mode 100644 index 0000000000..b98e06ac58 --- /dev/null +++ b/fyrox-graphics-wgpu/src/shaders/shared.wgsl @@ -0,0 +1,412 @@ +// Shared functions for all shaders in the engine. +// WGSL version for wgpu backend. + +const PI: f32 = 3.14159; + +fn S_SolveQuadraticEq(a: f32, b: f32, c: f32, minT: ptr, maxT: ptr) -> bool { + let twoA = 2.0 * a; + let det = b * b - 2.0 * twoA * c; + if (det < 0.0) { *minT = 0.0; *maxT = 0.0; return false; } + let sqrtDet = sqrt(det); + let root1 = (-b - sqrtDet) / twoA; + let root2 = (-b + sqrtDet) / twoA; + *minT = min(root1, root2); + *maxT = max(root1, root2); + return true; +} + +fn S_LightDistanceAttenuation(distance: f32, radius: f32) -> f32 { + return clamp(1.0 - distance * distance / (radius * radius), 0.0, 1.0); +} + +fn S_Project(worldPosition: vec3f, matrix: mat4x4f) -> vec3f { + let screenPos = matrix * vec4f(worldPosition, 1.0); + return (screenPos.xyz / screenPos.w) * 0.5 + 0.5; +} + +fn S_UnProject(screenPos: vec3f, matrix: mat4x4f) -> vec3f { + let clipSpacePos = vec4f(screenPos * 2.0 - 1.0, 1.0); + let position = matrix * clipSpacePos; + return position.xyz / position.w; +} + +fn S_DistributionGGX(N: vec3f, H: vec3f, roughness: f32) -> f32 { + let a = roughness * roughness; + let a2 = a * a; + let NdotH = max(dot(N, H), 0.0); + let NdotH2 = NdotH * NdotH; + let nom = a2; + var denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + return nom / denom; +} + +fn S_GeometrySchlickGGX(NdotV: f32, roughness: f32) -> f32 { + let r = (roughness + 1.0); + let k = (r * r) / 8.0; + let nom = NdotV; + let denom = NdotV * (1.0 - k) + k; + return nom / denom; +} + +fn S_GeometrySmith(N: vec3f, V: vec3f, L: vec3f, roughness: f32) -> f32 { + let NdotV = max(dot(N, V), 0.0); + let NdotL = max(dot(N, L), 0.0); + let ggx2 = S_GeometrySchlickGGX(NdotV, roughness); + let ggx1 = S_GeometrySchlickGGX(NdotL, roughness); + return ggx1 * ggx2; +} + +fn S_FresnelSchlick(cosTheta: f32, F0: vec3f) -> vec3f { + return F0 + (1.0 - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0); +} + +fn S_FresnelSchlickRoughness(cosTheta: f32, F0: vec3f, roughness: f32) -> vec3f { + return F0 + (max(vec3f(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0); +} + +struct TPBRContext { + lightColor: vec3f, + viewVector: vec3f, + fragmentToLight: vec3f, + fragmentNormal: vec3f, + metallic: f32, + roughness: f32, + albedo: vec3f, +}; + +fn S_PBR_CalculateLight(ctx: TPBRContext) -> vec3f { + let F0 = mix(vec3f(0.04), ctx.albedo, ctx.metallic); + let L = ctx.fragmentToLight; + let H = normalize(ctx.viewVector + L); + let NDF = S_DistributionGGX(ctx.fragmentNormal, H, ctx.roughness); + let G = S_GeometrySmith(ctx.fragmentNormal, ctx.viewVector, L, ctx.roughness); + let F = S_FresnelSchlick(max(dot(H, ctx.viewVector), 0.0), F0); + let numerator = NDF * G * F; + let denominator = 4.0 * max(dot(ctx.fragmentNormal, ctx.viewVector), 0.0) * max(dot(ctx.fragmentNormal, L), 0.0) + 0.001; + let specular = numerator / denominator; + let kS = F; + var kD = vec3f(1.0) - kS; + kD *= 1.0 - ctx.metallic; + let NdotL = max(dot(ctx.fragmentNormal, L), 0.0); + return (kD * ctx.albedo / PI + specular) * ctx.lightColor * NdotL; +} + +fn S_InScatter(start: vec3f, dir: vec3f, lightPos: vec3f, d: f32) -> f32 { + let q = start - lightPos; + let b = dot(dir, q); + let c = dot(q, q); + let s = 1.0 / sqrt(c - b * b); + let l = s * (atan((d + b) * s) - atan(b * s)); + return l; +} + +fn S_RayleighScatter(start: vec3f, dir: vec3f, lightPos: vec3f, d: f32) -> vec3f { + let scatter = S_InScatter(start, dir, lightPos, d); + return vec3f(0.55, 0.75, 1.0) * scatter; +} + +fn S_RaySphereIntersection(origin: vec3f, dir: vec3f, center: vec3f, radius: f32, minT: ptr, maxT: ptr) -> bool { + let d = origin - center; + let a = dot(dir, dir); + let b = 2.0 * dot(dir, d); + let c = dot(d, d) - radius * radius; + return S_SolveQuadraticEq(a, b, c, minT, maxT); +} + +fn S_PointShadow( + shadowsEnabled: bool, + softShadows: bool, + fragmentDistance: f32, + shadowBias: f32, + toLight: vec3f, + shadowMap_tex: texture_cube, + shadowMap_samp: sampler) -> f32 +{ + if (shadowsEnabled) { + let biasedFragmentDistance = fragmentDistance - shadowBias; + + if (softShadows) { + const directions = array( + vec3f(1, 1, 1), vec3f(1, -1, 1), vec3f(-1, -1, 1), vec3f(-1, 1, 1), + vec3f(1, 1, -1), vec3f(1, -1, -1), vec3f(-1, -1, -1), vec3f(-1, 1, -1), + vec3f(1, 1, 0), vec3f(1, -1, 0), vec3f(-1, -1, 0), vec3f(-1, 1, 0), + vec3f(1, 0, 1), vec3f(-1, 0, 1), vec3f(1, 0, -1), vec3f(-1, 0, -1), + vec3f(0, 1, 1), vec3f(0, -1, 1), vec3f(0, -1, -1), vec3f(0, 1, -1) + ); + + const diskRadius = 0.0025; + + var accumulator = 0.0; + + for (var i = 0; i < 20; i++) { + let fetchDirection = -toLight + directions[i] * diskRadius; + let shadowDistanceToLight = textureSample(shadowMap_tex, shadowMap_samp, fetchDirection).r; + if (biasedFragmentDistance > shadowDistanceToLight) { + accumulator += 1.0; + } + } + + return clamp(1.0 - accumulator / 20.0, 0.0, 1.0); + } else { + let shadowDistanceToLight = textureSample(shadowMap_tex, shadowMap_samp, -toLight).r; + return select(1.0, 0.0, biasedFragmentDistance > shadowDistanceToLight); + } + } else { + return 1.0; + } +} + +fn S_SpotShadowFactor( + shadowsEnabled: bool, + softShadows: bool, + shadowBias: f32, + fragmentPosition: vec3f, + lightViewProjMatrix: mat4x4f, + shadowMapInvSize: f32, + spotShadowTexture_tex: texture_2d, + spotShadowTexture_samp: sampler) -> f32 +{ + if (shadowsEnabled) { + let lightSpacePosition = S_Project(fragmentPosition, lightViewProjMatrix); + + let biasedLightSpaceFragmentDepth = lightSpacePosition.z - shadowBias; + + if (softShadows) { + var accumulator = 0.0; + + let stepSize = 0.5; + let kernelHalfSize = 2.0; + let kernelSize = 2.0 * kernelHalfSize; + let totalSamples = pow(kernelSize / stepSize, 2.0); + + var y = -kernelHalfSize; + loop { + if (y > kernelHalfSize) { break; } + var x = -kernelHalfSize; + loop { + if (x > kernelHalfSize) { break; } + let fetchTexCoord = lightSpacePosition.xy + vec2f(x, y) * shadowMapInvSize; + if (biasedLightSpaceFragmentDepth > textureSample(spotShadowTexture_tex, spotShadowTexture_samp, fetchTexCoord).r) { + accumulator += 1.0; + } + x += stepSize; + } + y += stepSize; + } + + return clamp(1.0 - accumulator / totalSamples, 0.0, 1.0); + } else { + return select(1.0, 0.0, biasedLightSpaceFragmentDepth > textureSample(spotShadowTexture_tex, spotShadowTexture_samp, lightSpacePosition.xy).r); + } + } else { + return 1.0; + } +} + +// Depth-texture variant of S_PointShadow (textureSample returns f32, no .r needed) +fn S_PointShadow_Depth( + shadowsEnabled: bool, + softShadows: bool, + fragmentDistance: f32, + shadowBias: f32, + toLight: vec3f, + shadowMap_tex: texture_depth_cube, + shadowMap_samp: sampler) -> f32 +{ + if (shadowsEnabled) { + let biasedFragmentDistance = fragmentDistance - shadowBias; + if (softShadows) { + const directions = array( + vec3f(1, 1, 1), vec3f(1, -1, 1), vec3f(-1, -1, 1), vec3f(-1, 1, 1), + vec3f(1, 1, -1), vec3f(1, -1, -1), vec3f(-1, -1, -1), vec3f(-1, 1, -1), + vec3f(1, 1, 0), vec3f(1, -1, 0), vec3f(-1, -1, 0), vec3f(-1, 1, 0), + vec3f(1, 0, 1), vec3f(-1, 0, 1), vec3f(1, 0, -1), vec3f(-1, 0, -1), + vec3f(0, 1, 1), vec3f(0, -1, 1), vec3f(0, -1, -1), vec3f(0, 1, -1) + ); + const diskRadius = 0.0025; + var accumulator = 0.0; + for (var i = 0; i < 20; i++) { + let fetchDirection = -toLight + directions[i] * diskRadius; + let shadowDistanceToLight = textureSample(shadowMap_tex, shadowMap_samp, fetchDirection); + if (biasedFragmentDistance > shadowDistanceToLight) { accumulator += 1.0; } + } + return clamp(1.0 - accumulator / 20.0, 0.0, 1.0); + } else { + let shadowDistanceToLight = textureSample(shadowMap_tex, shadowMap_samp, -toLight); + return select(1.0, 0.0, biasedFragmentDistance > shadowDistanceToLight); + } + } else { return 1.0; } +} + +// Depth-texture variant of S_SpotShadowFactor +fn S_SpotShadowFactor_Depth( + shadowsEnabled: bool, + softShadows: bool, + shadowBias: f32, + fragmentPosition: vec3f, + lightViewProjMatrix: mat4x4f, + shadowMapInvSize: f32, + spotShadowTexture_tex: texture_depth_2d, + spotShadowTexture_samp: sampler) -> f32 +{ + if (shadowsEnabled) { + let lightSpacePosition = S_Project(fragmentPosition, lightViewProjMatrix); + let biasedLightSpaceFragmentDepth = lightSpacePosition.z - shadowBias; + if (softShadows) { + var accumulator = 0.0; + let stepSize = 0.5; + let kernelHalfSize = 2.0; + let kernelSize = 2.0 * kernelHalfSize; + let totalSamples = pow(kernelSize / stepSize, 2.0); + var y = -kernelHalfSize; + loop { + if (y > kernelHalfSize) { break; } + var x = -kernelHalfSize; + loop { + if (x > kernelHalfSize) { break; } + let fetchTexCoord = lightSpacePosition.xy + vec2f(x, y) * shadowMapInvSize; + if (biasedLightSpaceFragmentDepth > textureSample(spotShadowTexture_tex, spotShadowTexture_samp, fetchTexCoord)) { accumulator += 1.0; } + x += stepSize; + } + y += stepSize; + } + return clamp(1.0 - accumulator / totalSamples, 0.0, 1.0); + } else { + return select(1.0, 0.0, biasedLightSpaceFragmentDepth > textureSample(spotShadowTexture_tex, spotShadowTexture_samp, lightSpacePosition.xy)); + } + } else { return 1.0; } +} + +fn Internal_FetchHeight(heightTexture_tex: texture_2d, heightTexture_samp: sampler, texCoords: vec2f, center: f32) -> f32 { + return clamp(textureSample(heightTexture_tex, heightTexture_samp, texCoords).r - center, 0.0, 1.0); +} + +fn S_ComputeParallaxTextureCoordinates(heightTexture_tex: texture_2d, heightTexture_samp: sampler, eyeVec: vec3f, texCoords: vec2f, center: f32, scale: f32) -> vec2f { + const minLayers = 8.0; + const maxLayers = 15.0; + const maxIterations = 15; + + let t = max(0.0, abs(dot(vec3f(0.0, 0.0, 1.0), eyeVec))); + let numLayers = mix(maxLayers, minLayers, t); + let layerDepth = 1.0 / numLayers; + var currentLayerDepth = 0.0; + + let deltaTexCoords = scale * eyeVec.xy / numLayers; + + var currentTexCoords = texCoords; + var currentDepthMapValue = Internal_FetchHeight(heightTexture_tex, heightTexture_samp, currentTexCoords, center); + + for (var i = 0; i < maxIterations; i++) { + if (currentLayerDepth < currentDepthMapValue) { + currentTexCoords -= deltaTexCoords; + currentDepthMapValue = Internal_FetchHeight(heightTexture_tex, heightTexture_samp, currentTexCoords, center); + currentLayerDepth += layerDepth; + } else { + break; + } + } + + let prev = currentTexCoords + deltaTexCoords; + let nextH = currentDepthMapValue - currentLayerDepth; + let prevH = Internal_FetchHeight(heightTexture_tex, heightTexture_samp, prev, center) - currentLayerDepth + layerDepth; + + let weight = nextH / (nextH - prevH); + + return prev * weight + currentTexCoords * (1.0 - weight); +} + +fn S_LinearIndexToPosition(index: i32, textureWidth: i32) -> vec2i { + let y = index / textureWidth; + let x = index - textureWidth * y; + return vec2i(x, y); +} + +fn S_FetchMatrix(storage_tex: texture_2d, storage_samp: sampler, index: i32) -> mat4x4f { + let textureWidth = i32(textureDimensions(storage_tex, 0).x); + let pos = S_LinearIndexToPosition(4 * index, textureWidth); + + let col1 = textureLoad(storage_tex, pos, 0); + let col2 = textureLoad(storage_tex, vec2i(pos.x + 1, pos.y), 0); + let col3 = textureLoad(storage_tex, vec2i(pos.x + 2, pos.y), 0); + let col4 = textureLoad(storage_tex, vec2i(pos.x + 3, pos.y), 0); + + return mat4x4f(col1, col2, col3, col4); +} + +struct TBlendShapeOffsets { + position: vec3f, + normal: vec3f, + tangent: vec3f, +}; + +fn S_FetchBlendShapeOffsets(storage_tex: texture_3d, storage_samp: sampler, vertexIndex: i32, blendShapeIndex: i32) -> TBlendShapeOffsets { + let textureWidth = i32(textureDimensions(storage_tex, 0).x); + let pos = vec3i(S_LinearIndexToPosition(3 * vertexIndex, textureWidth), blendShapeIndex); + let position = textureLoad(storage_tex, pos, 0).xyz; + let normal = textureLoad(storage_tex, vec3i(pos.x + 1, pos.y, pos.z), 0).xyz; + let tangent = textureLoad(storage_tex, vec3i(pos.x + 2, pos.y, pos.z), 0).xyz; + return TBlendShapeOffsets(position, normal, tangent); +} + +fn S_LinearToSRGB(color: vec4f) -> vec4f { + let a = 12.92 * color.rgb; + let b = 1.055 * pow(color.rgb, vec3f(1.0 / 2.4)) - 0.055; + let c = step(vec3f(0.0031308), color.rgb); + return vec4f(mix(a, b, c), color.a); +} + +fn S_SRGBToLinear(color: vec4f) -> vec4f { + let a = color.rgb / 12.92; + let b = pow((color.rgb + 0.055) / 1.055, vec3f(2.4)); + let c = step(vec3f(0.04045), color.rgb); + return vec4f(mix(a, b, c), color.a); +} + +fn S_Luminance(x: vec3f) -> f32 { + return dot(x, vec3f(0.2125, 0.7154, 0.0721)); +} + +fn S_RotateVec2(v: vec2f, angle: f32) -> vec2f { + let c = cos(angle); + let s = sin(angle); + let m = mat2x2f(c, -s, s, c); + return m * v; +} + +fn S_ConvertRgbToXyz(rgb: vec3f) -> vec3f { + var xyz: vec3f; + xyz.x = dot(vec3f(0.4124564, 0.3575761, 0.1804375), rgb); + xyz.y = dot(vec3f(0.2126729, 0.7151522, 0.0721750), rgb); + xyz.z = dot(vec3f(0.0193339, 0.1191920, 0.9503041), rgb); + return xyz; +} + +fn S_ConvertXyzToRgb(xyz: vec3f) -> vec3f { + var rgb: vec3f; + rgb.x = dot(vec3f(3.2404542, -1.5371385, -0.4985314), xyz); + rgb.y = dot(vec3f(-0.9692660, 1.8760108, 0.0415560), xyz); + rgb.z = dot(vec3f(0.0556434, -0.2040259, 1.0572252), xyz); + return rgb; +} + +fn S_ConvertXyzToYxy(xyz: vec3f) -> vec3f { + let inv = 1.0 / dot(xyz, vec3f(1.0, 1.0, 1.0)); + return vec3f(xyz.y, xyz.x * inv, xyz.y * inv); +} + +fn S_ConvertYxyToXyz(Yxy: vec3f) -> vec3f { + var xyz: vec3f; + xyz.x = Yxy.x * Yxy.y / Yxy.z; + xyz.y = Yxy.x; + xyz.z = Yxy.x * (1.0 - Yxy.y - Yxy.z) / Yxy.z; + return xyz; +} + +fn S_ConvertRgbToYxy(rgb: vec3f) -> vec3f { + return S_ConvertXyzToYxy(S_ConvertRgbToXyz(rgb)); +} + +fn S_ConvertYxyToRgb(Yxy: vec3f) -> vec3f { + return S_ConvertXyzToRgb(S_ConvertYxyToXyz(Yxy)); +} diff --git a/fyrox-graphics/src/gpu_program.rs b/fyrox-graphics/src/gpu_program.rs index 70886c6af1..f6f150e161 100644 --- a/fyrox-graphics/src/gpu_program.rs +++ b/fyrox-graphics/src/gpu_program.rs @@ -153,6 +153,10 @@ pub enum SamplerKind { /// The sampler follows the direction of the coordinates until it finds a place on one of the six faces of the cube. /// Each component of the resulting value is an unsigned integer. USamplerCube, + /// A sampler for a 2D depth texture. + DepthSampler2D, + /// A sampler for a cube depth texture. + DepthSamplerCube, } /// Shader property with default value. diff --git a/fyrox-impl/Cargo.toml b/fyrox-impl/Cargo.toml index bcde398f94..7751327315 100644 --- a/fyrox-impl/Cargo.toml +++ b/fyrox-impl/Cargo.toml @@ -23,7 +23,6 @@ fyrox-resource = { path = "../fyrox-resource", version = "2.0.0-rc.1" } fyrox-animation = { path = "../fyrox-animation", version = "2.0.0-rc.1" } fyrox-graph = { path = "../fyrox-graph", version = "2.0.0-rc.1" } fyrox-graphics = { path = "../fyrox-graphics", version = "2.0.0-rc.1" } -fyrox-graphics-gl = { path = "../fyrox-graphics-gl", version = "2.0.0-rc.1" } fyrox-graphics-wgpu = { path = "../fyrox-graphics-wgpu", version = "2.0.0-rc.1" } fyrox-texture = { path = "../fyrox-texture", version = "2.0.0-rc.1" } fyrox-autotile = { path = "../fyrox-autotile", version = "2.0.0-rc.1" } diff --git a/fyrox-impl/src/engine/mod.rs b/fyrox-impl/src/engine/mod.rs index 934ff84a3b..a81a51e8fe 100644 --- a/fyrox-impl/src/engine/mod.rs +++ b/fyrox-impl/src/engine/mod.rs @@ -113,7 +113,7 @@ use fyrox_animation::AnimationTracksData; use fyrox_core::dyntype::DynTypeConstructorContainer; use fyrox_core::NameProvider; use fyrox_graphics::server::SharedGraphicsServer; -use fyrox_graphics_gl::server::GlGraphicsServer; + use fyrox_sound::{ buffer::{loader::SoundBufferLoader, SoundBuffer}, renderer::hrtf::{HrirSphereLoader, HrirSphereResourceData}, diff --git a/fyrox-impl/src/lib.rs b/fyrox-impl/src/lib.rs index f5285a03a7..e4b9972081 100644 --- a/fyrox-impl/src/lib.rs +++ b/fyrox-impl/src/lib.rs @@ -69,9 +69,6 @@ pub use fyrox_autotile as autotile; #[doc(inline)] pub use fyrox_graphics as graphics; -#[doc(inline)] -pub use fyrox_graphics_gl as graphics_gl; - /// Defines a builder's `with_xxx` method. #[macro_export] macro_rules! define_with { diff --git a/fyrox-impl/src/renderer/shaders/ambient_light.shader b/fyrox-impl/src/renderer/shaders/ambient_light.shader index 6177cf25c5..2f3fbda94a 100644 --- a/fyrox-impl/src/renderer/shaders/ambient_light.shader +++ b/fyrox-impl/src/renderer/shaders/ambient_light.shader @@ -18,7 +18,7 @@ ), ( name: "depthTexture", - kind: Texture(kind: Sampler2D, fallback: White), + kind: Texture(kind: DepthSampler2D, fallback: White), binding: 3 ), ( @@ -97,61 +97,76 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertex_position: vec3f, + @location(1) vertex_tex_coord: vec2f, + } - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) tex_coord: vec2f, + } - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.tex_coord = input.vertex_tex_coord; + output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - in vec2 texCoord; + @fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) vec4f { + let depth = textureSample(depthTexture_tex, depthTexture_samp, tex_coord); + let fragment_position = S_UnProject(vec3f(tex_coord, depth), properties.invViewProj); + + let albedo = S_SRGBToLinear(textureSample(diffuseTexture_tex, diffuseTexture_samp, tex_coord)); - void main() - { - float depth = texture(depthTexture, texCoord).r; - vec3 fragmentPosition = S_UnProject(vec3(texCoord, depth), properties.invViewProj); + let fragment_normal = normalize(textureSample(normalTexture_tex, normalTexture_samp, tex_coord).xyz * 2.0 - 1.0); - vec4 albedo = S_SRGBToLinear(texture(diffuseTexture, texCoord)); + let material = textureSample(materialTexture_tex, materialTexture_samp, tex_coord).rgb; + let metallic = material.x; + let roughness = material.y; + let material_ao = material.z; - vec3 fragmentNormal = normalize(texture(normalTexture, texCoord).xyz * 2.0 - 1.0); + let view_vector = normalize(properties.cameraPosition - fragment_position); + let reflection_vector = -reflect(view_vector, fragment_normal); - vec3 material = texture(materialTexture, texCoord).rgb; - float metallic = material.x; - float roughness = material.y; - float materialAo = material.z; + let clamped_cos_view_angle = max(dot(fragment_normal, view_vector), 0.0); - vec3 viewVector = normalize(properties.cameraPosition - fragmentPosition); - vec3 reflectionVector = -reflect(viewVector, fragmentNormal); + let cube_map_size = textureDimensions(prefilteredSpecularMap_tex, 0); + let mip = roughness * (floor(log2(f32(cube_map_size.x))) + 1.0); - float clampedCosViewAngle = max(dot(fragmentNormal, viewVector), 0.0); + var reflection: vec3f; + if (properties.skyboxLighting != 0u) { + reflection = S_SRGBToLinear(textureSampleLevel(prefilteredSpecularMap_tex, prefilteredSpecularMap_samp, reflection_vector, mip)).rgb; + } else { + reflection = properties.ambientColor.rgb; + } - ivec2 cubeMapSize = textureSize(prefilteredSpecularMap, 0); - float mip = roughness * (floor(log2(float(cubeMapSize.x))) + 1.0); - vec3 reflection = properties.skyboxLighting ? S_SRGBToLinear(textureLod(prefilteredSpecularMap, reflectionVector, mip)).rgb : properties.ambientColor.rgb; + let F0 = mix(vec3f(0.04), albedo.rgb, metallic); + let F = S_FresnelSchlickRoughness(clamped_cos_view_angle, F0, roughness); + let kD = (vec3f(1.0) - F) * (1.0 - metallic); - vec3 F0 = mix(vec3(0.04), albedo.rgb, metallic); - vec3 F = S_FresnelSchlickRoughness(clampedCosViewAngle, F0, roughness); - vec3 kD = (vec3(1.0) - F) * (1.0 - metallic); + let envBRDF = textureSample(brdfLUT_tex, brdfLUT_samp, vec2f(clamped_cos_view_angle, roughness)).rg; + let specular = reflection * (F * envBRDF.x + envBRDF.y); - vec2 envBRDF = texture(brdfLUT, vec2(clampedCosViewAngle, roughness)).rg; - vec3 specular = reflection * (F * envBRDF.x + envBRDF.y); + let ambient_occlusion = textureSample(aoSampler_tex, aoSampler_samp, tex_coord).r * material_ao; + let baked_lighting = textureSample(bakedLightingTexture_tex, bakedLightingTexture_samp, tex_coord); - float ambientOcclusion = texture(aoSampler, texCoord).r * materialAo; - vec4 bakedLighting = texture(bakedLightingTexture, texCoord); + let irradiance = S_SRGBToLinear(textureSample(irradianceMap_tex, irradianceMap_samp, fragment_normal)).rgb; - vec3 irradiance = S_SRGBToLinear(texture(irradianceMap, fragmentNormal)).rgb; - vec3 diffuse = (bakedLighting.rgb + properties.environmentLightingBrightness * (properties.skyboxLighting ? irradiance : properties.ambientColor.rgb)) * albedo.rgb; + var ambient_lighting: vec3f; + if (properties.skyboxLighting != 0u) { + ambient_lighting = irradiance; + } else { + ambient_lighting = properties.ambientColor.rgb; + } + let diffuse = (baked_lighting.rgb + properties.environmentLightingBrightness * ambient_lighting) * albedo.rgb; - FragColor.rgb = (kD * diffuse + specular) * ambientOcclusion; - FragColor.a = bakedLighting.a; + let output_rgb = (kD * diffuse + specular) * ambient_occlusion; + return vec4f(output_rgb, baked_lighting.a); } "#, ) diff --git a/fyrox-impl/src/renderer/shaders/blit.shader b/fyrox-impl/src/renderer/shaders/blit.shader index 98a60fcd4d..5a9da7c5fa 100644 --- a/fyrox-impl/src/renderer/shaders/blit.shader +++ b/fyrox-impl/src/renderer/shaders/blit.shader @@ -41,29 +41,30 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + }; - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.texCoord = input.vertexTexCoord; + output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - - in vec2 texCoord; - - void main() - { - FragColor = texture(diffuseTexture, texCoord); + @fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f { + return textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord); } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/renderer/shaders/bloom.shader b/fyrox-impl/src/renderer/shaders/bloom.shader index 3c9ef200f4..75a58f555a 100644 --- a/fyrox-impl/src/renderer/shaders/bloom.shader +++ b/fyrox-impl/src/renderer/shaders/bloom.shader @@ -42,34 +42,35 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + }; - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.texCoord = input.vertexTexCoord; + output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - in vec2 texCoord; - - out vec4 outBrightColor; - - void main() { - vec3 hdrPixel = texture(hdrSampler, texCoord).rgb; - + @fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f { + let hdrPixel = textureSample(hdrSampler_tex, hdrSampler_samp, texCoord).rgb; if (S_Luminance(hdrPixel) > properties.threshold) { - outBrightColor = vec4(hdrPixel, 0.0); + return vec4f(hdrPixel, 0.0); } else { - outBrightColor = vec4(0.0); + return vec4f(0.0); } } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/renderer/shaders/blur.shader b/fyrox-impl/src/renderer/shaders/blur.shader index 8018ef25da..c8c3bb749d 100644 --- a/fyrox-impl/src/renderer/shaders/blur.shader +++ b/fyrox-impl/src/renderer/shaders/blur.shader @@ -41,38 +41,38 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertex_position: vec3f, + @location(1) vertex_tex_coord: vec2f, + } - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) tex_coord: vec2f, + } - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.tex_coord = input.vertex_tex_coord; + output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0); + return output; } "#, fragment_shader: r#" // Simple 4x4 box blur. - out float FragColor; - - in vec2 texCoord; - void main() - { - vec2 texelSize = 1.0 / vec2(textureSize(inputTexture, 0)); - float result = 0.0; - for (int y = -2; y < 2; ++y) - { - for (int x = -2; x < 2; ++x) - { - vec2 offset = vec2(float(x), float(y)) * texelSize; - result += texture(inputTexture, texCoord + offset).r; + @fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) f32 { + let texel_size = 1.0 / vec2f(textureDimensions(inputTexture_tex, 0)); + var result: f32 = 0.0; + for (var y: i32 = -2; y < 2; y++) { + for (var x: i32 = -2; x < 2; x++) { + let offset = vec2f(f32(x), f32(y)) * texel_size; + result += textureSample(inputTexture_tex, inputTexture_samp, tex_coord + offset).r; } } - FragColor = result / 16.0; + return result / 16.0; } "#, ) diff --git a/fyrox-impl/src/renderer/shaders/debug.shader b/fyrox-impl/src/renderer/shaders/debug.shader index dbe9791364..c0005c2dfe 100644 --- a/fyrox-impl/src/renderer/shaders/debug.shader +++ b/fyrox-impl/src/renderer/shaders/debug.shader @@ -36,29 +36,30 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec4 vertexColor; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexColor: vec4f, + }; - out vec4 color; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) color: vec4f, + }; - void main() - { - color = vertexColor; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.color = input.vertexColor; + output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - - in vec4 color; - - void main() - { - FragColor = color; + @fragment fn fs_main(@location(0) color: vec4f) -> @location(0) vec4f { + return color; } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/renderer/shaders/decal.shader b/fyrox-impl/src/renderer/shaders/decal.shader index 12c3c560ab..7f3cb7c34b 100644 --- a/fyrox-impl/src/renderer/shaders/decal.shader +++ b/fyrox-impl/src/renderer/shaders/decal.shader @@ -3,7 +3,7 @@ resources: [ ( name: "sceneDepth", - kind: Texture(kind: Sampler2D, fallback: White), + kind: Texture(kind: DepthSampler2D, fallback: White), binding: 0 ), ( @@ -72,68 +72,77 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; + struct VertexInput { + @location(0) vertex_position: vec3f, + } - out vec4 clipSpacePosition; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) clip_space_position: vec4f, + } - void main() - { - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); - clipSpacePosition = gl_Position; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0); + output.clip_space_position = output.position; + return output; } "#, fragment_shader: r#" - layout (location = 0) out vec4 outDiffuseMap; - layout (location = 1) out vec4 outNormalMap; - - in vec4 clipSpacePosition; + struct FragmentOutput { + @location(0) out_diffuse_map: vec4f, + @location(1) out_normal_map: vec4f, + } - void main() - { - vec2 screenPos = clipSpacePosition.xy / clipSpacePosition.w; + @fragment fn fs_main(@location(0) clip_space_position: vec4f) -> FragmentOutput { + let screen_pos = clip_space_position.xy / clip_space_position.w; - vec2 texCoord = vec2( - (1.0 + screenPos.x) / 2.0 + (0.5 / properties.resolution.x), - (1.0 + screenPos.y) / 2.0 + (0.5 / properties.resolution.y) + let tex_coord = vec2f( + (1.0 + screen_pos.x) / 2.0 + (0.5 / properties.resolution.x), + (1.0 + screen_pos.y) / 2.0 + (0.5 / properties.resolution.y) ); - uvec4 maskIndex = texture(decalMask, texCoord); + let mask_index = textureLoad(decalMask_tex, vec2i(tex_coord * vec2f(textureDimensions(decalMask_tex))), 0); // Masking. - if (maskIndex.r != properties.layerIndex) { + if (mask_index.r != properties.layerIndex) { discard; } - float sceneDepth = texture(sceneDepth, texCoord).r; + let scene_depth = textureSample(sceneDepth_tex, sceneDepth_samp, tex_coord); - vec3 sceneWorldPosition = S_UnProject(vec3(texCoord, sceneDepth), properties.invViewProj); + let scene_world_position = S_UnProject(vec3f(tex_coord, scene_depth), properties.invViewProj); - vec3 decalSpacePosition = (properties.invWorldDecal * vec4(sceneWorldPosition, 1.0)).xyz; + let decal_space_position = (properties.invWorldDecal * vec4f(scene_world_position, 1.0)).xyz; // Check if scene pixel is not inside decal bounds. - vec3 dpos = vec3(0.5) - abs(decalSpacePosition.xyz); + let dpos = vec3f(0.5) - abs(decal_space_position); if (dpos.x < 0.0 || dpos.y < 0.0 || dpos.z < 0.0) { discard; } - vec2 decalTexCoord = decalSpacePosition.xz + 0.5; + let decal_tex_coord = decal_space_position.xz + 0.5; + + let diffuse = properties.color * textureSample(diffuseTexture_tex, diffuseTexture_samp, decal_tex_coord); - outDiffuseMap = properties.color * texture(diffuseTexture, decalTexCoord); + let fragment_tangent = dpdx(scene_world_position); + let fragment_binormal = dpdy(scene_world_position); + let fragment_normal = cross(fragment_tangent, fragment_binormal); - vec3 fragmentTangent = dFdx(sceneWorldPosition); - vec3 fragmentBinormal = dFdy(sceneWorldPosition); - vec3 fragmentNormal = cross(fragmentTangent, fragmentBinormal); + var tangent_to_world: mat3x3f; + tangent_to_world[0] = normalize(fragment_tangent); // Tangent + tangent_to_world[1] = normalize(fragment_binormal); // Binormal + tangent_to_world[2] = normalize(fragment_normal); // Normal - mat3 tangentToWorld; - tangentToWorld[0] = normalize(fragmentTangent); // Tangent - tangentToWorld[1] = normalize(fragmentBinormal); // Binormal - tangentToWorld[2] = normalize(fragmentNormal); // Normal + let raw_normal = (textureSample(normalTexture_tex, normalTexture_samp, decal_tex_coord) * 2.0 - 1.0).xyz; + let world_space_normal = tangent_to_world * raw_normal; - vec3 rawNormal = (texture(normalTexture, decalTexCoord) * 2.0 - 1.0).xyz; - vec3 worldSpaceNormal = tangentToWorld * rawNormal; - outNormalMap = vec4(worldSpaceNormal * 0.5 + 0.5, outDiffuseMap.a); + var output: FragmentOutput; + output.out_diffuse_map = diffuse; + output.out_normal_map = vec4f(world_space_normal * 0.5 + 0.5, diffuse.a); + return output; } "#, ) diff --git a/fyrox-impl/src/renderer/shaders/deferred_directional_light.shader b/fyrox-impl/src/renderer/shaders/deferred_directional_light.shader index 3fd9e63a1b..2dec5a6e70 100644 --- a/fyrox-impl/src/renderer/shaders/deferred_directional_light.shader +++ b/fyrox-impl/src/renderer/shaders/deferred_directional_light.shader @@ -3,7 +3,7 @@ resources: [ ( name: "depthTexture", - kind: Texture(kind: Sampler2D, fallback: White), + kind: Texture(kind: DepthSampler2D, fallback: White), binding: 0 ), ( @@ -23,17 +23,17 @@ ), ( name: "shadowCascade0", - kind: Texture(kind: Sampler2D, fallback: White), + kind: Texture(kind: DepthSampler2D, fallback: White), binding: 4 ), ( name: "shadowCascade1", - kind: Texture(kind: Sampler2D, fallback: White), + kind: Texture(kind: DepthSampler2D, fallback: White), binding: 5 ), ( name: "shadowCascade2", - kind: Texture(kind: Sampler2D, fallback: White), + kind: Texture(kind: DepthSampler2D, fallback: White), binding: 6 ), ( @@ -93,61 +93,61 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertex_position: vec3f, + @location(1) vertex_tex_coord: vec2f, + } - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) tex_coord: vec2f, + } - void main() - { - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); - texCoord = vertexTexCoord; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0); + output.tex_coord = input.vertex_tex_coord; + return output; } "#, fragment_shader: r#" - in vec2 texCoord; - out vec4 FragColor; - - // Returns **inverted** shadow factor where 1 - fully bright, 0 - fully in shadow. - float CsmGetShadow(in sampler2D sampler, in vec3 fragmentPosition, in mat4 lightViewProjMatrix) - { - float invSize = 1.0 / float(textureSize(sampler, 0).x); - return S_SpotShadowFactor(properties.shadowsEnabled, properties.softShadows, - properties.shadowBias, fragmentPosition, lightViewProjMatrix, invSize, sampler); - } - - void main() - { - vec3 material = texture(materialTexture, texCoord).rgb; + @fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) vec4f { + let material = textureSample(materialTexture_tex, materialTexture_samp, tex_coord).rgb; - vec3 fragmentPosition = S_UnProject(vec3(texCoord, texture(depthTexture, texCoord).r), properties.invViewProj); - vec4 diffuseColor = texture(colorTexture, texCoord); + let fragment_position = S_UnProject(vec3f(tex_coord, textureSample(depthTexture_tex, depthTexture_samp, tex_coord)), properties.invViewProj); + let diffuse_color = textureSample(colorTexture_tex, colorTexture_samp, tex_coord); - TPBRContext ctx; - ctx.albedo = S_SRGBToLinear(diffuseColor).rgb; + var ctx: TPBRContext; + ctx.albedo = S_SRGBToLinear(diffuse_color).rgb; ctx.fragmentToLight = properties.lightDirection; - ctx.fragmentNormal = normalize(texture(normalTexture, texCoord).xyz * 2.0 - 1.0); + ctx.fragmentNormal = normalize(textureSample(normalTexture_tex, normalTexture_samp, tex_coord).xyz * 2.0 - 1.0); ctx.lightColor = properties.lightColor.rgb; ctx.metallic = material.x; ctx.roughness = material.y; - ctx.viewVector = normalize(properties.cameraPosition - fragmentPosition); + ctx.viewVector = normalize(properties.cameraPosition - fragment_position); - vec3 lighting = S_PBR_CalculateLight(ctx); + let lighting = S_PBR_CalculateLight(ctx); - float fragmentZViewSpace = abs((properties.viewMatrix * vec4(fragmentPosition, 1.0)).z); + let fragment_z_view_space = abs((properties.viewMatrix * vec4f(fragment_position, 1.0)).z); - float shadow = 1.0; - if (fragmentZViewSpace <= properties.cascadeDistances[0]) { - shadow = CsmGetShadow(shadowCascade0, fragmentPosition, properties.lightViewProjMatrices[0]); - } else if (fragmentZViewSpace <= properties.cascadeDistances[1]) { - shadow = CsmGetShadow(shadowCascade1, fragmentPosition, properties.lightViewProjMatrices[1]); - } else if (fragmentZViewSpace <= properties.cascadeDistances[2]) { - shadow = CsmGetShadow(shadowCascade2, fragmentPosition, properties.lightViewProjMatrices[2]); + var shadow: f32 = 1.0; + if (fragment_z_view_space <= properties.cascadeDistances[0].x) { + let inv_size = 1.0 / f32(textureDimensions(shadowCascade0_tex).x); + shadow = S_SpotShadowFactor_Depth(properties.shadowsEnabled != 0u, properties.softShadows != 0u, + properties.shadowBias, fragment_position, properties.lightViewProjMatrices[0], inv_size, shadowCascade0_tex, shadowCascade0_samp); + } else if (fragment_z_view_space <= properties.cascadeDistances[1].x) { + let inv_size = 1.0 / f32(textureDimensions(shadowCascade1_tex).x); + shadow = S_SpotShadowFactor_Depth(properties.shadowsEnabled != 0u, properties.softShadows != 0u, + properties.shadowBias, fragment_position, properties.lightViewProjMatrices[1], inv_size, shadowCascade1_tex, shadowCascade1_samp); + } else if (fragment_z_view_space <= properties.cascadeDistances[2].x) { + let inv_size = 1.0 / f32(textureDimensions(shadowCascade2_tex).x); + shadow = S_SpotShadowFactor_Depth(properties.shadowsEnabled != 0u, properties.softShadows != 0u, + properties.shadowBias, fragment_position, properties.lightViewProjMatrices[2], inv_size, shadowCascade2_tex, shadowCascade2_samp); } - FragColor = shadow * vec4(properties.lightIntensity * lighting, diffuseColor.a); + return shadow * vec4f(properties.lightIntensity * lighting, diffuse_color.a); } "#, ) diff --git a/fyrox-impl/src/renderer/shaders/deferred_point_light.shader b/fyrox-impl/src/renderer/shaders/deferred_point_light.shader index c67a3c80fe..f7a9a9ccbb 100644 --- a/fyrox-impl/src/renderer/shaders/deferred_point_light.shader +++ b/fyrox-impl/src/renderer/shaders/deferred_point_light.shader @@ -3,7 +3,7 @@ resources: [ ( name: "depthTexture", - kind: Texture(kind: Sampler2D, fallback: White), + kind: Texture(kind: DepthSampler2D, fallback: White), binding: 0 ), ( @@ -23,7 +23,7 @@ ), ( name: "pointShadowTexture", - kind: Texture(kind: SamplerCube, fallback: White), + kind: Texture(kind: DepthSamplerCube, fallback: White), binding: 4 ), ( @@ -86,51 +86,53 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertex_position: vec3f, + @location(1) vertex_tex_coord: vec2f, + } - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) tex_coord: vec2f, + } - void main() - { - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); - texCoord = vertexTexCoord; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0); + output.tex_coord = input.vertex_tex_coord; + return output; } "#, fragment_shader: r#" - in vec2 texCoord; - out vec4 FragColor; - - void main() - { - vec3 material = texture(materialTexture, texCoord).rgb; + @fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) vec4f { + let material = textureSample(materialTexture_tex, materialTexture_samp, tex_coord).rgb; - vec3 fragmentPosition = S_UnProject(vec3(texCoord, texture(depthTexture, texCoord).r), properties.invViewProj); - vec3 fragmentToLight = properties.lightPos - fragmentPosition; - float distance = length(fragmentToLight); + let fragment_position = S_UnProject(vec3f(tex_coord, textureSample(depthTexture_tex, depthTexture_samp, tex_coord)), properties.invViewProj); + let fragment_to_light = properties.lightPos - fragment_position; + let dist = length(fragment_to_light); - vec4 diffuseColor = texture(colorTexture, texCoord); + let diffuse_color = textureSample(colorTexture_tex, colorTexture_samp, tex_coord); - TPBRContext ctx; - ctx.albedo = S_SRGBToLinear(diffuseColor).rgb; - ctx.fragmentToLight = fragmentToLight / distance; - ctx.fragmentNormal = normalize(texture(normalTexture, texCoord).xyz * 2.0 - 1.0); + var ctx: TPBRContext; + ctx.albedo = S_SRGBToLinear(diffuse_color).rgb; + ctx.fragmentToLight = fragment_to_light / dist; + ctx.fragmentNormal = normalize(textureSample(normalTexture_tex, normalTexture_samp, tex_coord).xyz * 2.0 - 1.0); ctx.lightColor = properties.lightColor.rgb; ctx.metallic = material.x; ctx.roughness = material.y; - ctx.viewVector = normalize(properties.cameraPosition - fragmentPosition); + ctx.viewVector = normalize(properties.cameraPosition - fragment_position); - vec3 lighting = S_PBR_CalculateLight(ctx); + let lighting = S_PBR_CalculateLight(ctx); - float distanceAttenuation = S_LightDistanceAttenuation(distance, properties.lightRadius); + let distance_attenuation = S_LightDistanceAttenuation(dist, properties.lightRadius); - float shadow = S_PointShadow( - properties.shadowsEnabled, properties.softShadows, distance, properties.shadowBias, ctx.fragmentToLight, pointShadowTexture); - float finalShadow = mix(1.0, shadow, properties.shadowAlpha); + let shadow = S_PointShadow_Depth( + properties.shadowsEnabled != 0u, properties.softShadows != 0u, dist, properties.shadowBias, ctx.fragmentToLight, pointShadowTexture_tex, pointShadowTexture_samp); + let final_shadow = mix(1.0, shadow, properties.shadowAlpha); - FragColor = vec4(properties.lightIntensity * distanceAttenuation * finalShadow * lighting, diffuseColor.a); + return vec4f(properties.lightIntensity * distance_attenuation * final_shadow * lighting, diffuse_color.a); } "#, ) diff --git a/fyrox-impl/src/renderer/shaders/deferred_spot_light.shader b/fyrox-impl/src/renderer/shaders/deferred_spot_light.shader index 9fc386e468..69940bc0e9 100644 --- a/fyrox-impl/src/renderer/shaders/deferred_spot_light.shader +++ b/fyrox-impl/src/renderer/shaders/deferred_spot_light.shader @@ -3,7 +3,7 @@ resources: [ ( name: "depthTexture", - kind: Texture(kind: Sampler2D, fallback: White), + kind: Texture(kind: DepthSampler2D, fallback: White), binding: 0 ), ( @@ -23,7 +23,7 @@ ), ( name: "spotShadowTexture", - kind: Texture(kind: Sampler2D, fallback: White), + kind: Texture(kind: DepthSampler2D, fallback: White), binding: 4 ), ( @@ -97,60 +97,62 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertex_position: vec3f, + @location(1) vertex_tex_coord: vec2f, + } - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) tex_coord: vec2f, + } - void main() - { - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); - texCoord = vertexTexCoord; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0); + output.tex_coord = input.vertex_tex_coord; + return output; } "#, fragment_shader: r#" - in vec2 texCoord; - out vec4 FragColor; - - void main() - { - vec3 material = texture(materialTexture, texCoord).rgb; + @fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) vec4f { + let material = textureSample(materialTexture_tex, materialTexture_samp, tex_coord).rgb; - vec3 fragmentPosition = S_UnProject(vec3(texCoord, texture(depthTexture, texCoord).r), properties.invViewProj); - vec3 fragmentToLight = properties.lightPos - fragmentPosition; - float distance = length(fragmentToLight); - vec4 diffuseColor = texture(colorTexture, texCoord); + let fragment_position = S_UnProject(vec3f(tex_coord, textureSample(depthTexture_tex, depthTexture_samp, tex_coord)), properties.invViewProj); + let fragment_to_light = properties.lightPos - fragment_position; + let dist = length(fragment_to_light); + let diffuse_color = textureSample(colorTexture_tex, colorTexture_samp, tex_coord); - TPBRContext ctx; - ctx.albedo = S_SRGBToLinear(diffuseColor).rgb; - ctx.fragmentToLight = fragmentToLight / distance; - ctx.fragmentNormal = normalize(texture(normalTexture, texCoord).xyz * 2.0 - 1.0); + var ctx: TPBRContext; + ctx.albedo = S_SRGBToLinear(diffuse_color).rgb; + ctx.fragmentToLight = fragment_to_light / dist; + ctx.fragmentNormal = normalize(textureSample(normalTexture_tex, normalTexture_samp, tex_coord).xyz * 2.0 - 1.0); ctx.lightColor = properties.lightColor.rgb; ctx.metallic = material.x; ctx.roughness = material.y; - ctx.viewVector = normalize(properties.cameraPosition - fragmentPosition); + ctx.viewVector = normalize(properties.cameraPosition - fragment_position); - vec3 lighting = S_PBR_CalculateLight(ctx); + let lighting = S_PBR_CalculateLight(ctx); - float distanceAttenuation = S_LightDistanceAttenuation(distance, properties.lightRadius); + let distance_attenuation = S_LightDistanceAttenuation(dist, properties.lightRadius); - float spotAngleCos = dot(properties.lightDirection, ctx.fragmentToLight); - float coneFactor = smoothstep(properties.halfConeAngleCos, properties.halfHotspotConeAngleCos, spotAngleCos); + let spot_angle_cos = dot(properties.lightDirection, ctx.fragmentToLight); + let cone_factor = smoothstep(properties.halfConeAngleCos, properties.halfHotspotConeAngleCos, spot_angle_cos); - float shadow = S_SpotShadowFactor( - properties.shadowsEnabled, properties.softShadows, properties.shadowBias, fragmentPosition, - properties.lightViewProjMatrix, properties.shadowMapInvSize, spotShadowTexture); - float finalShadow = mix(1.0, shadow, properties.shadowAlpha); + let shadow = S_SpotShadowFactor_Depth( + properties.shadowsEnabled != 0u, properties.softShadows != 0u, properties.shadowBias, fragment_position, + properties.lightViewProjMatrix, properties.shadowMapInvSize, spotShadowTexture_tex, spotShadowTexture_samp); + let final_shadow = mix(1.0, shadow, properties.shadowAlpha); - vec4 cookieAttenuation = vec4(1.0); - if (properties.cookieEnabled) { - vec2 texCoords = S_Project(fragmentPosition, properties.lightViewProjMatrix).xy; - cookieAttenuation = texture(cookieTexture, texCoords); + var cookie_attenuation = vec4f(1.0); + if (properties.cookieEnabled != 0u) { + let cookie_tex_coords = S_Project(fragment_position, properties.lightViewProjMatrix).xy; + cookie_attenuation = textureSample(cookieTexture_tex, cookieTexture_samp, cookie_tex_coords); } - FragColor = cookieAttenuation * vec4(distanceAttenuation * properties.lightIntensity * coneFactor * finalShadow * lighting, diffuseColor.a); + return cookie_attenuation * vec4f(distance_attenuation * properties.lightIntensity * cone_factor * final_shadow * lighting, diffuse_color.a); } "#, ) diff --git a/fyrox-impl/src/renderer/shaders/fxaa.shader b/fyrox-impl/src/renderer/shaders/fxaa.shader index c9492fe5c5..a6a6fd29a6 100644 --- a/fyrox-impl/src/renderer/shaders/fxaa.shader +++ b/fyrox-impl/src/renderer/shaders/fxaa.shader @@ -42,15 +42,21 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; - - out vec2 texCoord; - - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + }; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; + + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.texCoord = input.vertexTexCoord; + output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, @@ -58,219 +64,146 @@ r#" // NVIDIA FXAA 3.11 // Original source code by TIMOTHY LOTTES - // - // Cleaned version - https://github.com/kosua20/Rendu/blob/master/resources/common/shaders/screens/fxaa.frag - // - // Final tweaks by mrDIMAS - - in vec2 texCoord; - out vec4 fragColor; - - // Settings for FXAA. - #define EDGE_THRESHOLD_MIN 0.0312 - #define EDGE_THRESHOLD_MAX 0.125 - #define QUALITY(q) ((q) < 5 ? 1.0: ((q) > 5 ? ((q) < 10 ? 2.0: ((q) < 11 ? 4.0: 8.0)): 1.5)) - #define ITERATIONS 12 - #define SUBPIXEL_QUALITY 0.75 - - float rgb2luma(vec3 rgb) { - return sqrt(dot(rgb, vec3(0.299, 0.587, 0.114))); + // WGSL port + + const EDGE_THRESHOLD_MIN: f32 = 0.0312; + const EDGE_THRESHOLD_MAX: f32 = 0.125; + const ITERATIONS: i32 = 12; + const SUBPIXEL_QUALITY: f32 = 0.75; + + fn quality(q: i32) -> f32 { + if (q < 5) { return 1.0; } + if (q > 5) { + if (q < 10) { return 2.0; } + if (q < 11) { return 4.0; } + return 8.0; + } + return 1.5; } - // Performs FXAA post-process anti-aliasing as described in the Nvidia FXAA white paper and the associated shader code. - void main() - { - vec4 colorCenter = texture(screenTexture, texCoord); - - // Luma at the current fragment - float lumaCenter = rgb2luma(colorCenter.rgb); + fn rgb2luma(rgb: vec3f) -> f32 { + return sqrt(dot(rgb, vec3f(0.299, 0.587, 0.114))); + } - // Luma at the four direct neighbours of the current fragment. - float lumaDown = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(0, -1)).rgb); - float lumaUp = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(0, 1)).rgb); - float lumaLeft = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(-1, 0)).rgb); - float lumaRight = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(1, 0)).rgb); + @fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f { + let colorCenter = textureSample(screenTexture_tex, screenTexture_samp, texCoord); + let lumaCenter = rgb2luma(colorCenter.rgb); - // Find the maximum and minimum luma around the current fragment. - float lumaMin = min(lumaCenter, min(min(lumaDown, lumaUp), min(lumaLeft, lumaRight))); - float lumaMax = max(lumaCenter, max(max(lumaDown, lumaUp), max(lumaLeft, lumaRight))); + let lumaDown = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(0.0, -properties.inverseScreenSize.y), 0.0).rgb); + let lumaUp = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(0.0, properties.inverseScreenSize.y), 0.0).rgb); + let lumaLeft = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(-properties.inverseScreenSize.x, 0.0), 0.0).rgb); + let lumaRight = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(properties.inverseScreenSize.x, 0.0), 0.0).rgb); - // Compute the delta. - float lumaRange = lumaMax - lumaMin; + let lumaMin = min(lumaCenter, min(min(lumaDown, lumaUp), min(lumaLeft, lumaRight))); + let lumaMax = max(lumaCenter, max(max(lumaDown, lumaUp), max(lumaLeft, lumaRight))); + let lumaRange = lumaMax - lumaMin; - // If the luma variation is lower that a threshold (or if we are in a really dark area), we are not on an edge, don't perform any AA. if (lumaRange < max(EDGE_THRESHOLD_MIN, lumaMax * EDGE_THRESHOLD_MAX)) { - fragColor = colorCenter; - return; + return colorCenter; } - // Query the 4 remaining corners lumas. - float lumaDownLeft = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(-1, -1)).rgb); - float lumaUpRight = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(1, 1)).rgb); - float lumaUpLeft = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(-1, 1)).rgb); - float lumaDownRight = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(1, -1)).rgb); - - // Combine the four edges lumas (using intermediary variables for future computations with the same values). - float lumaDownUp = lumaDown + lumaUp; - float lumaLeftRight = lumaLeft + lumaRight; - - // Same for corners - float lumaLeftCorners = lumaDownLeft + lumaUpLeft; - float lumaDownCorners = lumaDownLeft + lumaDownRight; - float lumaRightCorners = lumaDownRight + lumaUpRight; - float lumaUpCorners = lumaUpRight + lumaUpLeft; - - // Compute an estimation of the gradient along the horizontal and vertical axis. - float edgeHorizontal = abs(-2.0 * lumaLeft + lumaLeftCorners) + abs(-2.0 * lumaCenter + lumaDownUp) * 2.0 + abs(-2.0 * lumaRight + lumaRightCorners); - float edgeVertical = abs(-2.0 * lumaUp + lumaUpCorners) + abs(-2.0 * lumaCenter + lumaLeftRight) * 2.0 + abs(-2.0 * lumaDown + lumaDownCorners); + let lumaDownLeft = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(-properties.inverseScreenSize.x, -properties.inverseScreenSize.y), 0.0).rgb); + let lumaUpRight = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(properties.inverseScreenSize.x, properties.inverseScreenSize.y), 0.0).rgb); + let lumaUpLeft = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(-properties.inverseScreenSize.x, properties.inverseScreenSize.y), 0.0).rgb); + let lumaDownRight = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, texCoord + vec2f(properties.inverseScreenSize.x, -properties.inverseScreenSize.y), 0.0).rgb); - // Is the local edge horizontal or vertical ? - bool isHorizontal = (edgeHorizontal >= edgeVertical); + let lumaDownUp = lumaDown + lumaUp; + let lumaLeftRight = lumaLeft + lumaRight; + let lumaLeftCorners = lumaDownLeft + lumaUpLeft; + let lumaDownCorners = lumaDownLeft + lumaDownRight; + let lumaRightCorners = lumaDownRight + lumaUpRight; + let lumaUpCorners = lumaUpRight + lumaUpLeft; - // Choose the step size (one pixel) accordingly. - float stepLength = isHorizontal ? properties.inverseScreenSize.y : properties.inverseScreenSize.x; + let edgeHorizontal = abs(-2.0 * lumaLeft + lumaLeftCorners) + abs(-2.0 * lumaCenter + lumaDownUp) * 2.0 + abs(-2.0 * lumaRight + lumaRightCorners); + let edgeVertical = abs(-2.0 * lumaUp + lumaUpCorners) + abs(-2.0 * lumaCenter + lumaLeftRight) * 2.0 + abs(-2.0 * lumaDown + lumaDownCorners); - // Select the two neighboring texels lumas in the opposite direction to the local edge. - float luma1 = isHorizontal ? lumaDown : lumaLeft; - float luma2 = isHorizontal ? lumaUp : lumaRight; - // Compute gradients in this direction. - float gradient1 = luma1 - lumaCenter; - float gradient2 = luma2 - lumaCenter; + let isHorizontal = (edgeHorizontal >= edgeVertical); + var stepLength = select(properties.inverseScreenSize.x, properties.inverseScreenSize.y, isHorizontal); - // Which direction is the steepest ? - bool is1Steepest = abs(gradient1) >= abs(gradient2); + let luma1 = select(lumaLeft, lumaDown, isHorizontal); + let luma2 = select(lumaRight, lumaUp, isHorizontal); + let gradient1 = luma1 - lumaCenter; + let gradient2 = luma2 - lumaCenter; + let is1Steepest = abs(gradient1) >= abs(gradient2); + let gradientScaled = 0.25 * max(abs(gradient1), abs(gradient2)); - // Gradient in the corresponding direction, normalized. - float gradientScaled = 0.25 * max(abs(gradient1), abs(gradient2)); - - // Average luma in the correct direction. - float lumaLocalAverage = 0.0; + var lumaLocalAverage: f32; if (is1Steepest) { - // Switch the direction stepLength = -stepLength; lumaLocalAverage = 0.5 * (luma1 + lumaCenter); } else { lumaLocalAverage = 0.5 * (luma2 + lumaCenter); } - // Shift UV in the correct direction by half a pixel. - vec2 currentUv = texCoord; + var currentUv = texCoord; if (isHorizontal) { currentUv.y += stepLength * 0.5; } else { currentUv.x += stepLength * 0.5; } - // Compute offset (for each iteration step) in the right direction. - vec2 offset = isHorizontal ? vec2(properties.inverseScreenSize.x, 0.0) : vec2(0.0, properties.inverseScreenSize.y); - // Compute UVs to explore on each side of the edge, orthogonally. The QUALITY allows us to step faster. - vec2 uv1 = currentUv - offset * QUALITY(0); - vec2 uv2 = currentUv + offset * QUALITY(0); + let offset = select(vec2f(0.0, properties.inverseScreenSize.y), vec2f(properties.inverseScreenSize.x, 0.0), isHorizontal); + var uv1 = currentUv - offset * quality(0); + var uv2 = currentUv + offset * quality(0); - // Read the lumas at both current extremities of the exploration segment, and compute the delta wrt to the local average luma. - float lumaEnd1 = rgb2luma(textureLod(screenTexture, uv1, 0.0).rgb); - float lumaEnd2 = rgb2luma(textureLod(screenTexture, uv2, 0.0).rgb); - lumaEnd1 -= lumaLocalAverage; - lumaEnd2 -= lumaLocalAverage; + var lumaEnd1 = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, uv1, 0.0).rgb) - lumaLocalAverage; + var lumaEnd2 = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, uv2, 0.0).rgb) - lumaLocalAverage; - // If the luma deltas at the current extremities is larger than the local gradient, we have reached the side of the edge. - bool reached1 = abs(lumaEnd1) >= gradientScaled; - bool reached2 = abs(lumaEnd2) >= gradientScaled; - bool reachedBoth = reached1 && reached2; + var reached1 = abs(lumaEnd1) >= gradientScaled; + var reached2 = abs(lumaEnd2) >= gradientScaled; + var reachedBoth = reached1 && reached2; - // If the side is not reached, we continue to explore in this direction. - if (!reached1) { - uv1 -= offset * QUALITY(1); - } - if (!reached2) { - uv2 += offset * QUALITY(1); - } + if (!reached1) { uv1 -= offset * quality(1); } + if (!reached2) { uv2 += offset * quality(1); } - // If both sides have not been reached, continue to explore. - if (!reachedBoth) - { - for (int i = 2; i < ITERATIONS; i++) - { - // If needed, read luma in 1st direction, compute delta. + if (!reachedBoth) { + for (var i: i32 = 2; i < ITERATIONS; i++) { if (!reached1) { - lumaEnd1 = rgb2luma(textureLod(screenTexture, uv1, 0.0).rgb); - lumaEnd1 = lumaEnd1 - lumaLocalAverage; + lumaEnd1 = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, uv1, 0.0).rgb) - lumaLocalAverage; } - // If needed, read luma in opposite direction, compute delta. if (!reached2) { - lumaEnd2 = rgb2luma(textureLod(screenTexture, uv2, 0.0).rgb); - lumaEnd2 = lumaEnd2 - lumaLocalAverage; + lumaEnd2 = rgb2luma(textureSampleLevel(screenTexture_tex, screenTexture_samp, uv2, 0.0).rgb) - lumaLocalAverage; } - // If the luma deltas at the current extremities is larger than the local gradient, we have reached the side of the edge. reached1 = abs(lumaEnd1) >= gradientScaled; reached2 = abs(lumaEnd2) >= gradientScaled; reachedBoth = reached1 && reached2; - // If the side is not reached, we continue to explore in this direction, with a variable quality. - if (!reached1) { - uv1 -= offset * QUALITY(i); - } - if (!reached2) { - uv2 += offset * QUALITY(i); - } - - // If both sides have been reached, stop the exploration. + if (!reached1) { uv1 -= offset * quality(i); } + if (!reached2) { uv2 += offset * quality(i); } if (reachedBoth) { break; } } } - // Compute the distances to each side edge of the edge (!). - float distance1 = isHorizontal ? (texCoord.x - uv1.x) : (texCoord.y - uv1.y); - float distance2 = isHorizontal ? (uv2.x - texCoord.x) : (uv2.y - texCoord.y); - - // In which direction is the side of the edge closer ? - bool isDirection1 = distance1 < distance2; - float distanceFinal = min(distance1, distance2); - - // Thickness of the edge. - float edgeThickness = (distance1 + distance2); - - // Is the luma at center smaller than the local average ? - bool isLumaCenterSmaller = lumaCenter < lumaLocalAverage; - - // If the luma at center is smaller than at its neighbour, the delta luma at each end should be positive (same variation). - bool correctVariation1 = (lumaEnd1 < 0.0) != isLumaCenterSmaller; - bool correctVariation2 = (lumaEnd2 < 0.0) != isLumaCenterSmaller; - - // Only keep the result in the direction of the closer side of the edge. - bool correctVariation = isDirection1 ? correctVariation1 : correctVariation2; - - // UV offset: read in the direction of the closest side of the edge. - float pixelOffset = -distanceFinal / edgeThickness + 0.5; - - // If the luma variation is incorrect, do not offset. - float finalOffset = correctVariation ? pixelOffset : 0.0; - - // Sub-pixel shifting - // Full weighted average of the luma over the 3x3 neighborhood. - float lumaAverage = (1.0 / 12.0) * (2.0 * (lumaDownUp + lumaLeftRight) + lumaLeftCorners + lumaRightCorners); - // Ratio of the delta between the global average and the center luma, over the luma range in the 3x3 neighborhood. - float subPixelOffset1 = clamp(abs(lumaAverage - lumaCenter) / lumaRange, 0.0, 1.0); - float subPixelOffset2 = (-2.0 * subPixelOffset1 + 3.0) * subPixelOffset1 * subPixelOffset1; - // Compute a sub-pixel offset based on this delta. - float subPixelOffsetFinal = subPixelOffset2 * subPixelOffset2 * SUBPIXEL_QUALITY; - - // Pick the biggest of the two offsets. + let distance1 = select(texCoord.y - uv1.y, texCoord.x - uv1.x, isHorizontal); + let distance2 = select(uv2.y - texCoord.y, uv2.x - texCoord.x, isHorizontal); + let isDirection1 = distance1 < distance2; + let distanceFinal = min(distance1, distance2); + let edgeThickness = (distance1 + distance2); + let isLumaCenterSmaller = lumaCenter < lumaLocalAverage; + let correctVariation1 = (lumaEnd1 < 0.0) != isLumaCenterSmaller; + let correctVariation2 = (lumaEnd2 < 0.0) != isLumaCenterSmaller; + let correctVariation = select(correctVariation2, correctVariation1, isDirection1); + + let pixelOffset = -distanceFinal / edgeThickness + 0.5; + var finalOffset = select(0.0, pixelOffset, correctVariation); + + let lumaAverage = (1.0 / 12.0) * (2.0 * (lumaDownUp + lumaLeftRight) + lumaLeftCorners + lumaRightCorners); + let subPixelOffset1 = clamp(abs(lumaAverage - lumaCenter) / lumaRange, 0.0, 1.0); + let subPixelOffset2 = (-2.0 * subPixelOffset1 + 3.0) * subPixelOffset1 * subPixelOffset1; + let subPixelOffsetFinal = subPixelOffset2 * subPixelOffset2 * SUBPIXEL_QUALITY; finalOffset = max(finalOffset, subPixelOffsetFinal); - // Compute the final UV coordinates. - vec2 finalUv = texCoord; + var finalUv = texCoord; if (isHorizontal) { finalUv.y += finalOffset * stepLength; } else { finalUv.x += finalOffset * stepLength; } - // Read the color at the new UV coordinates, and use it. - vec3 finalColor = textureLod(screenTexture, finalUv, 0.0).rgb; - fragColor = vec4(finalColor, 1.0); + let finalColor = textureSampleLevel(screenTexture_tex, screenTexture_samp, finalUv, 0.0).rgb; + return vec4f(finalColor, 1.0); } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/renderer/shaders/gaussian_blur.shader b/fyrox-impl/src/renderer/shaders/gaussian_blur.shader index 94abb25a53..ffbdd68bd6 100644 --- a/fyrox-impl/src/renderer/shaders/gaussian_blur.shader +++ b/fyrox-impl/src/renderer/shaders/gaussian_blur.shader @@ -43,51 +43,52 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + }; - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.texCoord = input.vertexTexCoord; + output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - in vec2 texCoord; + @fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f { + const weights = array(0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); - out vec4 outColor; + let center = textureSample(image_tex, image_samp, texCoord); - void main() - { - const float weights[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); + var result = center.rgb * weights[0]; - vec4 center = texture(image, texCoord); + if (properties.horizontal != 0u) { + for (var i: i32 = 1; i < 5; i++) { + let fi = f32(i); - vec3 result = center.rgb * weights[0]; - - if (properties.horizontal) { - for (int i = 1; i < 5; ++i) { - float fi = float(i); - - result += texture(image, texCoord + vec2(properties.pixelSize.x * fi, 0.0)).rgb * weights[i]; - result += texture(image, texCoord - vec2(properties.pixelSize.x * fi, 0.0)).rgb * weights[i]; + result += textureSample(image_tex, image_samp, texCoord + vec2f(properties.pixelSize.x * fi, 0.0)).rgb * weights[i]; + result += textureSample(image_tex, image_samp, texCoord - vec2f(properties.pixelSize.x * fi, 0.0)).rgb * weights[i]; } } else { - for (int i = 1; i < 5; ++i) { - float fi = float(i); + for (var i: i32 = 1; i < 5; i++) { + let fi = f32(i); - result += texture(image, texCoord + vec2(0.0, properties.pixelSize.y * fi)).rgb * weights[i]; - result += texture(image, texCoord - vec2(0.0, properties.pixelSize.y * fi)).rgb * weights[i]; + result += textureSample(image_tex, image_samp, texCoord + vec2f(0.0, properties.pixelSize.y * fi)).rgb * weights[i]; + result += textureSample(image_tex, image_samp, texCoord - vec2f(0.0, properties.pixelSize.y * fi)).rgb * weights[i]; } } - outColor = vec4(result, center.a); + return vec4f(result, center.a); } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/renderer/shaders/hdr_adaptation.shader b/fyrox-impl/src/renderer/shaders/hdr_adaptation.shader index 3962732fe0..12d4a261dc 100644 --- a/fyrox-impl/src/renderer/shaders/hdr_adaptation.shader +++ b/fyrox-impl/src/renderer/shaders/hdr_adaptation.shader @@ -47,28 +47,32 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + }; - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.texCoord = input.vertexTexCoord; + output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - out float outLum; - - void main() { - float oldLum = texture(oldLumSampler, vec2(0.5, 0.5)).r; - float newLum = texture(newLumSampler, vec2(0.5, 0.5)).r; - outLum = oldLum + (newLum - oldLum) * properties.speed; + @fragment fn fs_main() -> @location(0) f32 { + let oldLum = textureSample(oldLumSampler_tex, oldLumSampler_samp, vec2f(0.5, 0.5)).r; + let newLum = textureSample(newLumSampler_tex, newLumSampler_samp, vec2f(0.5, 0.5)).r; + return oldLum + (newLum - oldLum) * properties.speed; } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/renderer/shaders/hdr_downscale.shader b/fyrox-impl/src/renderer/shaders/hdr_downscale.shader index 67b89ae4de..e9de91647b 100644 --- a/fyrox-impl/src/renderer/shaders/hdr_downscale.shader +++ b/fyrox-impl/src/renderer/shaders/hdr_downscale.shader @@ -42,53 +42,56 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + }; - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.texCoord = input.vertexTexCoord; + output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - in vec2 texCoord; + @fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) f32 { + let x = properties.invSize.x; + let y = properties.invSize.y; + let twoX = 2.0 * x; + let twoY = 2.0 * y; - out float outLum; + let a = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x - twoX, texCoord.y + twoY)).r; + let b = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x, texCoord.y + twoY)).r; + let c = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x + twoX, texCoord.y + twoY)).r; - void main() { - float x = properties.invSize.x; - float y = properties.invSize.y; - float twoX = 2.0 * x; - float twoY = 2.0 * y; + let d = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x - twoX, texCoord.y)).r; + let e = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x, texCoord.y)).r; + let f = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x + twoX, texCoord.y)).r; - float a = texture(lumSampler, vec2(texCoord.x - twoX, texCoord.y + twoY)).r; - float b = texture(lumSampler, vec2(texCoord.x, texCoord.y + twoY)).r; - float c = texture(lumSampler, vec2(texCoord.x + twoX, texCoord.y + twoY)).r; + let g = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x - twoX, texCoord.y - twoY)).r; + let h = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x, texCoord.y - twoY)).r; + let i = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x + twoX, texCoord.y - twoY)).r; - float d = texture(lumSampler, vec2(texCoord.x - twoX, texCoord.y)).r; - float e = texture(lumSampler, vec2(texCoord.x, texCoord.y)).r; - float f = texture(lumSampler, vec2(texCoord.x + twoX, texCoord.y)).r; + let j = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x - x, texCoord.y + y)).r; + let k = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x + x, texCoord.y + y)).r; + let l = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x - x, texCoord.y - y)).r; + let m = textureSample(lumSampler_tex, lumSampler_samp, vec2f(texCoord.x + x, texCoord.y - y)).r; - float g = texture(lumSampler, vec2(texCoord.x - twoX, texCoord.y - twoY)).r; - float h = texture(lumSampler, vec2(texCoord.x, texCoord.y - twoY)).r; - float i = texture(lumSampler, vec2(texCoord.x + twoX, texCoord.y - twoY)).r; - - float j = texture(lumSampler, vec2(texCoord.x - x, texCoord.y + y)).r; - float k = texture(lumSampler, vec2(texCoord.x + x, texCoord.y + y)).r; - float l = texture(lumSampler, vec2(texCoord.x - x, texCoord.y - y)).r; - float m = texture(lumSampler, vec2(texCoord.x + x, texCoord.y - y)).r; - - outLum = e * 0.125; + var outLum = e * 0.125; outLum += (a + c + g + i) * 0.03125; outLum += (b + d + f + h) * 0.0625; outLum += (j + k + l + m) * 0.125; + return outLum; } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/renderer/shaders/hdr_luminance.shader b/fyrox-impl/src/renderer/shaders/hdr_luminance.shader index 1dc7d4aae3..1d193053a3 100644 --- a/fyrox-impl/src/renderer/shaders/hdr_luminance.shader +++ b/fyrox-impl/src/renderer/shaders/hdr_luminance.shader @@ -42,34 +42,36 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + }; - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.texCoord = input.vertexTexCoord; + output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - in vec2 texCoord; - - out float outLum; - - void main() { - float totalLum = 0.0; - for (float y = -0.5; y < 0.5; y += 0.5) { - for (float x = -0.5; x < 0.5; x += 0.5) { - totalLum += S_Luminance(texture(frameSampler, texCoord - vec2(x, y) * properties.invSize).xyz); + @fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) f32 { + var totalLum: f32 = 0.0; + for (var y: f32 = -0.5; y < 0.5; y += 0.5) { + for (var x: f32 = -0.5; x < 0.5; x += 0.5) { + totalLum += S_Luminance(textureSample(frameSampler_tex, frameSampler_samp, texCoord - vec2f(x, y) * properties.invSize).xyz); } } - outLum = totalLum / 9.0; + return totalLum / 9.0; } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/renderer/shaders/hdr_map.shader b/fyrox-impl/src/renderer/shaders/hdr_map.shader index deabbdda6e..b3854f6905 100644 --- a/fyrox-impl/src/renderer/shaders/hdr_map.shader +++ b/fyrox-impl/src/renderer/shaders/hdr_map.shader @@ -61,52 +61,54 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + }; - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.texCoord = input.vertexTexCoord; + output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - in vec2 texCoord; - - out vec4 outLdrColor; - - vec3 ColorGrading(vec3 color) { - const float lutSize = 16.0; - const float a = (lutSize - 1.0) / lutSize; - const float b = 1.0 / (2.0 * lutSize); - vec3 scale = vec3(a); - vec3 offset = vec3(b); - return texture(colorMapSampler, scale * color + offset).rgb; + fn ColorGrading(color: vec3f) -> vec3f { + const lutSize: f32 = 16.0; + const a: f32 = (lutSize - 1.0) / lutSize; + const b: f32 = 1.0 / (2.0 * lutSize); + let scale = vec3f(a); + let offset = vec3f(b); + return textureSample(colorMapSampler_tex, colorMapSampler_samp, scale * color + offset).rgb; } // Narkowicz 2015, "ACES Filmic Tone Mapping Curve" - float TonemapACES(float x) { - const float a = 2.51; - const float b = 0.03; - const float c = 2.43; - const float d = 0.59; - const float e = 0.14; + fn TonemapACES(x: f32) -> f32 { + const a: f32 = 2.51; + const b: f32 = 0.03; + const c: f32 = 2.43; + const d: f32 = 0.59; + const e: f32 = 0.14; return (x * (a * x + b)) / (x * (c * x + d) + e); } - void main() { - vec4 hdrColor = texture(hdrSampler, texCoord) + texture(bloomSampler, texCoord); + @fragment fn fs_main(@location(0) texCoord: vec2f) -> @location(0) vec4f { + let hdrColor = textureSample(hdrSampler_tex, hdrSampler_samp, texCoord) + textureSample(bloomSampler_tex, bloomSampler_samp, texCoord); - vec3 Yxy = S_ConvertRgbToYxy(hdrColor.rgb); + var Yxy = S_ConvertRgbToYxy(hdrColor.rgb); - float lp; - if (properties.autoExposure) { - float avgLum = texture(lumSampler, vec2(0.5, 0.5)).r; - float clampedAvgLum = clamp(avgLum, properties.minLuminance, properties.maxLuminance); + var lp: f32; + if (properties.autoExposure != 0u) { + let avgLum = textureSample(lumSampler_tex, lumSampler_samp, vec2f(0.5, 0.5)).r; + let clampedAvgLum = clamp(avgLum, properties.minLuminance, properties.maxLuminance); lp = Yxy.x / (9.6 * clampedAvgLum + 0.0001); } else { lp = Yxy.x * properties.fixedExposure; @@ -114,15 +116,15 @@ Yxy.x = TonemapACES(lp); - vec4 ldrColor = vec4(S_ConvertYxyToRgb(Yxy), hdrColor.a); + let ldrColor = vec4f(S_ConvertYxyToRgb(Yxy), hdrColor.a); - if (properties.useColorGrading) { - outLdrColor = vec4(ColorGrading(S_LinearToSRGB(ldrColor).rgb), ldrColor.a); + if (properties.useColorGrading != 0u) { + return vec4f(ColorGrading(S_LinearToSRGB(ldrColor).rgb), ldrColor.a); } else { - outLdrColor = S_LinearToSRGB(ldrColor); + return S_LinearToSRGB(ldrColor); } } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/renderer/shaders/irradiance.shader b/fyrox-impl/src/renderer/shaders/irradiance.shader index 2f5d965141..35251b729c 100644 --- a/fyrox-impl/src/renderer/shaders/irradiance.shader +++ b/fyrox-impl/src/renderer/shaders/irradiance.shader @@ -41,56 +41,56 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; + struct VertexInput { + @location(0) vertexPosition: vec3f, + }; - out vec3 localPos; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) localPos: vec3f, + }; - void main() - { - localPos = vertexPosition; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.localPos = input.vertexPosition; + output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - in vec3 localPos; + @fragment fn fs_main(@location(0) localPos: vec3f) -> @location(0) vec4f { + let N = normalize(localPos); - void main() - { - vec3 N = normalize(localPos); + var irradiance = vec3f(0.0); - vec3 irradiance = vec3(0.0); + var up = vec3f(0.0, 1.0, 0.0); + let right = normalize(cross(up, N)); + up = normalize(cross(N, right)); - vec3 up = vec3(0.0, 1.0, 0.0); - vec3 right = normalize(cross(up, N)); - up = normalize(cross(N, right)); + let sampleDelta: f32 = 0.1; + var nrSamples: f32 = 0.0; + for (var phi: f32 = 0.0; phi < 2.0 * PI; phi += sampleDelta) { + let cosPhi = cos(phi); + let sinPhi = sin(phi); - float sampleDelta = 0.1; - float nrSamples = 0.0; - for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta) - { - float cosPhi = cos(phi); - float sinPhi = sin(phi); + for (var theta: f32 = 0.0; theta < 0.5 * PI; theta += sampleDelta) { + let cosTheta = cos(theta); + let sinTheta = sin(theta); - for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta) - { - float cosTheta = cos(theta); - float sinTheta = sin(theta); + let tangentSample = vec3f(sinTheta * cosPhi, sinTheta * sinPhi, cosTheta); + let sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; - vec3 tangentSample = vec3(sinTheta * cosPhi, sinTheta * sinPhi, cosTheta); - vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; - - irradiance += texture(environmentMap, sampleVec).rgb * cosTheta * sinTheta; - nrSamples++; + irradiance += textureSample(environmentMap_tex, environmentMap_samp, sampleVec).rgb * cosTheta * sinTheta; + nrSamples += 1.0; + } } - } - irradiance = PI * irradiance * (1.0 / float(nrSamples)); + irradiance = PI * irradiance * (1.0 / nrSamples); - FragColor = vec4(irradiance, 1.0); - } + return vec4f(irradiance, 1.0); + } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/renderer/shaders/pixel_counter.shader b/fyrox-impl/src/renderer/shaders/pixel_counter.shader index 84f32fb9f9..4dcc78aa92 100644 --- a/fyrox-impl/src/renderer/shaders/pixel_counter.shader +++ b/fyrox-impl/src/renderer/shaders/pixel_counter.shader @@ -40,21 +40,25 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; + struct VertexInput { + @location(0) vertex_position: vec3f, + } + + struct VertexOutput { + @builtin(position) position: vec4f, + } - void main() - { - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - - void main() - { - FragColor = vec4(1.0); + @fragment fn fs_main() -> @location(0) vec4f { + return vec4f(1.0); } "#, ) diff --git a/fyrox-impl/src/renderer/shaders/point_volumetric.shader b/fyrox-impl/src/renderer/shaders/point_volumetric.shader index 60fe6ae8d1..30375ce7f1 100644 --- a/fyrox-impl/src/renderer/shaders/point_volumetric.shader +++ b/fyrox-impl/src/renderer/shaders/point_volumetric.shader @@ -3,7 +3,7 @@ resources: [ ( name: "depthSampler", - kind: Texture(kind: Sampler2D, fallback: White), + kind: Texture(kind: DepthSampler2D, fallback: White), binding: 0 ), ( @@ -62,34 +62,36 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertex_position: vec3f, + @location(1) vertex_tex_coord: vec2f, + } - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) tex_coord: vec2f, + } - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.tex_coord = input.vertex_tex_coord; + output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - - in vec2 texCoord; - - void main() - { - vec3 fragmentPosition = S_UnProject(vec3(texCoord, texture(depthSampler, texCoord).r), properties.invProj); - float fragmentDepth = length(fragmentPosition); - vec3 viewDirection = fragmentPosition / fragmentDepth; + @fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) vec4f { + let fragmentPosition = S_UnProject(vec3f(tex_coord, textureSample(depthSampler_tex, depthSampler_samp, tex_coord)), properties.invProj); + let fragmentDepth = length(fragmentPosition); + let viewDirection = fragmentPosition / fragmentDepth; // Find intersection - vec3 scatter = vec3(0.0); - float minDepth, maxDepth; - if (S_RaySphereIntersection(vec3(0.0), viewDirection, properties.lightPosition, properties.lightRadius, minDepth, maxDepth)) + var scatter = vec3f(0.0); + var minDepth: f32; + var maxDepth: f32; + if (S_RaySphereIntersection(vec3f(0.0), viewDirection, properties.lightPosition, properties.lightRadius, &minDepth, &maxDepth)) { // Perform depth test. if (minDepth > 0.0 || fragmentDepth > minDepth) @@ -97,13 +99,13 @@ minDepth = max(minDepth, 0.0); maxDepth = clamp(maxDepth, 0.0, fragmentDepth); - vec3 closestPoint = viewDirection * minDepth; + let closestPoint = viewDirection * minDepth; scatter = properties.scatterFactor * S_InScatter(closestPoint, viewDirection, properties.lightPosition, maxDepth - minDepth); } } - FragColor = vec4(properties.lightColor.xyz * pow(clamp(properties.intensity * scatter, 0.0, 1.0), vec3(2.2)), 1.0); + return vec4f(properties.lightColor.xyz * pow(clamp(properties.intensity * scatter, vec3f(0.0), vec3f(1.0)), vec3f(2.2)), 1.0); } "#, ) diff --git a/fyrox-impl/src/renderer/shaders/prefilter.shader b/fyrox-impl/src/renderer/shaders/prefilter.shader index 5645bbd776..c04f6b6e45 100644 --- a/fyrox-impl/src/renderer/shaders/prefilter.shader +++ b/fyrox-impl/src/renderer/shaders/prefilter.shader @@ -42,85 +42,83 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; + struct VertexInput { + @location(0) vertex_position: vec3f, + } - out vec3 localPos; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) local_pos: vec3f, + } - void main() - { - localPos = vertexPosition; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.local_pos = input.vertex_position; + output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - - in vec3 localPos; - - float RadicalInverse_VdC(uint bits) - { + fn RadicalInverse_VdC(bits_in: u32) -> f32 { + var bits = bits_in; bits = (bits << 16u) | (bits >> 16u); bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return float(bits) * 2.3283064e-10; // / 0x100000000 + return f32(bits) * 2.3283064e-10; } - vec2 Hammersley(uint i, uint N) - { - return vec2(float(i)/float(N), RadicalInverse_VdC(i)); + fn Hammersley(i: u32, N: u32) -> vec2f { + return vec2f(f32(i) / f32(N), RadicalInverse_VdC(i)); } - vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) - { - float a = roughness*roughness; + fn ImportanceSampleGGX(Xi: vec2f, N: vec3f, roughness: f32) -> vec3f { + let a = roughness * roughness; - float phi = 2.0 * PI * Xi.x; - float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); - float sinTheta = sqrt(1.0 - cosTheta*cosTheta); + let phi = 2.0 * PI * Xi.x; + let cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a * a - 1.0) * Xi.y)); + let sinTheta = sqrt(1.0 - cosTheta * cosTheta); - vec3 H; + var H: vec3f; H.x = cos(phi) * sinTheta; H.y = sin(phi) * sinTheta; H.z = cosTheta; - vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); - vec3 tangent = normalize(cross(up, N)); - vec3 bitangent = cross(N, tangent); + let up = select(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 0.0, 1.0), abs(N.z) < 0.999); + let tangent = normalize(cross(up, N)); + let bitangent = cross(N, tangent); - vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; + let sampleVec = tangent * H.x + bitangent * H.y + N * H.z; return normalize(sampleVec); } + @fragment fn fs_main(@location(0) local_pos: vec3f) -> @location(0) vec4f { + let N = normalize(local_pos); + let R = N; + let V = R; - void main() - { - vec3 N = normalize(localPos); - vec3 R = N; - vec3 V = R; - - const uint SAMPLE_COUNT = 64u; - float totalWeight = 0.0; - vec3 prefilteredColor = vec3(0.0); - for(uint i = 0u; i < SAMPLE_COUNT; ++i) + const SAMPLE_COUNT: u32 = 64u; + var totalWeight: f32 = 0.0; + var prefilteredColor = vec3f(0.0); + for (var i: u32 = 0u; i < SAMPLE_COUNT; i++) { - vec2 Xi = Hammersley(i, SAMPLE_COUNT); - vec3 H = ImportanceSampleGGX(Xi, N, properties.roughness); - vec3 L = normalize(2.0 * dot(V, H) * H - V); + let Xi = Hammersley(i, SAMPLE_COUNT); + let H = ImportanceSampleGGX(Xi, N, properties.roughness); + let L = normalize(2.0 * dot(V, H) * H - V); - float NdotL = max(dot(N, L), 0.0); - if(NdotL > 0.0) + let NdotL = max(dot(N, L), 0.0); + if (NdotL > 0.0) { - prefilteredColor += texture(environmentMap, L).rgb * NdotL; + prefilteredColor += textureSample(environmentMap_tex, environmentMap_samp, L).rgb * NdotL; totalWeight += NdotL; } } prefilteredColor = prefilteredColor / totalWeight; - FragColor = vec4(prefilteredColor, 1.0); + return vec4f(prefilteredColor, 1.0); } "#, ) diff --git a/fyrox-impl/src/renderer/shaders/skybox.shader b/fyrox-impl/src/renderer/shaders/skybox.shader index 1ba68ead61..b361c3bb2d 100644 --- a/fyrox-impl/src/renderer/shaders/skybox.shader +++ b/fyrox-impl/src/renderer/shaders/skybox.shader @@ -41,26 +41,27 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; + struct VertexInput { + @location(0) vertex_position: vec3f, + } - out vec3 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) tex_coord: vec3f, + } - void main() - { - texCoord = vertexPosition; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.tex_coord = input.vertex_position; + output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - - in vec3 texCoord; - - void main() - { - FragColor = S_SRGBToLinear(texture(cubemapTexture, texCoord)); + @fragment fn fs_main(@location(0) tex_coord: vec3f) -> @location(0) vec4f { + return S_SRGBToLinear(textureSample(cubemapTexture_tex, cubemapTexture_samp, tex_coord)); } "#, ) diff --git a/fyrox-impl/src/renderer/shaders/spot_volumetric.shader b/fyrox-impl/src/renderer/shaders/spot_volumetric.shader index 7e47b6ed11..7599edda28 100644 --- a/fyrox-impl/src/renderer/shaders/spot_volumetric.shader +++ b/fyrox-impl/src/renderer/shaders/spot_volumetric.shader @@ -3,7 +3,7 @@ resources: [ ( name: "depthSampler", - kind: Texture(kind: Sampler2D, fallback: White), + kind: Texture(kind: DepthSampler2D, fallback: White), binding: 0 ), ( @@ -63,46 +63,48 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertex_position: vec3f, + @location(1) vertex_tex_coord: vec2f, + } - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) tex_coord: vec2f, + } - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.tex_coord = input.vertex_tex_coord; + output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - - in vec2 texCoord; - - void main() - { - vec3 fragmentPosition = S_UnProject(vec3(texCoord, texture(depthSampler, texCoord).r), properties.invProj); - float fragmentDepth = length(fragmentPosition); - vec3 viewDirection = fragmentPosition / fragmentDepth; + @fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) vec4f { + let fragmentPosition = S_UnProject(vec3f(tex_coord, textureSample(depthSampler_tex, depthSampler_samp, tex_coord)), properties.invProj); + let fragmentDepth = length(fragmentPosition); + let viewDirection = fragmentPosition / fragmentDepth; // Ray-cone intersection - float sqrConeAngleCos = properties.coneAngleCos * properties.coneAngleCos; - vec3 CO = -properties.lightPosition; - float DdotV = dot(viewDirection, properties.lightDirection); - float COdotV = dot(CO, properties.lightDirection); - float a = DdotV * DdotV - sqrConeAngleCos; - float b = 2.0 * (DdotV * COdotV - dot(viewDirection, CO) * sqrConeAngleCos); - float c = COdotV * COdotV - dot(CO, CO) * sqrConeAngleCos; + let sqrConeAngleCos = properties.coneAngleCos * properties.coneAngleCos; + let CO = -properties.lightPosition; + let DdotV = dot(viewDirection, properties.lightDirection); + let COdotV = dot(CO, properties.lightDirection); + let a = DdotV * DdotV - sqrConeAngleCos; + let b = 2.0 * (DdotV * COdotV - dot(viewDirection, CO) * sqrConeAngleCos); + let c = COdotV * COdotV - dot(CO, CO) * sqrConeAngleCos; // Find intersection - vec3 scatter = vec3(0.0); - float minDepth, maxDepth; - if (S_SolveQuadraticEq(a, b, c, minDepth, maxDepth)) + var scatter = vec3f(0.0); + var minDepth: f32; + var maxDepth: f32; + if (S_SolveQuadraticEq(a, b, c, &minDepth, &maxDepth)) { - float dt1 = dot((minDepth * viewDirection) - properties.lightPosition, properties.lightDirection); - float dt2 = dot((maxDepth * viewDirection) - properties.lightPosition, properties.lightDirection); + let dt1 = dot((minDepth * viewDirection) - properties.lightPosition, properties.lightDirection); + let dt2 = dot((maxDepth * viewDirection) - properties.lightPosition, properties.lightDirection); // Discard points on reflected cylinder and perform depth test. if ((dt1 > 0.0 || dt2 > 0.0) && (minDepth > 0.0 || fragmentDepth > minDepth)) @@ -127,7 +129,7 @@ } } - FragColor = vec4(properties.lightColor.xyz * pow(clamp(properties.intensity * scatter, 0.0, 1.0), vec3(2.2)), 1.0); + return vec4f(properties.lightColor.xyz * pow(clamp(properties.intensity * scatter, vec3f(0.0), vec3f(1.0)), vec3f(2.2)), 1.0); } "#, ) diff --git a/fyrox-impl/src/renderer/shaders/ssao.shader b/fyrox-impl/src/renderer/shaders/ssao.shader index d57f315c99..25c17e771b 100644 --- a/fyrox-impl/src/renderer/shaders/ssao.shader +++ b/fyrox-impl/src/renderer/shaders/ssao.shader @@ -3,7 +3,7 @@ resources: [ ( name: "depthSampler", - kind: Texture(kind: Sampler2D, fallback: White), + kind: Texture(kind: DepthSampler2D, fallback: White), binding: 0 ), ( @@ -57,54 +57,55 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; + struct VertexInput { + @location(0) vertex_position: vec3f, + @location(1) vertex_tex_coord: vec2f, + } - out vec2 texCoord; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) tex_coord: vec2f, + } - void main() - { - texCoord = vertexTexCoord; - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.tex_coord = input.vertex_tex_coord; + output.position = properties.worldViewProjection * vec4f(input.vertex_position, 1.0); + return output; } "#, fragment_shader: r#" - out float finalOcclusion; - - in vec2 texCoord; - - vec3 GetViewSpacePosition(vec2 screenCoord) { - return S_UnProject(vec3(screenCoord, texture(depthSampler, screenCoord).r), properties.inverseProjectionMatrix); + fn GetViewSpacePosition(screenCoord: vec2f) -> vec3f { + return S_UnProject(vec3f(screenCoord, textureSample(depthSampler_tex, depthSampler_samp, screenCoord)), properties.inverseProjectionMatrix); } - void main() { - vec3 fragPos = GetViewSpacePosition(texCoord); - vec3 worldSpaceNormal = texture(normalSampler, texCoord).xyz * 2.0 - 1.0; - vec3 viewSpaceNormal = normalize(properties.viewMatrix * worldSpaceNormal); - vec3 randomVec = normalize(texture(noiseSampler, texCoord * properties.noiseScale).xyz * 2.0 - 1.0); + @fragment fn fs_main(@location(0) tex_coord: vec2f) -> @location(0) f32 { + let fragPos = GetViewSpacePosition(tex_coord); + let worldSpaceNormal = textureSample(normalSampler_tex, normalSampler_samp, tex_coord).xyz * 2.0 - 1.0; + let viewSpaceNormal = normalize(properties.viewMatrix * worldSpaceNormal); + let randomVec = normalize(textureSample(noiseSampler_tex, noiseSampler_samp, tex_coord * properties.noiseScale).xyz * 2.0 - 1.0); - vec3 tangent = normalize(randomVec - viewSpaceNormal * dot(randomVec, viewSpaceNormal)); - vec3 bitangent = normalize(cross(viewSpaceNormal, tangent)); - mat3 TBN = mat3(tangent, bitangent, viewSpaceNormal); + let tangent = normalize(randomVec - viewSpaceNormal * dot(randomVec, viewSpaceNormal)); + let bitangent = normalize(cross(viewSpaceNormal, tangent)); + let TBN = mat3x3f(tangent, bitangent, viewSpaceNormal); - float occlusion = 0.0; - const int kernelSize = 32; - for (int i = 0; i < kernelSize; ++i) { - vec3 samplePoint = fragPos.xyz + TBN * properties.kernel[i] * properties.radius; + var occlusion: f32 = 0.0; + const kernelSize: i32 = 32; + for (var i: i32 = 0; i < kernelSize; i++) { + let samplePoint = fragPos + TBN * properties.kernel[i] * properties.radius; - vec4 offset = properties.projectionMatrix * vec4(samplePoint, 1.0); - offset.xy /= offset.w; - offset.xy = offset.xy * 0.5 + 0.5; + let offset = properties.projectionMatrix * vec4f(samplePoint, 1.0); + let screenUv = (offset.xy / offset.w) * 0.5 + 0.5; - vec3 position = GetViewSpacePosition(offset.xy); + let position = GetViewSpacePosition(screenUv); - float rangeCheck = smoothstep(0.0, 1.0, properties.radius / abs(fragPos.z - position.z)); - occlusion += rangeCheck * ((position.z > samplePoint.z + 0.04) ? 1.0 : 0.0); + let rangeCheck = smoothstep(0.0, 1.0, properties.radius / abs(fragPos.z - position.z)); + occlusion += rangeCheck * select(0.0, 1.0, position.z > samplePoint.z + 0.04); } - finalOcclusion = 1.0 - occlusion / float(kernelSize); + return 1.0 - occlusion / f32(kernelSize); } "#, ) diff --git a/fyrox-impl/src/renderer/shaders/visibility.shader b/fyrox-impl/src/renderer/shaders/visibility.shader index 3a6aff1cae..de27a08bff 100644 --- a/fyrox-impl/src/renderer/shaders/visibility.shader +++ b/fyrox-impl/src/renderer/shaders/visibility.shader @@ -59,34 +59,37 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; + struct VertexInput { + @location(0) vertexPosition: vec3f, + }; - flat out uint objectIndex; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) objectIndex: u32, + }; - void main() - { - objectIndex = uint(gl_InstanceID); - gl_Position = (properties.viewProjection * S_FetchMatrix(matrices, gl_InstanceID)) * vec4(vertexPosition, 1.0); + @vertex + fn vs_main(input: VertexInput, @builtin(instance_index) instance_index: u32) -> VertexOutput { + var output: VertexOutput; + output.objectIndex = instance_index; + output.position = (properties.viewProjection * S_FetchMatrix(matrices_tex, matrices_samp, i32(instance_index))) * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; + @fragment + fn fs_main(@location(0) objectIndex: u32, @builtin(position) fragCoord: vec4f) -> @location(0) vec4f { + var x = i32(fragCoord.x) / properties.tileSize; + var y = i32(properties.frameBufferHeight - fragCoord.y) / properties.tileSize; - flat in uint objectIndex; - - void main() - { - int x = int(gl_FragCoord.x) / properties.tileSize; - int y = int(properties.frameBufferHeight - gl_FragCoord.y) / properties.tileSize; - - int bitIndex = -1; - int tileDataIndex = x * 33; - int count = int(texelFetch(tileBuffer, ivec2(tileDataIndex, y), 0).x); - int objectsListStartIndex = tileDataIndex + 1; - for (int i = 0; i < count; ++i) { - uint pixelObjectIndex = uint(texelFetch(tileBuffer, ivec2(objectsListStartIndex + i, y), 0).x); + var bitIndex: i32 = -1; + var tileDataIndex = x * 33; + var count = i32(textureLoad(tileBuffer_tex, vec2i(tileDataIndex, y), 0).x); + var objectsListStartIndex = tileDataIndex + 1; + for (var i: i32 = 0; i < count; i++) { + var pixelObjectIndex = u32(textureLoad(tileBuffer_tex, vec2i(objectsListStartIndex + i, y), 0).x); if (pixelObjectIndex == objectIndex) { bitIndex = i; break; @@ -94,17 +97,17 @@ } if (bitIndex < 0) { - FragColor = vec4(0.0, 0.0, 0.0, 0.0); + return vec4f(0.0, 0.0, 0.0, 0.0); } else { - uint outMask = uint(1 << bitIndex); - float r = float(outMask & 255u) / 255.0; - float g = float((outMask & 65280u) >> 8) / 255.0; - float b = float((outMask & 16711680u) >> 16) / 255.0; - float a = float((outMask & 4278190080u) >> 24) / 255.0; - FragColor = vec4(r, g, b, a); + var outMask = 1u << u32(bitIndex); + var r = f32(outMask & 255u) / 255.0; + var g = f32((outMask & 65280u) >> 8) / 255.0; + var b = f32((outMask & 16711680u) >> 16) / 255.0; + var a = f32((outMask & 4278190080u) >> 24) / 255.0; + return vec4f(r, g, b, a); } } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/renderer/shaders/visibility_optimizer.shader b/fyrox-impl/src/renderer/shaders/visibility_optimizer.shader index f75e519cc3..da1b7c4dde 100644 --- a/fyrox-impl/src/renderer/shaders/visibility_optimizer.shader +++ b/fyrox-impl/src/renderer/shaders/visibility_optimizer.shader @@ -42,39 +42,45 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; + struct VertexInput { + @location(0) vertexPosition: vec3f, + }; - void main() - { - gl_Position = properties.viewProjection * vec4(vertexPosition, 1.0); + struct VertexOutput { + @builtin(position) position: vec4f, + }; + + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.position = properties.viewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - out uint optimizedVisibilityMask; - - void main() - { - int tileX = int(gl_FragCoord.x); - int tileY = int(gl_FragCoord.y); + @fragment + fn fs_main(@builtin(position) fragCoord: vec4f) -> @location(0) u32 { + var tileX = i32(fragCoord.x); + var tileY = i32(fragCoord.y); - int beginX = tileX * properties.tileSize; - int beginY = tileY * properties.tileSize; + var beginX = tileX * properties.tileSize; + var beginY = tileY * properties.tileSize; - int endX = (tileX + 1) * properties.tileSize; - int endY = (tileY + 1) * properties.tileSize; + var endX = (tileX + 1) * properties.tileSize; + var endY = (tileY + 1) * properties.tileSize; - int visibilityMask = 0; - for (int y = beginY; y < endY; ++y) { - for (int x = beginX; x < endX; ++x) { - ivec4 mask = ivec4(texelFetch(visibilityBuffer, ivec2(x, y), 0) * 255.0); - visibilityMask |= (mask.a << 24) | (mask.b << 16) | (mask.g << 8) | mask.r; + var visibilityMask: i32 = 0; + for (var y: i32 = beginY; y < endY; y++) { + for (var x: i32 = beginX; x < endX; x++) { + var mask = vec4i(textureLoad(visibilityBuffer_tex, vec2i(x, y), 0) * 255.0); + visibilityMask |= (mask.w << 24) | (mask.z << 16) | (mask.y << 8) | mask.x; } } - optimizedVisibilityMask = uint(visibilityMask); + return u32(visibilityMask); } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/renderer/shaders/volume_marker_lit.shader b/fyrox-impl/src/renderer/shaders/volume_marker_lit.shader index 714a2d45e4..f8fcb8dc2d 100644 --- a/fyrox-impl/src/renderer/shaders/volume_marker_lit.shader +++ b/fyrox-impl/src/renderer/shaders/volume_marker_lit.shader @@ -17,23 +17,29 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; + struct VertexInput { + @location(0) vertexPosition: vec3f, + }; - void main() - { - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + struct VertexOutput { + @builtin(position) position: vec4f, + }; + + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - - void main() - { - FragColor = vec4(1.0); + @fragment + fn fs_main() -> @location(0) vec4f { + return vec4f(1.0); } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/renderer/shaders/volume_marker_vol.shader b/fyrox-impl/src/renderer/shaders/volume_marker_vol.shader index d8ec418888..9095f03210 100644 --- a/fyrox-impl/src/renderer/shaders/volume_marker_vol.shader +++ b/fyrox-impl/src/renderer/shaders/volume_marker_vol.shader @@ -40,23 +40,29 @@ vertex_shader: r#" - layout (location = 0) in vec3 vertexPosition; + struct VertexInput { + @location(0) vertexPosition: vec3f, + }; - void main() - { - gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + struct VertexOutput { + @builtin(position) position: vec4f, + }; + + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.position = properties.worldViewProjection * vec4f(input.vertexPosition, 1.0); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - - void main() - { - FragColor = vec4(1.0); + @fragment + fn fs_main() -> @location(0) vec4f { + return vec4f(1.0); } "#, ) ] -) \ No newline at end of file +) diff --git a/fyrox-impl/src/resource/gltf/gltf_standard.shader b/fyrox-impl/src/resource/gltf/gltf_standard.shader index fd7446bee0..efad87897d 100644 --- a/fyrox-impl/src/resource/gltf/gltf_standard.shader +++ b/fyrox-impl/src/resource/gltf/gltf_standard.shader @@ -142,65 +142,72 @@ ), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 2) in vec3 vertexNormal; - layout(location = 3) in vec4 vertexTangent; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - layout(location = 6) in vec2 vertexSecondTexCoord; - - out vec3 position; - out vec3 normal; - out vec2 texCoord; - out vec3 tangent; - out vec3 binormal; - out vec2 secondTexCoord; - - void main() - { - vec4 localPosition = vec4(0); - vec3 localNormal = vec3(0); - vec3 localTangent = vec3(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - vec3 inputNormal = vertexNormal; - vec3 inputTangent = vertexTangent.xyz; - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(2) vertexNormal: vec3f, + @location(3) vertexTangent: vec4f, + @location(4) boneWeights: vec4f, + @location(5) boneIndices: vec4f, + @location(6) vertexSecondTexCoord: vec2f, + }; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) outPosition: vec3f, + @location(1) normal: vec3f, + @location(2) texCoord: vec2f, + @location(3) tangent: vec3f, + @location(4) binormal: vec3f, + @location(5) secondTexCoord: vec2f, + }; + + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + + var localPosition = vec4f(0.0); + var localNormal = vec3f(0.0); + var localTangent = vec3f(0.0); + + var inputPosition = vec4f(input.vertexPosition, 1.0); + var inputNormal = input.vertexNormal; + var inputTangent = input.vertexTangent.xyz; + + for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) { + var offsets = S_FetchBlendShapeOffsets(blendShapesStorage, vertex_index, i); + var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w); inputNormal += offsets.normal * weight; inputTangent += offsets.tangent * weight; } - if (fyrox_instanceData.useSkeletalAnimation) + if (fyrox_instanceData.useSkeletalAnimation != 0u) { - int i0 = int(boneIndices.x); - int i1 = int(boneIndices.y); - int i2 = int(boneIndices.z); - int i3 = int(boneIndices.w); - - mat4 m0 = fyrox_boneMatrices.matrices[i0]; - mat4 m1 = fyrox_boneMatrices.matrices[i1]; - mat4 m2 = fyrox_boneMatrices.matrices[i2]; - mat4 m3 = fyrox_boneMatrices.matrices[i3]; - - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; - - localNormal += mat3(m0) * inputNormal * boneWeights.x; - localNormal += mat3(m1) * inputNormal * boneWeights.y; - localNormal += mat3(m2) * inputNormal * boneWeights.z; - localNormal += mat3(m3) * inputNormal * boneWeights.w; - - localTangent += mat3(m0) * inputTangent * boneWeights.x; - localTangent += mat3(m1) * inputTangent * boneWeights.y; - localTangent += mat3(m2) * inputTangent * boneWeights.z; - localTangent += mat3(m3) * inputTangent * boneWeights.w; + var i0 = i32(input.boneIndices.x); + var i1 = i32(input.boneIndices.y); + var i2 = i32(input.boneIndices.z); + var i3 = i32(input.boneIndices.w); + + var m0 = fyrox_boneMatrices.matrices[i0]; + var m1 = fyrox_boneMatrices.matrices[i1]; + var m2 = fyrox_boneMatrices.matrices[i2]; + var m3 = fyrox_boneMatrices.matrices[i3]; + + localPosition += m0 * inputPosition * input.boneWeights.x; + localPosition += m1 * inputPosition * input.boneWeights.y; + localPosition += m2 * inputPosition * input.boneWeights.z; + localPosition += m3 * inputPosition * input.boneWeights.w; + + localNormal += mat3x3f(m0[0].xyz, m0[1].xyz, m0[2].xyz) * inputNormal * input.boneWeights.x; + localNormal += mat3x3f(m1[0].xyz, m1[1].xyz, m1[2].xyz) * inputNormal * input.boneWeights.y; + localNormal += mat3x3f(m2[0].xyz, m2[1].xyz, m2[2].xyz) * inputNormal * input.boneWeights.z; + localNormal += mat3x3f(m3[0].xyz, m3[1].xyz, m3[2].xyz) * inputNormal * input.boneWeights.w; + + localTangent += mat3x3f(m0[0].xyz, m0[1].xyz, m0[2].xyz) * inputTangent * input.boneWeights.x; + localTangent += mat3x3f(m1[0].xyz, m1[1].xyz, m1[2].xyz) * inputTangent * input.boneWeights.y; + localTangent += mat3x3f(m2[0].xyz, m2[1].xyz, m2[2].xyz) * inputTangent * input.boneWeights.z; + localTangent += mat3x3f(m3[0].xyz, m3[1].xyz, m3[2].xyz) * inputTangent * input.boneWeights.w; } else { @@ -209,42 +216,46 @@ localTangent = inputTangent; } - mat3 nm = mat3(fyrox_instanceData.worldMatrix); - normal = normalize(nm * localNormal); - tangent = normalize(nm * localTangent); - binormal = normalize(vertexTangent.w * cross(normal, tangent)); - texCoord = vertexTexCoord; - position = vec3(fyrox_instanceData.worldMatrix * localPosition); - secondTexCoord = vertexSecondTexCoord; + var nm = mat3x3f(fyrox_instanceData.worldMatrix[0].xyz, fyrox_instanceData.worldMatrix[1].xyz, fyrox_instanceData.worldMatrix[2].xyz); + output.normal = normalize(nm * localNormal); + output.tangent = normalize(nm * localTangent); + output.binormal = normalize(input.vertexTangent.w * cross(output.normal, output.tangent)); + output.texCoord = input.vertexTexCoord; + output.outPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz; + output.secondTexCoord = input.vertexSecondTexCoord; - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + output.position = fyrox_instanceData.worldViewProjection * localPosition; + return output; } "#, fragment_shader: r#" - layout(location = 0) out vec4 outColor; - layout(location = 1) out vec4 outNormal; - layout(location = 2) out vec4 outAmbient; - layout(location = 3) out vec4 outMaterial; - layout(location = 4) out uint outDecalMask; - - in vec3 position; - in vec3 normal; - in vec2 texCoord; - in vec3 tangent; - in vec3 binormal; - in vec2 secondTexCoord; - - void main() - { - mat3 tangentSpace = mat3(tangent, binormal, normal); - vec3 toFragment = normalize(position - fyrox_cameraData.position); - - vec2 tc; - if (fyrox_graphicsSettings.usePOM) { - vec3 toFragmentTangentSpace = normalize(transpose(tangentSpace) * toFragment); + struct GBufferOutput { + @location(0) outColor: vec4f, + @location(1) outNormal: vec4f, + @location(2) outAmbient: vec4f, + @location(3) outMaterial: vec4f, + @location(4) outDecalMask: u32, + }; + + @fragment + fn fs_main( + @location(0) position: vec3f, + @location(1) normal: vec3f, + @location(2) texCoord: vec2f, + @location(3) tangent: vec3f, + @location(4) binormal: vec3f, + @location(5) secondTexCoord: vec2f + ) -> GBufferOutput { + var tangentSpace = mat3x3f(tangent, binormal, normal); + var toFragment = normalize(position - fyrox_cameraData.position); + + var tc: vec2f; + if (fyrox_graphicsSettings.usePOM != 0u) { + var toFragmentTangentSpace = normalize(transpose(tangentSpace) * toFragment); tc = S_ComputeParallaxTextureCoordinates( - heightTexture, + heightTexture_tex, + heightTexture_samp, toFragmentTangentSpace, texCoord * properties.texCoordScale, properties.parallaxCenter, @@ -254,26 +265,28 @@ tc = texCoord * properties.texCoordScale; } - outColor = properties.diffuseColor * texture(diffuseTexture, tc); + var output: GBufferOutput; + output.outColor = properties.diffuseColor * textureSample(diffuseTexture_tex, diffuseTexture_samp, tc); // Alpha test. - if (outColor.a < 0.5) { + if (output.outColor.a < 0.5) { discard; } - outColor.a = 1.0; + output.outColor.a = 1.0; - vec4 n = normalize(texture(normalTexture, tc) * 2.0 - 1.0); - outNormal = vec4(normalize(tangentSpace * n.xyz) * 0.5 + 0.5, 1.0); + var n = normalize(textureSample(normalTexture_tex, normalTexture_samp, tc) * 2.0 - 1.0); + output.outNormal = vec4f(normalize(tangentSpace * n.xyz) * 0.5 + 0.5, 1.0); - outMaterial.x = properties.metallicFactor * texture(metallicRoughnessTexture, tc).b; // Metallic - outMaterial.y = properties.roughnessFactor * texture(metallicRoughnessTexture, tc).g; // Roughness - outMaterial.z = texture(aoTexture, tc).r; - outMaterial.a = 1.0; + output.outMaterial.x = properties.metallicFactor * textureSample(metallicRoughnessTexture_tex, metallicRoughnessTexture_samp, tc).b; // Metallic + output.outMaterial.y = properties.roughnessFactor * textureSample(metallicRoughnessTexture_tex, metallicRoughnessTexture_samp, tc).g; // Roughness + output.outMaterial.z = textureSample(aoTexture_tex, aoTexture_samp, tc).r; + output.outMaterial.a = 1.0; - outAmbient.xyz = properties.emissionStrength * texture(emissionTexture, tc).rgb + texture(lightmapTexture, secondTexCoord).rgb; - outAmbient.a = 1.0; + output.outAmbient = vec4f(properties.emissionStrength * textureSample(emissionTexture_tex, emissionTexture_samp, tc).rgb + textureSample(lightmapTexture_tex, lightmapTexture_samp, secondTexCoord).rgb, 1.0); - outDecalMask = properties.layerIndex; + output.outDecalMask = properties.layerIndex; + + return output; } "#, ), @@ -312,61 +325,66 @@ ), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - - out vec3 position; - out vec2 texCoord; - - void main() - { - vec4 localPosition = vec4(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(4) boneWeights: vec4f, + @location(5) boneIndices: vec4f, + }; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) outPosition: vec3f, + @location(1) texCoord: vec2f, + }; + + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + + var localPosition = vec4f(0.0); + + var inputPosition = vec4f(input.vertexPosition, 1.0); + + for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) { + var offsets = S_FetchBlendShapeOffsets(blendShapesStorage, vertex_index, i); + var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w); } - if (fyrox_instanceData.useSkeletalAnimation) + if (fyrox_instanceData.useSkeletalAnimation != 0u) { - int i0 = int(boneIndices.x); - int i1 = int(boneIndices.y); - int i2 = int(boneIndices.z); - int i3 = int(boneIndices.w); - - mat4 m0 = fyrox_boneMatrices.matrices[i0]; - mat4 m1 = fyrox_boneMatrices.matrices[i1]; - mat4 m2 = fyrox_boneMatrices.matrices[i2]; - mat4 m3 = fyrox_boneMatrices.matrices[i3]; - - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; + var i0 = i32(input.boneIndices.x); + var i1 = i32(input.boneIndices.y); + var i2 = i32(input.boneIndices.z); + var i3 = i32(input.boneIndices.w); + + var m0 = fyrox_boneMatrices.matrices[i0]; + var m1 = fyrox_boneMatrices.matrices[i1]; + var m2 = fyrox_boneMatrices.matrices[i2]; + var m3 = fyrox_boneMatrices.matrices[i3]; + + localPosition += m0 * inputPosition * input.boneWeights.x; + localPosition += m1 * inputPosition * input.boneWeights.y; + localPosition += m2 * inputPosition * input.boneWeights.z; + localPosition += m3 * inputPosition * input.boneWeights.w; } else { localPosition = inputPosition; } - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; - texCoord = vertexTexCoord; + output.position = fyrox_instanceData.worldViewProjection * localPosition; + output.texCoord = input.vertexTexCoord; + output.outPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz; + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - - in vec2 texCoord; - - void main() - { - FragColor = properties.diffuseColor * texture(diffuseTexture, texCoord); + @fragment + fn fs_main(@location(0) position: vec3f, @location(1) texCoord: vec2f) -> @location(0) vec4f { + return properties.diffuseColor * textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord); } "#, ), @@ -396,56 +414,62 @@ vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - - out vec2 texCoord; - - void main() - { - vec4 localPosition = vec4(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(4) boneWeights: vec4f, + @location(5) boneIndices: vec4f, + }; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; + + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + + var localPosition = vec4f(0.0); + + var inputPosition = vec4f(input.vertexPosition, 1.0); + + for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) { + var offsets = S_FetchBlendShapeOffsets(blendShapesStorage, vertex_index, i); + var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w); } - if (fyrox_instanceData.useSkeletalAnimation) + if (fyrox_instanceData.useSkeletalAnimation != 0u) { - vec4 vertex = vec4(vertexPosition, 1.0); + var vertex = vec4f(input.vertexPosition, 1.0); - mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; - mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; - mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; - mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + var m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)]; + var m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)]; + var m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)]; + var m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)]; - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; + localPosition += m0 * inputPosition * input.boneWeights.x; + localPosition += m1 * inputPosition * input.boneWeights.y; + localPosition += m2 * inputPosition * input.boneWeights.z; + localPosition += m3 * inputPosition * input.boneWeights.w; } else { localPosition = inputPosition; } - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; - texCoord = vertexTexCoord; + output.position = fyrox_instanceData.worldViewProjection * localPosition; + output.texCoord = input.vertexTexCoord; + return output; } "#, fragment_shader: r#" - in vec2 texCoord; - - void main() - { - if (texture(diffuseTexture, texCoord).a < 0.2) discard; + @fragment + fn fs_main(@location(0) texCoord: vec2f) { + if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; } } "#, ), @@ -475,56 +499,62 @@ vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - - out vec2 texCoord; - - void main() - { - vec4 localPosition = vec4(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(4) boneWeights: vec4f, + @location(5) boneIndices: vec4f, + }; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; + + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + + var localPosition = vec4f(0.0); + + var inputPosition = vec4f(input.vertexPosition, 1.0); + + for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) { + var offsets = S_FetchBlendShapeOffsets(blendShapesStorage, vertex_index, i); + var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w); } - if (fyrox_instanceData.useSkeletalAnimation) + if (fyrox_instanceData.useSkeletalAnimation != 0u) { - vec4 vertex = vec4(vertexPosition, 1.0); + var vertex = vec4f(input.vertexPosition, 1.0); - mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; - mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; - mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; - mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + var m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)]; + var m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)]; + var m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)]; + var m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)]; - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; + localPosition += m0 * inputPosition * input.boneWeights.x; + localPosition += m1 * inputPosition * input.boneWeights.y; + localPosition += m2 * inputPosition * input.boneWeights.z; + localPosition += m3 * inputPosition * input.boneWeights.w; } else { localPosition = inputPosition; } - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; - texCoord = vertexTexCoord; + output.position = fyrox_instanceData.worldViewProjection * localPosition; + output.texCoord = input.vertexTexCoord; + return output; } "#, fragment_shader: r#" - in vec2 texCoord; - - void main() - { - if (texture(diffuseTexture, texCoord).a < 0.2) discard; + @fragment + fn fs_main(@location(0) texCoord: vec2f) { + if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; } } "#, ), @@ -554,64 +584,73 @@ vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - - out vec2 texCoord; - out vec3 worldPosition; - - void main() - { - vec4 localPosition = vec4(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(4) boneWeights: vec4f, + @location(5) boneIndices: vec4f, + }; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + @location(1) worldPosition: vec3f, + }; + + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + + var localPosition = vec4f(0.0); + + var inputPosition = vec4f(input.vertexPosition, 1.0); + + for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) { + var offsets = S_FetchBlendShapeOffsets(blendShapesStorage, vertex_index, i); + var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w); } - if (fyrox_instanceData.useSkeletalAnimation) + if (fyrox_instanceData.useSkeletalAnimation != 0u) { - vec4 vertex = vec4(vertexPosition, 1.0); + var vertex = vec4f(input.vertexPosition, 1.0); - mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; - mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; - mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; - mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + var m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)]; + var m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)]; + var m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)]; + var m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)]; - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; + localPosition += m0 * inputPosition * input.boneWeights.x; + localPosition += m1 * inputPosition * input.boneWeights.y; + localPosition += m2 * inputPosition * input.boneWeights.z; + localPosition += m3 * inputPosition * input.boneWeights.w; } else { localPosition = inputPosition; } - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; - worldPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz; - texCoord = vertexTexCoord; + output.position = fyrox_instanceData.worldViewProjection * localPosition; + output.worldPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz; + output.texCoord = input.vertexTexCoord; + return output; } "#, fragment_shader: r#" - in vec2 texCoord; - in vec3 worldPosition; - - layout(location = 0) out float depth; - - void main() - { - if (texture(diffuseTexture, texCoord).a < 0.2) discard; - depth = length(fyrox_lightData.lightPosition - worldPosition); + struct PointShadowOutput { + @location(0) depth: f32, + }; + + @fragment + fn fs_main(@location(0) texCoord: vec2f, @location(1) worldPosition: vec3f) -> PointShadowOutput { + if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; } + var output: PointShadowOutput; + output.depth = length(fyrox_lightData.lightPosition - worldPosition); + return output; } "#, ) ], -) \ No newline at end of file +) diff --git a/fyrox-material/src/shader/standard/standard-two-sides.shader b/fyrox-material/src/shader/standard/standard-two-sides.shader index 8d63fd15f3..c7cc4dc3b6 100644 --- a/fyrox-material/src/shader/standard/standard-two-sides.shader +++ b/fyrox-material/src/shader/standard/standard-two-sides.shader @@ -50,565 +50,145 @@ ( name: "properties", kind: PropertyGroup([ - ( - name: "texCoordScale", - kind: Vector2(value: (1.0, 1.0)), - ), - ( - name: "layerIndex", - kind: UInt(value: 0), - ), - ( - name: "emissionStrength", - kind: Vector3(value: (2.0, 2.0, 2.0)), - ), - ( - name: "diffuseColor", - kind: Color(r: 255, g: 255, b: 255, a: 255), - ), - ( - name: "parallaxCenter", - kind: Float(value: 0.0), - ), - ( - name: "parallaxScale", - kind: Float(value: 0.08), - ), + (name: "texCoordScale", kind: Vector2(value: (1.0, 1.0))), + (name: "layerIndex", kind: UInt(value: 0)), + (name: "emissionStrength", kind: Vector3(value: (2.0, 2.0, 2.0))), + (name: "diffuseColor", kind: Color(r: 255, g: 255, b: 255, a: 255)), + (name: "parallaxCenter", kind: Float(value: 0.0)), + (name: "parallaxScale", kind: Float(value: 0.08)), ]), binding: 0 ), - ( - name: "fyrox_instanceData", - kind: PropertyGroup([ - // Autogenerated - ]), - binding: 1 - ), - ( - name: "fyrox_boneMatrices", - kind: PropertyGroup([ - // Autogenerated - ]), - binding: 2 - ), - ( - name: "fyrox_graphicsSettings", - kind: PropertyGroup([ - // Autogenerated - ]), - binding: 3 - ), - ( - name: "fyrox_cameraData", - kind: PropertyGroup([ - // Autogenerated - ]), - binding: 4 - ), - ( - name: "fyrox_lightData", - kind: PropertyGroup([ - // Autogenerated - ]), - binding: 5 - ), + (name: "fyrox_instanceData", kind: PropertyGroup([]), binding: 1), + (name: "fyrox_boneMatrices", kind: PropertyGroup([]), binding: 2), + (name: "fyrox_graphicsSettings", kind: PropertyGroup([]), binding: 3), + (name: "fyrox_cameraData", kind: PropertyGroup([]), binding: 4), + (name: "fyrox_lightData", kind: PropertyGroup([]), binding: 5), ], passes: [ ( name: "GBuffer", - draw_parameters: DrawParameters( - cull_face: None, - color_write: ColorMask( - red: true, - green: true, - blue: true, - alpha: true, - ), - depth_write: true, - stencil_test: None, - depth_test: Some(Less), - blend: None, - stencil_op: StencilOp( - fail: Keep, - zfail: Keep, - zpass: Keep, - write_mask: 0xFFFF_FFFF, - ), - scissor_box: None - ), + draw_parameters: DrawParameters(cull_face: None, color_write: ColorMask(red: true, green: true, blue: true, alpha: true), depth_write: true, stencil_test: None, depth_test: Some(Less), blend: None, stencil_op: StencilOp(fail: Keep, zfail: Keep, zpass: Keep, write_mask: 0xFFFF_FFFF), scissor_box: None), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 2) in vec3 vertexNormal; - layout(location = 3) in vec4 vertexTangent; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - layout(location = 6) in vec2 vertexSecondTexCoord; - - out vec3 position; - out vec3 normal; - out vec2 texCoord; - out vec3 tangent; - out vec3 binormal; - out vec2 secondTexCoord; - - void main() - { - vec4 localPosition = vec4(0); - vec3 localNormal = vec3(0); - vec3 localTangent = vec3(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - vec3 inputNormal = vertexNormal; - vec3 inputTangent = vertexTangent.xyz; - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; - inputNormal += offsets.normal * weight; - inputTangent += offsets.tangent * weight; - } - - if (fyrox_instanceData.useSkeletalAnimation) - { - int i0 = int(boneIndices.x); - int i1 = int(boneIndices.y); - int i2 = int(boneIndices.z); - int i3 = int(boneIndices.w); - - mat4 m0 = fyrox_boneMatrices.matrices[i0]; - mat4 m1 = fyrox_boneMatrices.matrices[i1]; - mat4 m2 = fyrox_boneMatrices.matrices[i2]; - mat4 m3 = fyrox_boneMatrices.matrices[i3]; - - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; - - localNormal += mat3(m0) * inputNormal * boneWeights.x; - localNormal += mat3(m1) * inputNormal * boneWeights.y; - localNormal += mat3(m2) * inputNormal * boneWeights.z; - localNormal += mat3(m3) * inputNormal * boneWeights.w; - - localTangent += mat3(m0) * inputTangent * boneWeights.x; - localTangent += mat3(m1) * inputTangent * boneWeights.y; - localTangent += mat3(m2) * inputTangent * boneWeights.z; - localTangent += mat3(m3) * inputTangent * boneWeights.w; - } - else - { - localPosition = inputPosition; - localNormal = inputNormal; - localTangent = inputTangent; - } - - mat3 nm = mat3(fyrox_instanceData.worldMatrix); - normal = normalize(nm * localNormal); - tangent = normalize(nm * localTangent); - binormal = normalize(vertexTangent.w * cross(normal, tangent)); - texCoord = vertexTexCoord; - position = vec3(fyrox_instanceData.worldMatrix * localPosition); - secondTexCoord = vertexSecondTexCoord; - - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + struct VertexInput { + @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f, @location(2) vertexNormal: vec3f, + @location(3) vertexTangent: vec4f, @location(4) boneWeights: vec4f, @location(5) boneIndices: vec4f, + @location(6) vertexSecondTexCoord: vec2f, @builtin(vertex_index) vertex_index: u32, + }; + struct VertexOutput { + @builtin(position) position: vec4f, @location(0) outPosition: vec3f, @location(1) outNormal: vec3f, + @location(2) texCoord: vec2f, @location(3) outTangent: vec3f, @location(4) outBinormal: vec3f, @location(5) secondTexCoord: vec2f, + }; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + var localPosition = vec4f(0.0); var localNormal = vec3f(0.0); var localTangent = vec3f(0.0); + var inputPosition = vec4f(input.vertexPosition, 1.0); var inputNormal = input.vertexNormal; var inputTangent = input.vertexTangent.xyz; + for (var i: i32 = 0; i < i32(fyrox_instanceData.blendShapesCount); i++) { + let offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); + let weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition += vec4f(offsets.position * weight, 0.0); inputNormal += offsets.normal * weight; inputTangent += offsets.tangent * weight; + } + if (fyrox_instanceData.useSkeletalAnimation != 0u) { + let m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)]; let m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)]; + let m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)]; let m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)]; + localPosition += m0 * inputPosition * input.boneWeights.x + m1 * inputPosition * input.boneWeights.y + m2 * inputPosition * input.boneWeights.z + m3 * inputPosition * input.boneWeights.w; + localNormal += mat3x3f(m0[0].xyz, m0[1].xyz, m0[2].xyz) * inputNormal * input.boneWeights.x + mat3x3f(m1[0].xyz, m1[1].xyz, m1[2].xyz) * inputNormal * input.boneWeights.y + mat3x3f(m2[0].xyz, m2[1].xyz, m2[2].xyz) * inputNormal * input.boneWeights.z + mat3x3f(m3[0].xyz, m3[1].xyz, m3[2].xyz) * inputNormal * input.boneWeights.w; + localTangent += mat3x3f(m0[0].xyz, m0[1].xyz, m0[2].xyz) * inputTangent * input.boneWeights.x + mat3x3f(m1[0].xyz, m1[1].xyz, m1[2].xyz) * inputTangent * input.boneWeights.y + mat3x3f(m2[0].xyz, m2[1].xyz, m2[2].xyz) * inputTangent * input.boneWeights.z + mat3x3f(m3[0].xyz, m3[1].xyz, m3[2].xyz) * inputTangent * input.boneWeights.w; + } else { localPosition = inputPosition; localNormal = inputNormal; localTangent = inputTangent; } + let nm = mat3x3f(fyrox_instanceData.worldMatrix[0].xyz, fyrox_instanceData.worldMatrix[1].xyz, fyrox_instanceData.worldMatrix[2].xyz); + output.outNormal = normalize(nm * localNormal); output.outTangent = normalize(nm * localTangent); + output.outBinormal = normalize(input.vertexTangent.w * cross(output.outNormal, output.outTangent)); + output.texCoord = input.vertexTexCoord; output.outPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz; + output.secondTexCoord = input.vertexSecondTexCoord; output.position = fyrox_instanceData.worldViewProjection * localPosition; + return output; } "#, fragment_shader: r#" - layout(location = 0) out vec4 outColor; - layout(location = 1) out vec4 outNormal; - layout(location = 2) out vec4 outAmbient; - layout(location = 3) out vec4 outMaterial; - layout(location = 4) out uint outDecalMask; - - in vec3 position; - in vec3 normal; - in vec2 texCoord; - in vec3 tangent; - in vec3 binormal; - in vec2 secondTexCoord; - - void main() - { - mat3 tangentSpace = mat3(tangent, binormal, normal); - vec3 toFragment = normalize(position - fyrox_cameraData.position); - - vec2 tc; - if (fyrox_graphicsSettings.usePOM) { - vec3 toFragmentTangentSpace = normalize(transpose(tangentSpace) * toFragment); - tc = S_ComputeParallaxTextureCoordinates( - heightTexture, - toFragmentTangentSpace, - texCoord * properties.texCoordScale, - properties.parallaxCenter, - properties.parallaxScale - ); - } else { - tc = texCoord * properties.texCoordScale; - } - - outColor = properties.diffuseColor * texture(diffuseTexture, tc); - - // Alpha test. - if (outColor.a < 0.5) { - discard; - } - outColor.a = 1.0; - - vec4 n = normalize(texture(normalTexture, tc) * 2.0 - 1.0); - outNormal = vec4(normalize(tangentSpace * n.xyz) * 0.5 + 0.5, 1.0); - - outMaterial.x = texture(metallicTexture, tc).r; - outMaterial.y = texture(roughnessTexture, tc).r; - outMaterial.z = texture(aoTexture, tc).r; - outMaterial.a = 1.0; - - outAmbient.xyz = properties.emissionStrength * texture(emissionTexture, tc).rgb + texture(lightmapTexture, secondTexCoord).rgb; - outAmbient.a = 1.0; - - outDecalMask = properties.layerIndex; + struct FragmentOutput { @location(0) outColor: vec4f, @location(1) outNormal: vec4f, @location(2) outAmbient: vec4f, @location(3) outMaterial: vec4f, @location(4) outDecalMask: u32 }; + @fragment fn fs_main(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) texCoord: vec2f, @location(3) tangent: vec3f, @location(4) binormal: vec3f, @location(5) secondTexCoord: vec2f) -> FragmentOutput { + var output: FragmentOutput; + let tangentSpace = mat3x3f(tangent, binormal, normal); + let toFragment = normalize(position - fyrox_cameraData.position); + var tc: vec2f; + if (fyrox_graphicsSettings.usePOM != 0u) { tc = S_ComputeParallaxTextureCoordinates(heightTexture_tex, heightTexture_samp, normalize(transpose(tangentSpace) * toFragment), texCoord * properties.texCoordScale, properties.parallaxCenter, properties.parallaxScale); } else { tc = texCoord * properties.texCoordScale; } + output.outColor = properties.diffuseColor * textureSample(diffuseTexture_tex, diffuseTexture_samp, tc); + if (output.outColor.a < 0.5) { discard; } output.outColor.a = 1.0; + let n = normalize(textureSample(normalTexture_tex, normalTexture_samp, tc) * 2.0 - 1.0); + output.outNormal = vec4f(normalize(tangentSpace * n.xyz) * 0.5 + 0.5, 1.0); + output.outMaterial.x = textureSample(metallicTexture_tex, metallicTexture_samp, tc).r; + output.outMaterial.y = textureSample(roughnessTexture_tex, roughnessTexture_samp, tc).r; + output.outMaterial.z = textureSample(aoTexture_tex, aoTexture_samp, tc).r; output.outMaterial.a = 1.0; + output.outAmbient = vec4f(properties.emissionStrength * textureSample(emissionTexture_tex, emissionTexture_samp, tc).rgb + textureSample(lightmapTexture_tex, lightmapTexture_samp, secondTexCoord).rgb, 1.0); output.outDecalMask = properties.layerIndex; + return output; } "#, ), ( name: "Forward", - draw_parameters: DrawParameters( - cull_face: None, - color_write: ColorMask( - red: true, - green: true, - blue: true, - alpha: true, - ), - depth_write: true, - stencil_test: None, - depth_test: Some(Less), - blend: Some(BlendParameters( - func: BlendFunc( - sfactor: SrcAlpha, - dfactor: OneMinusSrcAlpha, - alpha_sfactor: SrcAlpha, - alpha_dfactor: OneMinusSrcAlpha, - ), - equation: BlendEquation( - rgb: Add, - alpha: Add - ) - )), - stencil_op: StencilOp( - fail: Keep, - zfail: Keep, - zpass: Keep, - write_mask: 0xFFFF_FFFF, - ), - scissor_box: None - ), + draw_parameters: DrawParameters(cull_face: None, color_write: ColorMask(red: true, green: true, blue: true, alpha: true), depth_write: true, stencil_test: None, depth_test: Some(Less), blend: Some(BlendParameters(func: BlendFunc(sfactor: SrcAlpha, dfactor: OneMinusSrcAlpha, alpha_sfactor: SrcAlpha, alpha_dfactor: OneMinusSrcAlpha), equation: BlendEquation(rgb: Add, alpha: Add))), stencil_op: StencilOp(fail: Keep, zfail: Keep, zpass: Keep, write_mask: 0xFFFF_FFFF), scissor_box: None), vertex_shader: - r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - - out vec3 position; - out vec2 texCoord; - - void main() - { - vec4 localPosition = vec4(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; - } - - if (fyrox_instanceData.useSkeletalAnimation) - { - int i0 = int(boneIndices.x); - int i1 = int(boneIndices.y); - int i2 = int(boneIndices.z); - int i3 = int(boneIndices.w); - - mat4 m0 = fyrox_boneMatrices.matrices[i0]; - mat4 m1 = fyrox_boneMatrices.matrices[i1]; - mat4 m2 = fyrox_boneMatrices.matrices[i2]; - mat4 m3 = fyrox_boneMatrices.matrices[i3]; - - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; - } - else - { - localPosition = inputPosition; - } - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; - texCoord = vertexTexCoord; - } - "#, - - fragment_shader: - r#" - out vec4 FragColor; - - in vec2 texCoord; - - void main() - { - FragColor = properties.diffuseColor * S_SRGBToLinear(texture(diffuseTexture, texCoord)); + r#" + struct VertexInput { @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f, @location(4) boneWeights: vec4f, @location(5) boneIndices: vec4f, @builtin(vertex_index) vertex_index: u32 }; + struct VertexOutput { @builtin(position) position: vec4f, @location(0) outPosition: vec3f, @location(1) texCoord: vec2f }; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; var localPosition = vec4f(0.0); var inputPosition = vec4f(input.vertexPosition, 1.0); + for (var i: i32 = 0; i < i32(fyrox_instanceData.blendShapesCount); i++) { let offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); let weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; inputPosition += vec4f(offsets.position * weight, 0.0); } + if (fyrox_instanceData.useSkeletalAnimation != 0u) { let m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)]; let m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)]; let m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)]; let m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)]; localPosition += m0 * inputPosition * input.boneWeights.x + m1 * inputPosition * input.boneWeights.y + m2 * inputPosition * input.boneWeights.z + m3 * inputPosition * input.boneWeights.w; } else { localPosition = inputPosition; } + output.position = fyrox_instanceData.worldViewProjection * localPosition; output.texCoord = input.vertexTexCoord; return output; } - "#, + "#, + fragment_shader: r#"@fragment fn fs_main(@location(1) texCoord: vec2f) -> @location(0) vec4f { return properties.diffuseColor * S_SRGBToLinear(textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord)); }"#, ), ( name: "DirectionalShadow", - - draw_parameters: DrawParameters ( - cull_face: None, - color_write: ColorMask( - red: false, - green: false, - blue: false, - alpha: false, - ), - depth_write: true, - stencil_test: None, - depth_test: Some(Less), - blend: None, - stencil_op: StencilOp( - fail: Keep, - zfail: Keep, - zpass: Keep, - write_mask: 0xFFFF_FFFF, - ), - scissor_box: None - ), - + draw_parameters: DrawParameters(cull_face: None, color_write: ColorMask(red: false, green: false, blue: false, alpha: false), depth_write: true, stencil_test: None, depth_test: Some(Less), blend: None, stencil_op: StencilOp(fail: Keep, zfail: Keep, zpass: Keep, write_mask: 0xFFFF_FFFF), scissor_box: None), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - - out vec2 texCoord; - - void main() - { - vec4 localPosition = vec4(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; - } - - if (fyrox_instanceData.useSkeletalAnimation) - { - vec4 vertex = vec4(vertexPosition, 1.0); - - mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; - mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; - mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; - mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; - - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; - } - else - { - localPosition = inputPosition; - } - - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; - texCoord = vertexTexCoord; - } - "#, - - fragment_shader: - r#" - in vec2 texCoord; - - void main() - { - if (texture(diffuseTexture, texCoord).a < 0.2) discard; + struct VertexInput { @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f, @location(4) boneWeights: vec4f, @location(5) boneIndices: vec4f, @builtin(vertex_index) vertex_index: u32 }; + struct VertexOutput { @builtin(position) position: vec4f, @location(0) texCoord: vec2f }; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; var localPosition = vec4f(0.0); var inputPosition = vec4f(input.vertexPosition, 1.0); + for (var i: i32 = 0; i < i32(fyrox_instanceData.blendShapesCount); i++) { let offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); let weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; inputPosition += vec4f(offsets.position * weight, 0.0); } + if (fyrox_instanceData.useSkeletalAnimation != 0u) { let m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)]; let m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)]; let m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)]; let m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)]; localPosition += m0 * inputPosition * input.boneWeights.x + m1 * inputPosition * input.boneWeights.y + m2 * inputPosition * input.boneWeights.z + m3 * inputPosition * input.boneWeights.w; } else { localPosition = inputPosition; } + output.position = fyrox_instanceData.worldViewProjection * localPosition; output.texCoord = input.vertexTexCoord; return output; } "#, + fragment_shader: r#"@fragment fn fs_main(@location(0) texCoord: vec2f) { if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; } }"#, ), ( name: "SpotShadow", - - draw_parameters: DrawParameters ( - cull_face: None, - color_write: ColorMask( - red: false, - green: false, - blue: false, - alpha: false, - ), - depth_write: true, - stencil_test: None, - depth_test: Some(Less), - blend: None, - stencil_op: StencilOp( - fail: Keep, - zfail: Keep, - zpass: Keep, - write_mask: 0xFFFF_FFFF, - ), - scissor_box: None - ), - + draw_parameters: DrawParameters(cull_face: None, color_write: ColorMask(red: false, green: false, blue: false, alpha: false), depth_write: true, stencil_test: None, depth_test: Some(Less), blend: None, stencil_op: StencilOp(fail: Keep, zfail: Keep, zpass: Keep, write_mask: 0xFFFF_FFFF), scissor_box: None), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - - out vec2 texCoord; - - void main() - { - vec4 localPosition = vec4(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; - } - - if (fyrox_instanceData.useSkeletalAnimation) - { - vec4 vertex = vec4(vertexPosition, 1.0); - - mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; - mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; - mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; - mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; - - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; - } - else - { - localPosition = inputPosition; - } - - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; - texCoord = vertexTexCoord; - } - "#, - - fragment_shader: - r#" - in vec2 texCoord; - - void main() - { - if (texture(diffuseTexture, texCoord).a < 0.2) discard; + struct VertexInput { @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f, @location(4) boneWeights: vec4f, @location(5) boneIndices: vec4f, @builtin(vertex_index) vertex_index: u32 }; + struct VertexOutput { @builtin(position) position: vec4f, @location(0) texCoord: vec2f }; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; var localPosition = vec4f(0.0); var inputPosition = vec4f(input.vertexPosition, 1.0); + for (var i: i32 = 0; i < i32(fyrox_instanceData.blendShapesCount); i++) { let offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); let weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; inputPosition += vec4f(offsets.position * weight, 0.0); } + if (fyrox_instanceData.useSkeletalAnimation != 0u) { let m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)]; let m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)]; let m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)]; let m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)]; localPosition += m0 * inputPosition * input.boneWeights.x + m1 * inputPosition * input.boneWeights.y + m2 * inputPosition * input.boneWeights.z + m3 * inputPosition * input.boneWeights.w; } else { localPosition = inputPosition; } + output.position = fyrox_instanceData.worldViewProjection * localPosition; output.texCoord = input.vertexTexCoord; return output; } "#, + fragment_shader: r#"@fragment fn fs_main(@location(0) texCoord: vec2f) { if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; } }"#, ), ( name: "PointShadow", - - draw_parameters: DrawParameters ( - cull_face: None, - color_write: ColorMask( - red: true, - green: true, - blue: true, - alpha: true, - ), - depth_write: true, - stencil_test: None, - depth_test: Some(Less), - blend: None, - stencil_op: StencilOp( - fail: Keep, - zfail: Keep, - zpass: Keep, - write_mask: 0xFFFF_FFFF, - ), - scissor_box: None - ), - + draw_parameters: DrawParameters(cull_face: None, color_write: ColorMask(red: true, green: true, blue: true, alpha: true), depth_write: true, stencil_test: None, depth_test: Some(Less), blend: None, stencil_op: StencilOp(fail: Keep, zfail: Keep, zpass: Keep, write_mask: 0xFFFF_FFFF), scissor_box: None), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - - out vec2 texCoord; - out vec3 worldPosition; - - void main() - { - vec4 localPosition = vec4(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; - } - - if (fyrox_instanceData.useSkeletalAnimation) - { - vec4 vertex = vec4(vertexPosition, 1.0); - - mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; - mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; - mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; - mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; - - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; - } - else - { - localPosition = inputPosition; - } - - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; - worldPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz; - texCoord = vertexTexCoord; - } - "#, - - fragment_shader: - r#" - in vec2 texCoord; - in vec3 worldPosition; - - layout(location = 0) out float depth; - - void main() - { - if (texture(diffuseTexture, texCoord).a < 0.2) discard; - depth = length(fyrox_lightData.lightPosition - worldPosition); + struct VertexInput { @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f, @location(4) boneWeights: vec4f, @location(5) boneIndices: vec4f, @builtin(vertex_index) vertex_index: u32 }; + struct VertexOutput { @builtin(position) position: vec4f, @location(0) texCoord: vec2f, @location(1) worldPosition: vec3f }; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; var localPosition = vec4f(0.0); var inputPosition = vec4f(input.vertexPosition, 1.0); + for (var i: i32 = 0; i < i32(fyrox_instanceData.blendShapesCount); i++) { let offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); let weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; inputPosition += vec4f(offsets.position * weight, 0.0); } + if (fyrox_instanceData.useSkeletalAnimation != 0u) { let m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)]; let m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)]; let m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)]; let m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)]; localPosition += m0 * inputPosition * input.boneWeights.x + m1 * inputPosition * input.boneWeights.y + m2 * inputPosition * input.boneWeights.z + m3 * inputPosition * input.boneWeights.w; } else { localPosition = inputPosition; } + output.position = fyrox_instanceData.worldViewProjection * localPosition; output.worldPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz; output.texCoord = input.vertexTexCoord; return output; } "#, + fragment_shader: r#"@fragment fn fs_main(@location(0) texCoord: vec2f, @location(1) worldPosition: vec3f) -> @location(0) f32 { if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; } return length(fyrox_lightData.lightPosition - worldPosition); }"#, ) ], -) \ No newline at end of file +) diff --git a/fyrox-material/src/shader/standard/standard.shader b/fyrox-material/src/shader/standard/standard.shader index 2b6642dc4e..889518618f 100644 --- a/fyrox-material/src/shader/standard/standard.shader +++ b/fyrox-material/src/shader/standard/standard.shader @@ -139,109 +139,114 @@ ), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 2) in vec3 vertexNormal; - layout(location = 3) in vec4 vertexTangent; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - layout(location = 6) in vec2 vertexSecondTexCoord; - - out vec3 position; - out vec3 normal; - out vec2 texCoord; - out vec3 tangent; - out vec3 binormal; - out vec2 secondTexCoord; - - void main() - { - vec4 localPosition = vec4(0); - vec3 localNormal = vec3(0); - vec3 localTangent = vec3(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - vec3 inputNormal = vertexNormal; - vec3 inputTangent = vertexTangent.xyz; - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(2) vertexNormal: vec3f, + @location(3) vertexTangent: vec4f, + @location(4) boneWeights: vec4f, + @location(5) boneIndices: vec4f, + @location(6) vertexSecondTexCoord: vec2f, + @builtin(vertex_index) vertex_index: u32, + }; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) outPosition: vec3f, + @location(1) outNormal: vec3f, + @location(2) texCoord: vec2f, + @location(3) outTangent: vec3f, + @location(4) outBinormal: vec3f, + @location(5) secondTexCoord: vec2f, + }; + + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + var localPosition = vec4f(0.0); + var localNormal = vec3f(0.0); + var localTangent = vec3f(0.0); + + var inputPosition = vec4f(input.vertexPosition, 1.0); + var inputNormal = input.vertexNormal; + var inputTangent = input.vertexTangent.xyz; + + for (var i: i32 = 0; i < i32(fyrox_instanceData.blendShapesCount); i++) { + let offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); + let weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition += vec4f(offsets.position * weight, 0.0); inputNormal += offsets.normal * weight; inputTangent += offsets.tangent * weight; } - if (fyrox_instanceData.useSkeletalAnimation) - { - int i0 = int(boneIndices.x); - int i1 = int(boneIndices.y); - int i2 = int(boneIndices.z); - int i3 = int(boneIndices.w); - - mat4 m0 = fyrox_boneMatrices.matrices[i0]; - mat4 m1 = fyrox_boneMatrices.matrices[i1]; - mat4 m2 = fyrox_boneMatrices.matrices[i2]; - mat4 m3 = fyrox_boneMatrices.matrices[i3]; - - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; - - localNormal += mat3(m0) * inputNormal * boneWeights.x; - localNormal += mat3(m1) * inputNormal * boneWeights.y; - localNormal += mat3(m2) * inputNormal * boneWeights.z; - localNormal += mat3(m3) * inputNormal * boneWeights.w; - - localTangent += mat3(m0) * inputTangent * boneWeights.x; - localTangent += mat3(m1) * inputTangent * boneWeights.y; - localTangent += mat3(m2) * inputTangent * boneWeights.z; - localTangent += mat3(m3) * inputTangent * boneWeights.w; - } - else - { + if (fyrox_instanceData.useSkeletalAnimation != 0u) { + let i0 = i32(input.boneIndices.x); + let i1 = i32(input.boneIndices.y); + let i2 = i32(input.boneIndices.z); + let i3 = i32(input.boneIndices.w); + + let m0 = fyrox_boneMatrices.matrices[i0]; + let m1 = fyrox_boneMatrices.matrices[i1]; + let m2 = fyrox_boneMatrices.matrices[i2]; + let m3 = fyrox_boneMatrices.matrices[i3]; + + localPosition += m0 * inputPosition * input.boneWeights.x; + localPosition += m1 * inputPosition * input.boneWeights.y; + localPosition += m2 * inputPosition * input.boneWeights.z; + localPosition += m3 * inputPosition * input.boneWeights.w; + + localNormal += mat3x3f(m0[0].xyz, m0[1].xyz, m0[2].xyz) * inputNormal * input.boneWeights.x; + localNormal += mat3x3f(m1[0].xyz, m1[1].xyz, m1[2].xyz) * inputNormal * input.boneWeights.y; + localNormal += mat3x3f(m2[0].xyz, m2[1].xyz, m2[2].xyz) * inputNormal * input.boneWeights.z; + localNormal += mat3x3f(m3[0].xyz, m3[1].xyz, m3[2].xyz) * inputNormal * input.boneWeights.w; + + localTangent += mat3x3f(m0[0].xyz, m0[1].xyz, m0[2].xyz) * inputTangent * input.boneWeights.x; + localTangent += mat3x3f(m1[0].xyz, m1[1].xyz, m1[2].xyz) * inputTangent * input.boneWeights.y; + localTangent += mat3x3f(m2[0].xyz, m2[1].xyz, m2[2].xyz) * inputTangent * input.boneWeights.z; + localTangent += mat3x3f(m3[0].xyz, m3[1].xyz, m3[2].xyz) * inputTangent * input.boneWeights.w; + } else { localPosition = inputPosition; localNormal = inputNormal; localTangent = inputTangent; } - mat3 nm = mat3(fyrox_instanceData.worldMatrix); - normal = normalize(nm * localNormal); - tangent = normalize(nm * localTangent); - binormal = normalize(vertexTangent.w * cross(normal, tangent)); - texCoord = vertexTexCoord; - position = vec3(fyrox_instanceData.worldMatrix * localPosition); - secondTexCoord = vertexSecondTexCoord; - - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + let nm = mat3x3f(fyrox_instanceData.worldMatrix[0].xyz, fyrox_instanceData.worldMatrix[1].xyz, fyrox_instanceData.worldMatrix[2].xyz); + output.outNormal = normalize(nm * localNormal); + output.outTangent = normalize(nm * localTangent); + output.outBinormal = normalize(input.vertexTangent.w * cross(output.outNormal, output.outTangent)); + output.texCoord = input.vertexTexCoord; + output.outPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz; + output.secondTexCoord = input.vertexSecondTexCoord; + output.position = fyrox_instanceData.worldViewProjection * localPosition; + return output; } "#, fragment_shader: r#" - layout(location = 0) out vec4 outColor; - layout(location = 1) out vec4 outNormal; - layout(location = 2) out vec4 outAmbient; - layout(location = 3) out vec4 outMaterial; - layout(location = 4) out uint outDecalMask; - - in vec3 position; - in vec3 normal; - in vec2 texCoord; - in vec3 tangent; - in vec3 binormal; - in vec2 secondTexCoord; - - void main() - { - mat3 tangentSpace = mat3(tangent, binormal, normal); - vec3 toFragment = normalize(position - fyrox_cameraData.position); - - vec2 tc; - if (fyrox_graphicsSettings.usePOM) { - vec3 toFragmentTangentSpace = normalize(transpose(tangentSpace) * toFragment); + struct FragmentOutput { + @location(0) outColor: vec4f, + @location(1) outNormal: vec4f, + @location(2) outAmbient: vec4f, + @location(3) outMaterial: vec4f, + @location(4) outDecalMask: u32, + }; + + @fragment fn fs_main( + @location(0) position: vec3f, + @location(1) normal: vec3f, + @location(2) texCoord: vec2f, + @location(3) tangent: vec3f, + @location(4) binormal: vec3f, + @location(5) secondTexCoord: vec2f + ) -> FragmentOutput { + var output: FragmentOutput; + let tangentSpace = mat3x3f(tangent, binormal, normal); + let toFragment = normalize(position - fyrox_cameraData.position); + + var tc: vec2f; + if (fyrox_graphicsSettings.usePOM != 0u) { + let toFragmentTangentSpace = normalize(transpose(tangentSpace) * toFragment); tc = S_ComputeParallaxTextureCoordinates( - heightTexture, + heightTexture_tex, heightTexture_samp, toFragmentTangentSpace, texCoord * properties.texCoordScale, properties.parallaxCenter, @@ -251,26 +256,26 @@ tc = texCoord * properties.texCoordScale; } - outColor = properties.diffuseColor * texture(diffuseTexture, tc); + output.outColor = properties.diffuseColor * textureSample(diffuseTexture_tex, diffuseTexture_samp, tc); // Alpha test. - if (outColor.a < 0.5) { + if (output.outColor.a < 0.5) { discard; } - outColor.a = 1.0; + output.outColor.a = 1.0; - vec4 n = normalize(texture(normalTexture, tc) * 2.0 - 1.0); - outNormal = vec4(normalize(tangentSpace * n.xyz) * 0.5 + 0.5, 1.0); + let n = normalize(textureSample(normalTexture_tex, normalTexture_samp, tc) * 2.0 - 1.0); + output.outNormal = vec4f(normalize(tangentSpace * n.xyz) * 0.5 + 0.5, 1.0); - outMaterial.x = texture(metallicTexture, tc).r; - outMaterial.y = texture(roughnessTexture, tc).r; - outMaterial.z = texture(aoTexture, tc).r; - outMaterial.a = 1.0; + output.outMaterial.x = textureSample(metallicTexture_tex, metallicTexture_samp, tc).r; + output.outMaterial.y = textureSample(roughnessTexture_tex, roughnessTexture_samp, tc).r; + output.outMaterial.z = textureSample(aoTexture_tex, aoTexture_samp, tc).r; + output.outMaterial.a = 1.0; - outAmbient.xyz = properties.emissionStrength * texture(emissionTexture, tc).rgb + texture(lightmapTexture, secondTexCoord).rgb; - outAmbient.a = 1.0; + output.outAmbient = vec4f(properties.emissionStrength * textureSample(emissionTexture_tex, emissionTexture_samp, tc).rgb + textureSample(lightmapTexture_tex, lightmapTexture_samp, secondTexCoord).rgb, 1.0); - outDecalMask = properties.layerIndex; + output.outDecalMask = properties.layerIndex; + return output; } "#, ), @@ -309,61 +314,59 @@ ), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - - out vec3 position; - out vec2 texCoord; - - void main() - { - vec4 localPosition = vec4(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(4) boneWeights: vec4f, + @location(5) boneIndices: vec4f, + @builtin(vertex_index) vertex_index: u32, + }; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) outPosition: vec3f, + @location(1) texCoord: vec2f, + }; + + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + var localPosition = vec4f(0.0); + var inputPosition = vec4f(input.vertexPosition, 1.0); + + for (var i: i32 = 0; i < i32(fyrox_instanceData.blendShapesCount); i++) { + let offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); + let weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition += vec4f(offsets.position * weight, 0.0); } - if (fyrox_instanceData.useSkeletalAnimation) - { - int i0 = int(boneIndices.x); - int i1 = int(boneIndices.y); - int i2 = int(boneIndices.z); - int i3 = int(boneIndices.w); - - mat4 m0 = fyrox_boneMatrices.matrices[i0]; - mat4 m1 = fyrox_boneMatrices.matrices[i1]; - mat4 m2 = fyrox_boneMatrices.matrices[i2]; - mat4 m3 = fyrox_boneMatrices.matrices[i3]; - - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; - } - else - { + if (fyrox_instanceData.useSkeletalAnimation != 0u) { + let i0 = i32(input.boneIndices.x); + let i1 = i32(input.boneIndices.y); + let i2 = i32(input.boneIndices.z); + let i3 = i32(input.boneIndices.w); + + let m0 = fyrox_boneMatrices.matrices[i0]; + let m1 = fyrox_boneMatrices.matrices[i1]; + let m2 = fyrox_boneMatrices.matrices[i2]; + let m3 = fyrox_boneMatrices.matrices[i3]; + + localPosition += m0 * inputPosition * input.boneWeights.x; + localPosition += m1 * inputPosition * input.boneWeights.y; + localPosition += m2 * inputPosition * input.boneWeights.z; + localPosition += m3 * inputPosition * input.boneWeights.w; + } else { localPosition = inputPosition; } - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; - texCoord = vertexTexCoord; + output.position = fyrox_instanceData.worldViewProjection * localPosition; + output.texCoord = input.vertexTexCoord; + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - - in vec2 texCoord; - - void main() - { - FragColor = properties.diffuseColor * S_SRGBToLinear(texture(diffuseTexture, texCoord)); + @fragment fn fs_main(@location(1) texCoord: vec2f) -> @location(0) vec4f { + return properties.diffuseColor * S_SRGBToLinear(textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord)); } "#, ), @@ -393,56 +396,54 @@ vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - - out vec2 texCoord; - - void main() - { - vec4 localPosition = vec4(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(4) boneWeights: vec4f, + @location(5) boneIndices: vec4f, + @builtin(vertex_index) vertex_index: u32, + }; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; + + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + var localPosition = vec4f(0.0); + var inputPosition = vec4f(input.vertexPosition, 1.0); + + for (var i: i32 = 0; i < i32(fyrox_instanceData.blendShapesCount); i++) { + let offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); + let weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition += vec4f(offsets.position * weight, 0.0); } - if (fyrox_instanceData.useSkeletalAnimation) - { - vec4 vertex = vec4(vertexPosition, 1.0); - - mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; - mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; - mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; - mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + if (fyrox_instanceData.useSkeletalAnimation != 0u) { + let m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)]; + let m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)]; + let m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)]; + let m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)]; - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; - } - else - { + localPosition += m0 * inputPosition * input.boneWeights.x; + localPosition += m1 * inputPosition * input.boneWeights.y; + localPosition += m2 * inputPosition * input.boneWeights.z; + localPosition += m3 * inputPosition * input.boneWeights.w; + } else { localPosition = inputPosition; } - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; - texCoord = vertexTexCoord; + output.position = fyrox_instanceData.worldViewProjection * localPosition; + output.texCoord = input.vertexTexCoord; + return output; } "#, fragment_shader: r#" - in vec2 texCoord; - - void main() - { - if (texture(diffuseTexture, texCoord).a < 0.2) discard; + @fragment fn fs_main(@location(0) texCoord: vec2f) { + if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; } } "#, ), @@ -472,56 +473,54 @@ vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - - out vec2 texCoord; - - void main() - { - vec4 localPosition = vec4(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(4) boneWeights: vec4f, + @location(5) boneIndices: vec4f, + @builtin(vertex_index) vertex_index: u32, + }; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + }; + + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + var localPosition = vec4f(0.0); + var inputPosition = vec4f(input.vertexPosition, 1.0); + + for (var i: i32 = 0; i < i32(fyrox_instanceData.blendShapesCount); i++) { + let offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); + let weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition += vec4f(offsets.position * weight, 0.0); } - if (fyrox_instanceData.useSkeletalAnimation) - { - vec4 vertex = vec4(vertexPosition, 1.0); + if (fyrox_instanceData.useSkeletalAnimation != 0u) { + let m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)]; + let m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)]; + let m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)]; + let m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)]; - mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; - mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; - mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; - mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; - - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; - } - else - { + localPosition += m0 * inputPosition * input.boneWeights.x; + localPosition += m1 * inputPosition * input.boneWeights.y; + localPosition += m2 * inputPosition * input.boneWeights.z; + localPosition += m3 * inputPosition * input.boneWeights.w; + } else { localPosition = inputPosition; } - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; - texCoord = vertexTexCoord; + output.position = fyrox_instanceData.worldViewProjection * localPosition; + output.texCoord = input.vertexTexCoord; + return output; } "#, fragment_shader: r#" - in vec2 texCoord; - - void main() - { - if (texture(diffuseTexture, texCoord).a < 0.2) discard; + @fragment fn fs_main(@location(0) texCoord: vec2f) { + if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; } } "#, ), @@ -551,64 +550,59 @@ vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 4) in vec4 boneWeights; - layout(location = 5) in vec4 boneIndices; - - out vec2 texCoord; - out vec3 worldPosition; - - void main() - { - vec4 localPosition = vec4(0); - - vec4 inputPosition = vec4(vertexPosition, 1.0); - - for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { - TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); - float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; - inputPosition.xyz += offsets.position * weight; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(4) boneWeights: vec4f, + @location(5) boneIndices: vec4f, + @builtin(vertex_index) vertex_index: u32, + }; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + @location(1) worldPosition: vec3f, + }; + + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + var localPosition = vec4f(0.0); + var inputPosition = vec4f(input.vertexPosition, 1.0); + + for (var i: i32 = 0; i < i32(fyrox_instanceData.blendShapesCount); i++) { + let offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); + let weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition += vec4f(offsets.position * weight, 0.0); } - if (fyrox_instanceData.useSkeletalAnimation) - { - vec4 vertex = vec4(vertexPosition, 1.0); - - mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; - mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; - mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; - mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + if (fyrox_instanceData.useSkeletalAnimation != 0u) { + let m0 = fyrox_boneMatrices.matrices[i32(input.boneIndices.x)]; + let m1 = fyrox_boneMatrices.matrices[i32(input.boneIndices.y)]; + let m2 = fyrox_boneMatrices.matrices[i32(input.boneIndices.z)]; + let m3 = fyrox_boneMatrices.matrices[i32(input.boneIndices.w)]; - localPosition += m0 * inputPosition * boneWeights.x; - localPosition += m1 * inputPosition * boneWeights.y; - localPosition += m2 * inputPosition * boneWeights.z; - localPosition += m3 * inputPosition * boneWeights.w; - } - else - { + localPosition += m0 * inputPosition * input.boneWeights.x; + localPosition += m1 * inputPosition * input.boneWeights.y; + localPosition += m2 * inputPosition * input.boneWeights.z; + localPosition += m3 * inputPosition * input.boneWeights.w; + } else { localPosition = inputPosition; } - gl_Position = fyrox_instanceData.worldViewProjection * localPosition; - worldPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz; - texCoord = vertexTexCoord; + output.position = fyrox_instanceData.worldViewProjection * localPosition; + output.worldPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz; + output.texCoord = input.vertexTexCoord; + return output; } "#, fragment_shader: r#" - in vec2 texCoord; - in vec3 worldPosition; - - layout(location = 0) out float depth; - - void main() - { - if (texture(diffuseTexture, texCoord).a < 0.2) discard; - depth = length(fyrox_lightData.lightPosition - worldPosition); + @fragment fn fs_main(@location(0) texCoord: vec2f, @location(1) worldPosition: vec3f) -> @location(0) f32 { + if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; } + return length(fyrox_lightData.lightPosition - worldPosition); } "#, ) ], -) \ No newline at end of file +) diff --git a/fyrox-material/src/shader/standard/standard2d.shader b/fyrox-material/src/shader/standard/standard2d.shader index b2e6121657..d82f3cd1ac 100644 --- a/fyrox-material/src/shader/standard/standard2d.shader +++ b/fyrox-material/src/shader/standard/standard2d.shader @@ -68,56 +68,57 @@ ), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 2) in vec4 vertexColor; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(2) vertexColor: vec4f, + }; - out vec2 texCoord; - out vec4 color; - out vec3 fragmentPosition; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + @location(1) color: vec4f, + @location(2) fragmentPosition: vec3f, + }; - void main() - { - texCoord = vertexTexCoord; - fragmentPosition = (fyrox_instanceData.worldMatrix * vec4(vertexPosition, 1.0)).xyz; - gl_Position = fyrox_instanceData.worldViewProjection * vec4(vertexPosition, 1.0); - color = vertexColor; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.texCoord = input.vertexTexCoord; + output.fragmentPosition = (fyrox_instanceData.worldMatrix * vec4f(input.vertexPosition, 1.0)).xyz; + output.position = fyrox_instanceData.worldViewProjection * vec4f(input.vertexPosition, 1.0); + output.color = input.vertexColor; + return output; } "#, fragment_shader: r#" - out vec4 FragColor; + @fragment fn fs_main( + @location(0) texCoord: vec2f, + @location(1) color: vec4f, + @location(2) fragmentPosition: vec3f + ) -> @location(0) vec4f { + var lighting = fyrox_lightData.ambientLightColor.xyz; + for (var i: i32 = 0; i < min(i32(fyrox_lightsBlock.lightCount), 16); i++) { + let halfHotspotAngleCos = fyrox_lightsBlock.lightsParameters[i].x; + let halfConeAngleCos = fyrox_lightsBlock.lightsParameters[i].y; + let lightColor = fyrox_lightsBlock.lightsColorRadius[i].xyz; + let radius = fyrox_lightsBlock.lightsColorRadius[i].w; + let lightPosition = fyrox_lightsBlock.lightsPosition[i]; + let direction = fyrox_lightsBlock.lightsDirection[i]; - in vec2 texCoord; - in vec4 color; - in vec3 fragmentPosition; - - void main() - { - vec3 lighting = fyrox_lightData.ambientLightColor.xyz; - for(int i = 0; i < min(fyrox_lightsBlock.lightCount, 16); ++i) { - // "Unpack" light parameters. - float halfHotspotAngleCos = fyrox_lightsBlock.lightsParameters[i].x; - float halfConeAngleCos = fyrox_lightsBlock.lightsParameters[i].y; - vec3 lightColor = fyrox_lightsBlock.lightsColorRadius[i].xyz; - float radius = fyrox_lightsBlock.lightsColorRadius[i].w; - vec3 lightPosition = fyrox_lightsBlock.lightsPosition[i]; - vec3 direction = fyrox_lightsBlock.lightsDirection[i]; - - // Calculate lighting. - vec3 toFragment = fragmentPosition - lightPosition; - float distance = length(toFragment); - vec3 toFragmentNormalized = toFragment / distance; - float distanceAttenuation = S_LightDistanceAttenuation(distance, radius); - float spotAngleCos = dot(toFragmentNormalized, direction); - float directionalAttenuation = smoothstep(halfConeAngleCos, halfHotspotAngleCos, spotAngleCos); + let toFragment = fragmentPosition - lightPosition; + let distance = length(toFragment); + let toFragmentNormalized = toFragment / distance; + let distanceAttenuation = S_LightDistanceAttenuation(distance, radius); + let spotAngleCos = dot(toFragmentNormalized, direction); + let directionalAttenuation = smoothstep(halfConeAngleCos, halfHotspotAngleCos, spotAngleCos); lighting += lightColor * (distanceAttenuation * directionalAttenuation); } - FragColor = vec4(lighting, 1.0) * color * S_SRGBToLinear(texture(diffuseTexture, texCoord)); + return vec4f(lighting, 1.0) * color * S_SRGBToLinear(textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord)); } "#, ) ], -) \ No newline at end of file +) diff --git a/fyrox-material/src/shader/standard/standard_particle_system.shader b/fyrox-material/src/shader/standard/standard_particle_system.shader index db5b558ab9..26bb2d2960 100644 --- a/fyrox-material/src/shader/standard/standard_particle_system.shader +++ b/fyrox-material/src/shader/standard/standard_particle_system.shader @@ -9,7 +9,7 @@ ), ( name: "fyrox_sceneDepth", - kind: Texture(kind: Sampler2D, fallback: White), + kind: Texture(kind: DepthSampler2D, fallback: White), binding: 1 ), ( @@ -94,79 +94,81 @@ ), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 2) in float particleSize; - layout(location = 3) in float particleRotation; - layout(location = 4) in vec4 vertexColor; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(2) particleSize: f32, + @location(3) particleRotation: f32, + @location(4) vertexColor: vec4f, + }; - out vec2 texCoord; - out vec4 color; - out vec3 fragmentPosition; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + @location(1) color: vec4f, + @location(2) fragmentPosition: vec3f, + }; - void main() - { - color = S_SRGBToLinear(vertexColor); - texCoord = vertexTexCoord; - vec2 vertexOffset = S_RotateVec2(vertexTexCoord * 2.0 - 1.0, particleRotation); - vec4 worldPosition = fyrox_instanceData.worldMatrix * vec4(vertexPosition, 1.0); - vec3 offset = (vertexOffset.x * fyrox_cameraData.sideVector + vertexOffset.y * fyrox_cameraData.upVector) * particleSize; - vec4 finalPosition = worldPosition + vec4(offset.x, offset.y, offset.z, 0.0); - fragmentPosition = finalPosition.xyz; - gl_Position = fyrox_cameraData.viewProjectionMatrix * finalPosition; - } + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.color = S_SRGBToLinear(input.vertexColor); + output.texCoord = input.vertexTexCoord; + let vertexOffset = S_RotateVec2(input.vertexTexCoord * 2.0 - 1.0, input.particleRotation); + let worldPosition = fyrox_instanceData.worldMatrix * vec4f(input.vertexPosition, 1.0); + let offset = (vertexOffset.x * fyrox_cameraData.sideVector + vertexOffset.y * fyrox_cameraData.upVector) * input.particleSize; + let finalPosition = worldPosition + vec4f(offset.x, offset.y, offset.z, 0.0); + output.fragmentPosition = finalPosition.xyz; + output.position = fyrox_cameraData.viewProjectionMatrix * finalPosition; + return output; + } "#, fragment_shader: r#" - out vec4 FragColor; - - in vec2 texCoord; - in vec4 color; - in vec3 fragmentPosition; - - float toProjSpace(float z) - { - return (fyrox_cameraData.zFar * fyrox_cameraData.zNear) / (fyrox_cameraData.zFar - z * fyrox_cameraData.zRange); - } + fn toProjSpace(z: f32) -> f32 { + return (fyrox_cameraData.zFar * fyrox_cameraData.zNear) / (fyrox_cameraData.zFar - z * fyrox_cameraData.zRange); + } - void main() - { - ivec2 depthTextureSize = textureSize(fyrox_sceneDepth, 0); - vec2 pixelSize = vec2(1.0 / float(depthTextureSize.x), 1.0 / float(depthTextureSize.y)); - float sceneDepth = toProjSpace(texture(fyrox_sceneDepth, gl_FragCoord.xy * pixelSize).r); - float fragmentDepth = toProjSpace(gl_FragCoord.z); - float depthOpacity = smoothstep((sceneDepth - fragmentDepth) * properties.softBoundarySharpnessFactor, 0.0, 1.0); + @fragment fn fs_main( + @location(0) texCoord: vec2f, + @location(1) color: vec4f, + @location(2) fragmentPosition: vec3f, + @builtin(position) fragCoord: vec4f + ) -> @location(0) vec4f { + let depthTextureSize = textureDimensions(fyrox_sceneDepth_tex, 0); + let pixelSize = vec2f(1.0 / f32(depthTextureSize.x), 1.0 / f32(depthTextureSize.y)); + let sceneDepth = toProjSpace(textureSample(fyrox_sceneDepth_tex, fyrox_sceneDepth_samp, fragCoord.xy * pixelSize)); + let fragmentDepth = toProjSpace(fragCoord.z); + let depthOpacity = smoothstep((sceneDepth - fragmentDepth) * properties.softBoundarySharpnessFactor, 0.0, 1.0); - vec3 lighting; - if (properties.useLighting) { - lighting = fyrox_lightData.ambientLightColor.xyz; - for(int i = 0; i < min(fyrox_lightsBlock.lightCount, 16); ++i) { - // "Unpack" light parameters. - float halfHotspotAngleCos = fyrox_lightsBlock.lightsParameters[i].x; - float halfConeAngleCos = fyrox_lightsBlock.lightsParameters[i].y; - vec3 lightColor = fyrox_lightsBlock.lightsColorRadius[i].xyz; - float radius = fyrox_lightsBlock.lightsColorRadius[i].w; - vec3 lightPosition = fyrox_lightsBlock.lightsPosition[i]; - vec3 direction = fyrox_lightsBlock.lightsDirection[i]; + var lighting: vec3f; + if (properties.useLighting != 0u) { + lighting = fyrox_lightData.ambientLightColor.xyz; + for (var i: i32 = 0; i < min(i32(fyrox_lightsBlock.lightCount), 16); i++) { + let halfHotspotAngleCos = fyrox_lightsBlock.lightsParameters[i].x; + let halfConeAngleCos = fyrox_lightsBlock.lightsParameters[i].y; + let lightColor = fyrox_lightsBlock.lightsColorRadius[i].xyz; + let radius = fyrox_lightsBlock.lightsColorRadius[i].w; + let lightPosition = fyrox_lightsBlock.lightsPosition[i]; + let direction = fyrox_lightsBlock.lightsDirection[i]; - // Calculate lighting. - vec3 toFragment = fragmentPosition - lightPosition; - float distance = length(toFragment); - vec3 toFragmentNormalized = toFragment / distance; - float distanceAttenuation = S_LightDistanceAttenuation(distance, radius); - float spotAngleCos = dot(toFragmentNormalized, direction); - float directionalAttenuation = smoothstep(halfConeAngleCos, halfHotspotAngleCos, spotAngleCos); - lighting += lightColor * (distanceAttenuation * directionalAttenuation); - } - } else { - lighting = vec3(1.0); - } + let toFragment = fragmentPosition - lightPosition; + let dist = length(toFragment); + let toFragmentNormalized = toFragment / dist; + let distanceAttenuation = S_LightDistanceAttenuation(dist, radius); + let spotAngleCos = dot(toFragmentNormalized, direction); + let directionalAttenuation = smoothstep(halfConeAngleCos, halfHotspotAngleCos, spotAngleCos); + lighting += lightColor * (distanceAttenuation * directionalAttenuation); + } + } else { + lighting = vec3f(1.0); + } - FragColor = vec4(lighting, 1.0) * color * S_SRGBToLinear(texture(diffuseTexture, texCoord)).r; - FragColor.a *= depthOpacity; - } + var fragColor = vec4f(lighting, 1.0) * color * S_SRGBToLinear(textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord)).r; + fragColor.a *= depthOpacity; + return fragColor; + } "#, ) ], -) \ No newline at end of file +) diff --git a/fyrox-material/src/shader/standard/standard_sprite.shader b/fyrox-material/src/shader/standard/standard_sprite.shader index 428a5e5985..3b40e3bf55 100644 --- a/fyrox-material/src/shader/standard/standard_sprite.shader +++ b/fyrox-material/src/shader/standard/standard_sprite.shader @@ -61,42 +61,42 @@ ), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 2) in vec4 vertexParams; - layout(location = 3) in vec4 vertexColor; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(2) vertexParams: vec4f, + @location(3) vertexColor: vec4f, + }; - out vec2 texCoord; - out vec4 color; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + @location(1) color: vec4f, + }; - void main() - { - float size = vertexParams.x; - float rotation = vertexParams.y; - float dx = vertexParams.z; - float dy = vertexParams.w; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + let size = input.vertexParams.x; + let rotation = input.vertexParams.y; + let dx = input.vertexParams.z; + let dy = input.vertexParams.w; - texCoord = vertexTexCoord; - color = vertexColor; - vec2 vertexOffset = S_RotateVec2(vec2(dx, dy), rotation); - vec4 worldPosition = fyrox_instanceData.worldMatrix * vec4(vertexPosition, 1.0); - vec3 offset = (vertexOffset.x * fyrox_cameraData.sideVector + vertexOffset.y * fyrox_cameraData.upVector) * size; - gl_Position = fyrox_cameraData.viewProjectionMatrix * (worldPosition + vec4(offset.x, offset.y, offset.z, 0.0)); + output.texCoord = input.vertexTexCoord; + output.color = input.vertexColor; + let vertexOffset = S_RotateVec2(vec2f(dx, dy), rotation); + let worldPosition = fyrox_instanceData.worldMatrix * vec4f(input.vertexPosition, 1.0); + let offset = (vertexOffset.x * fyrox_cameraData.sideVector + vertexOffset.y * fyrox_cameraData.upVector) * size; + output.position = fyrox_cameraData.viewProjectionMatrix * (worldPosition + vec4f(offset.x, offset.y, offset.z, 0.0)); + return output; } "#, fragment_shader: r#" - out vec4 FragColor; - - in vec2 texCoord; - in vec4 color; - - void main() - { - FragColor = color * S_SRGBToLinear(texture(diffuseTexture, texCoord)); + @fragment fn fs_main(@location(0) texCoord: vec2f, @location(1) color: vec4f) -> @location(0) vec4f { + return color * S_SRGBToLinear(textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord)); } "#, ) ], -) \ No newline at end of file +) diff --git a/fyrox-material/src/shader/standard/terrain.shader b/fyrox-material/src/shader/standard/terrain.shader index 3b5e036627..47cf2bceff 100644 --- a/fyrox-material/src/shader/standard/terrain.shader +++ b/fyrox-material/src/shader/standard/terrain.shader @@ -2,510 +2,216 @@ name: "StandardTerrainShader", resources: [ - ( - name: "diffuseTexture", - kind: Texture(kind: Sampler2D, fallback: White), - binding: 0 - ), - ( - name: "normalTexture", - kind: Texture(kind: Sampler2D, fallback: Normal), - binding: 1 - ), - ( - name: "metallicTexture", - kind: Texture(kind: Sampler2D, fallback: Black), - binding: 2 - ), - ( - name: "roughnessTexture", - kind: Texture(kind: Sampler2D, fallback: White), - binding: 3 - ), - ( - name: "heightTexture", - kind: Texture(kind: Sampler2D, fallback: Black), - binding: 4 - ), - ( - name: "emissionTexture", - kind: Texture(kind: Sampler2D, fallback: Black), - binding: 5 - ), - ( - name: "lightmapTexture", - kind: Texture(kind: Sampler2D, fallback: Black), - binding: 6 - ), - ( - name: "aoTexture", - kind: Texture(kind: Sampler2D, fallback: White), - binding: 7 - ), - ( - name: "maskTexture", - kind: Texture(kind: Sampler2D, fallback: White), - binding: 8 - ), - ( - name: "heightMapTexture", - kind: Texture(kind: Sampler2D, fallback: White), - binding: 9 - ), - ( - name: "holeMaskTexture", - kind: Texture(kind: Sampler2D, fallback: White), - binding: 10 - ), + (name: "diffuseTexture", kind: Texture(kind: Sampler2D, fallback: White), binding: 0), + (name: "normalTexture", kind: Texture(kind: Sampler2D, fallback: Normal), binding: 1), + (name: "metallicTexture", kind: Texture(kind: Sampler2D, fallback: Black), binding: 2), + (name: "roughnessTexture", kind: Texture(kind: Sampler2D, fallback: White), binding: 3), + (name: "heightTexture", kind: Texture(kind: Sampler2D, fallback: Black), binding: 4), + (name: "emissionTexture", kind: Texture(kind: Sampler2D, fallback: Black), binding: 5), + (name: "lightmapTexture", kind: Texture(kind: Sampler2D, fallback: Black), binding: 6), + (name: "aoTexture", kind: Texture(kind: Sampler2D, fallback: White), binding: 7), + (name: "maskTexture", kind: Texture(kind: Sampler2D, fallback: White), binding: 8), + (name: "heightMapTexture", kind: Texture(kind: Sampler2D, fallback: White), binding: 9), + (name: "holeMaskTexture", kind: Texture(kind: Sampler2D, fallback: White), binding: 10), ( name: "properties", kind: PropertyGroup([ - ( - name: "nodeUvOffsets", - kind: Vector4(value: (0.0, 0.0, 0.0, 0.0)), - ), - ( - name: "texCoordScale", - kind: Vector2(value: (1.0, 1.0)), - ), - ( - name: "layerIndex", - kind: UInt(value: 0), - ), - ( - name: "emissionStrength", - kind: Vector3(value: (2.0, 2.0, 2.0)), - ), - ( - name: "diffuseColor", - kind: Color(r: 255, g: 255, b: 255, a: 255), - ), - ( - name: "parallaxCenter", - kind: Float(value: 0.0), - ), - ( - name: "parallaxScale", - kind: Float(value: 0.08), - ), + (name: "nodeUvOffsets", kind: Vector4(value: (0.0, 0.0, 0.0, 0.0))), + (name: "texCoordScale", kind: Vector2(value: (1.0, 1.0))), + (name: "layerIndex", kind: UInt(value: 0)), + (name: "emissionStrength", kind: Vector3(value: (2.0, 2.0, 2.0))), + (name: "diffuseColor", kind: Color(r: 255, g: 255, b: 255, a: 255)), + (name: "parallaxCenter", kind: Float(value: 0.0)), + (name: "parallaxScale", kind: Float(value: 0.08)), ]), binding: 0 ), - ( - name: "fyrox_instanceData", - kind: PropertyGroup([ - // Autogenerated - ]), - binding: 1 - ), - ( - name: "fyrox_graphicsSettings", - kind: PropertyGroup([ - // Autogenerated - ]), - binding: 2 - ), - ( - name: "fyrox_cameraData", - kind: PropertyGroup([ - // Autogenerated - ]), - binding: 3 - ), - ( - name: "fyrox_lightData", - kind: PropertyGroup([ - // Autogenerated - ]), - binding: 4 - ), + (name: "fyrox_instanceData", kind: PropertyGroup([]), binding: 1), + (name: "fyrox_graphicsSettings", kind: PropertyGroup([]), binding: 2), + (name: "fyrox_cameraData", kind: PropertyGroup([]), binding: 3), + (name: "fyrox_lightData", kind: PropertyGroup([]), binding: 4), ], passes: [ ( name: "GBuffer", - draw_parameters: DrawParameters( - cull_face: Some(Back), - color_write: ColorMask( - red: true, - green: true, - blue: true, - alpha: true, - ), - depth_write: true, - stencil_test: None, - depth_test: Some(LessOrEqual), - blend: Some(BlendParameters( - func: BlendFunc( - sfactor: SrcAlpha, - dfactor: OneMinusSrcAlpha, - alpha_sfactor: SrcAlpha, - alpha_dfactor: OneMinusSrcAlpha, - ), - equation: BlendEquation( - rgb: Add, - alpha: Max - ) - )), - stencil_op: StencilOp( - fail: Keep, - zfail: Keep, - zpass: Keep, - write_mask: 0xFFFF_FFFF, - ), - scissor_box: None - ), + draw_parameters: DrawParameters(cull_face: Some(Back), color_write: ColorMask(red: true, green: true, blue: true, alpha: true), depth_write: true, stencil_test: None, depth_test: Some(LessOrEqual), blend: Some(BlendParameters(func: BlendFunc(sfactor: SrcAlpha, dfactor: OneMinusSrcAlpha, alpha_sfactor: SrcAlpha, alpha_dfactor: OneMinusSrcAlpha), equation: BlendEquation(rgb: Add, alpha: Max))), stencil_op: StencilOp(fail: Keep, zfail: Keep, zpass: Keep, write_mask: 0xFFFF_FFFF), scissor_box: None), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 2) in vec3 vertexNormal; - layout(location = 3) in vec4 vertexTangent; - layout(location = 6) in vec2 vertexSecondTexCoord; - - out vec3 position; - out vec3 normal; - out vec2 texCoord; - out vec3 tangent; - out vec3 binormal; - out vec2 secondTexCoord; - - void main() - { - // Each node has tex coords in [0; 1] range, here we must scale and offset it - // to match the actual position. - vec2 actualTexCoords = vec2(vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy); - vec2 heightSize = vec2(textureSize(heightMapTexture, 0)); - vec2 innerSize = heightSize - 3.0; - vec2 pixelSize = 1.0 / heightSize; - vec2 heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; - float height = texture(heightMapTexture, heightCoords).r; - vec4 finalVertexPosition = vec4(vertexPosition.x, height, vertexPosition.z, 1.0); - float hx0 = texture(heightMapTexture, heightCoords + vec2(-1.0, 0.0) * pixelSize).r; - float hx1 = texture(heightMapTexture, heightCoords + vec2(1.0, 0.0) * pixelSize).r; - float hy0 = texture(heightMapTexture, heightCoords + vec2(0.0, -1.0) * pixelSize).r; - float hy1 = texture(heightMapTexture, heightCoords + vec2(0.0, 1.0) * pixelSize).r; - - vec3 n = vec3((hx0 - hx1) / 2.0, 1.0, (hy0 - hy1) / 2.0); - vec3 tan = vec3(n.y, -n.x, 0.0); - - mat3 nm = mat3(fyrox_instanceData.worldMatrix); - normal = normalize(nm * n); - tangent = normalize(nm * tan); - binormal = normalize(-1.0 * cross(normal, tangent)); - texCoord = actualTexCoords; - position = vec3(fyrox_instanceData.worldMatrix * finalVertexPosition); - secondTexCoord = vertexSecondTexCoord; - gl_Position = fyrox_instanceData.worldViewProjection * finalVertexPosition; + struct VertexInput { + @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f, @location(2) vertexNormal: vec3f, + @location(3) vertexTangent: vec4f, @location(6) vertexSecondTexCoord: vec2f, + }; + struct VertexOutput { + @builtin(position) position: vec4f, @location(0) outPosition: vec3f, @location(1) outNormal: vec3f, + @location(2) texCoord: vec2f, @location(3) outTangent: vec3f, @location(4) outBinormal: vec3f, @location(5) secondTexCoord: vec2f, + }; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + let actualTexCoords = input.vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy; + let heightSize = vec2f(textureDimensions(heightMapTexture_tex, 0)); + let innerSize = heightSize - 3.0; + let pixelSize = 1.0 / heightSize; + let heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; + let height = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords).r; + let finalVertexPosition = vec4f(input.vertexPosition.x, height, input.vertexPosition.z, 1.0); + let hx0 = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords + vec2f(-1.0, 0.0) * pixelSize).r; + let hx1 = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords + vec2f(1.0, 0.0) * pixelSize).r; + let hy0 = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords + vec2f(0.0, -1.0) * pixelSize).r; + let hy1 = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords + vec2f(0.0, 1.0) * pixelSize).r; + let n = vec3f((hx0 - hx1) / 2.0, 1.0, (hy0 - hy1) / 2.0); + let tan = vec3f(n.y, -n.x, 0.0); + let nm = mat3x3f(fyrox_instanceData.worldMatrix[0].xyz, fyrox_instanceData.worldMatrix[1].xyz, fyrox_instanceData.worldMatrix[2].xyz); + output.outNormal = normalize(nm * n); + output.outTangent = normalize(nm * tan); + output.outBinormal = normalize(-1.0 * cross(output.outNormal, output.outTangent)); + output.texCoord = actualTexCoords; + output.outPosition = (fyrox_instanceData.worldMatrix * finalVertexPosition).xyz; + output.secondTexCoord = input.vertexSecondTexCoord; + output.position = fyrox_instanceData.worldViewProjection * finalVertexPosition; + return output; } "#, fragment_shader: r#" - layout(location = 0) out vec4 outColor; - layout(location = 1) out vec4 outNormal; - layout(location = 2) out vec4 outAmbient; - layout(location = 3) out vec4 outMaterial; - layout(location = 4) out uint outDecalMask; - - in vec3 position; - in vec3 normal; - in vec2 texCoord; - in vec3 tangent; - in vec3 binormal; - in vec2 secondTexCoord; - - void main() - { - if (texture(holeMaskTexture, texCoord).r < 0.5) discard; - - mat3 tangentSpace = mat3(tangent, binormal, normal); - vec3 toFragment = normalize(position - fyrox_cameraData.position); - - vec2 tc; - if (fyrox_graphicsSettings.usePOM) { - vec3 toFragmentTangentSpace = normalize(transpose(tangentSpace) * toFragment); - tc = S_ComputeParallaxTextureCoordinates( - heightTexture, - toFragmentTangentSpace, - texCoord * properties.texCoordScale, - properties.parallaxCenter, - properties.parallaxScale - ); - } else { - tc = texCoord * properties.texCoordScale; - } - - outColor = properties.diffuseColor * texture(diffuseTexture, tc); - - vec3 n = normalize(texture(normalTexture, tc).xyz * 2.0 - 1.0); - outNormal = vec4(normalize(tangentSpace * n) * 0.5 + 0.5, 1.0); - - outMaterial.x = texture(metallicTexture, tc).r; - outMaterial.y = texture(roughnessTexture, tc).r; - outMaterial.z = texture(aoTexture, tc).r; - outMaterial.a = 1.0; - - outAmbient.xyz = properties.emissionStrength * texture(emissionTexture, tc).rgb + texture(lightmapTexture, secondTexCoord).rgb; - outAmbient.a = 1.0; - - outDecalMask = properties.layerIndex; - - float mask = texture(maskTexture, texCoord).r; - - outColor.a = mask; - outAmbient.a = mask; - outNormal.a = mask; - outMaterial.a = mask; + struct FragmentOutput { @location(0) outColor: vec4f, @location(1) outNormal: vec4f, @location(2) outAmbient: vec4f, @location(3) outMaterial: vec4f, @location(4) outDecalMask: u32 }; + @fragment fn fs_main(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) texCoord: vec2f, @location(3) tangent: vec3f, @location(4) binormal: vec3f, @location(5) secondTexCoord: vec2f) -> FragmentOutput { + var output: FragmentOutput; + if (textureSample(holeMaskTexture_tex, holeMaskTexture_samp, texCoord).r < 0.5) { discard; } + let tangentSpace = mat3x3f(tangent, binormal, normal); + let toFragment = normalize(position - fyrox_cameraData.position); + var tc: vec2f; + if (fyrox_graphicsSettings.usePOM != 0u) { tc = S_ComputeParallaxTextureCoordinates(heightTexture_tex, heightTexture_samp, normalize(transpose(tangentSpace) * toFragment), texCoord * properties.texCoordScale, properties.parallaxCenter, properties.parallaxScale); } else { tc = texCoord * properties.texCoordScale; } + output.outColor = properties.diffuseColor * textureSample(diffuseTexture_tex, diffuseTexture_samp, tc); + let n = normalize(textureSample(normalTexture_tex, normalTexture_samp, tc).xyz * 2.0 - 1.0); + output.outNormal = vec4f(normalize(tangentSpace * n) * 0.5 + 0.5, 1.0); + output.outMaterial.x = textureSample(metallicTexture_tex, metallicTexture_samp, tc).r; + output.outMaterial.y = textureSample(roughnessTexture_tex, roughnessTexture_samp, tc).r; + output.outMaterial.z = textureSample(aoTexture_tex, aoTexture_samp, tc).r; output.outMaterial.a = 1.0; + output.outAmbient = vec4f(properties.emissionStrength * textureSample(emissionTexture_tex, emissionTexture_samp, tc).rgb + textureSample(lightmapTexture_tex, lightmapTexture_samp, secondTexCoord).rgb, 1.0); output.outDecalMask = properties.layerIndex; + let mask = textureSample(maskTexture_tex, maskTexture_samp, texCoord).r; + output.outColor.a = mask; output.outAmbient.a = mask; output.outNormal.a = mask; output.outMaterial.a = mask; + return output; } "#, ), ( name: "Forward", - draw_parameters: DrawParameters( - cull_face: Some(Back), - color_write: ColorMask( - red: true, - green: true, - blue: true, - alpha: true, - ), - depth_write: true, - stencil_test: None, - depth_test: Some(Less), - blend: Some(BlendParameters( - func: BlendFunc( - sfactor: SrcAlpha, - dfactor: OneMinusSrcAlpha, - alpha_sfactor: SrcAlpha, - alpha_dfactor: OneMinusSrcAlpha, - ), - equation: BlendEquation( - rgb: Add, - alpha: Max - ) - )), - stencil_op: StencilOp( - fail: Keep, - zfail: Keep, - zpass: Keep, - write_mask: 0xFFFF_FFFF, - ), - scissor_box: None - ), + draw_parameters: DrawParameters(cull_face: Some(Back), color_write: ColorMask(red: true, green: true, blue: true, alpha: true), depth_write: true, stencil_test: None, depth_test: Some(Less), blend: Some(BlendParameters(func: BlendFunc(sfactor: SrcAlpha, dfactor: OneMinusSrcAlpha, alpha_sfactor: SrcAlpha, alpha_dfactor: OneMinusSrcAlpha), equation: BlendEquation(rgb: Add, alpha: Max))), stencil_op: StencilOp(fail: Keep, zfail: Keep, zpass: Keep, write_mask: 0xFFFF_FFFF), scissor_box: None), vertex_shader: - r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - - out vec3 position; - out vec2 texCoord; - - void main() - { - vec2 actualTexCoords = vec2(vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy); - vec2 heightSize = vec2(textureSize(heightMapTexture, 0)); - vec2 innerSize = heightSize - 3.0; - vec2 pixelSize = 1.0 / heightSize; - vec2 heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; - float height = texture(heightMapTexture, heightCoords).r; - vec4 finalVertexPosition = vec4(vertexPosition.x, height, vertexPosition.z, 1.0); - - gl_Position = fyrox_instanceData.worldViewProjection * finalVertexPosition; - texCoord = actualTexCoords; + r#" + struct VertexInput { @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f }; + struct VertexOutput { @builtin(position) position: vec4f, @location(0) outPosition: vec3f, @location(1) texCoord: vec2f }; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + let actualTexCoords = input.vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy; + let heightSize = vec2f(textureDimensions(heightMapTexture_tex, 0)); + let innerSize = heightSize - 3.0; + let pixelSize = 1.0 / heightSize; + let heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; + let height = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords).r; + let finalVertexPosition = vec4f(input.vertexPosition.x, height, input.vertexPosition.z, 1.0); + output.position = fyrox_instanceData.worldViewProjection * finalVertexPosition; + output.texCoord = actualTexCoords; + return output; } - "#, - - fragment_shader: - r#" - uniform vec4 diffuseColor; - - out vec4 FragColor; - - in vec2 texCoord; - - void main() - { - if (texture(holeMaskTexture, texCoord).r < 0.5) discard; - FragColor = properties.diffuseColor * S_SRGBToLinear(texture(diffuseTexture, texCoord)); + "#, + fragment_shader: + r#" + @fragment fn fs_main(@location(1) texCoord: vec2f) -> @location(0) vec4f { + if (textureSample(holeMaskTexture_tex, holeMaskTexture_samp, texCoord).r < 0.5) { discard; } + return properties.diffuseColor * S_SRGBToLinear(textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord)); } - "#, + "#, ), ( name: "SpotShadow", - - draw_parameters: DrawParameters ( - cull_face: Some(Back), - color_write: ColorMask( - red: false, - green: false, - blue: false, - alpha: false, - ), - depth_write: true, - stencil_test: None, - depth_test: Some(Less), - blend: None, - stencil_op: StencilOp( - fail: Keep, - zfail: Keep, - zpass: Keep, - write_mask: 0xFFFF_FFFF, - ), - scissor_box: None - ), - + draw_parameters: DrawParameters(cull_face: Some(Back), color_write: ColorMask(red: false, green: false, blue: false, alpha: false), depth_write: true, stencil_test: None, depth_test: Some(Less), blend: None, stencil_op: StencilOp(fail: Keep, zfail: Keep, zpass: Keep, write_mask: 0xFFFF_FFFF), scissor_box: None), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - - out vec2 texCoord; - - void main() - { - vec2 actualTexCoords = vec2(vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy); - vec2 heightSize = vec2(textureSize(heightMapTexture, 0)); - vec2 innerSize = heightSize - 3.0; - vec2 pixelSize = 1.0 / heightSize; - vec2 heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; - float height = texture(heightMapTexture, heightCoords).r; - vec4 finalVertexPosition = vec4(vertexPosition.x, height, vertexPosition.z, 1.0); - - gl_Position = fyrox_instanceData.worldViewProjection * finalVertexPosition; - texCoord = actualTexCoords; + struct VertexInput { @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f }; + struct VertexOutput { @builtin(position) position: vec4f, @location(0) texCoord: vec2f }; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + let actualTexCoords = input.vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy; + let heightSize = vec2f(textureDimensions(heightMapTexture_tex, 0)); + let innerSize = heightSize - 3.0; + let pixelSize = 1.0 / heightSize; + let heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; + let height = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords).r; + let finalVertexPosition = vec4f(input.vertexPosition.x, height, input.vertexPosition.z, 1.0); + output.position = fyrox_instanceData.worldViewProjection * finalVertexPosition; + output.texCoord = actualTexCoords; + return output; } "#, - fragment_shader: r#" - in vec2 texCoord; - - void main() - { - if (texture(holeMaskTexture, texCoord).r < 0.5) discard; - if (texture(diffuseTexture, texCoord).a < 0.2) discard; + @fragment fn fs_main(@location(0) texCoord: vec2f) { + if (textureSample(holeMaskTexture_tex, holeMaskTexture_samp, texCoord).r < 0.5) { discard; } + if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; } } "#, ), ( name: "DirectionalShadow", - - draw_parameters: DrawParameters ( - cull_face: Some(Back), - color_write: ColorMask( - red: false, - green: false, - blue: false, - alpha: false, - ), - depth_write: true, - stencil_test: None, - depth_test: Some(Less), - blend: None, - stencil_op: StencilOp( - fail: Keep, - zfail: Keep, - zpass: Keep, - write_mask: 0xFFFF_FFFF, - ), - scissor_box: None - ), - + draw_parameters: DrawParameters(cull_face: Some(Back), color_write: ColorMask(red: false, green: false, blue: false, alpha: false), depth_write: true, stencil_test: None, depth_test: Some(Less), blend: None, stencil_op: StencilOp(fail: Keep, zfail: Keep, zpass: Keep, write_mask: 0xFFFF_FFFF), scissor_box: None), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - - out vec2 texCoord; - - void main() - { - vec2 actualTexCoords = vec2(vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy); - vec2 heightSize = vec2(textureSize(heightMapTexture, 0)); - vec2 innerSize = heightSize - 3.0; - vec2 pixelSize = 1.0 / heightSize; - vec2 heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; - float height = texture(heightMapTexture, heightCoords).r; - vec4 finalVertexPosition = vec4(vertexPosition.x, height, vertexPosition.z, 1.0); - - gl_Position = fyrox_instanceData.worldViewProjection * finalVertexPosition; - texCoord = actualTexCoords; + struct VertexInput { @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f }; + struct VertexOutput { @builtin(position) position: vec4f, @location(0) texCoord: vec2f }; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + let actualTexCoords = input.vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy; + let heightSize = vec2f(textureDimensions(heightMapTexture_tex, 0)); + let innerSize = heightSize - 3.0; + let pixelSize = 1.0 / heightSize; + let heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; + let height = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords).r; + let finalVertexPosition = vec4f(input.vertexPosition.x, height, input.vertexPosition.z, 1.0); + output.position = fyrox_instanceData.worldViewProjection * finalVertexPosition; + output.texCoord = actualTexCoords; + return output; } "#, - fragment_shader: r#" - in vec2 texCoord; - - void main() - { - if (texture(holeMaskTexture, texCoord).r < 0.5) discard; - if (texture(diffuseTexture, texCoord).a < 0.2) discard; + @fragment fn fs_main(@location(0) texCoord: vec2f) { + if (textureSample(holeMaskTexture_tex, holeMaskTexture_samp, texCoord).r < 0.5) { discard; } + if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; } } "#, ), ( name: "PointShadow", - - draw_parameters: DrawParameters ( - cull_face: Some(Back), - color_write: ColorMask( - red: true, - green: true, - blue: true, - alpha: true, - ), - depth_write: true, - stencil_test: None, - depth_test: Some(Less), - blend: None, - stencil_op: StencilOp( - fail: Keep, - zfail: Keep, - zpass: Keep, - write_mask: 0xFFFF_FFFF, - ), - scissor_box: None - ), - + draw_parameters: DrawParameters(cull_face: Some(Back), color_write: ColorMask(red: true, green: true, blue: true, alpha: true), depth_write: true, stencil_test: None, depth_test: Some(Less), blend: None, stencil_op: StencilOp(fail: Keep, zfail: Keep, zpass: Keep, write_mask: 0xFFFF_FFFF), scissor_box: None), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - - out vec2 texCoord; - out vec3 worldPosition; - - void main() - { - vec2 actualTexCoords = vec2(vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy); - vec2 heightSize = vec2(textureSize(heightMapTexture, 0)); - vec2 innerSize = heightSize - 3.0; - vec2 pixelSize = 1.0 / heightSize; - vec2 heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; - float height = texture(heightMapTexture, heightCoords).r; - vec4 finalVertexPosition = vec4(vertexPosition.x, height, vertexPosition.z, 1.0); - - gl_Position = fyrox_instanceData.worldViewProjection * finalVertexPosition; - worldPosition = (fyrox_instanceData.worldMatrix * finalVertexPosition).xyz; - texCoord = actualTexCoords; + struct VertexInput { @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f }; + struct VertexOutput { @builtin(position) position: vec4f, @location(0) texCoord: vec2f, @location(1) worldPosition: vec3f }; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + let actualTexCoords = input.vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy; + let heightSize = vec2f(textureDimensions(heightMapTexture_tex, 0)); + let innerSize = heightSize - 3.0; + let pixelSize = 1.0 / heightSize; + let heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; + let height = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords).r; + let finalVertexPosition = vec4f(input.vertexPosition.x, height, input.vertexPosition.z, 1.0); + output.position = fyrox_instanceData.worldViewProjection * finalVertexPosition; + output.worldPosition = (fyrox_instanceData.worldMatrix * finalVertexPosition).xyz; + output.texCoord = actualTexCoords; + return output; } "#, - fragment_shader: r#" - in vec2 texCoord; - in vec3 worldPosition; - - layout(location = 0) out float depth; - - void main() - { - if (texture(holeMaskTexture, texCoord).r < 0.5) discard; - if (texture(diffuseTexture, texCoord).a < 0.2) discard; - depth = length(fyrox_lightData.lightPosition - worldPosition); + @fragment fn fs_main(@location(0) texCoord: vec2f, @location(1) worldPosition: vec3f) -> @location(0) f32 { + if (textureSample(holeMaskTexture_tex, holeMaskTexture_samp, texCoord).r < 0.5) { discard; } + if (textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord).a < 0.2) { discard; } + return length(fyrox_lightData.lightPosition - worldPosition); } "#, ) ], -) \ No newline at end of file +) diff --git a/fyrox-material/src/shader/standard/tile.shader b/fyrox-material/src/shader/standard/tile.shader index 4d5f842433..aa42f29ad0 100644 --- a/fyrox-material/src/shader/standard/tile.shader +++ b/fyrox-material/src/shader/standard/tile.shader @@ -68,56 +68,57 @@ ), vertex_shader: r#" - layout(location = 0) in vec3 vertexPosition; - layout(location = 1) in vec2 vertexTexCoord; - layout(location = 2) in vec4 vertexColor; + struct VertexInput { + @location(0) vertexPosition: vec3f, + @location(1) vertexTexCoord: vec2f, + @location(2) vertexColor: vec4f, + }; - out vec2 texCoord; - out vec4 color; - out vec3 fragmentPosition; + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + @location(1) color: vec4f, + @location(2) fragmentPosition: vec3f, + }; - void main() - { - texCoord = vertexTexCoord / vec2(textureSize(diffuseTexture, 0)); - fragmentPosition = (fyrox_instanceData.worldMatrix * vec4(vertexPosition, 1.0)).xyz; - gl_Position = fyrox_instanceData.worldViewProjection * vec4(vertexPosition, 1.0); - color = vertexColor; + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.texCoord = input.vertexTexCoord / vec2f(textureDimensions(diffuseTexture_tex, 0)); + output.fragmentPosition = (fyrox_instanceData.worldMatrix * vec4f(input.vertexPosition, 1.0)).xyz; + output.position = fyrox_instanceData.worldViewProjection * vec4f(input.vertexPosition, 1.0); + output.color = input.vertexColor; + return output; } "#, fragment_shader: r#" - out vec4 FragColor; + @fragment fn fs_main( + @location(0) texCoord: vec2f, + @location(1) color: vec4f, + @location(2) fragmentPosition: vec3f + ) -> @location(0) vec4f { + var lighting = fyrox_lightData.ambientLightColor.xyz; + for (var i: i32 = 0; i < min(i32(fyrox_lightsBlock.lightCount), 16); i++) { + let halfHotspotAngleCos = fyrox_lightsBlock.lightsParameters[i].x; + let halfConeAngleCos = fyrox_lightsBlock.lightsParameters[i].y; + let lightColor = fyrox_lightsBlock.lightsColorRadius[i].xyz; + let radius = fyrox_lightsBlock.lightsColorRadius[i].w; + let lightPosition = fyrox_lightsBlock.lightsPosition[i]; + let direction = fyrox_lightsBlock.lightsDirection[i]; - in vec2 texCoord; - in vec4 color; - in vec3 fragmentPosition; - - void main() - { - vec3 lighting = fyrox_lightData.ambientLightColor.xyz; - for(int i = 0; i < min(fyrox_lightsBlock.lightCount, 16); ++i) { - // "Unpack" light parameters. - float halfHotspotAngleCos = fyrox_lightsBlock.lightsParameters[i].x; - float halfConeAngleCos = fyrox_lightsBlock.lightsParameters[i].y; - vec3 lightColor = fyrox_lightsBlock.lightsColorRadius[i].xyz; - float radius = fyrox_lightsBlock.lightsColorRadius[i].w; - vec3 lightPosition = fyrox_lightsBlock.lightsPosition[i]; - vec3 direction = fyrox_lightsBlock.lightsDirection[i]; - - // Calculate lighting. - vec3 toFragment = fragmentPosition - lightPosition; - float distance = length(toFragment); - vec3 toFragmentNormalized = toFragment / distance; - float distanceAttenuation = S_LightDistanceAttenuation(distance, radius); - float spotAngleCos = dot(toFragmentNormalized, direction); - float directionalAttenuation = smoothstep(halfConeAngleCos, halfHotspotAngleCos, spotAngleCos); + let toFragment = fragmentPosition - lightPosition; + let distance = length(toFragment); + let toFragmentNormalized = toFragment / distance; + let distanceAttenuation = S_LightDistanceAttenuation(distance, radius); + let spotAngleCos = dot(toFragmentNormalized, direction); + let directionalAttenuation = smoothstep(halfConeAngleCos, halfHotspotAngleCos, spotAngleCos); lighting += lightColor * (distanceAttenuation * directionalAttenuation); } - FragColor = vec4(lighting, 1.0) * color * S_SRGBToLinear(texture(diffuseTexture, texCoord)); + return vec4f(lighting, 1.0) * color * S_SRGBToLinear(textureSample(diffuseTexture_tex, diffuseTexture_samp, texCoord)); } "#, ) ], -) \ No newline at end of file +) diff --git a/fyrox-material/src/shader/standard/widget.shader b/fyrox-material/src/shader/standard/widget.shader index dfb41726b7..d2a44d3864 100644 --- a/fyrox-material/src/shader/standard/widget.shader +++ b/fyrox-material/src/shader/standard/widget.shader @@ -19,96 +19,85 @@ ( name: "Forward", - // Drawing parameters explicitly controlled from code. - vertex_shader: r#" - layout (location = 0) in vec2 vertexPosition; - layout (location = 1) in vec2 vertexTexCoord; - layout (location = 2) in vec4 vertexColor; - - out vec2 texCoord; - out vec4 color; - out vec2 localPosition; - - void main() - { - texCoord = vertexTexCoord; - color = vertexColor; - - localPosition = vertexPosition; - vec3 worldSpaceVertex = fyrox_widgetData.worldMatrix * vec3(vertexPosition, 1.0); - gl_Position = fyrox_widgetData.projectionMatrix * vec4(worldSpaceVertex, 1.0); + struct VertexInput { + @location(0) vertexPosition: vec2f, + @location(1) vertexTexCoord: vec2f, + @location(2) vertexColor: vec4f, + }; + + struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoord: vec2f, + @location(1) color: vec4f, + @location(2) localPosition: vec2f, + }; + + @vertex fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.texCoord = input.vertexTexCoord; + output.color = input.vertexColor; + output.localPosition = input.vertexPosition; + let worldSpaceVertex = fyrox_widgetData.worldMatrix * vec3f(input.vertexPosition, 1.0); + output.position = fyrox_widgetData.projectionMatrix * vec4f(worldSpaceVertex, 1.0); + return output; } "#, fragment_shader: r#" - // IMPORTANT: UI is rendered in sRGB color space! - out vec4 fragColor; - - in vec2 texCoord; - in vec4 color; - in vec2 localPosition; - - float project_point(vec2 a, vec2 b, vec2 p) { - vec2 ab = b - a; + fn project_point(a: vec2f, b: vec2f, p: vec2f) -> f32 { + let ab = b - a; return clamp(dot(p - a, ab) / dot(ab, ab), 0.0, 1.0); } - int find_stop_index(float t) { - int idx = 0; - - for (int i = 0; i < fyrox_widgetData.gradientPointCount; ++i) { - if (t > fyrox_widgetData.gradientStops[i]) { + fn find_stop_index(t: f32) -> i32 { + var idx: i32 = 0; + for (var i: i32 = 0; i < i32(fyrox_widgetData.gradientPointCount); i++) { + if (t > fyrox_widgetData.gradientStops[i].x) { idx = i; } } - return idx; } - void main() - { - vec2 size = vec2(fyrox_widgetData.boundsMax.x - fyrox_widgetData.boundsMin.x, fyrox_widgetData.boundsMax.y - fyrox_widgetData.boundsMin.y); - vec2 normalizedPosition = (localPosition - fyrox_widgetData.boundsMin) / size; + @fragment fn fs_main( + @location(0) texCoord: vec2f, + @location(1) color: vec4f, + @location(2) localPosition: vec2f + ) -> @location(0) vec4f { + let size = vec2f(fyrox_widgetData.boundsMax.x - fyrox_widgetData.boundsMin.x, fyrox_widgetData.boundsMax.y - fyrox_widgetData.boundsMin.y); + let normalizedPosition = (localPosition - fyrox_widgetData.boundsMin) / size; + var fragColor: vec4f; if (fyrox_widgetData.brushType == 0) { - // Solid color fragColor = fyrox_widgetData.solidColor; } else { - // Gradient brush - float t = 0.0; - + var t: f32 = 0.0; if (fyrox_widgetData.brushType == 1) { - // Linear gradient t = project_point(fyrox_widgetData.gradientOrigin, fyrox_widgetData.gradientEnd, normalizedPosition); } else if (fyrox_widgetData.brushType == 2) { - // Radial gradient t = clamp(length(normalizedPosition - fyrox_widgetData.gradientOrigin), 0.0, 1.0); } - - int current = find_stop_index(t); - int next = min(current + 1, fyrox_widgetData.gradientPointCount); - float delta = fyrox_widgetData.gradientStops[next] - fyrox_widgetData.gradientStops[current]; - float mix_factor = (t - fyrox_widgetData.gradientStops[current]) / delta; + let current = find_stop_index(t); + let next = min(current + 1, i32(fyrox_widgetData.gradientPointCount)); + let delta = fyrox_widgetData.gradientStops[next] - fyrox_widgetData.gradientStops[current]; + let mix_factor = (t - fyrox_widgetData.gradientStops[current]) / delta; fragColor = mix(fyrox_widgetData.gradientColors[current], fyrox_widgetData.gradientColors[next], mix_factor); } - vec4 diffuseColor = texture(fyrox_widgetTexture, texCoord); + let diffuseColor = textureSample(fyrox_widgetTexture_tex, fyrox_widgetTexture_samp, texCoord); - if (fyrox_widgetData.isFont) - { + if (fyrox_widgetData.isFont != 0u) { fragColor.a *= diffuseColor.r; - } - else - { + } else { fragColor *= diffuseColor; } fragColor.a *= fyrox_widgetData.opacity; - fragColor *= color; + return fragColor; } "#, ), @@ -138,24 +127,18 @@ vertex_shader: r#" - layout (location = 0) in vec2 vertexPosition; - - void main() - { - vec3 worldSpaceVertex = fyrox_widgetData.worldMatrix * vec3(vertexPosition, 1.0); - gl_Position = fyrox_widgetData.projectionMatrix * vec4(worldSpaceVertex, 1.0); + @vertex fn vs_main(@location(0) vertexPosition: vec2f) -> @builtin(position) vec4f { + let worldSpaceVertex = fyrox_widgetData.worldMatrix * vec3f(vertexPosition, 1.0); + return fyrox_widgetData.projectionMatrix * vec4f(worldSpaceVertex, 1.0); } "#, fragment_shader: r#" - out vec4 FragColor; - - void main() - { - FragColor = vec4(1.0); + @fragment fn fs_main() -> @location(0) vec4f { + return vec4f(1.0); } "#, ) ] -) \ No newline at end of file +) From d9f26d902dcf12a6cecdf8f5374f45807611ac46 Mon Sep 17 00:00:00 2001 From: nice0hack Date: Tue, 7 Jul 2026 09:42:55 +0500 Subject: [PATCH 08/34] fix gui rendering --- fyrox-graphics-wgpu/src/framebuffer.rs | 131 +++++++++++++++--- fyrox-graphics-wgpu/src/program.rs | 5 + fyrox-graphics-wgpu/src/server.rs | 46 +++++- .../src/shader/standard/widget.shader | 15 +- 4 files changed, 176 insertions(+), 21 deletions(-) diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index ab8a1e790d..df776b1f22 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -33,6 +33,7 @@ use fyrox_graphics::{ gpu_texture::{image_2d_size_bytes, CubeMapFace, GpuTexture, GpuTextureKind}, CompareFunc, CullFace, DrawParameters, ElementRange, BlendMode, }; +use std::cell::{Cell, RefCell}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::rc::Weak; @@ -83,6 +84,32 @@ fn blend_factor_to_wgpu(f: fyrox_graphics::BlendFactor) -> wgpu::BlendFactor { } } +fn stencil_action_to_wgpu(a: fyrox_graphics::StencilAction) -> wgpu::StencilOperation { + match a { + fyrox_graphics::StencilAction::Keep => wgpu::StencilOperation::Keep, + fyrox_graphics::StencilAction::Zero => wgpu::StencilOperation::Zero, + fyrox_graphics::StencilAction::Replace => wgpu::StencilOperation::Replace, + fyrox_graphics::StencilAction::Incr => wgpu::StencilOperation::IncrementClamp, + fyrox_graphics::StencilAction::IncrWrap => wgpu::StencilOperation::IncrementWrap, + fyrox_graphics::StencilAction::Decr => wgpu::StencilOperation::DecrementClamp, + fyrox_graphics::StencilAction::DecrWrap => wgpu::StencilOperation::DecrementWrap, + fyrox_graphics::StencilAction::Invert => wgpu::StencilOperation::Invert, + } +} + +fn stencil_face_state(compare: CompareFunc, op: &fyrox_graphics::StencilOp) -> wgpu::StencilFaceState { + wgpu::StencilFaceState { + compare: compare_func_to_wgpu(compare), + fail_op: stencil_action_to_wgpu(op.fail), + depth_fail_op: stencil_action_to_wgpu(op.zfail), + pass_op: stencil_action_to_wgpu(op.zpass), + } +} + +fn format_has_stencil(fmt: wgpu::TextureFormat) -> bool { + matches!(fmt, wgpu::TextureFormat::Depth24PlusStencil8 | wgpu::TextureFormat::Depth32FloatStencil8) +} + fn cubemap_face_to_layer(face: CubeMapFace) -> u32 { match face { CubeMapFace::PositiveX => 0, @@ -107,6 +134,7 @@ pub struct PipelineKey { blend: bool, depth_test: bool, depth_write: bool, + stencil: bool, cull: u8, extra_vert_count: u8, } @@ -116,21 +144,29 @@ pub struct WgpuFrameBuffer { depth_attachment: Option, color_attachments: Vec, is_backbuffer: bool, + needs_clear: Cell, + pending_clear_color: RefCell, + pending_clear_depth: RefCell, } impl WgpuFrameBuffer { pub fn new(server: &WgpuGraphicsServer, depth: Option, colors: Vec) -> Result { - Ok(Self { server: server.weak_ref(), depth_attachment: depth, color_attachments: colors, is_backbuffer: false }) + Ok(Self { server: server.weak_ref(), depth_attachment: depth, color_attachments: colors, is_backbuffer: false, needs_clear: Cell::new(false), pending_clear_color: RefCell::new(wgpu::Color::BLACK), pending_clear_depth: RefCell::new(1.0) }) } - pub fn backbuffer(server: &WgpuGraphicsServer) -> Self { - Self { server: server.weak_ref(), depth_attachment: None, color_attachments: Default::default(), is_backbuffer: true } + pub fn backbuffer(server: &WgpuGraphicsServer, depth: Option) -> Self { + Self { server: server.weak_ref(), depth_attachment: depth, color_attachments: Default::default(), is_backbuffer: true, needs_clear: Cell::new(false), pending_clear_color: RefCell::new(wgpu::Color::BLACK), pending_clear_depth: RefCell::new(1.0) } } fn get_or_create_pipeline(&self, server: &WgpuGraphicsServer, program: &WgpuProgram, params: &DrawParameters, all_layouts: &[wgpu::VertexBufferLayout<'static>], cf: wgpu::TextureFormat, df: Option, pipeline_layout: &wgpu::PipelineLayout, element_kind: fyrox_graphics::ElementKind) -> wgpu::RenderPipeline { + let needs_stencil = params.stencil_test.is_some() || params.stencil_op.zpass != fyrox_graphics::StencilAction::Keep || params.stencil_op.fail != fyrox_graphics::StencilAction::Keep || params.stencil_op.zfail != fyrox_graphics::StencilAction::Keep; + let depth_fmt = df.unwrap_or(wgpu::TextureFormat::Depth32Float); + let stencil_supported = format_has_stencil(depth_fmt); + let effective_stencil = needs_stencil && stencil_supported; let key = PipelineKey { program_ptr: program as *const WgpuProgram as usize, color_format: cf, depth_format: df, sample_count: server.msaa_sample_count, blend: params.blend.is_some(), depth_test: params.depth_test.is_some(), depth_write: params.depth_write, + stencil: effective_stencil, cull: match params.cull_face { Some(CullFace::Back) => 2, Some(CullFace::Front) => 1, None => 0 }, extra_vert_count: all_layouts.len() as u8, }; @@ -142,15 +178,38 @@ impl WgpuFrameBuffer { alpha: wgpu::BlendComponent { src_factor: blend_factor_to_wgpu(bp.func.alpha_sfactor), dst_factor: blend_factor_to_wgpu(bp.func.alpha_dfactor), operation: blend_mode_to_wgpu(bp.equation.alpha) }, }); - let depth_stencil = if params.depth_test.is_some() || params.depth_write { + let wgpu_stencil_state = if effective_stencil { + let default_face = stencil_face_state(CompareFunc::Always, ¶ms.stencil_op); + let sf = params.stencil_test.as_ref().map(|st| stencil_face_state(st.func, ¶ms.stencil_op)) + .unwrap_or(default_face); + let read_mask = params.stencil_test.as_ref().map(|st| st.mask).unwrap_or(0xFFFF_FFFF); + wgpu::StencilState { + front: sf, + back: sf, + read_mask, + write_mask: params.stencil_op.write_mask, + } + } else { + wgpu::StencilState::default() + }; + + let depth_stencil = if params.depth_test.is_some() || params.depth_write || effective_stencil { Some(wgpu::DepthStencilState { - format: df.unwrap_or(wgpu::TextureFormat::Depth32Float), + format: depth_fmt, depth_write_enabled: Some(params.depth_write), depth_compare: Some(params.depth_test.map(compare_func_to_wgpu).unwrap_or(wgpu::CompareFunction::Always)), + stencil: wgpu_stencil_state, + bias: wgpu::DepthBiasState::default(), + }) + } else { + df.map(|f| wgpu::DepthStencilState { + format: f, + depth_write_enabled: Some(false), + depth_compare: Some(wgpu::CompareFunction::Always), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }) - } else { df.map(|f| wgpu::DepthStencilState { format: f, depth_write_enabled: Some(false), depth_compare: Some(wgpu::CompareFunction::Always), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default() }) }; + }; let cull = match params.cull_face { Some(CullFace::Back) => Some(wgpu::Face::Back), Some(CullFace::Front) => Some(wgpu::Face::Front), None => None }; @@ -185,10 +244,28 @@ impl WgpuFrameBuffer { if count == 0 { return Ok(DrawCallStatistics { triangles: 0 }); } let surface_tex = if self.is_backbuffer { - match server.surface.get_current_texture() { - wgpu::CurrentSurfaceTexture::Success(t) | wgpu::CurrentSurfaceTexture::Suboptimal(t) => Some(t), - other => return Err(FrameworkError::Custom(format!("Surface texture error: {other:?}"))), + // Only acquire a new frame if one isn't already stored from a prior draw call this frame. + if server.current_frame.borrow().is_none() { + match server.surface.get_current_texture() { + wgpu::CurrentSurfaceTexture::Success(t) | wgpu::CurrentSurfaceTexture::Suboptimal(t) => { + *server.current_frame.borrow_mut() = Some(t); + } + wgpu::CurrentSurfaceTexture::Timeout => { + log::warn!("Surface texture timeout, skipping frame"); + return Ok(DrawCallStatistics { triangles: 0 }); + } + wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => { + let config = server.surface_config.read().unwrap(); + server.surface.configure(&server.state.device, &config); + log::warn!("Surface lost/outdated, reconfigured"); + return Ok(DrawCallStatistics { triangles: 0 }); + } + other => return Err(FrameworkError::Custom(format!("Surface texture error: {other:?}"))), + } } + // Create a view from the stored frame (doesn't consume the SurfaceTexture). + let frame = server.current_frame.borrow(); + Some(frame.as_ref().unwrap().texture.create_view(&wgpu::TextureViewDescriptor::default())) } else { None }; let cf = if self.is_backbuffer { server.surface_config.read().unwrap().format } @@ -216,7 +293,7 @@ impl WgpuFrameBuffer { let mut encoder = server.state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("DrawEnc") }); let color_view = if self.is_backbuffer { - surface_tex.as_ref().unwrap().texture.create_view(&wgpu::TextureViewDescriptor::default()) + surface_tex.unwrap() } else { let first_color = self.color_attachments.first().unwrap(); if let Some(face) = first_color.cube_map_face() { @@ -249,15 +326,26 @@ impl WgpuFrameBuffer { }); { + // Backbuffer clears once per frame (flag set by swap_buffers, consumed on first draw). + // Offscreen FBOs clear when their clear() was called. + let has_stencil_aspect = df.map(format_has_stencil).unwrap_or(false); + let (color_load, depth_load, stencil_load) = if self.is_backbuffer && server.backbuffer_needs_clear.replace(false) { + (wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }), wgpu::LoadOp::Clear(1.0), if has_stencil_aspect { wgpu::LoadOp::Clear(0) } else { wgpu::LoadOp::Load }) + } else if !self.is_backbuffer && self.needs_clear.replace(false) { + (wgpu::LoadOp::Clear(*self.pending_clear_color.borrow()), wgpu::LoadOp::Clear(*self.pending_clear_depth.borrow()), if has_stencil_aspect { wgpu::LoadOp::Clear(0) } else { wgpu::LoadOp::Load }) + } else { + (wgpu::LoadOp::Load, wgpu::LoadOp::Load, wgpu::LoadOp::Load) + }; let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("DrawRP"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &color_view, resolve_target: None, depth_slice: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store } })], - depth_stencil_attachment: depth_view.as_ref().map(|v| wgpu::RenderPassDepthStencilAttachment { view: v, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store }), stencil_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store }) }), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &color_view, resolve_target: None, depth_slice: None, ops: wgpu::Operations { load: color_load, store: wgpu::StoreOp::Store } })], + depth_stencil_attachment: depth_view.as_ref().map(|v| wgpu::RenderPassDepthStencilAttachment { view: v, depth_ops: Some(wgpu::Operations { load: depth_load, store: wgpu::StoreOp::Store }), stencil_ops: Some(wgpu::Operations { load: stencil_load, store: wgpu::StoreOp::Store }) }), ..Default::default() }); rp.set_viewport(viewport.x() as f32, viewport.y() as f32, viewport.w() as f32, viewport.h() as f32, 0.0, 1.0); rp.set_pipeline(&pipeline); + if let Some(st) = ¶ms.stencil_test { rp.set_stencil_reference(st.ref_value); } if let Some(bg) = &bind_group { rp.set_bind_group(0, bg, &[]); } let vbs = geo.vertex_buffers(); for (i, vb) in vbs.iter().enumerate() { rp.set_vertex_buffer(i as u32, vb.slice(..)); } @@ -267,9 +355,9 @@ impl WgpuFrameBuffer { rp.set_index_buffer(eb.slice(..), wgpu::IndexFormat::Uint32); let ipe = geo.element_kind().index_per_element(); - let idx_count = (count * ipe) as u32; - let base_vert = (offset * ipe) as i32; - rp.draw_indexed(0..idx_count, base_vert, 0..instance_count); + let start_idx = (offset * ipe) as u32; + let end_idx = ((offset + count) * ipe) as u32; + rp.draw_indexed(start_idx..end_idx, 0, 0..instance_count); } server.state.queue.submit(std::iter::once(encoder.finish())); @@ -315,7 +403,18 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { Some(result) } else { None } } - fn clear(&self, _viewport: Rect, _color: Option, _depth: Option, _stencil: Option) {} + fn clear(&self, _viewport: Rect, color: Option, depth: Option, _stencil: Option) { + if let Some(c) = color { + *self.pending_clear_color.borrow_mut() = wgpu::Color { + r: c.r as f64 / 255.0, g: c.g as f64 / 255.0, + b: c.b as f64 / 255.0, a: c.a as f64 / 255.0, + }; + } + if let Some(d) = depth { + *self.pending_clear_depth.borrow_mut() = d; + } + self.needs_clear.set(true); + } fn draw(&self, geometry: &GpuGeometryBuffer, viewport: Rect, program: &GpuProgram, params: &DrawParameters, resources: &[ResourceBindGroup], element_range: ElementRange) -> Result { self.do_draw(1, geometry, viewport, program, params, resources, element_range) } diff --git a/fyrox-graphics-wgpu/src/program.rs b/fyrox-graphics-wgpu/src/program.rs index c886a6bfd3..a0600fee51 100644 --- a/fyrox-graphics-wgpu/src/program.rs +++ b/fyrox-graphics-wgpu/src/program.rs @@ -190,6 +190,11 @@ impl WgpuShader { } let declarations = generate_wgsl_declarations(resources); + if name.contains("Widget") { + use std::io::Write; + let mut f = std::fs::File::create("C:\\Users\\I.Kondrashkin\\projects\\Fyrox\\shader_dump.txt").unwrap(); + writeln!(f, "=== DECLS for {name} ===\n{declarations}=== SOURCE ===\n{source}").ok(); + } let shared = include_str!("shaders/shared.wgsl"); let mut wgsl = String::new(); diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index 94d82126bb..500a655f97 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -41,7 +41,7 @@ use fyrox_graphics::server::{ }; use fyrox_graphics::stats::PipelineStatistics; use fyrox_graphics::{PolygonFace, PolygonFillMode}; -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::rc::{Rc, Weak}; use std::sync::{Arc, RwLock}; @@ -69,6 +69,12 @@ pub struct WgpuGraphicsServer { pub dummy_vertex_buffer: wgpu::Buffer, /// Non-filtering sampler for textures with non-filterable formats (e.g. R32Float). non_filtering_sampler: wgpu::Sampler, + /// Holds the acquired surface frame between do_draw and swap_buffers. + pub current_frame: RefCell>, + /// Whether the backbuffer needs clearing at the start of the next frame. + pub backbuffer_needs_clear: Cell, + /// Cached depth-stencil texture for the backbuffer, with its (width, height). + backbuffer_depth_stencil: RefCell>, } impl WgpuGraphicsServer { @@ -188,6 +194,9 @@ impl WgpuGraphicsServer { pipeline_statistics: RefCell::new(PipelineStatistics::default()), dummy_vertex_buffer, non_filtering_sampler, + current_frame: RefCell::new(None), + backbuffer_needs_clear: Cell::new(true), + backbuffer_depth_stencil: RefCell::new(None), }); *server.weak_self.borrow_mut() = Some(Rc::downgrade(&server)); @@ -217,7 +226,32 @@ impl GraphicsServer for WgpuGraphicsServer { Ok(GpuFrameBuffer(Rc::new(WgpuFrameBuffer::new(self, depth, colors)?))) } fn back_buffer(&self) -> GpuFrameBuffer { - GpuFrameBuffer(Rc::new(WgpuFrameBuffer::backbuffer(self))) + let config = self.surface_config.read().unwrap(); + let (w, h) = (config.width, config.height); + drop(config); + + let mut cache = self.backbuffer_depth_stencil.borrow_mut(); + let needs_recreate = match cache.as_ref() { + Some((cw, ch, _)) => *cw != w || *ch != h, + None => true, + }; + if needs_recreate { + let depth_tex = match self.create_2d_render_target( + "BackbufferDepthStencil", + fyrox_graphics::gpu_texture::PixelKind::D24S8, + w as usize, + h as usize, + ) { + Ok(t) => t, + Err(e) => { + log::warn!("Failed to create backbuffer depth-stencil: {e}"); + return GpuFrameBuffer(Rc::new(WgpuFrameBuffer::backbuffer(self, None))); + } + }; + *cache = Some((w, h, depth_tex)); + } + let depth_attachment = cache.as_ref().map(|(_, _, tex)| Attachment::depth_stencil(tex.clone())); + GpuFrameBuffer(Rc::new(WgpuFrameBuffer::backbuffer(self, depth_attachment))) } fn create_query(&self) -> Result { Ok(GpuQuery(Rc::new(WgpuQuery::new(self)?))) @@ -244,7 +278,13 @@ impl GraphicsServer for WgpuGraphicsServer { fn finish(&self) { self.state.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None }).ok(); } fn invalidate_resource_bindings_cache(&self) { *self.pipeline_statistics.borrow_mut() = Default::default(); } fn pipeline_statistics(&self) -> PipelineStatistics { *self.pipeline_statistics.borrow() } - fn swap_buffers(&self) -> Result<(), FrameworkError> { Ok(()) } + fn swap_buffers(&self) -> Result<(), FrameworkError> { + if let Some(frame) = self.current_frame.borrow_mut().take() { + frame.present(); + } + self.backbuffer_needs_clear.set(true); + Ok(()) + } fn set_frame_size(&self, new_size: (u32, u32)) { if new_size.0 > 0 && new_size.1 > 0 { let mut config = self.surface_config.write().unwrap(); diff --git a/fyrox-material/src/shader/standard/widget.shader b/fyrox-material/src/shader/standard/widget.shader index d2a44d3864..153b4341f5 100644 --- a/fyrox-material/src/shader/standard/widget.shader +++ b/fyrox-material/src/shader/standard/widget.shader @@ -39,8 +39,17 @@ output.texCoord = input.vertexTexCoord; output.color = input.vertexColor; output.localPosition = input.vertexPosition; + + // Умножаем mat3x3 на vec3f (получаем vec3f) let worldSpaceVertex = fyrox_widgetData.worldMatrix * vec3f(input.vertexPosition, 1.0); - output.position = fyrox_widgetData.projectionMatrix * vec4f(worldSpaceVertex, 1.0); + + // Умножаем mat4x4 на vec4f (получаем vec4f) + var clip_pos = fyrox_widgetData.projectionMatrix * vec4f(worldSpaceVertex, 1.0); + + // WebGPU Z-range: конвертируем OpenGL [-1, 1] в WebGPU [0, 1] + clip_pos.z = (clip_pos.z + clip_pos.w) * 0.5; + + output.position = clip_pos; return output; } "#, @@ -129,7 +138,9 @@ r#" @vertex fn vs_main(@location(0) vertexPosition: vec2f) -> @builtin(position) vec4f { let worldSpaceVertex = fyrox_widgetData.worldMatrix * vec3f(vertexPosition, 1.0); - return fyrox_widgetData.projectionMatrix * vec4f(worldSpaceVertex, 1.0); + var clip_pos = fyrox_widgetData.projectionMatrix * vec4f(worldSpaceVertex, 1.0); + clip_pos.z = (clip_pos.z + clip_pos.w) * 0.5; + return clip_pos; } "#, From b171f02fd3eab1ff4d3c887d8ba479864115f075 Mon Sep 17 00:00:00 2001 From: nice0hack Date: Tue, 7 Jul 2026 11:10:54 +0500 Subject: [PATCH 09/34] fix editor crushes --- editor/resources/shaders/grid.shader | 2 +- fyrox-graphics-wgpu/src/framebuffer.rs | 91 +++++++++++++------ fyrox-graphics-wgpu/src/program.rs | 2 +- fyrox-graphics-wgpu/src/sampler.rs | 10 +- fyrox-graphics-wgpu/src/server.rs | 27 +++--- fyrox-graphics-wgpu/src/shaders/shared.wgsl | 42 +++++++++ fyrox-graphics-wgpu/src/texture.rs | 4 +- .../src/resource/gltf/gltf_standard.shader | 27 +++++- .../src/shader/standard/terrain.shader | 23 +++-- 9 files changed, 165 insertions(+), 63 deletions(-) diff --git a/editor/resources/shaders/grid.shader b/editor/resources/shaders/grid.shader index 590ac2ff97..827d327a93 100644 --- a/editor/resources/shaders/grid.shader +++ b/editor/resources/shaders/grid.shader @@ -101,7 +101,7 @@ @vertex fn vs_main(input: VertexInput) -> VertexOutput { var output: VertexOutput; - var invViewProj = inverse(fyrox_cameraData.viewProjectionMatrix); + var invViewProj = inverse_mat4(fyrox_cameraData.viewProjectionMatrix); output.nearPoint = Unproject(input.vertexPosition.x, input.vertexPosition.y, 0.0, invViewProj); output.farPoint = Unproject(input.vertexPosition.x, input.vertexPosition.y, 1.0, invViewProj); output.position = vec4f(input.vertexPosition, 1.0); diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index df776b1f22..72c6f4c80b 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -30,7 +30,7 @@ use fyrox_graphics::{ framebuffer::{Attachment, BufferDataUsage, DrawCallStatistics, GpuFrameBuffer, GpuFrameBufferTrait, ReadTarget, ResourceBindGroup, ResourceBinding}, geometry_buffer::GpuGeometryBuffer, gpu_program::GpuProgram, - gpu_texture::{image_2d_size_bytes, CubeMapFace, GpuTexture, GpuTextureKind}, + gpu_texture::{CubeMapFace, GpuTexture, GpuTextureKind}, CompareFunc, CullFace, DrawParameters, ElementRange, BlendMode, }; use std::cell::{Cell, RefCell}; @@ -135,6 +135,7 @@ pub struct PipelineKey { depth_test: bool, depth_write: bool, stencil: bool, + has_color: bool, cull: u8, extra_vert_count: u8, } @@ -158,7 +159,7 @@ impl WgpuFrameBuffer { Self { server: server.weak_ref(), depth_attachment: depth, color_attachments: Default::default(), is_backbuffer: true, needs_clear: Cell::new(false), pending_clear_color: RefCell::new(wgpu::Color::BLACK), pending_clear_depth: RefCell::new(1.0) } } - fn get_or_create_pipeline(&self, server: &WgpuGraphicsServer, program: &WgpuProgram, params: &DrawParameters, all_layouts: &[wgpu::VertexBufferLayout<'static>], cf: wgpu::TextureFormat, df: Option, pipeline_layout: &wgpu::PipelineLayout, element_kind: fyrox_graphics::ElementKind) -> wgpu::RenderPipeline { + fn get_or_create_pipeline(&self, server: &WgpuGraphicsServer, program: &WgpuProgram, params: &DrawParameters, all_layouts: &[wgpu::VertexBufferLayout<'static>], cf: wgpu::TextureFormat, df: Option, pipeline_layout: &wgpu::PipelineLayout, element_kind: fyrox_graphics::ElementKind, has_color: bool) -> wgpu::RenderPipeline { let needs_stencil = params.stencil_test.is_some() || params.stencil_op.zpass != fyrox_graphics::StencilAction::Keep || params.stencil_op.fail != fyrox_graphics::StencilAction::Keep || params.stencil_op.zfail != fyrox_graphics::StencilAction::Keep; let depth_fmt = df.unwrap_or(wgpu::TextureFormat::Depth32Float); let stencil_supported = format_has_stencil(depth_fmt); @@ -167,15 +168,30 @@ impl WgpuFrameBuffer { program_ptr: program as *const WgpuProgram as usize, color_format: cf, depth_format: df, sample_count: server.msaa_sample_count, blend: params.blend.is_some(), depth_test: params.depth_test.is_some(), depth_write: params.depth_write, stencil: effective_stencil, + has_color, cull: match params.cull_face { Some(CullFace::Back) => 2, Some(CullFace::Front) => 1, None => 0 }, extra_vert_count: all_layouts.len() as u8, }; let key_hash = { let mut h = DefaultHasher::new(); key.hash(&mut h); h.finish() }; { let cache = server.pipeline_cache.borrow(); if let Some(p) = cache.get(&key_hash) { return p.clone(); } } - let blend_state = params.blend.as_ref().map(|bp| wgpu::BlendState { - color: wgpu::BlendComponent { src_factor: blend_factor_to_wgpu(bp.func.sfactor), dst_factor: blend_factor_to_wgpu(bp.func.dfactor), operation: blend_mode_to_wgpu(bp.equation.rgb) }, - alpha: wgpu::BlendComponent { src_factor: blend_factor_to_wgpu(bp.func.alpha_sfactor), dst_factor: blend_factor_to_wgpu(bp.func.alpha_dfactor), operation: blend_mode_to_wgpu(bp.equation.alpha) }, + let blend_state = params.blend.as_ref().map(|bp| { + let rgb_op = blend_mode_to_wgpu(bp.equation.rgb); + let alpha_op = blend_mode_to_wgpu(bp.equation.alpha); + let is_minmax_rgb = matches!(rgb_op, wgpu::BlendOperation::Min | wgpu::BlendOperation::Max); + let is_minmax_alpha = matches!(alpha_op, wgpu::BlendOperation::Min | wgpu::BlendOperation::Max); + wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: if is_minmax_rgb { wgpu::BlendFactor::One } else { blend_factor_to_wgpu(bp.func.sfactor) }, + dst_factor: if is_minmax_rgb { wgpu::BlendFactor::One } else { blend_factor_to_wgpu(bp.func.dfactor) }, + operation: rgb_op, + }, + alpha: wgpu::BlendComponent { + src_factor: if is_minmax_alpha { wgpu::BlendFactor::One } else { blend_factor_to_wgpu(bp.func.alpha_sfactor) }, + dst_factor: if is_minmax_alpha { wgpu::BlendFactor::One } else { blend_factor_to_wgpu(bp.func.alpha_dfactor) }, + operation: alpha_op, + }, + } }); let wgpu_stencil_state = if effective_stencil { @@ -219,10 +235,12 @@ impl WgpuFrameBuffer { fyrox_graphics::ElementKind::Point => wgpu::PrimitiveTopology::PointList, }; + let color_target = [Some(wgpu::ColorTargetState { format: cf, blend: blend_state, write_mask: wgpu::ColorWrites::ALL })]; + let fragment_state = if has_color { Some(wgpu::FragmentState { module: program.fragment_module(), entry_point: Some("fs_main"), targets: &color_target, compilation_options: Default::default() }) } else { None }; let pipeline = server.state.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("RP"), layout: Some(pipeline_layout), vertex: wgpu::VertexState { module: program.vertex_module(), entry_point: Some("vs_main"), buffers: all_layouts, compilation_options: Default::default() }, - fragment: Some(wgpu::FragmentState { module: program.fragment_module(), entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format: cf, blend: blend_state, write_mask: wgpu::ColorWrites::ALL })], compilation_options: Default::default() }), + fragment: fragment_state, primitive: wgpu::PrimitiveState { topology: topo, strip_index_format: None, front_face: wgpu::FrontFace::Ccw, cull_mode: cull, ..Default::default() }, depth_stencil, multisample: wgpu::MultisampleState { count: server.msaa_sample_count, mask: !0, alpha_to_coverage_enabled: false }, @@ -287,27 +305,29 @@ impl WgpuFrameBuffer { let (_, pipeline_layout) = prog.get_or_create_layouts(&texture_formats); let (all_layouts, extra_vert_count) = build_vertex_layouts(geo); - let pipeline = self.get_or_create_pipeline(&server, prog, params, &all_layouts, cf, df, &pipeline_layout, geo.element_kind()); + let has_color = self.is_backbuffer || !self.color_attachments.is_empty(); + let pipeline = self.get_or_create_pipeline(&server, prog, params, &all_layouts, cf, df, &pipeline_layout, geo.element_kind(), has_color); let bind_group = create_bind_group(&server, prog, resources); let mut encoder = server.state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("DrawEnc") }); let color_view = if self.is_backbuffer { - surface_tex.unwrap() + Some(surface_tex.unwrap()) } else { - let first_color = self.color_attachments.first().unwrap(); - if let Some(face) = first_color.cube_map_face() { - let wt = first_color.texture.as_any().downcast_ref::().unwrap(); - wt.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { - dimension: Some(wgpu::TextureViewDimension::D2), - base_array_layer: cubemap_face_to_layer(face), - array_layer_count: Some(1), - mip_level_count: Some(1), - ..Default::default() - }) - } else { - first_color.texture.as_any().downcast_ref::().unwrap().wgpu_view().clone() - } + self.color_attachments.first().map(|first_color| { + if let Some(face) = first_color.cube_map_face() { + let wt = first_color.texture.as_any().downcast_ref::().unwrap(); + wt.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { + dimension: Some(wgpu::TextureViewDimension::D2), + base_array_layer: cubemap_face_to_layer(face), + array_layer_count: Some(1), + mip_level_count: Some(1), + ..Default::default() + }) + } else { + first_color.texture.as_any().downcast_ref::().unwrap().wgpu_view().clone() + } + }) }; let depth_view = self.depth_attachment.as_ref().map(|a| { @@ -336,9 +356,11 @@ impl WgpuFrameBuffer { } else { (wgpu::LoadOp::Load, wgpu::LoadOp::Load, wgpu::LoadOp::Load) }; + let color_att = color_view.as_ref().map(|view| wgpu::RenderPassColorAttachment { view, resolve_target: None, depth_slice: None, ops: wgpu::Operations { load: color_load, store: wgpu::StoreOp::Store } }); + let color_att_ref: &[Option>] = if color_att.is_some() { &[color_att] } else { &[] }; let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("DrawRP"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &color_view, resolve_target: None, depth_slice: None, ops: wgpu::Operations { load: color_load, store: wgpu::StoreOp::Store } })], + color_attachments: color_att_ref, depth_stencil_attachment: depth_view.as_ref().map(|v| wgpu::RenderPassDepthStencilAttachment { view: v, depth_ops: Some(wgpu::Operations { load: depth_load, store: wgpu::StoreOp::Store }), stencil_ops: Some(wgpu::Operations { load: stencil_load, store: wgpu::StoreOp::Store }) }), ..Default::default() }); @@ -382,13 +404,13 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { }; let wtex = texture.as_any().downcast_ref::()?; if let GpuTextureKind::Rectangle { width, height } = texture.kind() { - let pk = texture.pixel_kind(); - let total = image_2d_size_bytes(pk, width, height); - let buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { label: Some("ReadPx"), size: total as u64, usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false }); - let mut enc = server.state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("ReadPxEnc") }); let fmt = wtex.format(); - let bps = fmt.block_copy_size(None).unwrap_or(4); - enc.copy_texture_to_buffer(wgpu::TexelCopyTextureInfo { texture: wtex.wgpu_texture(), mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, wgpu::TexelCopyBufferInfo { buffer: &buf, layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((width as u32 * bps).max(256)), rows_per_image: Some(height as u32) } }, wgpu::Extent3d { width: width as u32, height: height as u32, depth_or_array_layers: 1 }); + let bps = fmt.block_copy_size(None).unwrap_or(4) as usize; + let bytes_per_row = (width * bps).max(256); + let padded_total = bytes_per_row * (height - 1) + width * bps; + let buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { label: Some("ReadPx"), size: padded_total as u64, usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false }); + let mut enc = server.state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("ReadPxEnc") }); + enc.copy_texture_to_buffer(wgpu::TexelCopyTextureInfo { texture: wtex.wgpu_texture(), mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, wgpu::TexelCopyBufferInfo { buffer: &buf, layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(bytes_per_row as u32), rows_per_image: Some(height as u32) } }, wgpu::Extent3d { width: width as u32, height: height as u32, depth_or_array_layers: 1 }); server.state.queue.submit(std::iter::once(enc.finish())); let slice = buf.slice(..); let (tx, rx) = std::sync::mpsc::channel(); @@ -396,8 +418,17 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { server.state.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None }).ok(); rx.recv().ok()?.ok()?; let mapped = slice.get_mapped_range(); - let mut result = vec![0u8; total]; - result.copy_from_slice(&mapped); + let unpadded_row = width * bps; + let mut result = vec![0u8; unpadded_row * height]; + if bytes_per_row == unpadded_row { + result.copy_from_slice(&mapped); + } else { + for y in 0..height { + let src_off = y * bytes_per_row; + let dst_off = y * unpadded_row; + result[dst_off..dst_off + unpadded_row].copy_from_slice(&mapped[src_off..src_off + unpadded_row]); + } + } drop(mapped); buf.unmap(); Some(result) diff --git a/fyrox-graphics-wgpu/src/program.rs b/fyrox-graphics-wgpu/src/program.rs index a0600fee51..6a72e9f2de 100644 --- a/fyrox-graphics-wgpu/src/program.rs +++ b/fyrox-graphics-wgpu/src/program.rs @@ -55,7 +55,7 @@ fn wgsl_property_type(kind: &ShaderPropertyKind) -> String { ShaderPropertyKind::UIntArray { max_len, .. } => format!("array"), ShaderPropertyKind::Bool { .. } => "u32".into(), ShaderPropertyKind::Vector2 { .. } => "vec2f".into(), - ShaderPropertyKind::Vector2Array { max_len, .. } => format!("array"), + ShaderPropertyKind::Vector2Array { max_len, .. } => format!("array"), ShaderPropertyKind::Vector3 { .. } => "vec3f".into(), ShaderPropertyKind::Vector3Array { max_len, .. } => format!("array"), ShaderPropertyKind::Vector4 { .. } => "vec4f".into(), diff --git a/fyrox-graphics-wgpu/src/sampler.rs b/fyrox-graphics-wgpu/src/sampler.rs index 4d36f956b6..d42d02abac 100644 --- a/fyrox-graphics-wgpu/src/sampler.rs +++ b/fyrox-graphics-wgpu/src/sampler.rs @@ -63,17 +63,19 @@ impl GpuSamplerTrait for WgpuSampler {} impl WgpuSampler { pub fn new(server: &WgpuGraphicsServer, desc: GpuSamplerDescriptor) -> Result { + let aniso = desc.anisotropy.clamp(1.0, 16.0) as u16; + let use_linear = aniso > 1; let sampler = server.state.device.create_sampler(&wgpu::SamplerDescriptor { label: if server.named_objects { Some("Sampler") } else { None }, address_mode_u: wrap_mode_to_wgpu(desc.s_wrap_mode), address_mode_v: wrap_mode_to_wgpu(desc.t_wrap_mode), address_mode_w: wrap_mode_to_wgpu(desc.r_wrap_mode), - mag_filter: mag_filter_to_wgpu(desc.mag_filter), - min_filter: min_filter_to_wgpu(desc.min_filter), - mipmap_filter: mipmap_filter_to_wgpu(desc.min_filter), + mag_filter: if use_linear { wgpu::FilterMode::Linear } else { mag_filter_to_wgpu(desc.mag_filter) }, + min_filter: if use_linear { wgpu::FilterMode::Linear } else { min_filter_to_wgpu(desc.min_filter) }, + mipmap_filter: if use_linear { wgpu::MipmapFilterMode::Linear } else { mipmap_filter_to_wgpu(desc.min_filter) }, lod_min_clamp: desc.min_lod.max(0.0), lod_max_clamp: desc.max_lod.max(0.0), - anisotropy_clamp: desc.anisotropy.clamp(1.0, 16.0) as u16, + anisotropy_clamp: aniso, compare: None, border_color: None, }); diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index 500a655f97..7c3d4a3443 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -236,19 +236,22 @@ impl GraphicsServer for WgpuGraphicsServer { None => true, }; if needs_recreate { - let depth_tex = match self.create_2d_render_target( - "BackbufferDepthStencil", - fyrox_graphics::gpu_texture::PixelKind::D24S8, - w as usize, - h as usize, - ) { - Ok(t) => t, - Err(e) => { - log::warn!("Failed to create backbuffer depth-stencil: {e}"); - return GpuFrameBuffer(Rc::new(WgpuFrameBuffer::backbuffer(self, None))); + if w > 0 && h > 0 { + match self.create_2d_render_target( + "BackbufferDepthStencil", + fyrox_graphics::gpu_texture::PixelKind::D24S8, + w as usize, + h as usize, + ) { + Ok(tex) => { *cache = Some((w, h, tex)); } + Err(e) => { + log::warn!("Failed to create backbuffer depth-stencil: {e}"); + *cache = None; + } } - }; - *cache = Some((w, h, depth_tex)); + } else { + *cache = None; + } } let depth_attachment = cache.as_ref().map(|(_, _, tex)| Attachment::depth_stencil(tex.clone())); GpuFrameBuffer(Rc::new(WgpuFrameBuffer::backbuffer(self, depth_attachment))) diff --git a/fyrox-graphics-wgpu/src/shaders/shared.wgsl b/fyrox-graphics-wgpu/src/shaders/shared.wgsl index b98e06ac58..7c18d1a32d 100644 --- a/fyrox-graphics-wgpu/src/shaders/shared.wgsl +++ b/fyrox-graphics-wgpu/src/shaders/shared.wgsl @@ -3,6 +3,48 @@ const PI: f32 = 3.14159; +fn inverse_mat4(m: mat4x4f) -> mat4x4f { + let m00 = m[0][0]; let m01 = m[0][1]; let m02 = m[0][2]; let m03 = m[0][3]; + let m10 = m[1][0]; let m11 = m[1][1]; let m12 = m[1][2]; let m13 = m[1][3]; + let m20 = m[2][0]; let m21 = m[2][1]; let m22 = m[2][2]; let m23 = m[2][3]; + let m30 = m[3][0]; let m31 = m[3][1]; let m32 = m[3][2]; let m33 = m[3][3]; + + let b00 = m00 * m11 - m01 * m10; + let b01 = m00 * m12 - m02 * m10; + let b02 = m00 * m13 - m03 * m10; + let b03 = m01 * m12 - m02 * m11; + let b04 = m01 * m13 - m03 * m11; + let b05 = m02 * m13 - m03 * m12; + let b06 = m20 * m31 - m21 * m30; + let b07 = m20 * m32 - m22 * m30; + let b08 = m20 * m33 - m23 * m30; + let b09 = m21 * m32 - m22 * m31; + let b10 = m21 * m33 - m23 * m31; + let b11 = m22 * m33 - m23 * m32; + + var inv = mat4x4f(); + inv[0][0] = m11 * b11 - m12 * b10 + m13 * b09; + inv[0][1] = m02 * b10 - m01 * b11 - m03 * b09; + inv[0][2] = m31 * b05 - m32 * b04 + m33 * b03; + inv[0][3] = m22 * b04 - m21 * b05 - m23 * b03; + inv[1][0] = m12 * b08 - m10 * b11 - m13 * b07; + inv[1][1] = m00 * b11 - m02 * b08 + m03 * b07; + inv[1][2] = m32 * b02 - m30 * b05 - m33 * b01; + inv[1][3] = m20 * b05 - m22 * b02 + m23 * b01; + inv[2][0] = m10 * b10 - m11 * b08 + m13 * b06; + inv[2][1] = m01 * b08 - m00 * b10 - m03 * b06; + inv[2][2] = m30 * b04 - m31 * b02 + m33 * b00; + inv[2][3] = m21 * b02 - m20 * b04 - m23 * b00; + inv[3][0] = m11 * b07 - m10 * b09 - m12 * b06; + inv[3][1] = m00 * b09 - m01 * b07 + m02 * b06; + inv[3][2] = m31 * b01 - m30 * b03 - m32 * b00; + inv[3][3] = m20 * b03 - m21 * b01 + m22 * b00; + + let det = m00 * b00 + m01 * b08 + m02 * b06 + m03 * b09; + let invDet = 1.0 / det; + return inv * invDet; +} + fn S_SolveQuadraticEq(a: f32, b: f32, c: f32, minT: ptr, maxT: ptr) -> bool { let twoA = 2.0 * a; let det = b * b - 2.0 * twoA * c; diff --git a/fyrox-graphics-wgpu/src/texture.rs b/fyrox-graphics-wgpu/src/texture.rs index e182ecf2c2..0a0b6485c9 100644 --- a/fyrox-graphics-wgpu/src/texture.rs +++ b/fyrox-graphics-wgpu/src/texture.rs @@ -136,7 +136,9 @@ impl WgpuTexture { pub fn new(server: &WgpuGraphicsServer, desc: GpuTextureDescriptor) -> Result { let format = pixel_kind_to_wgpu_format(desc.pixel_kind); let dimension = texture_dimension(desc.kind); - let (width, height, depth_or_layers) = texture_size(desc.kind); + let (raw_w, raw_h, depth_or_layers) = texture_size(desc.kind); + let width = raw_w.max(1); + let height = raw_h.max(1); let mip_count = desc.mip_count.max(1) as u32; let texture = server.state.device.create_texture(&wgpu::TextureDescriptor { diff --git a/fyrox-impl/src/resource/gltf/gltf_standard.shader b/fyrox-impl/src/resource/gltf/gltf_standard.shader index efad87897d..fd6aa11cbf 100644 --- a/fyrox-impl/src/resource/gltf/gltf_standard.shader +++ b/fyrox-impl/src/resource/gltf/gltf_standard.shader @@ -143,6 +143,7 @@ vertex_shader: r#" struct VertexInput { + @builtin(vertex_index) vertex_index: u32, @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f, @location(2) vertexNormal: vec3f, @@ -175,7 +176,7 @@ var inputTangent = input.vertexTangent.xyz; for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) { - var offsets = S_FetchBlendShapeOffsets(blendShapesStorage, vertex_index, i); + var offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w); inputNormal += offsets.normal * weight; @@ -326,10 +327,14 @@ vertex_shader: r#" struct VertexInput { + @builtin(vertex_index) vertex_index: u32, @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f, + @location(2) vertexNormal: vec3f, + @location(3) vertexTangent: vec4f, @location(4) boneWeights: vec4f, @location(5) boneIndices: vec4f, + @location(6) vertexSecondTexCoord: vec2f, }; struct VertexOutput { @@ -347,7 +352,7 @@ var inputPosition = vec4f(input.vertexPosition, 1.0); for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) { - var offsets = S_FetchBlendShapeOffsets(blendShapesStorage, vertex_index, i); + var offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w); } @@ -415,10 +420,14 @@ vertex_shader: r#" struct VertexInput { + @builtin(vertex_index) vertex_index: u32, @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f, + @location(2) vertexNormal: vec3f, + @location(3) vertexTangent: vec4f, @location(4) boneWeights: vec4f, @location(5) boneIndices: vec4f, + @location(6) vertexSecondTexCoord: vec2f, }; struct VertexOutput { @@ -435,7 +444,7 @@ var inputPosition = vec4f(input.vertexPosition, 1.0); for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) { - var offsets = S_FetchBlendShapeOffsets(blendShapesStorage, vertex_index, i); + var offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w); } @@ -500,10 +509,14 @@ vertex_shader: r#" struct VertexInput { + @builtin(vertex_index) vertex_index: u32, @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f, + @location(2) vertexNormal: vec3f, + @location(3) vertexTangent: vec4f, @location(4) boneWeights: vec4f, @location(5) boneIndices: vec4f, + @location(6) vertexSecondTexCoord: vec2f, }; struct VertexOutput { @@ -520,7 +533,7 @@ var inputPosition = vec4f(input.vertexPosition, 1.0); for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) { - var offsets = S_FetchBlendShapeOffsets(blendShapesStorage, vertex_index, i); + var offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w); } @@ -585,10 +598,14 @@ vertex_shader: r#" struct VertexInput { + @builtin(vertex_index) vertex_index: u32, @location(0) vertexPosition: vec3f, @location(1) vertexTexCoord: vec2f, + @location(2) vertexNormal: vec3f, + @location(3) vertexTangent: vec4f, @location(4) boneWeights: vec4f, @location(5) boneIndices: vec4f, + @location(6) vertexSecondTexCoord: vec2f, }; struct VertexOutput { @@ -606,7 +623,7 @@ var inputPosition = vec4f(input.vertexPosition, 1.0); for (var i: i32 = 0; i < fyrox_instanceData.blendShapesCount; i++) { - var offsets = S_FetchBlendShapeOffsets(blendShapesStorage, vertex_index, i); + var offsets = S_FetchBlendShapeOffsets(blendShapesStorage_tex, blendShapesStorage_samp, i32(input.vertex_index), i); var weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; inputPosition = vec4f(inputPosition.xyz + offsets.position * weight, inputPosition.w); } diff --git a/fyrox-material/src/shader/standard/terrain.shader b/fyrox-material/src/shader/standard/terrain.shader index 47cf2bceff..22905f9668 100644 --- a/fyrox-material/src/shader/standard/terrain.shader +++ b/fyrox-material/src/shader/standard/terrain.shader @@ -53,12 +53,13 @@ let innerSize = heightSize - 3.0; let pixelSize = 1.0 / heightSize; let heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; - let height = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords).r; + let heightDim = textureDimensions(heightMapTexture_tex, 0); + let height = textureLoad(heightMapTexture_tex, vec2i(heightCoords * vec2f(heightDim)), 0).r; let finalVertexPosition = vec4f(input.vertexPosition.x, height, input.vertexPosition.z, 1.0); - let hx0 = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords + vec2f(-1.0, 0.0) * pixelSize).r; - let hx1 = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords + vec2f(1.0, 0.0) * pixelSize).r; - let hy0 = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords + vec2f(0.0, -1.0) * pixelSize).r; - let hy1 = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords + vec2f(0.0, 1.0) * pixelSize).r; + let hx0 = textureLoad(heightMapTexture_tex, vec2i((heightCoords + vec2f(-1.0, 0.0) * pixelSize) * vec2f(heightDim)), 0).r; + let hx1 = textureLoad(heightMapTexture_tex, vec2i((heightCoords + vec2f(1.0, 0.0) * pixelSize) * vec2f(heightDim)), 0).r; + let hy0 = textureLoad(heightMapTexture_tex, vec2i((heightCoords + vec2f(0.0, -1.0) * pixelSize) * vec2f(heightDim)), 0).r; + let hy1 = textureLoad(heightMapTexture_tex, vec2i((heightCoords + vec2f(0.0, 1.0) * pixelSize) * vec2f(heightDim)), 0).r; let n = vec3f((hx0 - hx1) / 2.0, 1.0, (hy0 - hy1) / 2.0); let tan = vec3f(n.y, -n.x, 0.0); let nm = mat3x3f(fyrox_instanceData.worldMatrix[0].xyz, fyrox_instanceData.worldMatrix[1].xyz, fyrox_instanceData.worldMatrix[2].xyz); @@ -109,7 +110,8 @@ let innerSize = heightSize - 3.0; let pixelSize = 1.0 / heightSize; let heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; - let height = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords).r; + let heightDim = textureDimensions(heightMapTexture_tex, 0); + let height = textureLoad(heightMapTexture_tex, vec2i(heightCoords * vec2f(heightDim)), 0).r; let finalVertexPosition = vec4f(input.vertexPosition.x, height, input.vertexPosition.z, 1.0); output.position = fyrox_instanceData.worldViewProjection * finalVertexPosition; output.texCoord = actualTexCoords; @@ -138,7 +140,8 @@ let innerSize = heightSize - 3.0; let pixelSize = 1.0 / heightSize; let heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; - let height = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords).r; + let heightDim = textureDimensions(heightMapTexture_tex, 0); + let height = textureLoad(heightMapTexture_tex, vec2i(heightCoords * vec2f(heightDim)), 0).r; let finalVertexPosition = vec4f(input.vertexPosition.x, height, input.vertexPosition.z, 1.0); output.position = fyrox_instanceData.worldViewProjection * finalVertexPosition; output.texCoord = actualTexCoords; @@ -167,7 +170,8 @@ let innerSize = heightSize - 3.0; let pixelSize = 1.0 / heightSize; let heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; - let height = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords).r; + let heightDim = textureDimensions(heightMapTexture_tex, 0); + let height = textureLoad(heightMapTexture_tex, vec2i(heightCoords * vec2f(heightDim)), 0).r; let finalVertexPosition = vec4f(input.vertexPosition.x, height, input.vertexPosition.z, 1.0); output.position = fyrox_instanceData.worldViewProjection * finalVertexPosition; output.texCoord = actualTexCoords; @@ -196,7 +200,8 @@ let innerSize = heightSize - 3.0; let pixelSize = 1.0 / heightSize; let heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; - let height = textureSample(heightMapTexture_tex, heightMapTexture_samp, heightCoords).r; + let heightDim = textureDimensions(heightMapTexture_tex, 0); + let height = textureLoad(heightMapTexture_tex, vec2i(heightCoords * vec2f(heightDim)), 0).r; let finalVertexPosition = vec4f(input.vertexPosition.x, height, input.vertexPosition.z, 1.0); output.position = fyrox_instanceData.worldViewProjection * finalVertexPosition; output.worldPosition = (fyrox_instanceData.worldMatrix * finalVertexPosition).xyz; From d41b385406b64aab3855c48e0f0da75de726bb04 Mon Sep 17 00:00:00 2001 From: nicehack Date: Tue, 7 Jul 2026 15:18:13 +0500 Subject: [PATCH 10/34] fix double lightening and highlighting selected objects --- editor/src/highlight.rs | 7 +++++++ fyrox-graphics-wgpu/src/server.rs | 12 +++++++++--- fyrox-impl/src/renderer/mod.rs | 19 +++++++++++++++---- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/editor/src/highlight.rs b/editor/src/highlight.rs index f4c36d7b21..582db8c595 100644 --- a/editor/src/highlight.rs +++ b/editor/src/highlight.rs @@ -107,6 +107,13 @@ impl SceneRenderPass for HighlightRenderPass { return Ok(Default::default()); } + // Skip highlight rendering when nothing is selected. + // Without this, the edge-detect shader reads stale FBO content on wgpu + // because the deferred clear never executes when no draw calls happen. + if self.nodes_to_highlight.is_empty() { + return Ok(Default::default()); + } + // Draw selected nodes in the temporary frame buffer first. { let render_pass_name = ImmutableString::new("Forward"); diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index 7c3d4a3443..a3f4969c98 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -146,9 +146,15 @@ impl WgpuGraphicsServer { .map_err(|e| FrameworkError::Custom(format!("Failed to request device: {e}")))?; let surface_caps = surface.get_capabilities(&adapter); - let Some(surface_format) = surface_caps.formats.first().copied() else { - return Err(FrameworkError::Custom("Surface has no supported formats".into())); - }; + // Prefer linear (non-sRGB) formats to avoid double gamma correction. + // The engine applies its own gamma correction in the HDR tone-mapping pass. + let surface_format = surface_caps + .formats + .iter() + .copied() + .find(|f| !f.is_srgb()) + .or_else(|| surface_caps.formats.first().copied()) + .ok_or_else(|| FrameworkError::Custom("Surface has no supported formats".into()))?; let present_mode = if vsync { wgpu::PresentMode::AutoVsync } else { wgpu::PresentMode::AutoNoVsync }; diff --git a/fyrox-impl/src/renderer/mod.rs b/fyrox-impl/src/renderer/mod.rs index 7a707a24ed..cd78ff9b90 100644 --- a/fyrox-impl/src/renderer/mod.rs +++ b/fyrox-impl/src/renderer/mod.rs @@ -168,16 +168,21 @@ fn recreate_render_data_if_needed( frame_size: Vector2, final_frame_texture: FrameTextureKind, ) -> Result<(), FrameworkError> { - if data.gbuffer.width != frame_size.x as i32 || data.gbuffer.height != frame_size.y as i32 { + let new_w = frame_size.x as i32; + let new_h = frame_size.y as i32; + // Use >1px tolerance to prevent oscillation from fractional DPI scaling. + if (data.gbuffer.width - new_w).unsigned_abs() > 1 + || (data.gbuffer.height - new_h).unsigned_abs() > 1 + { Log::info(format!( "Associated scene rendering data was re-created for {} ({}), because render \ - frame size was changed. Old is {}x{}, new {}x{}!", + frame size was changed. Old is {}x{}, new is {}x{}!", parent, std::any::type_name::(), data.gbuffer.width, data.gbuffer.height, - frame_size.x, - frame_size.y + new_w, + new_h )); *data = RenderDataContainer::new(server, frame_size, final_frame_texture)?; @@ -1246,6 +1251,12 @@ impl Renderer { dt: f32, resource_manager: &ResourceManager, ) -> Result<&SceneRenderData, FrameworkError> { + if scene_handle.is_none() { + return Err(FrameworkError::Custom( + "Attempted to render scene with invalid handle".into(), + )); + } + let graph = &scene.graph; let _debug_scope = self.server.begin_scope(&format!("Scene {:p}", scene)); From 1e78ee6c8e0528042d9fe0e70ec79e1f9ecc0600 Mon Sep 17 00:00:00 2001 From: nicehack Date: Thu, 9 Jul 2026 13:30:08 +0500 Subject: [PATCH 11/34] automatic selection of the preferred backend --- fyrox-graphics-wgpu/src/server.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index a3f4969c98..6f8c879145 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -91,14 +91,17 @@ impl WgpuGraphicsServer { let size = window.inner_size(); #[cfg(not(target_arch = "wasm32"))] - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_with_display_handle( - Box::new(window_target.owned_display_handle()), - )); + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..wgpu::InstanceDescriptor::new_with_display_handle( + Box::new(window_target.owned_display_handle()) + ) + }); #[cfg(target_arch = "wasm32")] let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: wgpu::Backends::GL, - ..Default::default() + ..wgpu::InstanceDescriptor::new_without_display_handle() }); #[cfg(not(target_arch = "wasm32"))] From ade4d638f2c23e548d37054e14d997cd0baed74f Mon Sep 17 00:00:00 2001 From: nicehack Date: Thu, 9 Jul 2026 15:20:32 +0500 Subject: [PATCH 12/34] added docs --- fyrox-graphics-wgpu/src/buffer.rs | 63 +- fyrox-graphics-wgpu/src/framebuffer.rs | 775 +++++++++++++++++---- fyrox-graphics-wgpu/src/geometry_buffer.rs | 182 +++-- fyrox-graphics-wgpu/src/lib.rs | 61 ++ fyrox-graphics-wgpu/src/program.rs | 429 +++++++++--- fyrox-graphics-wgpu/src/query.rs | 38 +- fyrox-graphics-wgpu/src/read_buffer.rs | 129 +++- fyrox-graphics-wgpu/src/sampler.rs | 102 ++- fyrox-graphics-wgpu/src/server.rs | 213 +++++- fyrox-graphics-wgpu/src/texture.rs | 362 ++++++++-- 10 files changed, 1894 insertions(+), 460 deletions(-) diff --git a/fyrox-graphics-wgpu/src/buffer.rs b/fyrox-graphics-wgpu/src/buffer.rs index 1b074f58e7..d038abc075 100644 --- a/fyrox-graphics-wgpu/src/buffer.rs +++ b/fyrox-graphics-wgpu/src/buffer.rs @@ -26,6 +26,10 @@ use fyrox_graphics::{ use std::cell::Cell; use std::rc::Weak; +/// Maps a Fyrox [`BufferKind`] to the corresponding wgpu [`BufferUsages`] flags. +/// +/// All buffers additionally get `COPY_DST` to allow data uploads via `queue.write_buffer`. +/// `PixelWrite` buffers also get `COPY_SRC` to allow copying to readback buffers. fn buffer_usage_to_wgpu(kind: BufferKind) -> wgpu::BufferUsages { let mut flags = match kind { BufferKind::Vertex => wgpu::BufferUsages::VERTEX, @@ -41,6 +45,11 @@ fn buffer_usage_to_wgpu(kind: BufferKind) -> wgpu::BufferUsages { flags } +/// Wgpu implementation of [`GpuBufferTrait`](fyrox_graphics::buffer::GpuBufferTrait). +/// +/// Wraps a [`wgpu::Buffer`] and tracks its memory usage via [`ServerMemoryUsage`]. +/// Supports writing data larger than the buffer capacity (truncated with a warning) +/// and synchronous GPU readback via mapped buffers. pub struct WgpuBuffer { server: Weak, buffer: wgpu::Buffer, @@ -50,13 +59,21 @@ pub struct WgpuBuffer { } impl WgpuBuffer { + /// Creates a new GPU buffer from the given descriptor. + /// + /// The buffer is created with at least 1 byte of capacity (wgpu requires non-zero size). + /// Memory usage is tracked on the server. pub fn new( server: &WgpuGraphicsServer, desc: GpuBufferDescriptor, ) -> Result { let wgpu_usage = buffer_usage_to_wgpu(desc.kind); let buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor { - label: if server.named_objects { Some(desc.name) } else { None }, + label: if server.named_objects { + Some(desc.name) + } else { + None + }, size: desc.size.max(1) as u64, usage: wgpu_usage, mapped_at_creation: false, @@ -71,6 +88,7 @@ impl WgpuBuffer { }) } + /// Returns a reference to the underlying [`wgpu::Buffer`]. pub fn wgpu_buffer(&self) -> &wgpu::Buffer { &self.buffer } @@ -85,20 +103,35 @@ impl Drop for WgpuBuffer { } impl GpuBufferTrait for WgpuBuffer { - fn usage(&self) -> BufferUsage { self.usage } - fn kind(&self) -> BufferKind { self.kind } - fn size(&self) -> usize { self.size.get() } + fn usage(&self) -> BufferUsage { + self.usage + } + fn kind(&self) -> BufferKind { + self.kind + } + fn size(&self) -> usize { + self.size.get() + } fn write_data(&self, data: &[u8]) -> Result<(), FrameworkError> { - if data.is_empty() { return Ok(()); } + if data.is_empty() { + return Ok(()); + } let Some(server) = self.server.upgrade() else { return Err(FrameworkError::GraphicsServerUnavailable); }; if data.len() <= self.size.get() { server.state.queue.write_buffer(&self.buffer, 0, data); } else { - log::warn!("WgpuBuffer::write_data: data ({} bytes) exceeds buffer ({} bytes)", data.len(), self.size.get()); - server.state.queue.write_buffer(&self.buffer, 0, &data[..self.size.get()]); + log::warn!( + "WgpuBuffer::write_data: data ({} bytes) exceeds buffer ({} bytes)", + data.len(), + self.size.get() + ); + server + .state + .queue + .write_buffer(&self.buffer, 0, &data[..self.size.get()]); } Ok(()) } @@ -109,9 +142,19 @@ impl GpuBufferTrait for WgpuBuffer { }; let buffer_slice = self.buffer.slice(..data.len() as u64); let (tx, rx) = std::sync::mpsc::channel(); - buffer_slice.map_async(wgpu::MapMode::Read, move |result| { tx.send(result).ok(); }); - server.state.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None }).ok(); - rx.recv().map_err(|_| FrameworkError::Custom("Channel closed".into()))? + buffer_slice.map_async(wgpu::MapMode::Read, move |result| { + tx.send(result).ok(); + }); + server + .state + .device + .poll(wgpu::PollType::Wait { + submission_index: None, + timeout: None, + }) + .ok(); + rx.recv() + .map_err(|_| FrameworkError::Custom("Channel closed".into()))? .map_err(|e| FrameworkError::Custom(format!("Buffer map failed: {e}")))?; let mapped = buffer_slice.get_mapped_range(); data.copy_from_slice(&mapped); diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index 72c6f4c80b..3b9c85d9a1 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -19,6 +19,7 @@ // SOFTWARE. use crate::buffer::WgpuBuffer; +use crate::format_helpers::{is_filterable_format, SAMPLER_BINDING_OFFSET, UNIFORM_BINDING_OFFSET}; use crate::geometry_buffer::WgpuGeometryBuffer; use crate::program::WgpuProgram; use crate::sampler::WgpuSampler; @@ -27,17 +28,21 @@ use crate::texture::WgpuTexture; use fyrox_graphics::{ core::{color::Color, math::Rect}, error::FrameworkError, - framebuffer::{Attachment, BufferDataUsage, DrawCallStatistics, GpuFrameBuffer, GpuFrameBufferTrait, ReadTarget, ResourceBindGroup, ResourceBinding}, + framebuffer::{ + Attachment, BufferDataUsage, DrawCallStatistics, GpuFrameBuffer, GpuFrameBufferTrait, + ReadTarget, ResourceBindGroup, ResourceBinding, + }, geometry_buffer::GpuGeometryBuffer, gpu_program::GpuProgram, gpu_texture::{CubeMapFace, GpuTexture, GpuTextureKind}, - CompareFunc, CullFace, DrawParameters, ElementRange, BlendMode, + BlendMode, CompareFunc, CullFace, DrawParameters, ElementRange, }; use std::cell::{Cell, RefCell}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::rc::Weak; +/// Maps a Fyrox [`CompareFunc`] to a wgpu [`CompareFunction`]. fn compare_func_to_wgpu(f: CompareFunc) -> wgpu::CompareFunction { match f { CompareFunc::Never => wgpu::CompareFunction::Never, @@ -51,6 +56,7 @@ fn compare_func_to_wgpu(f: CompareFunc) -> wgpu::CompareFunction { } } +/// Maps a Fyrox [`BlendMode`] to a wgpu [`BlendOperation`]. fn blend_mode_to_wgpu(m: BlendMode) -> wgpu::BlendOperation { match m { BlendMode::Add => wgpu::BlendOperation::Add, @@ -61,6 +67,10 @@ fn blend_mode_to_wgpu(m: BlendMode) -> wgpu::BlendOperation { } } +/// Maps a Fyrox [`BlendFactor`] to a wgpu [`BlendFactor`]. +/// +/// Note: `ConstantColor`/`ConstantAlpha` both map to `Constant` (wgpu has a single +/// constant color set via `set_blend_constant`). fn blend_factor_to_wgpu(f: fyrox_graphics::BlendFactor) -> wgpu::BlendFactor { use fyrox_graphics::BlendFactor; match f { @@ -75,7 +85,9 @@ fn blend_factor_to_wgpu(f: fyrox_graphics::BlendFactor) -> wgpu::BlendFactor { BlendFactor::DstAlpha => wgpu::BlendFactor::DstAlpha, BlendFactor::OneMinusDstAlpha => wgpu::BlendFactor::OneMinusDstAlpha, BlendFactor::ConstantColor | BlendFactor::ConstantAlpha => wgpu::BlendFactor::Constant, - BlendFactor::OneMinusConstantColor | BlendFactor::OneMinusConstantAlpha => wgpu::BlendFactor::OneMinusConstant, + BlendFactor::OneMinusConstantColor | BlendFactor::OneMinusConstantAlpha => { + wgpu::BlendFactor::OneMinusConstant + } BlendFactor::SrcAlphaSaturate => wgpu::BlendFactor::SrcAlphaSaturated, BlendFactor::Src1Color => wgpu::BlendFactor::Src, BlendFactor::OneMinusSrc1Color => wgpu::BlendFactor::OneMinusSrc, @@ -84,6 +96,7 @@ fn blend_factor_to_wgpu(f: fyrox_graphics::BlendFactor) -> wgpu::BlendFactor { } } +/// Maps a Fyrox [`StencilAction`] to a wgpu [`StencilOperation`]. fn stencil_action_to_wgpu(a: fyrox_graphics::StencilAction) -> wgpu::StencilOperation { match a { fyrox_graphics::StencilAction::Keep => wgpu::StencilOperation::Keep, @@ -97,7 +110,11 @@ fn stencil_action_to_wgpu(a: fyrox_graphics::StencilAction) -> wgpu::StencilOper } } -fn stencil_face_state(compare: CompareFunc, op: &fyrox_graphics::StencilOp) -> wgpu::StencilFaceState { +/// Builds a [`StencilFaceState`] from a compare function and stencil operation. +fn stencil_face_state( + compare: CompareFunc, + op: &fyrox_graphics::StencilOp, +) -> wgpu::StencilFaceState { wgpu::StencilFaceState { compare: compare_func_to_wgpu(compare), fail_op: stencil_action_to_wgpu(op.fail), @@ -106,10 +123,15 @@ fn stencil_face_state(compare: CompareFunc, op: &fyrox_graphics::StencilOp) -> w } } +/// Returns `true` if the texture format includes a stencil component. fn format_has_stencil(fmt: wgpu::TextureFormat) -> bool { - matches!(fmt, wgpu::TextureFormat::Depth24PlusStencil8 | wgpu::TextureFormat::Depth32FloatStencil8) + matches!( + fmt, + wgpu::TextureFormat::Depth24PlusStencil8 | wgpu::TextureFormat::Depth32FloatStencil8 + ) } +/// Maps a [`CubeMapFace`] to its array layer index (0-5). fn cubemap_face_to_layer(face: CubeMapFace) -> u32 { match face { CubeMapFace::PositiveX => 0, @@ -121,10 +143,15 @@ fn cubemap_face_to_layer(face: CubeMapFace) -> u32 { } } -fn texture_format_for_attachment(tex: &GpuTexture) -> wgpu::TextureFormat { - tex.as_any().downcast_ref::().unwrap().format() +fn texture_format_for_attachment(tex: &GpuTexture) -> Option { + Some(tex.as_any().downcast_ref::()?.format()) } +/// Hashable key for the render pipeline cache. +/// +/// Encodes all state that affects pipeline creation: program identity, color/depth +/// formats, sample count, blend/depth/stencil/cull mode, and the number of extra +/// vertex buffer slots. Two draw calls with identical keys can share a pipeline. #[derive(Hash, PartialEq, Eq, Clone)] pub struct PipelineKey { program_ptr: usize, @@ -140,6 +167,17 @@ pub struct PipelineKey { extra_vert_count: u8, } +/// Wgpu implementation of [`GpuFrameBufferTrait`](fyrox_graphics::framebuffer::GpuFrameBufferTrait). +/// +/// Represents a render target with optional depth and color attachments. Supports +/// both offscreen framebuffers and the screen backbuffer. Contains the core draw +/// call logic, pipeline caching, and bind group creation. +/// +/// # Clear Behavior +/// +/// Clearing is deferred: [`clear`](Self::clear) stores the values and sets a flag. +/// The actual `LoadOp::Clear` is applied at the next [`draw`](Self::draw) call. +/// The backbuffer clears once per frame (flag set by `swap_buffers`). pub struct WgpuFrameBuffer { server: Weak, depth_attachment: Option, @@ -151,44 +189,123 @@ pub struct WgpuFrameBuffer { } impl WgpuFrameBuffer { - pub fn new(server: &WgpuGraphicsServer, depth: Option, colors: Vec) -> Result { - Ok(Self { server: server.weak_ref(), depth_attachment: depth, color_attachments: colors, is_backbuffer: false, needs_clear: Cell::new(false), pending_clear_color: RefCell::new(wgpu::Color::BLACK), pending_clear_depth: RefCell::new(1.0) }) + /// Creates a new offscreen framebuffer with the given depth and color attachments. + pub fn new( + server: &WgpuGraphicsServer, + depth: Option, + colors: Vec, + ) -> Result { + Ok(Self { + server: server.weak_ref(), + depth_attachment: depth, + color_attachments: colors, + is_backbuffer: false, + needs_clear: Cell::new(false), + pending_clear_color: RefCell::new(wgpu::Color::BLACK), + pending_clear_depth: RefCell::new(1.0), + }) } + /// Creates a backbuffer framebuffer that renders to the screen surface. + /// + /// The backbuffer acquires a surface texture on the first draw call per frame + /// and presents it via [`swap_buffers`](WgpuGraphicsServer::swap_buffers). pub fn backbuffer(server: &WgpuGraphicsServer, depth: Option) -> Self { - Self { server: server.weak_ref(), depth_attachment: depth, color_attachments: Default::default(), is_backbuffer: true, needs_clear: Cell::new(false), pending_clear_color: RefCell::new(wgpu::Color::BLACK), pending_clear_depth: RefCell::new(1.0) } + Self { + server: server.weak_ref(), + depth_attachment: depth, + color_attachments: Default::default(), + is_backbuffer: true, + needs_clear: Cell::new(false), + pending_clear_color: RefCell::new(wgpu::Color::BLACK), + pending_clear_depth: RefCell::new(1.0), + } } - fn get_or_create_pipeline(&self, server: &WgpuGraphicsServer, program: &WgpuProgram, params: &DrawParameters, all_layouts: &[wgpu::VertexBufferLayout<'static>], cf: wgpu::TextureFormat, df: Option, pipeline_layout: &wgpu::PipelineLayout, element_kind: fyrox_graphics::ElementKind, has_color: bool) -> wgpu::RenderPipeline { - let needs_stencil = params.stencil_test.is_some() || params.stencil_op.zpass != fyrox_graphics::StencilAction::Keep || params.stencil_op.fail != fyrox_graphics::StencilAction::Keep || params.stencil_op.zfail != fyrox_graphics::StencilAction::Keep; + fn get_or_create_pipeline( + &self, + server: &WgpuGraphicsServer, + program: &WgpuProgram, + params: &DrawParameters, + all_layouts: &[wgpu::VertexBufferLayout<'static>], + cf: wgpu::TextureFormat, + df: Option, + pipeline_layout: &wgpu::PipelineLayout, + element_kind: fyrox_graphics::ElementKind, + has_color: bool, + ) -> wgpu::RenderPipeline { + let needs_stencil = params.stencil_test.is_some() + || params.stencil_op.zpass != fyrox_graphics::StencilAction::Keep + || params.stencil_op.fail != fyrox_graphics::StencilAction::Keep + || params.stencil_op.zfail != fyrox_graphics::StencilAction::Keep; let depth_fmt = df.unwrap_or(wgpu::TextureFormat::Depth32Float); let stencil_supported = format_has_stencil(depth_fmt); let effective_stencil = needs_stencil && stencil_supported; let key = PipelineKey { - program_ptr: program as *const WgpuProgram as usize, color_format: cf, depth_format: df, sample_count: server.msaa_sample_count, - blend: params.blend.is_some(), depth_test: params.depth_test.is_some(), depth_write: params.depth_write, + program_ptr: program as *const WgpuProgram as usize, + color_format: cf, + depth_format: df, + sample_count: server.msaa_sample_count, + blend: params.blend.is_some(), + depth_test: params.depth_test.is_some(), + depth_write: params.depth_write, stencil: effective_stencil, has_color, - cull: match params.cull_face { Some(CullFace::Back) => 2, Some(CullFace::Front) => 1, None => 0 }, + cull: match params.cull_face { + Some(CullFace::Back) => 2, + Some(CullFace::Front) => 1, + None => 0, + }, extra_vert_count: all_layouts.len() as u8, }; - let key_hash = { let mut h = DefaultHasher::new(); key.hash(&mut h); h.finish() }; - { let cache = server.pipeline_cache.borrow(); if let Some(p) = cache.get(&key_hash) { return p.clone(); } } + let key_hash = { + let mut h = DefaultHasher::new(); + key.hash(&mut h); + h.finish() + }; + { + let cache = server.pipeline_cache.borrow(); + if let Some(p) = cache.get(&key_hash) { + return p.clone(); + } + } let blend_state = params.blend.as_ref().map(|bp| { let rgb_op = blend_mode_to_wgpu(bp.equation.rgb); let alpha_op = blend_mode_to_wgpu(bp.equation.alpha); - let is_minmax_rgb = matches!(rgb_op, wgpu::BlendOperation::Min | wgpu::BlendOperation::Max); - let is_minmax_alpha = matches!(alpha_op, wgpu::BlendOperation::Min | wgpu::BlendOperation::Max); + let is_minmax_rgb = matches!( + rgb_op, + wgpu::BlendOperation::Min | wgpu::BlendOperation::Max + ); + let is_minmax_alpha = matches!( + alpha_op, + wgpu::BlendOperation::Min | wgpu::BlendOperation::Max + ); wgpu::BlendState { color: wgpu::BlendComponent { - src_factor: if is_minmax_rgb { wgpu::BlendFactor::One } else { blend_factor_to_wgpu(bp.func.sfactor) }, - dst_factor: if is_minmax_rgb { wgpu::BlendFactor::One } else { blend_factor_to_wgpu(bp.func.dfactor) }, + src_factor: if is_minmax_rgb { + wgpu::BlendFactor::One + } else { + blend_factor_to_wgpu(bp.func.sfactor) + }, + dst_factor: if is_minmax_rgb { + wgpu::BlendFactor::One + } else { + blend_factor_to_wgpu(bp.func.dfactor) + }, operation: rgb_op, }, alpha: wgpu::BlendComponent { - src_factor: if is_minmax_alpha { wgpu::BlendFactor::One } else { blend_factor_to_wgpu(bp.func.alpha_sfactor) }, - dst_factor: if is_minmax_alpha { wgpu::BlendFactor::One } else { blend_factor_to_wgpu(bp.func.alpha_dfactor) }, + src_factor: if is_minmax_alpha { + wgpu::BlendFactor::One + } else { + blend_factor_to_wgpu(bp.func.alpha_sfactor) + }, + dst_factor: if is_minmax_alpha { + wgpu::BlendFactor::One + } else { + blend_factor_to_wgpu(bp.func.alpha_dfactor) + }, operation: alpha_op, }, } @@ -196,9 +313,16 @@ impl WgpuFrameBuffer { let wgpu_stencil_state = if effective_stencil { let default_face = stencil_face_state(CompareFunc::Always, ¶ms.stencil_op); - let sf = params.stencil_test.as_ref().map(|st| stencil_face_state(st.func, ¶ms.stencil_op)) + let sf = params + .stencil_test + .as_ref() + .map(|st| stencil_face_state(st.func, ¶ms.stencil_op)) .unwrap_or(default_face); - let read_mask = params.stencil_test.as_ref().map(|st| st.mask).unwrap_or(0xFFFF_FFFF); + let read_mask = params + .stencil_test + .as_ref() + .map(|st| st.mask) + .unwrap_or(0xFFFF_FFFF); wgpu::StencilState { front: sf, back: sf, @@ -209,25 +333,35 @@ impl WgpuFrameBuffer { wgpu::StencilState::default() }; - let depth_stencil = if params.depth_test.is_some() || params.depth_write || effective_stencil { - Some(wgpu::DepthStencilState { - format: depth_fmt, - depth_write_enabled: Some(params.depth_write), - depth_compare: Some(params.depth_test.map(compare_func_to_wgpu).unwrap_or(wgpu::CompareFunction::Always)), - stencil: wgpu_stencil_state, - bias: wgpu::DepthBiasState::default(), - }) - } else { - df.map(|f| wgpu::DepthStencilState { - format: f, - depth_write_enabled: Some(false), - depth_compare: Some(wgpu::CompareFunction::Always), - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }) - }; + let depth_stencil = + if params.depth_test.is_some() || params.depth_write || effective_stencil { + Some(wgpu::DepthStencilState { + format: depth_fmt, + depth_write_enabled: Some(params.depth_write), + depth_compare: Some( + params + .depth_test + .map(compare_func_to_wgpu) + .unwrap_or(wgpu::CompareFunction::Always), + ), + stencil: wgpu_stencil_state, + bias: wgpu::DepthBiasState::default(), + }) + } else { + df.map(|f| wgpu::DepthStencilState { + format: f, + depth_write_enabled: Some(false), + depth_compare: Some(wgpu::CompareFunction::Always), + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }) + }; - let cull = match params.cull_face { Some(CullFace::Back) => Some(wgpu::Face::Back), Some(CullFace::Front) => Some(wgpu::Face::Front), None => None }; + let cull = match params.cull_face { + Some(CullFace::Back) => Some(wgpu::Face::Back), + Some(CullFace::Front) => Some(wgpu::Face::Front), + None => None, + }; let topo = match element_kind { fyrox_graphics::ElementKind::Triangle => wgpu::PrimitiveTopology::TriangleList, @@ -235,37 +369,103 @@ impl WgpuFrameBuffer { fyrox_graphics::ElementKind::Point => wgpu::PrimitiveTopology::PointList, }; - let color_target = [Some(wgpu::ColorTargetState { format: cf, blend: blend_state, write_mask: wgpu::ColorWrites::ALL })]; - let fragment_state = if has_color { Some(wgpu::FragmentState { module: program.fragment_module(), entry_point: Some("fs_main"), targets: &color_target, compilation_options: Default::default() }) } else { None }; - let pipeline = server.state.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("RP"), layout: Some(pipeline_layout), - vertex: wgpu::VertexState { module: program.vertex_module(), entry_point: Some("vs_main"), buffers: all_layouts, compilation_options: Default::default() }, - fragment: fragment_state, - primitive: wgpu::PrimitiveState { topology: topo, strip_index_format: None, front_face: wgpu::FrontFace::Ccw, cull_mode: cull, ..Default::default() }, - depth_stencil, - multisample: wgpu::MultisampleState { count: server.msaa_sample_count, mask: !0, alpha_to_coverage_enabled: false }, - multiview_mask: None, - cache: None, - }); + let color_target = [Some(wgpu::ColorTargetState { + format: cf, + blend: blend_state, + write_mask: wgpu::ColorWrites::ALL, + })]; + let fragment_state = if has_color { + Some(wgpu::FragmentState { + module: program.fragment_module(), + entry_point: Some("fs_main"), + targets: &color_target, + compilation_options: Default::default(), + }) + } else { + None + }; + let pipeline = + server + .state + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("RP"), + layout: Some(pipeline_layout), + vertex: wgpu::VertexState { + module: program.vertex_module(), + entry_point: Some("vs_main"), + buffers: all_layouts, + compilation_options: Default::default(), + }, + fragment: fragment_state, + primitive: wgpu::PrimitiveState { + topology: topo, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: cull, + ..Default::default() + }, + depth_stencil, + multisample: wgpu::MultisampleState { + count: server.msaa_sample_count, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview_mask: None, + cache: None, + }); - server.pipeline_cache.borrow_mut().insert(key_hash, pipeline.clone()); + server + .pipeline_cache + .borrow_mut() + .insert(key_hash, pipeline.clone()); pipeline } - fn do_draw(&self, instance_count: u32, geometry: &GpuGeometryBuffer, viewport: Rect, program: &GpuProgram, params: &DrawParameters, resources: &[ResourceBindGroup], element_range: ElementRange) -> Result { - let server = self.server.upgrade().unwrap(); - let geo = geometry.as_any().downcast_ref::().unwrap(); - let prog = program.as_any().downcast_ref::().unwrap(); + fn do_draw( + &self, + instance_count: u32, + geometry: &GpuGeometryBuffer, + viewport: Rect, + program: &GpuProgram, + params: &DrawParameters, + resources: &[ResourceBindGroup], + element_range: ElementRange, + ) -> Result { + let server = self + .server + .upgrade() + .ok_or(FrameworkError::GraphicsServerUnavailable)?; + let geo = geometry + .as_any() + .downcast_ref::() + .ok_or_else(|| FrameworkError::Custom("Expected WgpuGeometryBuffer".into()))?; + let prog = program + .as_any() + .downcast_ref::() + .ok_or_else(|| FrameworkError::Custom("Expected WgpuProgram".into()))?; - let (offset, count) = match element_range { ElementRange::Full => (0, geo.element_count()), ElementRange::Specific { offset, count } => (offset, count) }; - if offset + count > geo.element_count() { return Err(FrameworkError::InvalidElementRange { start: offset, end: offset + count, total: geo.element_count() }); } - if count == 0 { return Ok(DrawCallStatistics { triangles: 0 }); } + let (offset, count) = match element_range { + ElementRange::Full => (0, geo.element_count()), + ElementRange::Specific { offset, count } => (offset, count), + }; + if offset + count > geo.element_count() { + return Err(FrameworkError::InvalidElementRange { + start: offset, + end: offset + count, + total: geo.element_count(), + }); + } + if count == 0 { + return Ok(DrawCallStatistics { triangles: 0 }); + } let surface_tex = if self.is_backbuffer { // Only acquire a new frame if one isn't already stored from a prior draw call this frame. if server.current_frame.borrow().is_none() { match server.surface.get_current_texture() { - wgpu::CurrentSurfaceTexture::Success(t) | wgpu::CurrentSurfaceTexture::Suboptimal(t) => { + wgpu::CurrentSurfaceTexture::Success(t) + | wgpu::CurrentSurfaceTexture::Suboptimal(t) => { *server.current_frame.borrow_mut() = Some(t); } wgpu::CurrentSurfaceTexture::Timeout => { @@ -278,26 +478,57 @@ impl WgpuFrameBuffer { log::warn!("Surface lost/outdated, reconfigured"); return Ok(DrawCallStatistics { triangles: 0 }); } - other => return Err(FrameworkError::Custom(format!("Surface texture error: {other:?}"))), + other => { + return Err(FrameworkError::Custom(format!( + "Surface texture error: {other:?}" + ))) + } } } // Create a view from the stored frame (doesn't consume the SurfaceTexture). let frame = server.current_frame.borrow(); - Some(frame.as_ref().unwrap().texture.create_view(&wgpu::TextureViewDescriptor::default())) - } else { None }; + Some( + frame + .as_ref() + .expect("frame should be Some after acquisition") + .texture + .create_view(&wgpu::TextureViewDescriptor::default()), + ) + } else { + None + }; - let cf = if self.is_backbuffer { server.surface_config.read().unwrap().format } - else if let Some(fc) = self.color_attachments.first() { texture_format_for_attachment(&fc.texture) } - else { wgpu::TextureFormat::Rgba8Unorm }; + let cf = if self.is_backbuffer { + server.surface_config.read().unwrap().format + } else if let Some(fc) = self.color_attachments.first() { + texture_format_for_attachment(&fc.texture).unwrap_or(wgpu::TextureFormat::Rgba8Unorm) + } else { + wgpu::TextureFormat::Rgba8Unorm + }; - let df = self.depth_attachment.as_ref().map(|a| texture_format_for_attachment(&a.texture)); + let df = self + .depth_attachment + .as_ref() + .and_then(|a| texture_format_for_attachment(&a.texture)); // Collect actual texture formats from resources for layout creation let mut texture_formats: Vec<(usize, wgpu::TextureFormat)> = Vec::new(); for group in resources { for binding in group.bindings { - if let ResourceBinding::Texture { texture, binding: loc, .. } = binding { - let wt = texture.as_any().downcast_ref::().unwrap(); + if let ResourceBinding::Texture { + texture, + binding: loc, + .. + } = binding + { + let wt = texture + .as_any() + .downcast_ref::() + .ok_or_else(|| { + FrameworkError::Custom( + "Expected WgpuTexture in resource binding".into(), + ) + })?; texture_formats.push((*loc, wt.format())); } } @@ -306,17 +537,37 @@ impl WgpuFrameBuffer { let (all_layouts, extra_vert_count) = build_vertex_layouts(geo); let has_color = self.is_backbuffer || !self.color_attachments.is_empty(); - let pipeline = self.get_or_create_pipeline(&server, prog, params, &all_layouts, cf, df, &pipeline_layout, geo.element_kind(), has_color); + let pipeline = self.get_or_create_pipeline( + &server, + prog, + params, + &all_layouts, + cf, + df, + &pipeline_layout, + geo.element_kind(), + has_color, + ); let bind_group = create_bind_group(&server, prog, resources); - let mut encoder = server.state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("DrawEnc") }); + let mut encoder = + server + .state + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("DrawEnc"), + }); let color_view = if self.is_backbuffer { - Some(surface_tex.unwrap()) + Some(surface_tex.expect("surface texture should be set for backbuffer")) } else { self.color_attachments.first().map(|first_color| { if let Some(face) = first_color.cube_map_face() { - let wt = first_color.texture.as_any().downcast_ref::().unwrap(); + let wt = first_color + .texture + .as_any() + .downcast_ref::() + .expect("color attachment should be WgpuTexture"); wt.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { dimension: Some(wgpu::TextureViewDimension::D2), base_array_layer: cubemap_face_to_layer(face), @@ -325,13 +576,23 @@ impl WgpuFrameBuffer { ..Default::default() }) } else { - first_color.texture.as_any().downcast_ref::().unwrap().wgpu_view().clone() + first_color + .texture + .as_any() + .downcast_ref::() + .expect("color attachment should be WgpuTexture") + .wgpu_view() + .clone() } }) }; let depth_view = self.depth_attachment.as_ref().map(|a| { - let wt = a.texture.as_any().downcast_ref::().unwrap(); + let wt = a + .texture + .as_any() + .downcast_ref::() + .expect("depth attachment should be WgpuTexture"); if let Some(face) = a.cube_map_face() { wt.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { dimension: Some(wgpu::TextureViewDimension::D2), @@ -349,30 +610,94 @@ impl WgpuFrameBuffer { // Backbuffer clears once per frame (flag set by swap_buffers, consumed on first draw). // Offscreen FBOs clear when their clear() was called. let has_stencil_aspect = df.map(format_has_stencil).unwrap_or(false); - let (color_load, depth_load, stencil_load) = if self.is_backbuffer && server.backbuffer_needs_clear.replace(false) { - (wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }), wgpu::LoadOp::Clear(1.0), if has_stencil_aspect { wgpu::LoadOp::Clear(0) } else { wgpu::LoadOp::Load }) - } else if !self.is_backbuffer && self.needs_clear.replace(false) { - (wgpu::LoadOp::Clear(*self.pending_clear_color.borrow()), wgpu::LoadOp::Clear(*self.pending_clear_depth.borrow()), if has_stencil_aspect { wgpu::LoadOp::Clear(0) } else { wgpu::LoadOp::Load }) - } else { - (wgpu::LoadOp::Load, wgpu::LoadOp::Load, wgpu::LoadOp::Load) - }; - let color_att = color_view.as_ref().map(|view| wgpu::RenderPassColorAttachment { view, resolve_target: None, depth_slice: None, ops: wgpu::Operations { load: color_load, store: wgpu::StoreOp::Store } }); - let color_att_ref: &[Option>] = if color_att.is_some() { &[color_att] } else { &[] }; + let (color_load, depth_load, stencil_load) = + if self.is_backbuffer && server.backbuffer_needs_clear.replace(false) { + ( + wgpu::LoadOp::Clear(wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }), + wgpu::LoadOp::Clear(1.0), + if has_stencil_aspect { + wgpu::LoadOp::Clear(0) + } else { + wgpu::LoadOp::Load + }, + ) + } else if !self.is_backbuffer && self.needs_clear.replace(false) { + ( + wgpu::LoadOp::Clear(*self.pending_clear_color.borrow()), + wgpu::LoadOp::Clear(*self.pending_clear_depth.borrow()), + if has_stencil_aspect { + wgpu::LoadOp::Clear(0) + } else { + wgpu::LoadOp::Load + }, + ) + } else { + (wgpu::LoadOp::Load, wgpu::LoadOp::Load, wgpu::LoadOp::Load) + }; + let color_att = color_view + .as_ref() + .map(|view| wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + depth_slice: None, + ops: wgpu::Operations { + load: color_load, + store: wgpu::StoreOp::Store, + }, + }); + let color_att_ref: &[Option>] = + if color_att.is_some() { + &[color_att] + } else { + &[] + }; let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("DrawRP"), color_attachments: color_att_ref, - depth_stencil_attachment: depth_view.as_ref().map(|v| wgpu::RenderPassDepthStencilAttachment { view: v, depth_ops: Some(wgpu::Operations { load: depth_load, store: wgpu::StoreOp::Store }), stencil_ops: Some(wgpu::Operations { load: stencil_load, store: wgpu::StoreOp::Store }) }), + depth_stencil_attachment: depth_view.as_ref().map(|v| { + wgpu::RenderPassDepthStencilAttachment { + view: v, + depth_ops: Some(wgpu::Operations { + load: depth_load, + store: wgpu::StoreOp::Store, + }), + stencil_ops: Some(wgpu::Operations { + load: stencil_load, + store: wgpu::StoreOp::Store, + }), + } + }), ..Default::default() }); - rp.set_viewport(viewport.x() as f32, viewport.y() as f32, viewport.w() as f32, viewport.h() as f32, 0.0, 1.0); + rp.set_viewport( + viewport.x() as f32, + viewport.y() as f32, + viewport.w() as f32, + viewport.h() as f32, + 0.0, + 1.0, + ); rp.set_pipeline(&pipeline); - if let Some(st) = ¶ms.stencil_test { rp.set_stencil_reference(st.ref_value); } - if let Some(bg) = &bind_group { rp.set_bind_group(0, bg, &[]); } + if let Some(st) = ¶ms.stencil_test { + rp.set_stencil_reference(st.ref_value); + } + if let Some(bg) = &bind_group { + rp.set_bind_group(0, bg, &[]); + } let vbs = geo.vertex_buffers(); - for (i, vb) in vbs.iter().enumerate() { rp.set_vertex_buffer(i as u32, vb.slice(..)); } + for (i, vb) in vbs.iter().enumerate() { + rp.set_vertex_buffer(i as u32, vb.slice(..)); + } let geo_buf_count = vbs.len() as u32; - for i in 0..extra_vert_count { rp.set_vertex_buffer(geo_buf_count + i, server.dummy_vertex_buffer.slice(..)); } + for i in 0..extra_vert_count { + rp.set_vertex_buffer(geo_buf_count + i, server.dummy_vertex_buffer.slice(..)); + } let eb = geo.element_buffer(); rp.set_index_buffer(eb.slice(..), wgpu::IndexFormat::Uint32); @@ -383,17 +708,40 @@ impl WgpuFrameBuffer { } server.state.queue.submit(std::iter::once(encoder.finish())); - Ok(DrawCallStatistics { triangles: count * instance_count as usize }) + Ok(DrawCallStatistics { + triangles: count * instance_count as usize, + }) } } impl GpuFrameBufferTrait for WgpuFrameBuffer { - fn color_attachments(&self) -> &[Attachment] { &self.color_attachments } - fn depth_attachment(&self) -> Option<&Attachment> { self.depth_attachment.as_ref() } + fn color_attachments(&self) -> &[Attachment] { + &self.color_attachments + } + fn depth_attachment(&self) -> Option<&Attachment> { + self.depth_attachment.as_ref() + } fn set_cubemap_face(&self, i: usize, face: CubeMapFace, level: usize) { - if let Some(a) = self.color_attachments.get(i) { a.set_cube_map_face(Some(face)); a.set_level(level); } + if let Some(a) = self.color_attachments.get(i) { + a.set_cube_map_face(Some(face)); + a.set_level(level); + } } - fn blit_to(&self, _dest: &GpuFrameBuffer, _sx0: i32, _sy0: i32, _sx1: i32, _sy1: i32, _dx0: i32, _dy0: i32, _dx1: i32, _dy1: i32, _c: bool, _d: bool, _s: bool) { + fn blit_to( + &self, + _dest: &GpuFrameBuffer, + _sx0: i32, + _sy0: i32, + _sx1: i32, + _sy1: i32, + _dx0: i32, + _dy0: i32, + _dx1: i32, + _dy1: i32, + _c: bool, + _d: bool, + _s: bool, + ) { log::warn!("blit_to not yet implemented for wgpu"); } fn read_pixels(&self, read_target: ReadTarget) -> Option> { @@ -408,14 +756,54 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { let bps = fmt.block_copy_size(None).unwrap_or(4) as usize; let bytes_per_row = (width * bps).max(256); let padded_total = bytes_per_row * (height - 1) + width * bps; - let buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { label: Some("ReadPx"), size: padded_total as u64, usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false }); - let mut enc = server.state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("ReadPxEnc") }); - enc.copy_texture_to_buffer(wgpu::TexelCopyTextureInfo { texture: wtex.wgpu_texture(), mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, wgpu::TexelCopyBufferInfo { buffer: &buf, layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(bytes_per_row as u32), rows_per_image: Some(height as u32) } }, wgpu::Extent3d { width: width as u32, height: height as u32, depth_or_array_layers: 1 }); + let buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("ReadPx"), + size: padded_total as u64, + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let mut enc = + server + .state + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("ReadPxEnc"), + }); + enc.copy_texture_to_buffer( + wgpu::TexelCopyTextureInfo { + texture: wtex.wgpu_texture(), + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::TexelCopyBufferInfo { + buffer: &buf, + layout: wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(bytes_per_row as u32), + rows_per_image: Some(height as u32), + }, + }, + wgpu::Extent3d { + width: width as u32, + height: height as u32, + depth_or_array_layers: 1, + }, + ); server.state.queue.submit(std::iter::once(enc.finish())); let slice = buf.slice(..); let (tx, rx) = std::sync::mpsc::channel(); - slice.map_async(wgpu::MapMode::Read, move |r| { tx.send(r).ok(); }); - server.state.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None }).ok(); + slice.map_async(wgpu::MapMode::Read, move |r| { + tx.send(r).ok(); + }); + server + .state + .device + .poll(wgpu::PollType::Wait { + submission_index: None, + timeout: None, + }) + .ok(); rx.recv().ok()?.ok()?; let mapped = slice.get_mapped_range(); let unpadded_row = width * bps; @@ -426,19 +814,30 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { for y in 0..height { let src_off = y * bytes_per_row; let dst_off = y * unpadded_row; - result[dst_off..dst_off + unpadded_row].copy_from_slice(&mapped[src_off..src_off + unpadded_row]); + result[dst_off..dst_off + unpadded_row] + .copy_from_slice(&mapped[src_off..src_off + unpadded_row]); } } drop(mapped); buf.unmap(); Some(result) - } else { None } + } else { + None + } } - fn clear(&self, _viewport: Rect, color: Option, depth: Option, _stencil: Option) { + fn clear( + &self, + _viewport: Rect, + color: Option, + depth: Option, + _stencil: Option, + ) { if let Some(c) = color { *self.pending_clear_color.borrow_mut() = wgpu::Color { - r: c.r as f64 / 255.0, g: c.g as f64 / 255.0, - b: c.b as f64 / 255.0, a: c.a as f64 / 255.0, + r: c.r as f64 / 255.0, + g: c.g as f64 / 255.0, + b: c.b as f64 / 255.0, + a: c.a as f64 / 255.0, }; } if let Some(d) = depth { @@ -446,38 +845,101 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { } self.needs_clear.set(true); } - fn draw(&self, geometry: &GpuGeometryBuffer, viewport: Rect, program: &GpuProgram, params: &DrawParameters, resources: &[ResourceBindGroup], element_range: ElementRange) -> Result { - self.do_draw(1, geometry, viewport, program, params, resources, element_range) + fn draw( + &self, + geometry: &GpuGeometryBuffer, + viewport: Rect, + program: &GpuProgram, + params: &DrawParameters, + resources: &[ResourceBindGroup], + element_range: ElementRange, + ) -> Result { + self.do_draw( + 1, + geometry, + viewport, + program, + params, + resources, + element_range, + ) } - fn draw_instances(&self, instance_count: usize, geometry: &GpuGeometryBuffer, viewport: Rect, program: &GpuProgram, params: &DrawParameters, resources: &[ResourceBindGroup], element_range: ElementRange) -> Result { - self.do_draw(instance_count as u32, geometry, viewport, program, params, resources, element_range) + fn draw_instances( + &self, + instance_count: usize, + geometry: &GpuGeometryBuffer, + viewport: Rect, + program: &GpuProgram, + params: &DrawParameters, + resources: &[ResourceBindGroup], + element_range: ElementRange, + ) -> Result { + self.do_draw( + instance_count as u32, + geometry, + viewport, + program, + params, + resources, + element_range, + ) } } /// Expected vertex attribute locations that the standard shaders may need but /// geometry might not provide (e.g. boneWeights, boneIndices, vertexSecondTexCoord). -const EXTRA_VERTEX_ATTRS: &[(u32, wgpu::VertexFormat)] = &[ - (4, wgpu::VertexFormat::Float32x4), // boneWeights - (5, wgpu::VertexFormat::Float32x4), // boneIndices - (6, wgpu::VertexFormat::Float32x2), // vertexSecondTexCoord +/// +/// Each entry is a `(location, format, &'static [VertexAttribute])` triple. The +/// attribute arrays are `const` statics so they have `'static` lifetime without +/// needing `Box::leak`, avoiding per-draw-call memory leaks. +const EXTRA_VERTEX_LAYOUTS: &[(u32, &[wgpu::VertexAttribute])] = &[ + ( + 4, + &[wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x4, + offset: 0, + shader_location: 4, + }], + ), // boneWeights + ( + 5, + &[wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x4, + offset: 0, + shader_location: 5, + }], + ), // boneIndices + ( + 6, + &[wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x2, + offset: 0, + shader_location: 6, + }], + ), // vertexSecondTexCoord ]; /// Builds the full vertex buffer layout list, adding dummy entries for attributes -/// the shader expects but the geometry doesn't provide. Returns (layouts, extra_count). +/// the shader expects but the geometry doesn't provide. Returns `(layouts, extra_count)`. +/// +/// Extra layouts use `array_stride: 0` and point to a small dummy vertex buffer +/// on the server, so the shader reads valid (zeroed) data for missing attributes. fn build_vertex_layouts(geo: &WgpuGeometryBuffer) -> (Vec>, u32) { let geo_layouts = geo.vertex_buffer_layouts(); let mut provided = std::collections::HashSet::new(); for layout in geo_layouts { - for attr in layout.attributes { provided.insert(attr.shader_location); } + for attr in layout.attributes { + provided.insert(attr.shader_location); + } } let mut all: Vec> = geo_layouts.to_vec(); let mut extra = 0u32; - for &(loc, fmt) in EXTRA_VERTEX_ATTRS { + for &(loc, attrs) in EXTRA_VERTEX_LAYOUTS { if !provided.contains(&loc) { all.push(wgpu::VertexBufferLayout { array_stride: 0, step_mode: wgpu::VertexStepMode::Vertex, - attributes: Box::leak(vec![wgpu::VertexAttribute { format: fmt, offset: 0, shader_location: loc }].into_boxed_slice()), + attributes: attrs, }); extra += 1; } @@ -485,41 +947,82 @@ fn build_vertex_layouts(geo: &WgpuGeometryBuffer) -> (Vec bool { - !matches!(fmt, wgpu::TextureFormat::R32Float | wgpu::TextureFormat::Rg32Float | wgpu::TextureFormat::Rgba32Float - | wgpu::TextureFormat::R8Uint | wgpu::TextureFormat::R16Uint | wgpu::TextureFormat::R32Uint - | wgpu::TextureFormat::R8Sint | wgpu::TextureFormat::R16Sint | wgpu::TextureFormat::R32Sint) -} - -fn create_bind_group(server: &WgpuGraphicsServer, program: &WgpuProgram, groups: &[ResourceBindGroup]) -> Option { +fn create_bind_group( + server: &WgpuGraphicsServer, + program: &WgpuProgram, + groups: &[ResourceBindGroup], +) -> Option { let mut entries = Vec::new(); let mut texture_formats: Vec<(usize, wgpu::TextureFormat)> = Vec::new(); for group in groups { for binding in group.bindings { match binding { - ResourceBinding::Texture { texture, sampler, binding: loc } => { + ResourceBinding::Texture { + texture, + sampler, + binding: loc, + } => { let wt = texture.as_any().downcast_ref::()?; let ws = sampler.as_any().downcast_ref::()?; texture_formats.push((*loc, wt.format())); - entries.push(wgpu::BindGroupEntry { binding: *loc as u32, resource: wgpu::BindingResource::TextureView(wt.wgpu_binding_view()) }); + entries.push(wgpu::BindGroupEntry { + binding: *loc as u32, + resource: wgpu::BindingResource::TextureView(wt.wgpu_binding_view()), + }); let sampler_res = if is_filterable_format(wt.format()) { wgpu::BindingResource::Sampler(ws.wgpu_sampler()) } else { wgpu::BindingResource::Sampler(server.non_filtering_sampler()) }; - entries.push(wgpu::BindGroupEntry { binding: (*loc + 100) as u32, resource: sampler_res }); + entries.push(wgpu::BindGroupEntry { + binding: (*loc + SAMPLER_BINDING_OFFSET) as u32, + resource: sampler_res, + }); } - ResourceBinding::Buffer { buffer, binding: loc, data_usage } => { + ResourceBinding::Buffer { + buffer, + binding: loc, + data_usage, + } => { let wb = buffer.as_any().downcast_ref::()?; match data_usage { - BufferDataUsage::UseEverything => entries.push(wgpu::BindGroupEntry { binding: (*loc + 200) as u32, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: wb.wgpu_buffer(), offset: 0, size: None }) }), - BufferDataUsage::UseSegment { offset, size } => entries.push(wgpu::BindGroupEntry { binding: (*loc + 200) as u32, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: wb.wgpu_buffer(), offset: *offset as u64, size: Some(std::num::NonZeroU64::new(*size as u64).unwrap()) }) }), + BufferDataUsage::UseEverything => entries.push(wgpu::BindGroupEntry { + binding: (*loc + UNIFORM_BINDING_OFFSET) as u32, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: wb.wgpu_buffer(), + offset: 0, + size: None, + }), + }), + BufferDataUsage::UseSegment { offset, size } => { + let nonzero_size = std::num::NonZeroU64::new(*size as u64) + .expect("BufferDataUsage::UseSegment size must be non-zero"); + entries.push(wgpu::BindGroupEntry { + binding: (*loc + UNIFORM_BINDING_OFFSET) as u32, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: wb.wgpu_buffer(), + offset: *offset as u64, + size: Some(nonzero_size), + }), + }) + } } } } } } - if entries.is_empty() { return None; } + if entries.is_empty() { + return None; + } let (bgl, _) = program.get_or_create_layouts(&texture_formats); - Some(server.state.device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("BG"), layout: &bgl, entries: &entries })) + Some( + server + .state + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("BG"), + layout: &bgl, + entries: &entries, + }), + ) } diff --git a/fyrox-graphics-wgpu/src/geometry_buffer.rs b/fyrox-graphics-wgpu/src/geometry_buffer.rs index 8cb2100135..bec8014ccf 100644 --- a/fyrox-graphics-wgpu/src/geometry_buffer.rs +++ b/fyrox-graphics-wgpu/src/geometry_buffer.rs @@ -22,12 +22,16 @@ use crate::server::WgpuGraphicsServer; use fyrox_graphics::{ core::{array_as_u8_slice, math::TriangleDefinition}, error::FrameworkError, - geometry_buffer::{AttributeKind, ElementsDescriptor, GpuGeometryBufferDescriptor, GpuGeometryBufferTrait}, + geometry_buffer::{ + AttributeKind, ElementsDescriptor, GpuGeometryBufferDescriptor, GpuGeometryBufferTrait, + }, ElementKind, }; use std::cell::{Cell, RefCell}; use std::rc::Weak; +/// Maps a Fyrox [`AttributeKind`] + component count + normalization flag to a +/// wgpu [`VertexFormat`]. fn attribute_format(kind: AttributeKind, cc: usize, normalized: bool) -> wgpu::VertexFormat { match (kind, cc, normalized) { (AttributeKind::Float, 1, _) => wgpu::VertexFormat::Float32, @@ -48,6 +52,13 @@ fn attribute_format(kind: AttributeKind, cc: usize, normalized: bool) -> wgpu::V } } +/// Wgpu implementation of [`GpuGeometryBufferTrait`](fyrox_graphics::geometry_buffer::GpuGeometryBufferTrait). +/// +/// Holds vertex buffers, their layouts, and an index (element) buffer for indexed +/// drawing. Supports triangles, lines, and points as element types. +/// +/// Vertex attribute layouts are leaked once at construction to satisfy wgpu's +/// `'static` lifetime requirement — see the comment in [`new`](Self::new) for details. pub struct WgpuGeometryBuffer { _server: Weak, vertex_buffers: RefCell>, @@ -58,7 +69,15 @@ pub struct WgpuGeometryBuffer { } impl WgpuGeometryBuffer { - pub fn new(server: &WgpuGraphicsServer, desc: GpuGeometryBufferDescriptor) -> Result { + /// Creates a new geometry buffer from the given descriptor. + /// + /// Creates vertex buffers with their attribute layouts and an index buffer. + /// Attribute layouts are leaked once to satisfy wgpu's `'static` lifetime + /// requirement for `VertexBufferLayout::attributes`. + pub fn new( + server: &WgpuGraphicsServer, + desc: GpuGeometryBufferDescriptor, + ) -> Result { let mut vertex_buffers = Vec::new(); let mut vertex_buffer_layouts = Vec::new(); @@ -66,22 +85,47 @@ impl WgpuGeometryBuffer { let data_size = buf.data.bytes.map(|b| b.len()).unwrap_or(0); let label_str = format!("{}VB{i}", desc.name); let buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor { - label: if server.named_objects { Some(&label_str) } else { None }, + label: if server.named_objects { + Some(&label_str) + } else { + None + }, size: data_size.max(1) as u64, usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); - if let Some(data) = buf.data.bytes { if !data.is_empty() { server.state.queue.write_buffer(&buffer, 0, data); } } + if let Some(data) = buf.data.bytes { + if !data.is_empty() { + server.state.queue.write_buffer(&buffer, 0, data); + } + } let mut attributes = Vec::new(); let mut offset = 0u64; for attr in buf.attributes { - attributes.push(wgpu::VertexAttribute { format: attribute_format(attr.kind, attr.component_count, attr.normalized), offset, shader_location: attr.location }); + attributes.push(wgpu::VertexAttribute { + format: attribute_format(attr.kind, attr.component_count, attr.normalized), + offset, + shader_location: attr.location, + }); offset += (attr.kind.size() * attr.component_count) as u64; } - let step_mode = if buf.attributes.iter().any(|a| a.divisor > 0) { wgpu::VertexStepMode::Instance } else { wgpu::VertexStepMode::Vertex }; - vertex_buffer_layouts.push(wgpu::VertexBufferLayout { array_stride: buf.data.element_size as u64, step_mode, attributes: attributes.leak() }); + let step_mode = if buf.attributes.iter().any(|a| a.divisor > 0) { + wgpu::VertexStepMode::Instance + } else { + wgpu::VertexStepMode::Vertex + }; + // NOTE: Attributes are leaked to satisfy wgpu's `'static` lifetime requirement + // for `VertexBufferLayout::attributes`. Each geometry buffer leaks one small + // `Vec` per vertex buffer (typically < 100 bytes). This is + // acceptable because geometry buffers are long-lived (tied to mesh lifetime) + // and relatively few in number. + vertex_buffer_layouts.push(wgpu::VertexBufferLayout { + array_stride: buf.data.element_size as u64, + step_mode, + attributes: attributes.leak(), + }); vertex_buffers.push(buffer); } @@ -93,21 +137,73 @@ impl WgpuGeometryBuffer { let ib_label = format!("{}IB", desc.name); let element_buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor { - label: if server.named_objects { Some(&ib_label) } else { None }, + label: if server.named_objects { + Some(&ib_label) + } else { + None + }, size: element_data.len().max(1) as u64, usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); - if !element_data.is_empty() { server.state.queue.write_buffer(&element_buffer, 0, element_data); } + if !element_data.is_empty() { + server + .state + .queue + .write_buffer(&element_buffer, 0, element_data); + } - Ok(Self { _server: server.weak_ref(), vertex_buffers: RefCell::new(vertex_buffers), vertex_buffer_layouts, element_buffer: RefCell::new(element_buffer), element_count: Cell::new(element_count), element_kind: desc.elements.element_kind() }) + Ok(Self { + _server: server.weak_ref(), + vertex_buffers: RefCell::new(vertex_buffers), + vertex_buffer_layouts, + element_buffer: RefCell::new(element_buffer), + element_count: Cell::new(element_count), + element_kind: desc.elements.element_kind(), + }) } - pub fn element_count(&self) -> usize { self.element_count.get() } - pub fn element_kind(&self) -> ElementKind { self.element_kind } - pub fn vertex_buffers(&self) -> std::cell::Ref<'_, Vec> { self.vertex_buffers.borrow() } - pub fn vertex_buffer_layouts(&self) -> &[wgpu::VertexBufferLayout<'static>] { &self.vertex_buffer_layouts } - pub fn element_buffer(&self) -> std::cell::Ref<'_, wgpu::Buffer> { self.element_buffer.borrow() } + /// Returns the number of elements (triangles, lines, or points) in the index buffer. + pub fn element_count(&self) -> usize { + self.element_count.get() + } + /// Returns the type of elements stored in the index buffer. + pub fn element_kind(&self) -> ElementKind { + self.element_kind + } + /// Returns a borrow of the vertex buffer list. + pub fn vertex_buffers(&self) -> std::cell::Ref<'_, Vec> { + self.vertex_buffers.borrow() + } + /// Returns the vertex buffer layout descriptors for all vertex buffers. + pub fn vertex_buffer_layouts(&self) -> &[wgpu::VertexBufferLayout<'static>] { + &self.vertex_buffer_layouts + } + /// Returns a borrow of the index (element) buffer. + pub fn element_buffer(&self) -> std::cell::Ref<'_, wgpu::Buffer> { + self.element_buffer.borrow() + } + + /// Writes index data to the element buffer, recreating it if the new data is larger + /// than the current buffer capacity. + fn write_element_data(&self, count: usize, data: &[u8]) { + if let Some(server) = self._server.upgrade() { + self.element_count.set(count); + let mut eb = self.element_buffer.borrow_mut(); + if (data.len() as u64) <= eb.size() { + server.state.queue.write_buffer(&eb, 0, data); + } else { + let new_buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: data.len() as u64, + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + server.state.queue.write_buffer(&new_buf, 0, data); + *eb = new_buf; + } + } + } } impl GpuGeometryBufferTrait for WgpuGeometryBuffer { @@ -118,7 +214,6 @@ impl GpuGeometryBufferTrait for WgpuGeometryBuffer { if (data.len() as u64) <= buf.size() { server.state.queue.write_buffer(buf, 0, data); } else { - // Recreate buffer with correct size let new_buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { label: None, size: data.len() as u64, @@ -131,59 +226,16 @@ impl GpuGeometryBufferTrait for WgpuGeometryBuffer { } } } - fn element_count(&self) -> usize { self.element_count.get() } + fn element_count(&self) -> usize { + self.element_count.get() + } fn set_triangles(&self, triangles: &[TriangleDefinition]) { - if let Some(server) = self._server.upgrade() { - self.element_count.set(triangles.len()); - let data = array_as_u8_slice(triangles); - let mut eb = self.element_buffer.borrow_mut(); - if (data.len() as u64) <= eb.size() { - server.state.queue.write_buffer(&eb, 0, data); - } else { - let new_buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { - label: None, size: data.len() as u64, - usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - server.state.queue.write_buffer(&new_buf, 0, data); - *eb = new_buf; - } - } + self.write_element_data(triangles.len(), array_as_u8_slice(triangles)); } fn set_lines(&self, lines: &[[u32; 2]]) { - if let Some(server) = self._server.upgrade() { - self.element_count.set(lines.len()); - let data = array_as_u8_slice(lines); - let mut eb = self.element_buffer.borrow_mut(); - if (data.len() as u64) <= eb.size() { - server.state.queue.write_buffer(&eb, 0, data); - } else { - let new_buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { - label: None, size: data.len() as u64, - usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - server.state.queue.write_buffer(&new_buf, 0, data); - *eb = new_buf; - } - } + self.write_element_data(lines.len(), array_as_u8_slice(lines)); } fn set_points(&self, points: &[u32]) { - if let Some(server) = self._server.upgrade() { - self.element_count.set(points.len()); - let data = array_as_u8_slice(points); - let mut eb = self.element_buffer.borrow_mut(); - if (data.len() as u64) <= eb.size() { - server.state.queue.write_buffer(&eb, 0, data); - } else { - let new_buf = server.state.device.create_buffer(&wgpu::BufferDescriptor { - label: None, size: data.len() as u64, - usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - server.state.queue.write_buffer(&new_buf, 0, data); - *eb = new_buf; - } - } + self.write_element_data(points.len(), array_as_u8_slice(points)); } } diff --git a/fyrox-graphics-wgpu/src/lib.rs b/fyrox-graphics-wgpu/src/lib.rs index cc2f8ec070..70bb5e00e8 100644 --- a/fyrox-graphics-wgpu/src/lib.rs +++ b/fyrox-graphics-wgpu/src/lib.rs @@ -18,12 +18,73 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +//! Wgpu-based graphics server implementation for the Fyrox game engine. +//! +//! This crate provides a concrete [`GraphicsServer`](fyrox_graphics::server::GraphicsServer) +//! implementation backed by the [wgpu](https://wgpu.rs/) graphics API. It is the primary +//! rendering backend for Fyrox and supports desktop (Vulkan/Metal/DX12) and web (WebGL2/WebGPU) +//! targets. +//! +//! # Architecture +//! +//! The entry point is [`server::WgpuGraphicsServer`], which creates and owns the wgpu +//! device, queue, and surface. All GPU resources (textures, buffers, shaders, etc.) are +//! created through the server and implement the corresponding trait from [`fyrox_graphics`]. +//! +//! ```text +//! WgpuGraphicsServer +//! ├── WgpuTexture (texture.rs) — 1D/2D/3D/Cube textures +//! ├── WgpuBuffer (buffer.rs) — uniform/vertex/index buffers +//! ├── WgpuSampler (sampler.rs) — texture sampling state +//! ├── WgpuGeometryBuffer (geometry_buffer.rs) — mesh vertex + index data +//! ├── WgpuFrameBuffer (framebuffer.rs) — render targets + draw calls +//! ├── WgpuProgram (program.rs) — compiled shader programs +//! ├── WgpuQuery (query.rs) — GPU queries (stub) +//! └── WgpuAsyncReadBuffer(read_buffer.rs) — async pixel readback +//! ``` +//! +//! # Binding Layout +//! +//! All shader resources use a fixed binding scheme within bind group 0: +//! +//! | Resource kind | Binding slot | +//! |---------------------|------------------------| +//! | Texture view | `N` | +//! | Sampler | `N + 100` | +//! | Uniform buffer | `N + 200` | +//! +//! Where `N` is the resource's declared binding index from [`ShaderResourceDefinition`]. +//! +//! # Pipeline Caching +//! +//! Render pipelines are immutable and expensive to create. The backend caches them +//! by a hash of the full render state (program, formats, blend, depth, stencil, cull) +//! in a per-server [`HashMap`](std::collections::HashMap). +//! +//! # Shared Shader Library +//! +//! Every compiled shader includes `shaders/shared.wgsl`, which provides PBR lighting, +//! shadow mapping, color space conversion, and other common utilities. + +#![warn(missing_docs)] + +/// Generic GPU buffer implementation (uniform, vertex, index, pixel read/write). pub mod buffer; +/// Texture format helpers and binding offset constants. +pub mod format_helpers; +/// Render target (framebuffer) implementation with draw call logic and pipeline caching. pub mod framebuffer; +/// Geometry buffer implementation (vertex + index buffers for mesh rendering). pub mod geometry_buffer; +/// Shader compilation and program management. pub mod program; +/// GPU query stub implementation. pub mod query; +/// Async pixel readback buffer implementation. pub mod read_buffer; +/// Texture sampler implementation. pub mod sampler; +/// The main graphics server — entry point for all GPU resource creation. pub mod server; +/// Texture creation, upload, and format mapping. pub mod texture; diff --git a/fyrox-graphics-wgpu/src/program.rs b/fyrox-graphics-wgpu/src/program.rs index 6a72e9f2de..20651b1d8c 100644 --- a/fyrox-graphics-wgpu/src/program.rs +++ b/fyrox-graphics-wgpu/src/program.rs @@ -18,11 +18,17 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use crate::format_helpers::{ + sample_type_for_format, SAMPLER_BINDING_OFFSET, UNIFORM_BINDING_OFFSET, +}; use crate::server::WgpuGraphicsServer; use fyrox_graphics::{ core::log::{Log, MessageKind}, error::FrameworkError, - gpu_program::{GpuProgramTrait, GpuShaderTrait, SamplerKind, ShaderKind, ShaderPropertyKind, ShaderResourceDefinition, ShaderResourceKind}, + gpu_program::{ + GpuProgramTrait, GpuShaderTrait, SamplerKind, ShaderKind, ShaderPropertyKind, + ShaderResourceDefinition, ShaderResourceKind, + }, }; use std::borrow::Cow; use std::cell::RefCell; @@ -79,11 +85,20 @@ fn generate_wgsl_declarations(resources: &[ShaderResourceDefinition]) -> String match res.kind { ShaderResourceKind::Texture { kind, .. } => { let tex_type = wgsl_texture_type(kind); - decls += &format!("@group(0) @binding({}) var {}_tex: {};\n", res.binding, res.name, tex_type); - decls += &format!("@group(0) @binding({}) var {}_samp: sampler;\n", res.binding + 100, res.name); + decls += &format!( + "@group(0) @binding({}) var {}_tex: {};\n", + res.binding, res.name, tex_type + ); + decls += &format!( + "@group(0) @binding({}) var {}_samp: sampler;\n", + res.binding + SAMPLER_BINDING_OFFSET, + res.name + ); } ShaderResourceKind::PropertyGroup(ref fields) => { - if fields.is_empty() { continue; } + if fields.is_empty() { + continue; + } decls += &format!("struct T{} {{\n", res.name); for f in fields { let n = &f.name; @@ -91,7 +106,12 @@ fn generate_wgsl_declarations(resources: &[ShaderResourceDefinition]) -> String decls += &format!(" {n}: {ty},\n"); } decls += "}\n"; - decls += &format!("@group(0) @binding({}) var {}: T{};\n", res.binding + 200, res.name, res.name); + decls += &format!( + "@group(0) @binding({}) var {}: T{};\n", + res.binding + UNIFORM_BINDING_OFFSET, + res.name, + res.name + ); } } } @@ -100,31 +120,22 @@ fn generate_wgsl_declarations(resources: &[ShaderResourceDefinition]) -> String } /// Compiles WGSL source into a wgpu shader module. -fn compile_wgsl(device: &wgpu::Device, name: &str, wgsl: &str) -> Result { +fn compile_wgsl( + device: &wgpu::Device, + name: &str, + wgsl: &str, +) -> Result { let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some(name), source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(wgsl)), }); - Log::writeln(MessageKind::Information, format!("Shader {name} compiled successfully!")); + Log::writeln( + MessageKind::Information, + format!("Shader {name} compiled successfully!"), + ); Ok(shader_module) } -fn sample_type_for_format(fmt: wgpu::TextureFormat) -> wgpu::TextureSampleType { - use wgpu::TextureFormat as F; - match fmt { - F::Depth16Unorm | F::Depth24Plus | F::Depth24PlusStencil8 - | F::Depth32Float | F::Depth32FloatStencil8 => wgpu::TextureSampleType::Depth, - F::R8Uint | F::R16Uint | F::R32Uint - | F::Rg8Uint | F::Rg16Uint | F::Rg32Uint - | F::Rgba8Uint | F::Rgba16Uint | F::Rgba32Uint => wgpu::TextureSampleType::Uint, - F::R8Sint | F::R16Sint | F::R32Sint - | F::Rg8Sint | F::Rg16Sint | F::Rg32Sint - | F::Rgba8Sint | F::Rgba16Sint | F::Rgba32Sint => wgpu::TextureSampleType::Sint, - F::R32Float | F::Rg32Float | F::Rgba32Float => wgpu::TextureSampleType::Float { filterable: false }, - _ => wgpu::TextureSampleType::Float { filterable: true }, - } -} - /// Creates bind group layout using actual texture formats when available. /// `texture_formats` provides the actual wgpu format for specific bindings. fn create_bind_group_layout_with_formats( @@ -132,43 +143,92 @@ fn create_bind_group_layout_with_formats( resources: &[ShaderResourceDefinition], texture_formats: &[(usize, wgpu::TextureFormat)], ) -> wgpu::BindGroupLayout { - let fmt_map: std::collections::HashMap = texture_formats.iter().copied().collect(); + let fmt_map: std::collections::HashMap = + texture_formats.iter().copied().collect(); let mut entries = Vec::new(); for res in resources { match res.kind { ShaderResourceKind::Texture { kind, .. } => { let vd = match kind { - SamplerKind::Sampler1D | SamplerKind::USampler1D => wgpu::TextureViewDimension::D1, - SamplerKind::Sampler2D | SamplerKind::USampler2D | SamplerKind::DepthSampler2D => wgpu::TextureViewDimension::D2, - SamplerKind::Sampler3D | SamplerKind::USampler3D => wgpu::TextureViewDimension::D3, - SamplerKind::SamplerCube | SamplerKind::USamplerCube | SamplerKind::DepthSamplerCube => wgpu::TextureViewDimension::Cube, + SamplerKind::Sampler1D | SamplerKind::USampler1D => { + wgpu::TextureViewDimension::D1 + } + SamplerKind::Sampler2D + | SamplerKind::USampler2D + | SamplerKind::DepthSampler2D => wgpu::TextureViewDimension::D2, + SamplerKind::Sampler3D | SamplerKind::USampler3D => { + wgpu::TextureViewDimension::D3 + } + SamplerKind::SamplerCube + | SamplerKind::USamplerCube + | SamplerKind::DepthSamplerCube => wgpu::TextureViewDimension::Cube, }; // Use actual texture format if available, otherwise fall back to kind-based inference let st = if let Some(&fmt) = fmt_map.get(&res.binding) { sample_type_for_format(fmt) } else { match kind { - SamplerKind::DepthSampler2D | SamplerKind::DepthSamplerCube => wgpu::TextureSampleType::Depth, - SamplerKind::USampler1D | SamplerKind::USampler2D | SamplerKind::USampler3D | SamplerKind::USamplerCube => wgpu::TextureSampleType::Uint, + SamplerKind::DepthSampler2D | SamplerKind::DepthSamplerCube => { + wgpu::TextureSampleType::Depth + } + SamplerKind::USampler1D + | SamplerKind::USampler2D + | SamplerKind::USampler3D + | SamplerKind::USamplerCube => wgpu::TextureSampleType::Uint, _ => wgpu::TextureSampleType::Float { filterable: true }, } }; - entries.push(wgpu::BindGroupLayoutEntry { binding: res.binding as u32, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: st, view_dimension: vd, multisampled: false }, count: None }); - let sampler_binding = if matches!(st, wgpu::TextureSampleType::Float { filterable: false } | wgpu::TextureSampleType::Uint | wgpu::TextureSampleType::Sint) { + entries.push(wgpu::BindGroupLayoutEntry { + binding: res.binding as u32, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: st, + view_dimension: vd, + multisampled: false, + }, + count: None, + }); + let sampler_binding = if matches!( + st, + wgpu::TextureSampleType::Float { filterable: false } + | wgpu::TextureSampleType::Uint + | wgpu::TextureSampleType::Sint + ) { wgpu::SamplerBindingType::NonFiltering } else { wgpu::SamplerBindingType::Filtering }; - entries.push(wgpu::BindGroupLayoutEntry { binding: (res.binding + 100) as u32, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(sampler_binding), count: None }); + entries.push(wgpu::BindGroupLayoutEntry { + binding: (res.binding + SAMPLER_BINDING_OFFSET) as u32, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(sampler_binding), + count: None, + }); } ShaderResourceKind::PropertyGroup { .. } => { - entries.push(wgpu::BindGroupLayoutEntry { binding: (res.binding + 200) as u32, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None }, count: None }); + entries.push(wgpu::BindGroupLayoutEntry { + binding: (res.binding + UNIFORM_BINDING_OFFSET) as u32, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }); } } } - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("ShaderBindGroupLayout"), entries: &entries }) + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("ShaderBindGroupLayout"), + entries: &entries, + }) } +/// Wgpu implementation of [`GpuShaderTrait`](fyrox_graphics::gpu_program::GpuShaderTrait). +/// +/// Wraps a compiled [`wgpu::ShaderModule`]. Shaders are compiled from WGSL source +/// with automatically generated resource binding declarations prepended. pub struct WgpuShader { _server: Weak, module: wgpu::ShaderModule, @@ -178,23 +238,44 @@ pub struct WgpuShader { impl GpuShaderTrait for WgpuShader {} impl WgpuShader { - pub fn new(server: &WgpuGraphicsServer, name: String, kind: ShaderKind, source: String, resources: &[ShaderResourceDefinition], _line_offset: isize) -> Result { + /// Compiles a WGSL shader from source with resource binding declarations. + /// + /// The compilation pipeline: + /// 1. Validates resource definitions for duplicate bindings/names + /// 2. Generates `@group(0) @binding(N)` WGSL declarations via [`generate_wgsl_declarations`] + /// 3. Prepends declarations + [`shared.wgsl`](shaders/shared.wgsl) to the user source + /// 4. Compiles the combined WGSL as a [`wgpu::ShaderModule`] + pub fn new( + server: &WgpuGraphicsServer, + name: String, + kind: ShaderKind, + source: String, + resources: &[ShaderResourceDefinition], + _line_offset: isize, + ) -> Result { for r in resources { for o in resources { - if std::ptr::eq(r, o) { continue; } + if std::ptr::eq(r, o) { + continue; + } if std::mem::discriminant(&r.kind) == std::mem::discriminant(&o.kind) { - if r.binding == o.binding { return Err(FrameworkError::Custom(format!("Resource {} and {} same binding {}", r.name, o.name, r.binding))); } - if r.name == o.name { return Err(FrameworkError::Custom(format!("Duplicate resource name {}", r.name))); } + if r.binding == o.binding { + return Err(FrameworkError::Custom(format!( + "Resource {} and {} same binding {}", + r.name, o.name, r.binding + ))); + } + if r.name == o.name { + return Err(FrameworkError::Custom(format!( + "Duplicate resource name {}", + r.name + ))); + } } } } let declarations = generate_wgsl_declarations(resources); - if name.contains("Widget") { - use std::io::Write; - let mut f = std::fs::File::create("C:\\Users\\I.Kondrashkin\\projects\\Fyrox\\shader_dump.txt").unwrap(); - writeln!(f, "=== DECLS for {name} ===\n{declarations}=== SOURCE ===\n{source}").ok(); - } let shared = include_str!("shaders/shared.wgsl"); let mut wgsl = String::new(); @@ -204,12 +285,27 @@ impl WgpuShader { wgsl += &source; let module = compile_wgsl(&server.state.device, &name, &wgsl)?; - Ok(Self { _server: server.weak_ref(), module, _kind: kind }) + Ok(Self { + _server: server.weak_ref(), + module, + _kind: kind, + }) } - pub fn wgpu_module(&self) -> &wgpu::ShaderModule { &self.module } + /// Returns a reference to the compiled [`wgpu::ShaderModule`]. + pub fn wgpu_module(&self) -> &wgpu::ShaderModule { + &self.module + } } +/// Wgpu implementation of [`GpuProgramTrait`](fyrox_graphics::gpu_program::GpuProgramTrait). +/// +/// A shader program consisting of a vertex and fragment [`wgpu::ShaderModule`], +/// along with resource definitions and lazily-cached bind group / pipeline layouts. +/// +/// The layout cache is keyed on the actual texture formats passed to +/// [`get_or_create_layouts`](Self::get_or_create_layouts), ensuring correct +/// sample type inference for each unique set of bound textures. pub struct WgpuProgram { server: Weak, name: String, @@ -222,22 +318,71 @@ pub struct WgpuProgram { impl GpuProgramTrait for WgpuProgram {} impl WgpuProgram { - pub fn from_source(server: &WgpuGraphicsServer, name: &str, vs: String, vs_off: isize, fs: String, fs_off: isize, resources: &[ShaderResourceDefinition]) -> Result { - let vert = WgpuShader::new(server, format!("{name}_VS"), ShaderKind::Vertex, vs, resources, vs_off)?; - let frag = WgpuShader::new(server, format!("{name}_FS"), ShaderKind::Fragment, fs, resources, fs_off)?; + /// Creates a program by compiling vertex and fragment shaders from source. + /// + /// Both shaders share the same resource definitions. The vertex shader is + /// named `{name}_VS` and the fragment shader `{name}_FS`. + pub fn from_source( + server: &WgpuGraphicsServer, + name: &str, + vs: String, + vs_off: isize, + fs: String, + fs_off: isize, + resources: &[ShaderResourceDefinition], + ) -> Result { + let vert = WgpuShader::new( + server, + format!("{name}_VS"), + ShaderKind::Vertex, + vs, + resources, + vs_off, + )?; + let frag = WgpuShader::new( + server, + format!("{name}_FS"), + ShaderKind::Fragment, + fs, + resources, + fs_off, + )?; Self::from_modules(server, name, &vert, &frag, resources) } - pub fn from_shaders(server: &WgpuGraphicsServer, name: &str, vs: &fyrox_graphics::gpu_program::GpuShader, fs: &fyrox_graphics::gpu_program::GpuShader, resources: &[ShaderResourceDefinition]) -> Result { - let vert = vs.as_any().downcast_ref::().ok_or_else(|| FrameworkError::Custom("Expected WgpuShader".into()))?; - let frag = fs.as_any().downcast_ref::().ok_or_else(|| FrameworkError::Custom("Expected WgpuShader".into()))?; + /// Creates a program from pre-compiled shaders. + /// + /// The shaders must be [`WgpuShader`] instances (downcast from trait objects). + pub fn from_shaders( + server: &WgpuGraphicsServer, + name: &str, + vs: &fyrox_graphics::gpu_program::GpuShader, + fs: &fyrox_graphics::gpu_program::GpuShader, + resources: &[ShaderResourceDefinition], + ) -> Result { + let vert = vs + .as_any() + .downcast_ref::() + .ok_or_else(|| FrameworkError::Custom("Expected WgpuShader".into()))?; + let frag = fs + .as_any() + .downcast_ref::() + .ok_or_else(|| FrameworkError::Custom("Expected WgpuShader".into()))?; Self::from_modules(server, name, vert, frag, resources) } - fn from_modules(server: &WgpuGraphicsServer, name: &str, vert: &WgpuShader, frag: &WgpuShader, resources: &[ShaderResourceDefinition]) -> Result { + fn from_modules( + server: &WgpuGraphicsServer, + name: &str, + vert: &WgpuShader, + frag: &WgpuShader, + resources: &[ShaderResourceDefinition], + ) -> Result { Ok(Self { - server: server.weak_ref(), name: name.to_owned(), - vertex_module: vert.module.clone(), fragment_module: frag.module.clone(), + server: server.weak_ref(), + name: name.to_owned(), + vertex_module: vert.module.clone(), + fragment_module: frag.module.clone(), resources: resources.to_vec(), cached_layouts: RefCell::new(None), }) @@ -245,26 +390,51 @@ impl WgpuProgram { /// Lazily create bind group layout + pipeline layout based on actual texture formats. /// `texture_formats` maps resource binding -> actual wgpu texture format for textures. - pub fn get_or_create_layouts(&self, texture_formats: &[(usize, wgpu::TextureFormat)]) -> (wgpu::BindGroupLayout, wgpu::PipelineLayout) { + pub fn get_or_create_layouts( + &self, + texture_formats: &[(usize, wgpu::TextureFormat)], + ) -> (wgpu::BindGroupLayout, wgpu::PipelineLayout) { if let Some((ref bgl, ref pl)) = *self.cached_layouts.borrow() { return (bgl.clone(), pl.clone()); } - let server = self.server.upgrade().unwrap(); - let bgl = create_bind_group_layout_with_formats(&server.state.device, &self.resources, texture_formats); - let pl = server.state.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some(&format!("{}_PL", self.name)), - bind_group_layouts: &[Some(&bgl)], - ..Default::default() - }); + let server = self + .server + .upgrade() + .expect("WgpuGraphicsServer dropped before WgpuProgram"); + let bgl = create_bind_group_layout_with_formats( + &server.state.device, + &self.resources, + texture_formats, + ); + let pl = server + .state + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some(&format!("{}_PL", self.name)), + bind_group_layouts: &[Some(&bgl)], + ..Default::default() + }); let result = (bgl.clone(), pl.clone()); *self.cached_layouts.borrow_mut() = Some(result.clone()); result } - pub fn vertex_module(&self) -> &wgpu::ShaderModule { &self.vertex_module } - pub fn fragment_module(&self) -> &wgpu::ShaderModule { &self.fragment_module } - pub fn resources(&self) -> &[ShaderResourceDefinition] { &self.resources } - pub fn name(&self) -> &str { &self.name } + /// Returns a reference to the vertex shader module. + pub fn vertex_module(&self) -> &wgpu::ShaderModule { + &self.vertex_module + } + /// Returns a reference to the fragment shader module. + pub fn fragment_module(&self) -> &wgpu::ShaderModule { + &self.fragment_module + } + /// Returns the resource definitions for this program. + pub fn resources(&self) -> &[ShaderResourceDefinition] { + &self.resources + } + /// Returns the program name (used for debug labels). + pub fn name(&self) -> &str { + &self.name + } } #[cfg(test)] @@ -276,12 +446,18 @@ mod tests { let resources = vec![ ShaderResourceDefinition { name: "diffuseTexture".into(), - kind: ShaderResourceKind::Texture { kind: SamplerKind::Sampler2D, fallback: Default::default() }, + kind: ShaderResourceKind::Texture { + kind: SamplerKind::Sampler2D, + fallback: Default::default(), + }, binding: 0, }, ShaderResourceDefinition { name: "shadowMap".into(), - kind: ShaderResourceKind::Texture { kind: SamplerKind::SamplerCube, fallback: Default::default() }, + kind: ShaderResourceKind::Texture { + kind: SamplerKind::SamplerCube, + fallback: Default::default(), + }, binding: 1, }, ]; @@ -295,17 +471,20 @@ mod tests { #[test] fn test_generate_wgsl_declarations_uniforms() { use fyrox_graphics::gpu_program::ShaderProperty; - let resources = vec![ - ShaderResourceDefinition { - name: "properties".into(), - kind: ShaderResourceKind::PropertyGroup(vec![ - ShaderProperty::new("field0", ShaderPropertyKind::Float { value: 0.0 }), - ShaderProperty::new("field1", ShaderPropertyKind::Vector3 { value: Default::default() }), - ShaderProperty::new("field2", ShaderPropertyKind::Bool { value: false }), - ]), - binding: 0, - }, - ]; + let resources = vec![ShaderResourceDefinition { + name: "properties".into(), + kind: ShaderResourceKind::PropertyGroup(vec![ + ShaderProperty::new("field0", ShaderPropertyKind::Float { value: 0.0 }), + ShaderProperty::new( + "field1", + ShaderPropertyKind::Vector3 { + value: Default::default(), + }, + ), + ShaderProperty::new("field2", ShaderPropertyKind::Bool { value: false }), + ]), + binding: 0, + }]; let decls = generate_wgsl_declarations(&resources); assert!(decls.contains("struct Tproperties {")); assert!(decls.contains("field0: f32,")); @@ -318,23 +497,85 @@ mod tests { fn test_wgsl_texture_type_mapping() { assert_eq!(wgsl_texture_type(SamplerKind::Sampler2D), "texture_2d"); assert_eq!(wgsl_texture_type(SamplerKind::Sampler3D), "texture_3d"); - assert_eq!(wgsl_texture_type(SamplerKind::SamplerCube), "texture_cube"); - assert_eq!(wgsl_texture_type(SamplerKind::USampler2D), "texture_2d"); + assert_eq!( + wgsl_texture_type(SamplerKind::SamplerCube), + "texture_cube" + ); + assert_eq!( + wgsl_texture_type(SamplerKind::USampler2D), + "texture_2d" + ); } #[test] fn test_wgsl_property_type_mapping() { - assert_eq!(wgsl_property_type(&ShaderPropertyKind::Float { value: 0.0 }), "f32"); - assert_eq!(wgsl_property_type(&ShaderPropertyKind::Int { value: 0 }), "i32"); - assert_eq!(wgsl_property_type(&ShaderPropertyKind::UInt { value: 0 }), "u32"); - assert_eq!(wgsl_property_type(&ShaderPropertyKind::Bool { value: false }), "u32"); - assert_eq!(wgsl_property_type(&ShaderPropertyKind::Vector2 { value: Default::default() }), "vec2f"); - assert_eq!(wgsl_property_type(&ShaderPropertyKind::Vector3 { value: Default::default() }), "vec3f"); - assert_eq!(wgsl_property_type(&ShaderPropertyKind::Vector4 { value: Default::default() }), "vec4f"); - assert_eq!(wgsl_property_type(&ShaderPropertyKind::Matrix2 { value: Default::default() }), "mat2x2f"); - assert_eq!(wgsl_property_type(&ShaderPropertyKind::Matrix3 { value: Default::default() }), "mat3x3f"); - assert_eq!(wgsl_property_type(&ShaderPropertyKind::Matrix4 { value: Default::default() }), "mat4x4f"); - assert_eq!(wgsl_property_type(&ShaderPropertyKind::Color { r: 255, g: 255, b: 255, a: 255 }), "vec4f"); - assert_eq!(wgsl_property_type(&ShaderPropertyKind::FloatArray { max_len: 32, value: vec![] }), "array"); + assert_eq!( + wgsl_property_type(&ShaderPropertyKind::Float { value: 0.0 }), + "f32" + ); + assert_eq!( + wgsl_property_type(&ShaderPropertyKind::Int { value: 0 }), + "i32" + ); + assert_eq!( + wgsl_property_type(&ShaderPropertyKind::UInt { value: 0 }), + "u32" + ); + assert_eq!( + wgsl_property_type(&ShaderPropertyKind::Bool { value: false }), + "u32" + ); + assert_eq!( + wgsl_property_type(&ShaderPropertyKind::Vector2 { + value: Default::default() + }), + "vec2f" + ); + assert_eq!( + wgsl_property_type(&ShaderPropertyKind::Vector3 { + value: Default::default() + }), + "vec3f" + ); + assert_eq!( + wgsl_property_type(&ShaderPropertyKind::Vector4 { + value: Default::default() + }), + "vec4f" + ); + assert_eq!( + wgsl_property_type(&ShaderPropertyKind::Matrix2 { + value: Default::default() + }), + "mat2x2f" + ); + assert_eq!( + wgsl_property_type(&ShaderPropertyKind::Matrix3 { + value: Default::default() + }), + "mat3x3f" + ); + assert_eq!( + wgsl_property_type(&ShaderPropertyKind::Matrix4 { + value: Default::default() + }), + "mat4x4f" + ); + assert_eq!( + wgsl_property_type(&ShaderPropertyKind::Color { + r: 255, + g: 255, + b: 255, + a: 255 + }), + "vec4f" + ); + assert_eq!( + wgsl_property_type(&ShaderPropertyKind::FloatArray { + max_len: 32, + value: vec![] + }), + "array" + ); } } diff --git a/fyrox-graphics-wgpu/src/query.rs b/fyrox-graphics-wgpu/src/query.rs index 5f46849caf..24f804ed40 100644 --- a/fyrox-graphics-wgpu/src/query.rs +++ b/fyrox-graphics-wgpu/src/query.rs @@ -27,22 +27,48 @@ use std::cell::Cell; use std::fmt::Debug; use std::rc::Weak; +/// Wgpu implementation of [`GpuQueryTrait`](fyrox_graphics::query::GpuQueryTrait). +/// +/// **Stub implementation**: wgpu does not currently expose occlusion queries, +/// so this type tracks the active state but always returns `u32::MAX` from +/// [`try_get_result`](Self::try_get_result). This is sufficient for the engine's +/// query-based culling to work (everything is considered visible). pub struct WgpuQuery { _server: Weak, active: Cell, } -impl Debug for WgpuQuery { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("WgpuQuery").field("active", &self.active.get()).finish() } } +impl Debug for WgpuQuery { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WgpuQuery") + .field("active", &self.active.get()) + .finish() + } +} impl WgpuQuery { + /// Creates a new stub query. pub fn new(server: &WgpuGraphicsServer) -> Result { - Ok(Self { _server: server.weak_ref(), active: Cell::new(false) }) + Ok(Self { + _server: server.weak_ref(), + active: Cell::new(false), + }) } } impl GpuQueryTrait for WgpuQuery { - fn begin(&self, _kind: QueryKind) { self.active.set(true); } - fn end(&self) { self.active.set(false); } - fn is_started(&self) -> bool { self.active.get() } - fn try_get_result(&self) -> Option { Some(QueryResult::SamplesPassed(u32::MAX)) } + fn begin(&self, _kind: QueryKind) { + self.active.set(true); + } + fn end(&self) { + self.active.set(false); + } + fn is_started(&self) -> bool { + self.active.get() + } + /// Always returns `Some(QueryResult::SamplesPassed(u32::MAX))` — see the + /// struct-level documentation for details. + fn try_get_result(&self) -> Option { + Some(QueryResult::SamplesPassed(u32::MAX)) + } } diff --git a/fyrox-graphics-wgpu/src/read_buffer.rs b/fyrox-graphics-wgpu/src/read_buffer.rs index a93513b566..01228f95a6 100644 --- a/fyrox-graphics-wgpu/src/read_buffer.rs +++ b/fyrox-graphics-wgpu/src/read_buffer.rs @@ -20,15 +20,23 @@ use crate::server::WgpuGraphicsServer; use fyrox_graphics::{ - error::FrameworkError, - gpu_texture::GpuTextureKind, - read_buffer::GpuAsyncReadBufferTrait, - core::math::Rect, - framebuffer::GpuFrameBufferTrait, + core::math::Rect, error::FrameworkError, framebuffer::GpuFrameBufferTrait, + gpu_texture::GpuTextureKind, read_buffer::GpuAsyncReadBufferTrait, }; use std::cell::Cell; use std::rc::Weak; +/// Wgpu implementation of [`GpuAsyncReadBufferTrait`](fyrox_graphics::read_buffer::GpuAsyncReadBufferTrait). +/// +/// Provides asynchronous pixel readback from a framebuffer's color attachment to +/// CPU-accessible memory. Internally uses a `MAP_READ | COPY_DST` buffer and +/// `copy_texture_to_buffer` to transfer pixel data. +/// +/// # Usage +/// +/// 1. Create via [`GraphicsServer::create_async_read_buffer`](fyrox_graphics::server::GraphicsServer::create_async_read_buffer) +/// 2. Call [`schedule_pixels_transfer`](Self::schedule_pixels_transfer) to start the transfer +/// 3. Poll [`try_read`](Self::try_read) each frame until the data is ready pub struct WgpuAsyncReadBuffer { server: Weak, buffer: wgpu::Buffer, @@ -39,47 +47,119 @@ pub struct WgpuAsyncReadBuffer { } impl WgpuAsyncReadBuffer { - pub fn new(server: &WgpuGraphicsServer, name: &str, pixel_size: usize, pixel_count: usize) -> Result { + /// Creates a new async read buffer with enough capacity for `pixel_count` pixels + /// of `pixel_size` bytes each. + pub fn new( + server: &WgpuGraphicsServer, + name: &str, + pixel_size: usize, + pixel_count: usize, + ) -> Result { let size_bytes = pixel_count * pixel_size; let buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor { - label: if server.named_objects { Some(name) } else { None }, + label: if server.named_objects { + Some(name) + } else { + None + }, size: size_bytes.max(1) as u64, usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); - Ok(Self { server: server.weak_ref(), buffer, _pixel_count: pixel_count, pixel_size, request_pending: Cell::new(false), size_bytes }) + Ok(Self { + server: server.weak_ref(), + buffer, + _pixel_count: pixel_count, + pixel_size, + request_pending: Cell::new(false), + size_bytes, + }) } } impl GpuAsyncReadBufferTrait for WgpuAsyncReadBuffer { - fn schedule_pixels_transfer(&self, framebuffer: &dyn GpuFrameBufferTrait, color_buffer_index: u32, _rect: Option>) -> Result<(), FrameworkError> { - if self.request_pending.get() { return Ok(()); } - let Some(server) = self.server.upgrade() else { return Err(FrameworkError::GraphicsServerUnavailable); }; - let color_attachment = framebuffer.color_attachments().get(color_buffer_index as usize).ok_or_else(|| FrameworkError::Custom("No color attachment".into()))?; - let wgpu_tex = color_attachment.texture.as_any().downcast_ref::().ok_or_else(|| FrameworkError::Custom("Expected WgpuTexture".into()))?; - let (w, h) = match color_attachment.texture.kind() { GpuTextureKind::Rectangle { width, height } => (width, height), _ => return Err(FrameworkError::Custom("Only rectangular textures".into())) }; + fn schedule_pixels_transfer( + &self, + framebuffer: &dyn GpuFrameBufferTrait, + color_buffer_index: u32, + _rect: Option>, + ) -> Result<(), FrameworkError> { + if self.request_pending.get() { + return Ok(()); + } + let Some(server) = self.server.upgrade() else { + return Err(FrameworkError::GraphicsServerUnavailable); + }; + let color_attachment = framebuffer + .color_attachments() + .get(color_buffer_index as usize) + .ok_or_else(|| FrameworkError::Custom("No color attachment".into()))?; + let wgpu_tex = color_attachment + .texture + .as_any() + .downcast_ref::() + .ok_or_else(|| FrameworkError::Custom("Expected WgpuTexture".into()))?; + let (w, h) = match color_attachment.texture.kind() { + GpuTextureKind::Rectangle { width, height } => (width, height), + _ => return Err(FrameworkError::Custom("Only rectangular textures".into())), + }; let bpp = self.pixel_size; - let mut encoder = server.state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("AsyncReadEnc") }); + let mut encoder = + server + .state + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("AsyncReadEnc"), + }); encoder.copy_texture_to_buffer( - wgpu::TexelCopyTextureInfo { texture: wgpu_tex.wgpu_texture(), mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, - wgpu::TexelCopyBufferInfo { buffer: &self.buffer, layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((w as u32 * bpp as u32).max(256)), rows_per_image: Some(h as u32) } }, - wgpu::Extent3d { width: w as u32, height: h as u32, depth_or_array_layers: 1 }, + wgpu::TexelCopyTextureInfo { + texture: wgpu_tex.wgpu_texture(), + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::TexelCopyBufferInfo { + buffer: &self.buffer, + layout: wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some((w as u32 * bpp as u32).max(256)), + rows_per_image: Some(h as u32), + }, + }, + wgpu::Extent3d { + width: w as u32, + height: h as u32, + depth_or_array_layers: 1, + }, ); server.state.queue.submit(std::iter::once(encoder.finish())); self.request_pending.set(true); Ok(()) } - fn is_request_running(&self) -> bool { self.request_pending.get() } + fn is_request_running(&self) -> bool { + self.request_pending.get() + } fn try_read(&self) -> Option> { - if !self.request_pending.get() { return None; } + if !self.request_pending.get() { + return None; + } let server = self.server.upgrade()?; let slice = self.buffer.slice(..self.size_bytes as u64); let (tx, rx) = std::sync::mpsc::channel(); - slice.map_async(wgpu::MapMode::Read, move |r| { tx.send(r).ok(); }); - server.state.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None }).ok(); + slice.map_async(wgpu::MapMode::Read, move |r| { + tx.send(r).ok(); + }); + server + .state + .device + .poll(wgpu::PollType::Wait { + submission_index: None, + timeout: None, + }) + .ok(); match rx.recv() { Ok(Ok(())) => { let mapped = slice.get_mapped_range(); @@ -90,7 +170,10 @@ impl GpuAsyncReadBufferTrait for WgpuAsyncReadBuffer { self.request_pending.set(false); Some(result) } - _ => { self.request_pending.set(false); None } + _ => { + self.request_pending.set(false); + None + } } } } diff --git a/fyrox-graphics-wgpu/src/sampler.rs b/fyrox-graphics-wgpu/src/sampler.rs index d42d02abac..585d595dcc 100644 --- a/fyrox-graphics-wgpu/src/sampler.rs +++ b/fyrox-graphics-wgpu/src/sampler.rs @@ -21,29 +21,46 @@ use crate::server::WgpuGraphicsServer; use fyrox_graphics::{ error::FrameworkError, - sampler::{GpuSamplerDescriptor, GpuSamplerTrait, MagnificationFilter, MinificationFilter, WrapMode}, + sampler::{ + GpuSamplerDescriptor, GpuSamplerTrait, MagnificationFilter, MinificationFilter, WrapMode, + }, }; use std::fmt::Debug; use std::rc::Weak; +/// Maps a Fyrox [`MinificationFilter`] to a wgpu [`FilterMode`]. +/// +/// Nearest-mipmap variants map to `Nearest`; all others map to `Linear`. fn min_filter_to_wgpu(f: MinificationFilter) -> wgpu::FilterMode { match f { - MinificationFilter::Nearest | MinificationFilter::NearestMipMapNearest | MinificationFilter::NearestMipMapLinear => wgpu::FilterMode::Nearest, + MinificationFilter::Nearest + | MinificationFilter::NearestMipMapNearest + | MinificationFilter::NearestMipMapLinear => wgpu::FilterMode::Nearest, _ => wgpu::FilterMode::Linear, } } +/// Maps a Fyrox [`MagnificationFilter`] to a wgpu [`FilterMode`]. fn mag_filter_to_wgpu(f: MagnificationFilter) -> wgpu::FilterMode { - match f { MagnificationFilter::Nearest => wgpu::FilterMode::Nearest, _ => wgpu::FilterMode::Linear } + match f { + MagnificationFilter::Nearest => wgpu::FilterMode::Nearest, + _ => wgpu::FilterMode::Linear, + } } +/// Maps a Fyrox [`MinificationFilter`] to a wgpu [`MipmapFilterMode`]. +/// +/// Only `LinearMipMap*` variants produce linear mipmap filtering. fn mipmap_filter_to_wgpu(f: MinificationFilter) -> wgpu::MipmapFilterMode { match f { - MinificationFilter::NearestMipMapLinear | MinificationFilter::LinearMipMapLinear => wgpu::MipmapFilterMode::Linear, + MinificationFilter::NearestMipMapLinear | MinificationFilter::LinearMipMapLinear => { + wgpu::MipmapFilterMode::Linear + } _ => wgpu::MipmapFilterMode::Nearest, } } +/// Maps a Fyrox [`WrapMode`] to a wgpu [`AddressMode`]. fn wrap_mode_to_wgpu(m: WrapMode) -> wgpu::AddressMode { match m { WrapMode::Repeat => wgpu::AddressMode::Repeat, @@ -53,34 +70,75 @@ fn wrap_mode_to_wgpu(m: WrapMode) -> wgpu::AddressMode { } } +/// Wgpu implementation of [`GpuSamplerTrait`](fyrox_graphics::sampler::GpuSamplerTrait). +/// +/// Wraps a [`wgpu::Sampler`] configured from a [`GpuSamplerDescriptor`]. When +/// anisotropy is greater than 1, all filters are forced to `Linear` (this is a +/// wgpu/WebGPU requirement). pub struct WgpuSampler { _server: Weak, sampler: wgpu::Sampler, } -impl Debug for WgpuSampler { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("WgpuSampler").finish() } } +impl Debug for WgpuSampler { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WgpuSampler").finish() + } +} impl GpuSamplerTrait for WgpuSampler {} impl WgpuSampler { - pub fn new(server: &WgpuGraphicsServer, desc: GpuSamplerDescriptor) -> Result { + /// Creates a new sampler from the given descriptor. + /// + /// If `anisotropy > 1`, all min/mag/mipmap filters are forced to `Linear` + /// regardless of the descriptor settings. + pub fn new( + server: &WgpuGraphicsServer, + desc: GpuSamplerDescriptor, + ) -> Result { let aniso = desc.anisotropy.clamp(1.0, 16.0) as u16; let use_linear = aniso > 1; - let sampler = server.state.device.create_sampler(&wgpu::SamplerDescriptor { - label: if server.named_objects { Some("Sampler") } else { None }, - address_mode_u: wrap_mode_to_wgpu(desc.s_wrap_mode), - address_mode_v: wrap_mode_to_wgpu(desc.t_wrap_mode), - address_mode_w: wrap_mode_to_wgpu(desc.r_wrap_mode), - mag_filter: if use_linear { wgpu::FilterMode::Linear } else { mag_filter_to_wgpu(desc.mag_filter) }, - min_filter: if use_linear { wgpu::FilterMode::Linear } else { min_filter_to_wgpu(desc.min_filter) }, - mipmap_filter: if use_linear { wgpu::MipmapFilterMode::Linear } else { mipmap_filter_to_wgpu(desc.min_filter) }, - lod_min_clamp: desc.min_lod.max(0.0), - lod_max_clamp: desc.max_lod.max(0.0), - anisotropy_clamp: aniso, - compare: None, - border_color: None, - }); - Ok(Self { _server: server.weak_ref(), sampler }) + let sampler = server + .state + .device + .create_sampler(&wgpu::SamplerDescriptor { + label: if server.named_objects { + Some("Sampler") + } else { + None + }, + address_mode_u: wrap_mode_to_wgpu(desc.s_wrap_mode), + address_mode_v: wrap_mode_to_wgpu(desc.t_wrap_mode), + address_mode_w: wrap_mode_to_wgpu(desc.r_wrap_mode), + mag_filter: if use_linear { + wgpu::FilterMode::Linear + } else { + mag_filter_to_wgpu(desc.mag_filter) + }, + min_filter: if use_linear { + wgpu::FilterMode::Linear + } else { + min_filter_to_wgpu(desc.min_filter) + }, + mipmap_filter: if use_linear { + wgpu::MipmapFilterMode::Linear + } else { + mipmap_filter_to_wgpu(desc.min_filter) + }, + lod_min_clamp: desc.min_lod.max(0.0), + lod_max_clamp: desc.max_lod.max(0.0), + anisotropy_clamp: aniso, + compare: None, + border_color: None, + }); + Ok(Self { + _server: server.weak_ref(), + sampler, + }) } - pub fn wgpu_sampler(&self) -> &wgpu::Sampler { &self.sampler } + /// Returns a reference to the underlying [`wgpu::Sampler`]. + pub fn wgpu_sampler(&self) -> &wgpu::Sampler { + &self.sampler + } } diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index 6f8c879145..3337339eaa 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -48,21 +48,54 @@ use std::sync::{Arc, RwLock}; use winit::event_loop::ActiveEventLoop; use winit::window::{Window, WindowAttributes}; +/// Core wgpu objects shared across the server. +/// +/// Holds the wgpu instance, adapter, device, and queue. Shared via [`Arc`] so that +/// GPU commands can be submitted from any resource type. pub struct WgpuState { + /// The wgpu instance (entry point for creating adapters and surfaces). pub instance: wgpu::Instance, + /// The selected physical device. pub adapter: wgpu::Adapter, + /// The logical device for creating GPU resources. pub device: wgpu::Device, + /// The command queue for submitting GPU work. pub queue: wgpu::Queue, } +/// The main wgpu-based graphics server. +/// +/// Implements [`GraphicsServer`](fyrox_graphics::server::GraphicsServer) and serves +/// as the entry point for all GPU resource creation. Manages the wgpu device, surface, +/// pipeline cache, and memory usage tracking. +/// +/// # Pipeline Cache +/// +/// Render pipelines are cached by a hash of [`PipelineKey`] to avoid recreating +/// identical pipelines across draw calls. The cache is stored in a [`RefCell`] +/// and grows monotonically during the session. +/// +/// # Backbuffer Lifecycle +/// +/// The backbuffer acquires a surface texture on the first draw call per frame. +/// Multiple draw calls to the backbuffer reuse the same texture. The frame is +/// presented via [`swap_buffers`](Self::swap_buffers), which also sets the +/// clear flag for the next frame. pub struct WgpuGraphicsServer { + /// Core wgpu objects (instance, adapter, device, queue). pub state: Arc, + /// The rendering surface (window or canvas). pub surface: wgpu::Surface<'static>, + /// Current surface configuration (format, size, present mode). pub surface_config: RwLock, + /// Whether to set debug labels on GPU objects. pub named_objects: bool, + /// MSAA sample count (currently forced to 1). pub msaa_sample_count: u32, + /// Hash-based cache of render pipelines, keyed by [`PipelineKey`]. pub pipeline_cache: RefCell>, weak_self: RefCell>>, + /// Tracked GPU memory usage (buffers + textures). pub memory_usage: RefCell, pipeline_statistics: RefCell, /// Small buffer bound to extra vertex slots when geometry lacks attributes the shader expects. @@ -78,6 +111,21 @@ pub struct WgpuGraphicsServer { } impl WgpuGraphicsServer { + /// Creates a new wgpu graphics server with a window and GPU device. + /// + /// Initializes wgpu with the primary backend (Vulkan/Metal/DX12 on native, + /// WebGL2 on WASM). Prefers non-sRGB surface formats to avoid double gamma + /// correction (the engine applies its own in the HDR tone-mapping pass). + /// + /// Returns the created [`Window`] and a shared [`GraphicsServer`] handle. + /// + /// # Arguments + /// + /// * `vsync` — enable vertical sync (`PresentMode::AutoVsync` vs `AutoNoVsync`) + /// * `_msaa_sample_count` — currently ignored (MSAA not yet implemented) + /// * `window_target` — the winit event loop for window/surface creation + /// * `window_attributes` — initial window configuration + /// * `named_objects` — whether to set debug labels on GPU objects pub fn new( vsync: bool, _msaa_sample_count: Option, @@ -93,9 +141,9 @@ impl WgpuGraphicsServer { #[cfg(not(target_arch = "wasm32"))] let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: wgpu::Backends::PRIMARY, - ..wgpu::InstanceDescriptor::new_with_display_handle( - Box::new(window_target.owned_display_handle()) - ) + ..wgpu::InstanceDescriptor::new_with_display_handle(Box::new( + window_target.owned_display_handle(), + )) }); #[cfg(target_arch = "wasm32")] @@ -121,8 +169,10 @@ impl WgpuGraphicsServer { let web_window = fyrox_core::web_sys::window().unwrap(); let document = web_window.document().unwrap(); let body = document.body().unwrap(); - body.append_child(&canvas).expect("Append canvas to HTML body"); - instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas)) + body.append_child(&canvas) + .expect("Append canvas to HTML body"); + instance + .create_surface(wgpu::SurfaceTarget::Canvas(canvas)) .map_err(|e| FrameworkError::Custom(format!("Failed to create surface: {e}")))? }; @@ -133,19 +183,17 @@ impl WgpuGraphicsServer { })) .map_err(|e| FrameworkError::Custom(format!("No suitable WGPU adapter found: {e}")))?; - let (device, queue) = block_on(adapter.request_device( - &wgpu::DeviceDescriptor { - label: None, - required_features: wgpu::Features::empty(), - required_limits: if cfg!(target_arch = "wasm32") { - wgpu::Limits::downlevel_webgl2_defaults() - } else { - wgpu::Limits::default() - }, - memory_hints: wgpu::MemoryHints::Performance, - ..Default::default() + let (device, queue) = block_on(adapter.request_device(&wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: if cfg!(target_arch = "wasm32") { + wgpu::Limits::downlevel_webgl2_defaults() + } else { + wgpu::Limits::default() }, - )) + memory_hints: wgpu::MemoryHints::Performance, + ..Default::default() + })) .map_err(|e| FrameworkError::Custom(format!("Failed to request device: {e}")))?; let surface_caps = surface.get_capabilities(&adapter); @@ -159,7 +207,11 @@ impl WgpuGraphicsServer { .or_else(|| surface_caps.formats.first().copied()) .ok_or_else(|| FrameworkError::Custom("Surface has no supported formats".into()))?; - let present_mode = if vsync { wgpu::PresentMode::AutoVsync } else { wgpu::PresentMode::AutoNoVsync }; + let present_mode = if vsync { + wgpu::PresentMode::AutoVsync + } else { + wgpu::PresentMode::AutoNoVsync + }; let surface_config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, @@ -192,7 +244,12 @@ impl WgpuGraphicsServer { }); let server = Rc::new(Self { - state: Arc::new(WgpuState { instance, adapter, device, queue }), + state: Arc::new(WgpuState { + instance, + adapter, + device, + queue, + }), surface, surface_config: RwLock::new(surface_config), named_objects, @@ -213,9 +270,16 @@ impl WgpuGraphicsServer { Ok((window, server)) } + /// Returns a [`Weak`] reference to this server. + /// + /// Used by resource types to avoid reference cycles. Resources store a weak + /// reference and call [`upgrade`](Weak::upgrade) when they need the server. pub fn weak_ref(&self) -> Weak { self.weak_self.borrow().clone().unwrap() } + /// Returns the non-filtering sampler used for textures with non-filterable formats + /// (e.g. `R32Float`, `R32Uint`). These formats require + /// [`SamplerBindingType::NonFiltering`](wgpu::SamplerBindingType::NonFiltering). pub fn non_filtering_sampler(&self) -> &wgpu::Sampler { &self.non_filtering_sampler } @@ -231,8 +295,14 @@ impl GraphicsServer for WgpuGraphicsServer { fn create_sampler(&self, desc: GpuSamplerDescriptor) -> Result { Ok(GpuSampler(Rc::new(WgpuSampler::new(self, desc)?))) } - fn create_frame_buffer(&self, depth: Option, colors: Vec) -> Result { - Ok(GpuFrameBuffer(Rc::new(WgpuFrameBuffer::new(self, depth, colors)?))) + fn create_frame_buffer( + &self, + depth: Option, + colors: Vec, + ) -> Result { + Ok(GpuFrameBuffer(Rc::new(WgpuFrameBuffer::new( + self, depth, colors, + )?))) } fn back_buffer(&self) -> GpuFrameBuffer { let config = self.surface_config.read().unwrap(); @@ -252,7 +322,9 @@ impl GraphicsServer for WgpuGraphicsServer { w as usize, h as usize, ) { - Ok(tex) => { *cache = Some((w, h, tex)); } + Ok(tex) => { + *cache = Some((w, h, tex)); + } Err(e) => { log::warn!("Failed to create backbuffer depth-stencil: {e}"); *cache = None; @@ -262,34 +334,97 @@ impl GraphicsServer for WgpuGraphicsServer { *cache = None; } } - let depth_attachment = cache.as_ref().map(|(_, _, tex)| Attachment::depth_stencil(tex.clone())); + let depth_attachment = cache + .as_ref() + .map(|(_, _, tex)| Attachment::depth_stencil(tex.clone())); GpuFrameBuffer(Rc::new(WgpuFrameBuffer::backbuffer(self, depth_attachment))) } fn create_query(&self) -> Result { Ok(GpuQuery(Rc::new(WgpuQuery::new(self)?))) } - fn create_shader(&self, name: String, kind: ShaderKind, source: String, resources: &[ShaderResourceDefinition], line_offset: isize) -> Result { - Ok(GpuShader(Rc::new(WgpuShader::new(self, name, kind, source, resources, line_offset)?))) + fn create_shader( + &self, + name: String, + kind: ShaderKind, + source: String, + resources: &[ShaderResourceDefinition], + line_offset: isize, + ) -> Result { + Ok(GpuShader(Rc::new(WgpuShader::new( + self, + name, + kind, + source, + resources, + line_offset, + )?))) } - fn create_program(&self, name: &str, vs: String, vs_offset: isize, fs: String, fs_offset: isize, resources: &[ShaderResourceDefinition]) -> Result { - Ok(GpuProgram(Rc::new(WgpuProgram::from_source(self, name, vs, vs_offset, fs, fs_offset, resources)?))) + fn create_program( + &self, + name: &str, + vs: String, + vs_offset: isize, + fs: String, + fs_offset: isize, + resources: &[ShaderResourceDefinition], + ) -> Result { + Ok(GpuProgram(Rc::new(WgpuProgram::from_source( + self, name, vs, vs_offset, fs, fs_offset, resources, + )?))) } - fn create_program_from_shaders(&self, name: &str, vs: &GpuShader, fs: &GpuShader, resources: &[ShaderResourceDefinition]) -> Result { - Ok(GpuProgram(Rc::new(WgpuProgram::from_shaders(self, name, vs, fs, resources)?))) + fn create_program_from_shaders( + &self, + name: &str, + vs: &GpuShader, + fs: &GpuShader, + resources: &[ShaderResourceDefinition], + ) -> Result { + Ok(GpuProgram(Rc::new(WgpuProgram::from_shaders( + self, name, vs, fs, resources, + )?))) } - fn create_async_read_buffer(&self, name: &str, pixel_size: usize, pixel_count: usize) -> Result { - Ok(GpuAsyncReadBuffer(Rc::new(WgpuAsyncReadBuffer::new(self, name, pixel_size, pixel_count)?))) + fn create_async_read_buffer( + &self, + name: &str, + pixel_size: usize, + pixel_count: usize, + ) -> Result { + Ok(GpuAsyncReadBuffer(Rc::new(WgpuAsyncReadBuffer::new( + self, + name, + pixel_size, + pixel_count, + )?))) } - fn create_geometry_buffer(&self, desc: GpuGeometryBufferDescriptor) -> Result { - Ok(GpuGeometryBuffer(Rc::new(WgpuGeometryBuffer::new(self, desc)?))) + fn create_geometry_buffer( + &self, + desc: GpuGeometryBufferDescriptor, + ) -> Result { + Ok(GpuGeometryBuffer(Rc::new(WgpuGeometryBuffer::new( + self, desc, + )?))) } fn weak(&self) -> Weak { self.weak_ref() as Weak } - fn flush(&self) { self.state.queue.submit(std::iter::empty()); } - fn finish(&self) { self.state.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None }).ok(); } - fn invalidate_resource_bindings_cache(&self) { *self.pipeline_statistics.borrow_mut() = Default::default(); } - fn pipeline_statistics(&self) -> PipelineStatistics { *self.pipeline_statistics.borrow() } + fn flush(&self) { + self.state.queue.submit(std::iter::empty()); + } + fn finish(&self) { + self.state + .device + .poll(wgpu::PollType::Wait { + submission_index: None, + timeout: None, + }) + .ok(); + } + fn invalidate_resource_bindings_cache(&self) { + *self.pipeline_statistics.borrow_mut() = Default::default(); + } + fn pipeline_statistics(&self) -> PipelineStatistics { + *self.pipeline_statistics.borrow() + } fn swap_buffers(&self) -> Result<(), FrameworkError> { if let Some(frame) = self.current_frame.borrow_mut().take() { frame.present(); @@ -319,7 +454,9 @@ impl GraphicsServer for WgpuGraphicsServer { fn generate_mipmap(&self, _texture: &GpuTexture) { log::warn!("generate_mipmap: not yet fully implemented"); } - fn memory_usage(&self) -> ServerMemoryUsage { self.memory_usage.borrow().clone() } + fn memory_usage(&self) -> ServerMemoryUsage { + self.memory_usage.borrow().clone() + } fn push_debug_group(&self, _name: &str) {} fn pop_debug_group(&self) {} } diff --git a/fyrox-graphics-wgpu/src/texture.rs b/fyrox-graphics-wgpu/src/texture.rs index 0a0b6485c9..fbdac3b081 100644 --- a/fyrox-graphics-wgpu/src/texture.rs +++ b/fyrox-graphics-wgpu/src/texture.rs @@ -21,11 +21,19 @@ use crate::server::WgpuGraphicsServer; use fyrox_graphics::{ error::FrameworkError, - gpu_texture::{image_1d_size_bytes, image_2d_size_bytes, image_3d_size_bytes, GpuTextureDescriptor, GpuTextureKind, GpuTextureTrait, PixelKind}, + gpu_texture::{ + image_1d_size_bytes, image_2d_size_bytes, image_3d_size_bytes, GpuTextureDescriptor, + GpuTextureKind, GpuTextureTrait, PixelKind, + }, }; use std::cell::Cell; use std::rc::Weak; +/// Maps a Fyrox [`PixelKind`] to the corresponding wgpu [`TextureFormat`]. +/// +/// 3-component formats (RGB8, BGR8, SRGB8, RGB16F, RGB32F) are mapped to their +/// 4-component equivalents because wgpu has no 3-component texture formats. +/// The actual data expansion happens in [`expand_to_rgba`]. fn pixel_kind_to_wgpu_format(kind: PixelKind) -> wgpu::TextureFormat { match kind { PixelKind::R32F => wgpu::TextureFormat::R32Float, @@ -53,8 +61,14 @@ fn pixel_kind_to_wgpu_format(kind: PixelKind) -> wgpu::TextureFormat { /// Returns true if the pixel kind is 3-component and needs expansion to 4-component /// for wgpu compatibility (wgpu has no 3-component texture formats). fn needs_rgba_expansion(pk: PixelKind) -> bool { - matches!(pk, PixelKind::RGB8 | PixelKind::BGR8 | PixelKind::SRGB8 - | PixelKind::RGB16F | PixelKind::RGB32F) + matches!( + pk, + PixelKind::RGB8 + | PixelKind::BGR8 + | PixelKind::SRGB8 + | PixelKind::RGB16F + | PixelKind::RGB32F + ) } /// Expands 3-component pixel data to 4-component by adding an opaque alpha channel. @@ -97,7 +111,9 @@ fn expand_to_rgba(pk: PixelKind, data: &[u8]) -> Vec { fn texture_dimension(kind: GpuTextureKind) -> wgpu::TextureDimension { match kind { GpuTextureKind::Line { .. } => wgpu::TextureDimension::D1, - GpuTextureKind::Rectangle { .. } | GpuTextureKind::Cube { .. } => wgpu::TextureDimension::D2, + GpuTextureKind::Rectangle { .. } | GpuTextureKind::Cube { .. } => { + wgpu::TextureDimension::D2 + } GpuTextureKind::Volume { .. } => wgpu::TextureDimension::D3, } } @@ -107,7 +123,11 @@ fn texture_size(kind: GpuTextureKind) -> (u32, u32, u32) { GpuTextureKind::Line { length } => (length as u32, 1, 1), GpuTextureKind::Rectangle { width, height } => (width as u32, height as u32, 1), GpuTextureKind::Cube { size } => (size as u32, size as u32, 6), - GpuTextureKind::Volume { width, height, depth } => (width as u32, height as u32, depth as u32), + GpuTextureKind::Volume { + width, + height, + depth, + } => (width as u32, height as u32, depth as u32), } } @@ -120,6 +140,50 @@ fn texture_view_dimension(kind: GpuTextureKind) -> wgpu::TextureViewDimension { } } +/// Writes a single mip level (or cubemap face) to a texture, expanding 3-component +/// pixel data to 4-component if needed for wgpu compatibility. +fn write_mip_data( + queue: &wgpu::Queue, + texture: &wgpu::Texture, + mip: u32, + origin: wgpu::Origin3d, + extent: wgpu::Extent3d, + pk: PixelKind, + data: &[u8], + fmt: wgpu::TextureFormat, +) { + let expanded; + let upload_data = if needs_rgba_expansion(pk) { + expanded = expand_to_rgba(pk, data); + expanded.as_slice() + } else { + data + }; + let bps = fmt.block_copy_size(None).unwrap_or(4); + queue.write_texture( + wgpu::TexelCopyTextureInfo { + texture, + mip_level: mip, + origin, + aspect: wgpu::TextureAspect::All, + }, + upload_data, + wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some((extent.width * bps).max(1)), + rows_per_image: Some(extent.height.max(1)), + }, + extent, + ); +} + +/// Wgpu implementation of [`GpuTextureTrait`](fyrox_graphics::gpu_texture::GpuTextureTrait). +/// +/// Wraps a [`wgpu::Texture`] with two views: a full-aspect view for rendering and +/// a separate `DepthOnly` view for shader bindings (depth-stencil textures require +/// this because wgpu rejects `Depth+Stencil` aspect on texture bindings). +/// +/// Memory usage is tracked on the server and decremented on drop. pub struct WgpuTexture { server: Weak, texture: wgpu::Texture, @@ -133,7 +197,15 @@ pub struct WgpuTexture { } impl WgpuTexture { - pub fn new(server: &WgpuGraphicsServer, desc: GpuTextureDescriptor) -> Result { + /// Creates a new GPU texture from the given descriptor. + /// + /// Creates the wgpu texture, views (including a separate `DepthOnly` binding + /// view for depth-stencil formats), uploads initial data if provided, and + /// tracks memory usage on the server. + pub fn new( + server: &WgpuGraphicsServer, + desc: GpuTextureDescriptor, + ) -> Result { let format = pixel_kind_to_wgpu_format(desc.pixel_kind); let dimension = texture_dimension(desc.kind); let (raw_w, raw_h, depth_or_layers) = texture_size(desc.kind); @@ -141,16 +213,30 @@ impl WgpuTexture { let height = raw_h.max(1); let mip_count = desc.mip_count.max(1) as u32; - let texture = server.state.device.create_texture(&wgpu::TextureDescriptor { - label: if server.named_objects { Some(desc.name) } else { None }, - size: wgpu::Extent3d { width, height, depth_or_array_layers: depth_or_layers }, - mip_level_count: mip_count, - sample_count: 1, - dimension, - format, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT, - view_formats: &[], - }); + let texture = server + .state + .device + .create_texture(&wgpu::TextureDescriptor { + label: if server.named_objects { + Some(desc.name) + } else { + None + }, + size: wgpu::Extent3d { + width, + height, + depth_or_array_layers: depth_or_layers, + }, + mip_level_count: mip_count, + sample_count: 1, + dimension, + format, + usage: wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::COPY_SRC + | wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); let view = texture.create_view(&wgpu::TextureViewDescriptor { label: None, @@ -177,41 +263,88 @@ impl WgpuTexture { }; let size_bytes = calc_size_bytes(desc.kind, desc.pixel_kind, desc.mip_count); - if let Some(data) = desc.data { Self::upload(&server.state.queue, &texture, desc.kind, desc.pixel_kind, data, desc.mip_count)?; } + if let Some(data) = desc.data { + Self::upload( + &server.state.queue, + &texture, + desc.kind, + desc.pixel_kind, + data, + desc.mip_count, + )?; + } server.memory_usage.borrow_mut().textures += size_bytes; - Ok(Self { server: server.weak_ref(), texture, view, binding_view, kind: Cell::new(desc.kind), pixel_kind: Cell::new(desc.pixel_kind), size_bytes: Cell::new(size_bytes) }) + Ok(Self { + server: server.weak_ref(), + texture, + view, + binding_view, + kind: Cell::new(desc.kind), + pixel_kind: Cell::new(desc.pixel_kind), + size_bytes: Cell::new(size_bytes), + }) } - fn upload(queue: &wgpu::Queue, texture: &wgpu::Texture, kind: GpuTextureKind, pk: PixelKind, data: &[u8], mip_count: usize) -> Result<(), FrameworkError> { + fn upload( + queue: &wgpu::Queue, + texture: &wgpu::Texture, + kind: GpuTextureKind, + pk: PixelKind, + data: &[u8], + mip_count: usize, + ) -> Result<(), FrameworkError> { let mip_count = mip_count.max(1); - let needs_expansion = needs_rgba_expansion(pk); + let fmt = pixel_kind_to_wgpu_format(pk); let mut offset = 0; for mip in 0..mip_count { match kind { GpuTextureKind::Line { length } => { if let Some(l) = length.checked_shr(mip as u32) { let sz = image_1d_size_bytes(pk, l); - if offset + sz > data.len() { break; } - let slice = &data[offset..offset+sz]; - let expanded; - let upload_data = if needs_expansion { expanded = expand_to_rgba(pk, slice); &expanded } else { slice }; - let fmt = pixel_kind_to_wgpu_format(pk); - let bps = fmt.block_copy_size(None).unwrap_or(4); - queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, upload_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(l as u32 * bps), rows_per_image: Some(1) }, wgpu::Extent3d { width: l as u32, height: 1, depth_or_array_layers: 1 }); + if offset + sz > data.len() { + break; + } + write_mip_data( + queue, + texture, + mip as u32, + wgpu::Origin3d::ZERO, + wgpu::Extent3d { + width: l as u32, + height: 1, + depth_or_array_layers: 1, + }, + pk, + &data[offset..offset + sz], + fmt, + ); offset += sz; } } GpuTextureKind::Rectangle { width, height } => { - if let (Some(w), Some(h)) = (width.checked_shr(mip as u32), height.checked_shr(mip as u32)) { + if let (Some(w), Some(h)) = ( + width.checked_shr(mip as u32), + height.checked_shr(mip as u32), + ) { let sz = image_2d_size_bytes(pk, w, h); - if offset + sz > data.len() { break; } - let slice = &data[offset..offset+sz]; - let expanded; - let upload_data = if needs_expansion { expanded = expand_to_rgba(pk, slice); &expanded } else { slice }; - let fmt = pixel_kind_to_wgpu_format(pk); - let bps = fmt.block_copy_size(None).unwrap_or(4); - queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, upload_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((w as u32 * bps).max(1)), rows_per_image: Some(h as u32) }, wgpu::Extent3d { width: w as u32, height: h as u32, depth_or_array_layers: 1 }); + if offset + sz > data.len() { + break; + } + write_mip_data( + queue, + texture, + mip as u32, + wgpu::Origin3d::ZERO, + wgpu::Extent3d { + width: w as u32, + height: h as u32, + depth_or_array_layers: 1, + }, + pk, + &data[offset..offset + sz], + fmt, + ); offset += sz; } } @@ -220,27 +353,59 @@ impl WgpuTexture { let bpf = image_2d_size_bytes(pk, s, s); for face in 0..6u32 { let fo = offset + (face as usize) * bpf; - if fo + bpf > data.len() { break; } - let slice = &data[fo..fo+bpf]; - let expanded; - let upload_data = if needs_expansion { expanded = expand_to_rgba(pk, slice); &expanded } else { slice }; - let fmt = pixel_kind_to_wgpu_format(pk); - let bps = fmt.block_copy_size(None).unwrap_or(4); - queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d { x: 0, y: 0, z: face }, aspect: wgpu::TextureAspect::All }, upload_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((s as u32 * bps).max(1)), rows_per_image: Some(s as u32) }, wgpu::Extent3d { width: s as u32, height: s as u32, depth_or_array_layers: 1 }); + if fo + bpf > data.len() { + break; + } + write_mip_data( + queue, + texture, + mip as u32, + wgpu::Origin3d { + x: 0, + y: 0, + z: face, + }, + wgpu::Extent3d { + width: s as u32, + height: s as u32, + depth_or_array_layers: 1, + }, + pk, + &data[fo..fo + bpf], + fmt, + ); } offset += 6 * bpf; } } - GpuTextureKind::Volume { width, height, depth } => { - if let (Some(w), Some(h), Some(d)) = (width.checked_shr(mip as u32), height.checked_shr(mip as u32), depth.checked_shr(mip as u32)) { + GpuTextureKind::Volume { + width, + height, + depth, + } => { + if let (Some(w), Some(h), Some(d)) = ( + width.checked_shr(mip as u32), + height.checked_shr(mip as u32), + depth.checked_shr(mip as u32), + ) { let sz = image_3d_size_bytes(pk, w, h, d); - if offset + sz > data.len() { break; } - let slice = &data[offset..offset+sz]; - let expanded; - let upload_data = if needs_expansion { expanded = expand_to_rgba(pk, slice); &expanded } else { slice }; - let fmt = pixel_kind_to_wgpu_format(pk); - let bps = fmt.block_copy_size(None).unwrap_or(4); - queue.write_texture(wgpu::TexelCopyTextureInfo { texture, mip_level: mip as u32, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, upload_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some((w as u32 * bps).max(1)), rows_per_image: Some(h as u32) }, wgpu::Extent3d { width: w as u32, height: h as u32, depth_or_array_layers: d as u32 }); + if offset + sz > data.len() { + break; + } + write_mip_data( + queue, + texture, + mip as u32, + wgpu::Origin3d::ZERO, + wgpu::Extent3d { + width: w as u32, + height: h as u32, + depth_or_array_layers: d as u32, + }, + pk, + &data[offset..offset + sz], + fmt, + ); offset += sz; } } @@ -249,11 +414,26 @@ impl WgpuTexture { Ok(()) } - pub fn wgpu_texture(&self) -> &wgpu::Texture { &self.texture } - pub fn wgpu_view(&self) -> &wgpu::TextureView { &self.view } - /// Returns a view suitable for shader bindings (DepthOnly for depth-stencil textures). - pub fn wgpu_binding_view(&self) -> &wgpu::TextureView { &self.binding_view } - pub fn format(&self) -> wgpu::TextureFormat { self.texture.format() } + /// Returns a reference to the underlying [`wgpu::Texture`]. + pub fn wgpu_texture(&self) -> &wgpu::Texture { + &self.texture + } + /// Returns a reference to the full-aspect [`wgpu::TextureView`]. + pub fn wgpu_view(&self) -> &wgpu::TextureView { + &self.view + } + /// Returns a view suitable for shader bindings. + /// + /// For depth-stencil textures, this is a `DepthOnly`-aspect view (wgpu rejects + /// `Depth+Stencil` aspect on texture bindings). For all other textures, this + /// is the same as [`wgpu_view`](Self::wgpu_view). + pub fn wgpu_binding_view(&self) -> &wgpu::TextureView { + &self.binding_view + } + /// Returns the wgpu texture format of this texture. + pub fn format(&self) -> wgpu::TextureFormat { + self.texture.format() + } } fn calc_size_bytes(kind: GpuTextureKind, pk: PixelKind, mip_count: usize) -> usize { @@ -261,10 +441,37 @@ fn calc_size_bytes(kind: GpuTextureKind, pk: PixelKind, mip_count: usize) -> usi let mut total = 0; for mip in 0..mip_count { match kind { - GpuTextureKind::Line { length } => { if let Some(l) = length.checked_shr(mip as u32) { total += image_1d_size_bytes(pk, l); } } - GpuTextureKind::Rectangle { width, height } => { if let (Some(w), Some(h)) = (width.checked_shr(mip as u32), height.checked_shr(mip as u32)) { total += image_2d_size_bytes(pk, w, h); } } - GpuTextureKind::Cube { size } => { if let Some(s) = size.checked_shr(mip as u32) { total += 6 * image_2d_size_bytes(pk, s, s); } } - GpuTextureKind::Volume { width, height, depth } => { if let (Some(w), Some(h), Some(d)) = (width.checked_shr(mip as u32), height.checked_shr(mip as u32), depth.checked_shr(mip as u32)) { total += image_3d_size_bytes(pk, w, h, d); } } + GpuTextureKind::Line { length } => { + if let Some(l) = length.checked_shr(mip as u32) { + total += image_1d_size_bytes(pk, l); + } + } + GpuTextureKind::Rectangle { width, height } => { + if let (Some(w), Some(h)) = ( + width.checked_shr(mip as u32), + height.checked_shr(mip as u32), + ) { + total += image_2d_size_bytes(pk, w, h); + } + } + GpuTextureKind::Cube { size } => { + if let Some(s) = size.checked_shr(mip as u32) { + total += 6 * image_2d_size_bytes(pk, s, s); + } + } + GpuTextureKind::Volume { + width, + height, + depth, + } => { + if let (Some(w), Some(h), Some(d)) = ( + width.checked_shr(mip as u32), + height.checked_shr(mip as u32), + depth.checked_shr(mip as u32), + ) { + total += image_3d_size_bytes(pk, w, h, d); + } + } } } total @@ -272,14 +479,24 @@ fn calc_size_bytes(kind: GpuTextureKind, pk: PixelKind, mip_count: usize) -> usi impl Drop for WgpuTexture { fn drop(&mut self) { - if let Some(server) = self.server.upgrade() { server.memory_usage.borrow_mut().textures -= self.size_bytes.get(); } + if let Some(server) = self.server.upgrade() { + server.memory_usage.borrow_mut().textures -= self.size_bytes.get(); + } self.texture.destroy(); } } impl GpuTextureTrait for WgpuTexture { - fn set_data(&self, kind: GpuTextureKind, pk: PixelKind, mip_count: usize, data: Option<&[u8]>) -> Result { - let Some(server) = self.server.upgrade() else { return Err(FrameworkError::GraphicsServerUnavailable); }; + fn set_data( + &self, + kind: GpuTextureKind, + pk: PixelKind, + mip_count: usize, + data: Option<&[u8]>, + ) -> Result { + let Some(server) = self.server.upgrade() else { + return Err(FrameworkError::GraphicsServerUnavailable); + }; let new_size = calc_size_bytes(kind, pk, mip_count); let mut mu = server.memory_usage.borrow_mut(); mu.textures -= self.size_bytes.get(); @@ -288,9 +505,22 @@ impl GpuTextureTrait for WgpuTexture { self.size_bytes.set(new_size); self.kind.set(kind); self.pixel_kind.set(pk); - if let Some(data) = data { Self::upload(&server.state.queue, &self.texture, kind, pk, data, mip_count)?; } + if let Some(data) = data { + Self::upload( + &server.state.queue, + &self.texture, + kind, + pk, + data, + mip_count, + )?; + } Ok(new_size) } - fn kind(&self) -> GpuTextureKind { self.kind.get() } - fn pixel_kind(&self) -> PixelKind { self.pixel_kind.get() } + fn kind(&self) -> GpuTextureKind { + self.kind.get() + } + fn pixel_kind(&self) -> PixelKind { + self.pixel_kind.get() + } } From 004789b97b3a80203e419022e3d482da354c3b6b Mon Sep 17 00:00:00 2001 From: nicehack Date: Fri, 10 Jul 2026 16:53:55 +0500 Subject: [PATCH 13/34] added format helpers --- fyrox-graphics-wgpu/src/format_helpers.rs | 101 ++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 fyrox-graphics-wgpu/src/format_helpers.rs diff --git a/fyrox-graphics-wgpu/src/format_helpers.rs b/fyrox-graphics-wgpu/src/format_helpers.rs new file mode 100644 index 0000000000..f5a8e75681 --- /dev/null +++ b/fyrox-graphics-wgpu/src/format_helpers.rs @@ -0,0 +1,101 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! Shared texture format helpers and binding offset constants. +//! +//! This module centralizes format-related utilities used across the crate to avoid +//! duplication and keep the binding convention in one place. + +/// Offset added to a texture binding slot to obtain the corresponding sampler slot. +/// +/// For a texture bound at slot `N`, its sampler is bound at `N + SAMPLER_BINDING_OFFSET`. +pub const SAMPLER_BINDING_OFFSET: usize = 100; + +/// Offset added to a resource binding slot to obtain the uniform buffer slot. +/// +/// For a property group at binding `N`, its uniform buffer is bound at +/// `N + UNIFORM_BINDING_OFFSET`. +pub const UNIFORM_BINDING_OFFSET: usize = 200; + +/// Returns `true` if the given texture format supports filtering samplers. +/// +/// Non-filterable formats (32-bit float, uint, sint) require +/// [`wgpu::SamplerBindingType::NonFiltering`] when creating bind group layouts. +/// +/// # Examples +/// +/// ```ignore +/// use fyrox_graphics_wgpu::format_helpers::is_filterable_format; +/// +/// assert!(is_filterable_format(wgpu::TextureFormat::Rgba8Unorm)); +/// assert!(!is_filterable_format(wgpu::TextureFormat::R32Float)); +/// ``` +pub fn is_filterable_format(fmt: wgpu::TextureFormat) -> bool { + !matches!( + fmt, + wgpu::TextureFormat::R32Float + | wgpu::TextureFormat::Rg32Float + | wgpu::TextureFormat::Rgba32Float + | wgpu::TextureFormat::R8Uint + | wgpu::TextureFormat::R16Uint + | wgpu::TextureFormat::R32Uint + | wgpu::TextureFormat::R8Sint + | wgpu::TextureFormat::R16Sint + | wgpu::TextureFormat::R32Sint + ) +} + +/// Returns the appropriate [`wgpu::TextureSampleType`] for the given texture format. +/// +/// Used when creating bind group layouts from actual texture formats rather than +/// from [`SamplerKind`](fyrox_graphics::gpu_program::SamplerKind) inference. +/// Handles depth, uint, sint, and non-filterable float formats correctly. +pub fn sample_type_for_format(fmt: wgpu::TextureFormat) -> wgpu::TextureSampleType { + use wgpu::TextureFormat as F; + match fmt { + F::Depth16Unorm + | F::Depth24Plus + | F::Depth24PlusStencil8 + | F::Depth32Float + | F::Depth32FloatStencil8 => wgpu::TextureSampleType::Depth, + F::R8Uint + | F::R16Uint + | F::R32Uint + | F::Rg8Uint + | F::Rg16Uint + | F::Rg32Uint + | F::Rgba8Uint + | F::Rgba16Uint + | F::Rgba32Uint => wgpu::TextureSampleType::Uint, + F::R8Sint + | F::R16Sint + | F::R32Sint + | F::Rg8Sint + | F::Rg16Sint + | F::Rg32Sint + | F::Rgba8Sint + | F::Rgba16Sint + | F::Rgba32Sint => wgpu::TextureSampleType::Sint, + F::R32Float | F::Rg32Float | F::Rgba32Float => { + wgpu::TextureSampleType::Float { filterable: false } + } + _ => wgpu::TextureSampleType::Float { filterable: true }, + } +} From 433b3b53f487a61ba8d4b525b4dc564f51554d3c Mon Sep 17 00:00:00 2001 From: nicehack Date: Tue, 14 Jul 2026 11:30:48 +0500 Subject: [PATCH 14/34] fixed the display of materials and rendering has been slightly optimized --- fyrox-graphics-wgpu/src/format_helpers.rs | 25 ++++ fyrox-graphics-wgpu/src/framebuffer.rs | 159 +++++++++++++--------- fyrox-graphics-wgpu/src/server.rs | 31 ++++- fyrox-graphics-wgpu/src/texture.rs | 1 - 4 files changed, 148 insertions(+), 68 deletions(-) diff --git a/fyrox-graphics-wgpu/src/format_helpers.rs b/fyrox-graphics-wgpu/src/format_helpers.rs index f5a8e75681..82b48b33c0 100644 --- a/fyrox-graphics-wgpu/src/format_helpers.rs +++ b/fyrox-graphics-wgpu/src/format_helpers.rs @@ -62,6 +62,31 @@ pub fn is_filterable_format(fmt: wgpu::TextureFormat) -> bool { ) } +/// Returns `true` if the given texture format is an integer format. +/// +/// Hardware blending (e.g., alpha blending) relies on floating-point arithmetic. +/// Integer formats (such as `R8Uint`, `R16Sint`, etc.) cannot be blended by the GPU. +/// When configuring a [`wgpu::ColorTargetState`] for these formats, the `blend` +/// field MUST be explicitly set to `None` to prevent `wgpu` validation errors. +/// +/// # Examples +/// +/// ```ignore +/// use fyrox_graphics_wgpu::format_helpers::is_integer_format; +/// +/// assert!(is_integer_format(wgpu::TextureFormat::R8Uint)); +/// assert!(!is_integer_format(wgpu::TextureFormat::Rgba8Unorm)); +/// ``` +pub fn is_integer_format(fmt: wgpu::TextureFormat) -> bool { + use wgpu::TextureFormat as F; + matches!( + fmt, + F::R8Uint | F::R8Sint | F::R16Uint | F::R16Sint | F::R32Uint | F::R32Sint | + F::Rg8Uint | F::Rg8Sint | F::Rg16Uint | F::Rg16Sint | F::Rg32Uint | F::Rg32Sint | + F::Rgba8Uint | F::Rgba8Sint | F::Rgba16Uint | F::Rgba16Sint | F::Rgba32Uint | F::Rgba32Sint + ) +} + /// Returns the appropriate [`wgpu::TextureSampleType`] for the given texture format. /// /// Used when creating bind group layouts from actual texture formats rather than diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index 3b9c85d9a1..fb1da28609 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -19,7 +19,7 @@ // SOFTWARE. use crate::buffer::WgpuBuffer; -use crate::format_helpers::{is_filterable_format, SAMPLER_BINDING_OFFSET, UNIFORM_BINDING_OFFSET}; +use crate::format_helpers::{is_filterable_format, is_integer_format, SAMPLER_BINDING_OFFSET, UNIFORM_BINDING_OFFSET}; use crate::geometry_buffer::WgpuGeometryBuffer; use crate::program::WgpuProgram; use crate::sampler::WgpuSampler; @@ -155,7 +155,7 @@ fn texture_format_for_attachment(tex: &GpuTexture) -> Option, depth_format: Option, sample_count: u32, blend: bool, @@ -228,7 +228,7 @@ impl WgpuFrameBuffer { program: &WgpuProgram, params: &DrawParameters, all_layouts: &[wgpu::VertexBufferLayout<'static>], - cf: wgpu::TextureFormat, + color_formats: &[wgpu::TextureFormat], df: Option, pipeline_layout: &wgpu::PipelineLayout, element_kind: fyrox_graphics::ElementKind, @@ -243,7 +243,7 @@ impl WgpuFrameBuffer { let effective_stencil = needs_stencil && stencil_supported; let key = PipelineKey { program_ptr: program as *const WgpuProgram as usize, - color_format: cf, + color_formats: color_formats.to_vec(), depth_format: df, sample_count: server.msaa_sample_count, blend: params.blend.is_some(), @@ -369,16 +369,27 @@ impl WgpuFrameBuffer { fyrox_graphics::ElementKind::Point => wgpu::PrimitiveTopology::PointList, }; - let color_target = [Some(wgpu::ColorTargetState { - format: cf, - blend: blend_state, - write_mask: wgpu::ColorWrites::ALL, - })]; + let color_targets: Vec> = color_formats + .iter() + .map(|&format| { + let blend = if is_integer_format(format) { + None + } else { + blend_state.clone() + }; + + Some(wgpu::ColorTargetState { + format, + blend, + write_mask: wgpu::ColorWrites::ALL, + }) + }) + .collect(); let fragment_state = if has_color { Some(wgpu::FragmentState { module: program.fragment_module(), entry_point: Some("fs_main"), - targets: &color_target, + targets: &color_targets, compilation_options: Default::default(), }) } else { @@ -498,12 +509,19 @@ impl WgpuFrameBuffer { None }; - let cf = if self.is_backbuffer { - server.surface_config.read().unwrap().format - } else if let Some(fc) = self.color_attachments.first() { - texture_format_for_attachment(&fc.texture).unwrap_or(wgpu::TextureFormat::Rgba8Unorm) + // Collect color formats from ALL attachments (MRT support). + // For backbuffer: single format from surface config. + // For offscreen FBOs: one format per color attachment (e.g. G-Buffer has 5). + let color_formats: Vec = if self.is_backbuffer { + vec![server.surface_config.read().unwrap().format] } else { - wgpu::TextureFormat::Rgba8Unorm + self.color_attachments + .iter() + .map(|a| { + texture_format_for_attachment(&a.texture) + .unwrap_or(wgpu::TextureFormat::Rgba8Unorm) + }) + .collect() }; let df = self @@ -542,7 +560,7 @@ impl WgpuFrameBuffer { prog, params, &all_layouts, - cf, + &color_formats, df, &pipeline_layout, geo.element_kind(), @@ -550,41 +568,47 @@ impl WgpuFrameBuffer { ); let bind_group = create_bind_group(&server, prog, resources); - let mut encoder = + + // Take or create frame encoder (batched: one encoder per frame, not per draw call). + let mut encoder = server.frame_encoder.borrow_mut().take().unwrap_or_else(|| { server .state .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("DrawEnc"), - }); + label: Some("FrameEnc"), + }) + }); - let color_view = if self.is_backbuffer { - Some(surface_tex.expect("surface texture should be set for backbuffer")) + // Create views for ALL color attachments (MRT). + let color_views: Vec = if self.is_backbuffer { + vec![surface_tex.expect("surface texture should be set for backbuffer")] } else { - self.color_attachments.first().map(|first_color| { - if let Some(face) = first_color.cube_map_face() { - let wt = first_color - .texture - .as_any() - .downcast_ref::() - .expect("color attachment should be WgpuTexture"); - wt.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { - dimension: Some(wgpu::TextureViewDimension::D2), - base_array_layer: cubemap_face_to_layer(face), - array_layer_count: Some(1), - mip_level_count: Some(1), - ..Default::default() - }) - } else { - first_color - .texture - .as_any() - .downcast_ref::() - .expect("color attachment should be WgpuTexture") - .wgpu_view() - .clone() - } - }) + self.color_attachments + .iter() + .map(|att| { + if let Some(face) = att.cube_map_face() { + let wt = att + .texture + .as_any() + .downcast_ref::() + .expect("color attachment should be WgpuTexture"); + wt.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { + dimension: Some(wgpu::TextureViewDimension::D2), + base_array_layer: cubemap_face_to_layer(face), + array_layer_count: Some(1), + mip_level_count: Some(1), + ..Default::default() + }) + } else { + att.texture + .as_any() + .downcast_ref::() + .expect("color attachment should be WgpuTexture") + .wgpu_view() + .clone() + } + }) + .collect() }; let depth_view = self.depth_attachment.as_ref().map(|a| { @@ -639,26 +663,26 @@ impl WgpuFrameBuffer { } else { (wgpu::LoadOp::Load, wgpu::LoadOp::Load, wgpu::LoadOp::Load) }; - let color_att = color_view - .as_ref() - .map(|view| wgpu::RenderPassColorAttachment { - view, - resolve_target: None, - depth_slice: None, - ops: wgpu::Operations { - load: color_load, - store: wgpu::StoreOp::Store, - }, - }); - let color_att_ref: &[Option>] = - if color_att.is_some() { - &[color_att] - } else { - &[] - }; + + // Build color attachments for ALL render targets (MRT). + let color_attachments: Vec> = color_views + .iter() + .map(|view| { + Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + depth_slice: None, + ops: wgpu::Operations { + load: color_load, + store: wgpu::StoreOp::Store, + }, + }) + }) + .collect(); + let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("DrawRP"), - color_attachments: color_att_ref, + color_attachments: &color_attachments, depth_stencil_attachment: depth_view.as_ref().map(|v| { wgpu::RenderPassDepthStencilAttachment { view: v, @@ -706,8 +730,11 @@ impl WgpuFrameBuffer { let end_idx = ((offset + count) * ipe) as u32; rp.draw_indexed(start_idx..end_idx, 0, 0..instance_count); } + // Render pass dropped here; encoder is free for next draw call. + + // Return encoder to server for reuse by subsequent draw calls. + *server.frame_encoder.borrow_mut() = Some(encoder); - server.state.queue.submit(std::iter::once(encoder.finish())); Ok(DrawCallStatistics { triangles: count * instance_count as usize, }) @@ -746,6 +773,10 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { } fn read_pixels(&self, read_target: ReadTarget) -> Option> { let server = self.server.upgrade()?; + // Flush any pending frame encoder so prior draws are submitted before readback. + if let Some(encoder) = server.frame_encoder.borrow_mut().take() { + server.state.queue.submit(std::iter::once(encoder.finish())); + } let texture = match read_target { ReadTarget::Depth | ReadTarget::Stencil => &self.depth_attachment.as_ref()?.texture, ReadTarget::Color(i) => &self.color_attachments.get(i)?.texture, diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index 3337339eaa..44f3de6c48 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -108,6 +108,10 @@ pub struct WgpuGraphicsServer { pub backbuffer_needs_clear: Cell, /// Cached depth-stencil texture for the backbuffer, with its (width, height). backbuffer_depth_stencil: RefCell>, + /// Per-frame command encoder. Lazily created on first draw, submitted in swap_buffers. + /// Storing it on the server (not per-framebuffer) because multiple framebuffers + /// (G-Buffer, HDR, backbuffer) share the same frame and benefit from a single submit. + pub frame_encoder: RefCell>, } impl WgpuGraphicsServer { @@ -189,7 +193,7 @@ impl WgpuGraphicsServer { required_limits: if cfg!(target_arch = "wasm32") { wgpu::Limits::downlevel_webgl2_defaults() } else { - wgpu::Limits::default() + adapter.limits() }, memory_hints: wgpu::MemoryHints::Performance, ..Default::default() @@ -263,6 +267,7 @@ impl WgpuGraphicsServer { current_frame: RefCell::new(None), backbuffer_needs_clear: Cell::new(true), backbuffer_depth_stencil: RefCell::new(None), + frame_encoder: RefCell::new(None), }); *server.weak_self.borrow_mut() = Some(Rc::downgrade(&server)); @@ -408,7 +413,11 @@ impl GraphicsServer for WgpuGraphicsServer { self.weak_ref() as Weak } fn flush(&self) { - self.state.queue.submit(std::iter::empty()); + // flush in Fyrox means "send the accumulated commands to the video card right now." + // An empty submit is not needed here, just close and send the encoder, if there is one. + if let Some(encoder) = self.frame_encoder.borrow_mut().take() { + self.state.queue.submit(std::iter::once(encoder.finish())); + } } fn finish(&self) { self.state @@ -426,17 +435,33 @@ impl GraphicsServer for WgpuGraphicsServer { *self.pipeline_statistics.borrow() } fn swap_buffers(&self) -> Result<(), FrameworkError> { + // Submit all batched draw commands from this frame + if let Some(encoder) = self.frame_encoder.borrow_mut().take() { + self.state.queue.submit(std::iter::once(encoder.finish())); + } if let Some(frame) = self.current_frame.borrow_mut().take() { frame.present(); } - self.backbuffer_needs_clear.set(true); + self.backbuffer_needs_clear.replace(true); Ok(()) } fn set_frame_size(&self, new_size: (u32, u32)) { if new_size.0 > 0 && new_size.1 > 0 { let mut config = self.surface_config.write().unwrap(); + + if config.width == new_size.0 && config.height == new_size.1 { + return; + } + config.width = new_size.0; config.height = new_size.1; + + if let Some(encoder) = self.frame_encoder.borrow_mut().take() { + self.state.queue.submit(std::iter::once(encoder.finish())); + } + + self.current_frame.borrow_mut().take(); + self.surface.configure(&self.state.device, &config); } } diff --git a/fyrox-graphics-wgpu/src/texture.rs b/fyrox-graphics-wgpu/src/texture.rs index fbdac3b081..26eb0c4fdd 100644 --- a/fyrox-graphics-wgpu/src/texture.rs +++ b/fyrox-graphics-wgpu/src/texture.rs @@ -482,7 +482,6 @@ impl Drop for WgpuTexture { if let Some(server) = self.server.upgrade() { server.memory_usage.borrow_mut().textures -= self.size_bytes.get(); } - self.texture.destroy(); } } From cad71e9ff506e1940a5da69c4efd6bd7407e8e23 Mon Sep 17 00:00:00 2001 From: nicehack Date: Mon, 20 Jul 2026 21:55:01 +0500 Subject: [PATCH 15/34] fixed window resizing --- fyrox-graphics-wgpu/src/framebuffer.rs | 99 ++++++++++++++++++-------- 1 file changed, 71 insertions(+), 28 deletions(-) diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index fb1da28609..4ee5f09b94 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -25,6 +25,7 @@ use crate::program::WgpuProgram; use crate::sampler::WgpuSampler; use crate::server::WgpuGraphicsServer; use crate::texture::WgpuTexture; +use fyrox_core::log::Log; use fyrox_graphics::{ core::{color::Color, math::Rect}, error::FrameworkError, @@ -186,6 +187,7 @@ pub struct WgpuFrameBuffer { needs_clear: Cell, pending_clear_color: RefCell, pending_clear_depth: RefCell, + backbuffer_depth_cache: RefCell>, } impl WgpuFrameBuffer { @@ -203,6 +205,7 @@ impl WgpuFrameBuffer { needs_clear: Cell::new(false), pending_clear_color: RefCell::new(wgpu::Color::BLACK), pending_clear_depth: RefCell::new(1.0), + backbuffer_depth_cache: RefCell::new(None), }) } @@ -219,6 +222,7 @@ impl WgpuFrameBuffer { needs_clear: Cell::new(false), pending_clear_color: RefCell::new(wgpu::Color::BLACK), pending_clear_depth: RefCell::new(1.0), + backbuffer_depth_cache: RefCell::new(None), } } @@ -471,8 +475,10 @@ impl WgpuFrameBuffer { return Ok(DrawCallStatistics { triangles: 0 }); } + let mut current_width = 0; + let mut current_height = 0; + let surface_tex = if self.is_backbuffer { - // Only acquire a new frame if one isn't already stored from a prior draw call this frame. if server.current_frame.borrow().is_none() { match server.surface.get_current_texture() { wgpu::CurrentSurfaceTexture::Success(t) @@ -480,13 +486,13 @@ impl WgpuFrameBuffer { *server.current_frame.borrow_mut() = Some(t); } wgpu::CurrentSurfaceTexture::Timeout => { - log::warn!("Surface texture timeout, skipping frame"); + Log::warn("Surface texture timeout, skipping frame"); return Ok(DrawCallStatistics { triangles: 0 }); } wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => { let config = server.surface_config.read().unwrap(); server.surface.configure(&server.state.device, &config); - log::warn!("Surface lost/outdated, reconfigured"); + Log::warn("Surface lost/outdated, reconfigured"); return Ok(DrawCallStatistics { triangles: 0 }); } other => { @@ -496,12 +502,15 @@ impl WgpuFrameBuffer { } } } - // Create a view from the stored frame (doesn't consume the SurfaceTexture). + let frame = server.current_frame.borrow(); + let frame_ref = frame.as_ref().expect("frame should be Some after acquisition"); + + current_width = frame_ref.texture.size().width; + current_height = frame_ref.texture.size().height; + Some( - frame - .as_ref() - .expect("frame should be Some after acquisition") + frame_ref .texture .create_view(&wgpu::TextureViewDescriptor::default()), ) @@ -524,10 +533,13 @@ impl WgpuFrameBuffer { .collect() }; - let df = self - .depth_attachment - .as_ref() - .and_then(|a| texture_format_for_attachment(&a.texture)); + let df = if self.is_backbuffer { + Some(wgpu::TextureFormat::Depth24PlusStencil8) + } else { + self.depth_attachment + .as_ref() + .and_then(|a| texture_format_for_attachment(&a.texture)) + }; // Collect actual texture formats from resources for layout creation let mut texture_formats: Vec<(usize, wgpu::TextureFormat)> = Vec::new(); @@ -611,24 +623,55 @@ impl WgpuFrameBuffer { .collect() }; - let depth_view = self.depth_attachment.as_ref().map(|a| { - let wt = a - .texture - .as_any() - .downcast_ref::() - .expect("depth attachment should be WgpuTexture"); - if let Some(face) = a.cube_map_face() { - wt.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { - dimension: Some(wgpu::TextureViewDimension::D2), - base_array_layer: cubemap_face_to_layer(face), - array_layer_count: Some(1), - mip_level_count: Some(1), - ..Default::default() - }) - } else { - wt.wgpu_view().clone() + let depth_view = if self.is_backbuffer { + let mut backbuffer_depth_cache = self.backbuffer_depth_cache.borrow_mut(); + + let needs_recreate = match backbuffer_depth_cache.as_ref() { + Some((cw, ch, _)) => *cw != current_width || *ch != current_height, + None => true, + }; + + if needs_recreate && current_width > 0 && current_height > 0 { + let depth_texture = server.state.device.create_texture(&wgpu::TextureDescriptor { + label: Some("DynamicBackbufferDepth"), + size: wgpu::Extent3d { + width: current_width, + height: current_height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: server.msaa_sample_count, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth24PlusStencil8, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + *backbuffer_depth_cache = Some((current_width, current_height, depth_texture)); } - }); + + backbuffer_depth_cache + .as_ref() + .map(|(_, _, tex)| tex.create_view(&wgpu::TextureViewDescriptor::default())) + } else { + self.depth_attachment.as_ref().map(|a| { + let wt = a + .texture + .as_any() + .downcast_ref::() + .expect("depth attachment should be WgpuTexture"); + if let Some(face) = a.cube_map_face() { + wt.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { + dimension: Some(wgpu::TextureViewDimension::D2), + base_array_layer: cubemap_face_to_layer(face), + array_layer_count: Some(1), + mip_level_count: Some(1), + ..Default::default() + }) + } else { + wt.wgpu_view().clone() + } + }) + }; { // Backbuffer clears once per frame (flag set by swap_buffers, consumed on first draw). From a87c8f544cf369845e082b1c3e4d907d29097725 Mon Sep 17 00:00:00 2001 From: nicehack Date: Tue, 21 Jul 2026 16:18:33 +0500 Subject: [PATCH 16/34] GL editor shaders restored --- Cargo.toml | 1 + editor-standalone/Cargo.toml | 7 +- editor/Cargo.toml | 4 +- editor/resources/shaders/opengl/gizmo.shader | 85 ++++++++ editor/resources/shaders/opengl/grid.shader | 205 ++++++++++++++++++ .../resources/shaders/opengl/highlight.shader | 100 +++++++++ .../resources/shaders/opengl/overlay.shader | 87 ++++++++ .../shaders/opengl/sprite_gizmo.shader | 104 +++++++++ .../resources/shaders/{ => wgpu}/gizmo.shader | 0 .../resources/shaders/{ => wgpu}/grid.shader | 0 .../shaders/{ => wgpu}/highlight.shader | 0 .../shaders/{ => wgpu}/overlay.shader | 0 .../shaders/{ => wgpu}/sprite_gizmo.shader | 0 editor/src/highlight.rs | 8 +- editor/src/lib.rs | 13 +- editor/src/overlay.rs | 8 +- editor/src/plugins/collider/mod.rs | 13 +- editor/src/scene/mod.rs | 13 +- fyrox-graphics-gl/src/program.rs | 2 + fyrox-impl/Cargo.toml | 5 +- fyrox-impl/src/engine/mod.rs | 11 +- fyrox/Cargo.toml | 2 + 22 files changed, 655 insertions(+), 13 deletions(-) create mode 100644 editor/resources/shaders/opengl/gizmo.shader create mode 100644 editor/resources/shaders/opengl/grid.shader create mode 100644 editor/resources/shaders/opengl/highlight.shader create mode 100644 editor/resources/shaders/opengl/overlay.shader create mode 100644 editor/resources/shaders/opengl/sprite_gizmo.shader rename editor/resources/shaders/{ => wgpu}/gizmo.shader (100%) rename editor/resources/shaders/{ => wgpu}/grid.shader (100%) rename editor/resources/shaders/{ => wgpu}/highlight.shader (100%) rename editor/resources/shaders/{ => wgpu}/overlay.shader (100%) rename editor/resources/shaders/{ => wgpu}/sprite_gizmo.shader (100%) diff --git a/Cargo.toml b/Cargo.toml index 7951a2f67b..7036a87971 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "fyrox-texture", "fyrox-autotile", "fyrox-material", + "fyrox-graphics-gl", "fyrox-graphics-wgpu"] resolver = "2" diff --git a/editor-standalone/Cargo.toml b/editor-standalone/Cargo.toml index bb41fcc835..2422685f35 100644 --- a/editor-standalone/Cargo.toml +++ b/editor-standalone/Cargo.toml @@ -14,5 +14,10 @@ include = ["/src/**/*", "/Cargo.toml", "/LICENSE", "/README.md"] [dependencies] fyrox = { version = "2.0.0-rc.1", path = "../fyrox" } -fyroxed_base = { version = "2.0.0-rc.1", path = "../editor" } +fyroxed_base = { version = "2.0.0-rc.1", path = "../editor", default-features = false } clap = { version = "4", features = ["derive"] } + +[features] +default = ["backend_opengl"] +backend_opengl = ["fyrox/backend_opengl", "fyroxed_base/backend_opengl"] +backend_wgpu = ["fyrox/backend_wgpu", "fyroxed_base/backend_wgpu"] diff --git a/editor/Cargo.toml b/editor/Cargo.toml index fce00646af..a47665d527 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -32,5 +32,7 @@ notify = "8" bitflags = "2.9.1" [features] -default = ["fyrox/default"] +default = ["fyrox/default", "backend_opengl"] dylib_engine = ["fyrox/dylib"] +backend_opengl = ["fyrox/backend_opengl"] +backend_wgpu = ["fyrox/backend_wgpu"] diff --git a/editor/resources/shaders/opengl/gizmo.shader b/editor/resources/shaders/opengl/gizmo.shader new file mode 100644 index 0000000000..7023806919 --- /dev/null +++ b/editor/resources/shaders/opengl/gizmo.shader @@ -0,0 +1,85 @@ +( + name: "GizmoShader", + + resources: [ + ( + name: "properties", + kind: PropertyGroup([ + ( + name: "diffuseColor", + kind: Color(r: 255, g: 255, b: 255, a: 255), + ), + ]), + binding: 0 + ), + ( + name: "fyrox_instanceData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 1 + ), + ], + + disabled_passes: ["GBuffer", "DirectionalShadow", "PointShadow", "SpotShadow"], + + passes: [ + ( + name: "Forward", + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + + void main() + { + gl_Position = fyrox_instanceData.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + void main() + { + FragColor = properties.diffuseColor; + + // Pull depth towards near clipping plane so the gizmo will be drawn on top + // of everything, but its parts will be correctly handled by depth test. + gl_FragDepth = gl_FragCoord.z * 0.001; + } + "#, + ), + ], +) \ No newline at end of file diff --git a/editor/resources/shaders/opengl/grid.shader b/editor/resources/shaders/opengl/grid.shader new file mode 100644 index 0000000000..1b21785a3f --- /dev/null +++ b/editor/resources/shaders/opengl/grid.shader @@ -0,0 +1,205 @@ +( + name: "GridShader", + + resources: [ + ( + name: "properties", + kind: PropertyGroup([ + ( + name: "diffuseColor", + kind: Color(r: 40, g: 40, b: 40, a: 255), + ), + ( + name: "xAxisColor", + kind: Color(r: 255, g: 0, b: 0, a: 255), + ), + ( + name: "yAxisColor", + kind: Color(r: 0, g: 255, b: 0, a: 255), + ), + ( + name: "zAxisColor", + kind: Color(r: 0, g: 0, b: 255, a: 255), + ), + ( + name: "orientation", + kind: Int(value: 0), + ), + ( + name: "isPerspective", + kind: Bool(value: false) + ), + ( + name: "scale", + kind: Vector2(value: (1.0, 1.0)) + ) + ]), + binding: 0 + ), + ( + name: "fyrox_cameraData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 1 + ), + ], + + disabled_passes: ["GBuffer", "DirectionalShadow", "PointShadow", "SpotShadow"], + + passes: [ + ( + name: "Forward", + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + + out vec3 nearPoint; + out vec3 farPoint; + + vec3 Unproject(float x, float y, float z, mat4 matrix) + { + vec4 position = matrix * vec4(x, y, z, 1.0); + return position.xyz / position.w; + } + + void main() + { + mat4 invViewProj = inverse(fyrox_cameraData.viewProjectionMatrix); + nearPoint = Unproject(vertexPosition.x, vertexPosition.y, 0.0, invViewProj); + farPoint = Unproject(vertexPosition.x, vertexPosition.y, 1.0, invViewProj); + gl_Position = vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + // Original code: https://asliceofrendering.com/scene%20helper/2020/01/05/InfiniteGrid/ + // Fixed and adapted for Fyrox. + + out vec4 FragColor; + + in vec3 nearPoint; + in vec3 farPoint; + + vec4 grid(vec3 fragPos3D) { + vec2 projection; + vec3 planeNormal; + vec4 xColor; + vec4 yColor; + if (properties.orientation == 0) { + projection = fragPos3D.xz; + planeNormal = vec3(0.0, 1.0, 0.0); + xColor = properties.xAxisColor; + yColor = properties.zAxisColor; + } else if (properties.orientation == 1) { + projection = fragPos3D.xy; + planeNormal = vec3(0.0, 0.0, 1.0); + xColor = properties.yAxisColor; + yColor = properties.xAxisColor; + } else if (properties.orientation == 2) { + projection = fragPos3D.zy; + planeNormal = vec3(1.0, 0.0, 0.0); + xColor = properties.zAxisColor; + yColor = properties.yAxisColor; + } + + vec2 coord = projection * properties.scale; + vec2 derivative = fwidth(coord); + vec2 grid = abs(fract(coord - 0.5) - 0.5) / derivative; + float line = min(grid.x, grid.y); + float minX = 0.5 * min(derivative.x, 1.0); + float minY = 0.5 * min(derivative.y, 1.0); + + vec4 color = properties.diffuseColor; + float alpha = 1.0 - min(line, 1.0); + // Sharpen lines a bit. + color.a = alpha >= 0.5 ? 1.0 : 0.0; + + if (projection.x > -minX && projection.x < minX) { + color.xyz = xColor.xyz; + } else if (projection.y > -minY && projection.y < minY) { + color.xyz = yColor.xyz; + } else { + vec3 viewDir = fragPos3D - fyrox_cameraData.position; + // This helps to negate moire pattern at large distances. + float cosAngle = abs(dot(planeNormal, normalize(viewDir))); + color.a *= cosAngle; + } + + return color; + } + + float computeDepth(vec3 pos) { + vec4 clip_space_pos = fyrox_cameraData.viewProjectionMatrix * vec4(pos.xyz, 1.0); + return (clip_space_pos.z / clip_space_pos.w); + } + + void main() + { + float nearCoord; + float farCoord; + if (properties.orientation == 0) { + nearCoord = nearPoint.y; + farCoord = farPoint.y; + } else if (properties.orientation == 1) { + nearCoord = nearPoint.z; + farCoord = farPoint.z; + } else if (properties.orientation == 2) { + nearCoord = nearPoint.x; + farCoord = farPoint.x; + } + + float denominator = farCoord - nearCoord; + float t = denominator != 0.0 ? -nearCoord / denominator : 0.0; + + vec3 fragPos3D = nearPoint + t * (farPoint - nearPoint); + + float depth = computeDepth(fragPos3D); + gl_FragDepth = ((gl_DepthRange.diff * depth) + gl_DepthRange.near + gl_DepthRange.far) / 2.0; + + FragColor = grid(fragPos3D); + if (properties.isPerspective) { + FragColor.a *= float(t > 0.0); + } + + // Alpha test to prevent blending issues. + if (FragColor.a < 0.01) { + discard; + } + } + "#, + ), + ], +) \ No newline at end of file diff --git a/editor/resources/shaders/opengl/highlight.shader b/editor/resources/shaders/opengl/highlight.shader new file mode 100644 index 0000000000..5987561dd8 --- /dev/null +++ b/editor/resources/shaders/opengl/highlight.shader @@ -0,0 +1,100 @@ +( + name: "Highlight", + resources: [ + ( + name: "frameTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "color", kind: Vector4()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + layout (location = 0) out vec4 outColor; + + in vec2 texCoord; + + void main() { + ivec2 size = textureSize(frameTexture, 0); + + float w = 1.0 / float(size.x); + float h = 1.0 / float(size.y); + + float n[9]; + n[0] = texture(frameTexture, texCoord + vec2(-w, -h)).a; + n[1] = texture(frameTexture, texCoord + vec2(0.0, -h)).a; + n[2] = texture(frameTexture, texCoord + vec2(w, -h)).a; + n[3] = texture(frameTexture, texCoord + vec2( -w, 0.0)).a; + n[4] = texture(frameTexture, texCoord).a; + n[5] = texture(frameTexture, texCoord + vec2(w, 0.0)).a; + n[6] = texture(frameTexture, texCoord + vec2(-w, h)).a; + n[7] = texture(frameTexture, texCoord + vec2(0.0, h)).a; + n[8] = texture(frameTexture, texCoord + vec2(w, h)).a; + + float sobel_edge_h = n[2] + (2.0 * n[5]) + n[8] - (n[0] + (2.0 * n[3]) + n[6]); + float sobel_edge_v = n[0] + (2.0 * n[1]) + n[2] - (n[6] + (2.0 * n[7]) + n[8]); + float sobel = sqrt((sobel_edge_h * sobel_edge_h) + (sobel_edge_v * sobel_edge_v)); + + outColor = vec4(properties.color.rgb, properties.color.a * sobel); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/editor/resources/shaders/opengl/overlay.shader b/editor/resources/shaders/opengl/overlay.shader new file mode 100644 index 0000000000..07c43416f2 --- /dev/null +++ b/editor/resources/shaders/opengl/overlay.shader @@ -0,0 +1,87 @@ +( + name: "Overlay", + resources: [ + ( + name: "diffuseTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "viewProjectionMatrix", kind: Matrix4()), + (name: "worldMatrix", kind: Matrix4()), + (name: "cameraSideVector", kind: Vector3()), + (name: "cameraUpVector", kind: Vector3()), + (name: "size", kind: Float()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + vec2 vertexOffset = vertexTexCoord * 2.0 - 1.0; + vec4 worldPosition = properties.worldMatrix * vec4(vertexPosition, 1.0); + vec3 offset = (vertexOffset.x * properties.cameraSideVector + vertexOffset.y * properties.cameraUpVector) * properties.size; + gl_Position = properties.viewProjectionMatrix * (worldPosition + vec4(offset.x, offset.y, offset.z, 0.0)); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec2 texCoord; + + void main() + { + FragColor = texture(diffuseTexture, texCoord); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/editor/resources/shaders/opengl/sprite_gizmo.shader b/editor/resources/shaders/opengl/sprite_gizmo.shader new file mode 100644 index 0000000000..442e73c59d --- /dev/null +++ b/editor/resources/shaders/opengl/sprite_gizmo.shader @@ -0,0 +1,104 @@ +( + name: "SpriteGizmoShader", + + resources: [ + ( + name: "diffuseTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "fyrox_instanceData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 0 + ), + ( + name: "fyrox_cameraData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 1 + ), + ], + + disabled_passes: ["GBuffer", "DirectionalShadow", "PointShadow", "SpotShadow"], + + passes: [ + ( + name: "Forward", + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 2) in vec2 vertexParams; + layout(location = 3) in vec4 vertexColor; + + out vec2 texCoord; + out vec4 color; + + void main() + { + float size = vertexParams.x; + float rotation = vertexParams.y; + + texCoord = vertexTexCoord; + color = vertexColor; + vec2 vertexOffset = S_RotateVec2(vertexTexCoord * 2.0 - 1.0, rotation); + vec4 worldPosition = fyrox_instanceData.worldMatrix * vec4(vertexPosition, 1.0); + vec3 offset = (vertexOffset.x * fyrox_cameraData.sideVector + vertexOffset.y * fyrox_cameraData.upVector) * size; + gl_Position = fyrox_cameraData.viewProjectionMatrix * (worldPosition + vec4(offset.x, offset.y, offset.z, 0.0)); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec2 texCoord; + in vec4 color; + + void main() + { + FragColor = color * S_SRGBToLinear(texture(diffuseTexture, texCoord)); + + // Pull depth towards near clipping plane so the gizmo will be drawn on top + // of everything, but its parts will be correctly handled by depth test. + gl_FragDepth = gl_FragCoord.z * 0.001; + } + "#, + ), + ], +) \ No newline at end of file diff --git a/editor/resources/shaders/gizmo.shader b/editor/resources/shaders/wgpu/gizmo.shader similarity index 100% rename from editor/resources/shaders/gizmo.shader rename to editor/resources/shaders/wgpu/gizmo.shader diff --git a/editor/resources/shaders/grid.shader b/editor/resources/shaders/wgpu/grid.shader similarity index 100% rename from editor/resources/shaders/grid.shader rename to editor/resources/shaders/wgpu/grid.shader diff --git a/editor/resources/shaders/highlight.shader b/editor/resources/shaders/wgpu/highlight.shader similarity index 100% rename from editor/resources/shaders/highlight.shader rename to editor/resources/shaders/wgpu/highlight.shader diff --git a/editor/resources/shaders/overlay.shader b/editor/resources/shaders/wgpu/overlay.shader similarity index 100% rename from editor/resources/shaders/overlay.shader rename to editor/resources/shaders/wgpu/overlay.shader diff --git a/editor/resources/shaders/sprite_gizmo.shader b/editor/resources/shaders/wgpu/sprite_gizmo.shader similarity index 100% rename from editor/resources/shaders/sprite_gizmo.shader rename to editor/resources/shaders/wgpu/sprite_gizmo.shader diff --git a/editor/src/highlight.rs b/editor/src/highlight.rs index 582db8c595..a8bd404be1 100644 --- a/editor/src/highlight.rs +++ b/editor/src/highlight.rs @@ -42,6 +42,12 @@ use crate::{ }; use std::{any::TypeId, cell::RefCell, rc::Rc}; +#[cfg(feature = "backend_opengl")] +const HIGHLIGHT_SHADER_SRC: &str = include_str!("../resources/shaders/opengl/highlight.shader"); + +#[cfg(feature = "backend_wgpu")] +const HIGHLIGHT_SHADER_SRC: &str = include_str!("../resources/shaders/wgpu/highlight.shader"); + pub struct HighlightRenderPass { framebuffer: GpuFrameBuffer, edge_detect_shader: RenderPassContainer, @@ -79,7 +85,7 @@ impl HighlightRenderPass { framebuffer: Self::create_frame_buffer(server, width, height), edge_detect_shader: RenderPassContainer::from_str( server, - include_str!("../resources/shaders/highlight.shader"), + HIGHLIGHT_SHADER_SRC, ) .unwrap(), scene_handle: Default::default(), diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 4270c6041c..402adfbf43 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -273,15 +273,26 @@ macro_rules! load_image { }; } +#[cfg(feature = "backend_opengl")] static GIZMO_SHADER: LazyLock = LazyLock::new(|| { ShaderResource::from_str( Uuid::new_v4(), - include_str!("../resources/shaders/gizmo.shader"), + include_str!("../resources/shaders/opengl/gizmo.shader"), Default::default(), ) .unwrap() }); +#[cfg(feature = "backend_wgpu")] +static GIZMO_SHADER: LazyLock = LazyLock::new(|| { + ShaderResource::from_str( + Uuid::new_v4(), + include_str!("../resources/shaders/wgpu/gizmo.shader"), + Default::default(), + ) + .unwrap() +}); + pub fn make_color_material(color: Color) -> MaterialResource { let mut material = Material::from_shader(GIZMO_SHADER.clone()); material.set_property("diffuseColor", color); diff --git a/editor/src/overlay.rs b/editor/src/overlay.rs index d162c25927..a0900b8686 100644 --- a/editor/src/overlay.rs +++ b/editor/src/overlay.rs @@ -44,6 +44,12 @@ use crate::{ use fyrox::core::uuid::Uuid; use std::{any::TypeId, cell::RefCell, rc::Rc}; +#[cfg(feature = "backend_opengl")] +const OVERLAY_SHADER_SRC: &str = include_str!("../resources/shaders/opengl/overlay.shader"); + +#[cfg(feature = "backend_wgpu")] +const OVERLAY_SHADER_SRC: &str = include_str!("../resources/shaders/wgpu/overlay.shader"); + pub struct OverlayRenderPass { quad: GpuGeometryBuffer, shader: RenderPassContainer, @@ -65,7 +71,7 @@ impl OverlayRenderPass { .unwrap(), shader: RenderPassContainer::from_str( server, - include_str!("../resources/shaders/overlay.shader"), + OVERLAY_SHADER_SRC, ) .unwrap(), sound_icon: TextureResource::load_from_memory( diff --git a/editor/src/plugins/collider/mod.rs b/editor/src/plugins/collider/mod.rs index 27782f42ab..ec82ea9e1d 100644 --- a/editor/src/plugins/collider/mod.rs +++ b/editor/src/plugins/collider/mod.rs @@ -270,15 +270,26 @@ fn make_shape_gizmo( } } +#[cfg(feature = "backend_opengl")] static GIZMO_SHADER: LazyLock = LazyLock::new(|| { ShaderResource::from_str( Uuid::new_v4(), - include_str!("../../../resources/shaders/sprite_gizmo.shader"), + include_str!("../../../resources/shaders/opengl/sprite_gizmo.shader"), Default::default(), ) .unwrap() }); +#[cfg(feature = "backend_wgpu")] +static GIZMO_SHADER: LazyLock = LazyLock::new(|| { + ShaderResource::from_str( + Uuid::new_v4(), + include_str!("../../../resources/shaders/wgpu/sprite_gizmo.shader"), + Default::default(), + ) + .unwrap() +}); + fn make_handle(scene: &mut Scene, root: Handle, visible: bool) -> Handle { let mut material = Material::from_shader(GIZMO_SHADER.clone()); diff --git a/editor/src/scene/mod.rs b/editor/src/scene/mod.rs index 77b68d629e..95b3b86774 100644 --- a/editor/src/scene/mod.rs +++ b/editor/src/scene/mod.rs @@ -152,15 +152,26 @@ pub struct GameScene { pub settings_receiver: Receiver, } +#[cfg(feature = "backend_opengl")] static GRID_SHADER: LazyLock = LazyLock::new(|| { ShaderResource::from_str( Uuid::new_v4(), - include_str!("../../resources/shaders/grid.shader"), + include_str!("../../resources/shaders/opengl/grid.shader"), Default::default(), ) .unwrap() }); +#[cfg(feature = "backend_wgpu")] +static GRID_SHADER: LazyLock = LazyLock::new(|| { + ShaderResource::from_str( + Uuid::new_v4(), + include_str!("../../resources/shaders/wgpu/grid.shader"), + Default::default(), + ) + .unwrap() +}); + fn make_grid_material() -> MaterialResource { let material = Material::from_shader(GRID_SHADER.clone()); MaterialResource::new_embedded(material) diff --git a/fyrox-graphics-gl/src/program.rs b/fyrox-graphics-gl/src/program.rs index 096ab1f259..a4a1efefde 100644 --- a/fyrox-graphics-gl/src/program.rs +++ b/fyrox-graphics-gl/src/program.rs @@ -44,6 +44,8 @@ fn glsl_sampler_name(sampler: SamplerKind) -> &'static str { SamplerKind::USampler2D => "usampler2D", SamplerKind::USampler3D => "usampler3D", SamplerKind::USamplerCube => "usamplerCube", + SamplerKind::DepthSampler2D => "sampler2D", + SamplerKind::DepthSamplerCube => "samplerCube", } } diff --git a/fyrox-impl/Cargo.toml b/fyrox-impl/Cargo.toml index 7751327315..0f6254420e 100644 --- a/fyrox-impl/Cargo.toml +++ b/fyrox-impl/Cargo.toml @@ -23,10 +23,11 @@ fyrox-resource = { path = "../fyrox-resource", version = "2.0.0-rc.1" } fyrox-animation = { path = "../fyrox-animation", version = "2.0.0-rc.1" } fyrox-graph = { path = "../fyrox-graph", version = "2.0.0-rc.1" } fyrox-graphics = { path = "../fyrox-graphics", version = "2.0.0-rc.1" } -fyrox-graphics-wgpu = { path = "../fyrox-graphics-wgpu", version = "2.0.0-rc.1" } fyrox-texture = { path = "../fyrox-texture", version = "2.0.0-rc.1" } fyrox-autotile = { path = "../fyrox-autotile", version = "2.0.0-rc.1" } fyrox-material = { path = "../fyrox-material", version = "2.0.0-rc.1" } +fyrox-graphics-gl = { path = "../fyrox-graphics-gl", version = "2.0.0-rc.1", optional = true } +fyrox-graphics-wgpu = { path = "../fyrox-graphics-wgpu", version = "2.0.0-rc.1", optional = true } rapier2d = { version = "0.33", features = ["debug-render", ] } rapier3d = { version = "0.33", features = ["debug-render"] } image = { version = "0.25.1", default-features = false, features = ["gif", "jpeg", "png", "tga", "tiff", "bmp"] } @@ -93,6 +94,8 @@ opener = { version = "0.8", default-features = false, features = ["reveal"] } # === end of additional deps === [features] +backend_opengl = ["dep:fyrox-graphics-gl"] +backend_wgpu = ["dep:fyrox-graphics-wgpu"] mesh_analysis = [] [target.'cfg(target_os = "android")'.dependencies] diff --git a/fyrox-impl/src/engine/mod.rs b/fyrox-impl/src/engine/mod.rs index a81a51e8fe..9fc35706f8 100644 --- a/fyrox-impl/src/engine/mod.rs +++ b/fyrox-impl/src/engine/mod.rs @@ -142,7 +142,6 @@ use winit::{ window::Icon, window::WindowAttributes, }; -use fyrox_graphics_wgpu::server::WgpuGraphicsServer; /// Serialization context holds runtime type information that allows to create unknown types using /// their UUIDs and a respective constructors. @@ -1023,11 +1022,12 @@ pub type GraphicsServerConstructorCallback = dyn Fn( #[derive(Clone)] pub struct GraphicsServerConstructor(Rc); -impl GraphicsServerConstructor { - pub fn wgpu() -> Self { +#[cfg(feature = "backend_opengl")] +impl Default for GraphicsServerConstructor { + fn default() -> Self { Self(Rc::new( |params, window_target, window_builder, named_objects| { - WgpuGraphicsServer::new( + fyrox_graphics_gl::server::GlGraphicsServer::new( params.vsync, params.msaa_sample_count, window_target, @@ -1039,11 +1039,12 @@ impl GraphicsServerConstructor { } } +#[cfg(all(feature = "backend_wgpu", not(feature = "backend_opengl")))] impl Default for GraphicsServerConstructor { fn default() -> Self { Self(Rc::new( |params, window_target, window_builder, named_objects| { - WgpuGraphicsServer::new( + fyrox_graphics_wgpu::server::WgpuGraphicsServer::new( params.vsync, params.msaa_sample_count, window_target, diff --git a/fyrox/Cargo.toml b/fyrox/Cargo.toml index 13d933db8c..3564faade0 100644 --- a/fyrox/Cargo.toml +++ b/fyrox/Cargo.toml @@ -18,6 +18,8 @@ rust-version = "1.87" default = ["fyrox-impl"] dylib = ["fyrox-dylib"] mesh_analysis = ["fyrox-impl/mesh_analysis", "fyrox-dylib/mesh_analysis"] +backend_opengl = ["fyrox-impl/backend_opengl"] +backend_wgpu = ["fyrox-impl/backend_wgpu"] [dependencies] fyrox-impl = { version = "2.0.0-rc.1", path = "../fyrox-impl", optional = true } From 9888a53d171214232f4fcb5679ad348d608fa6c6 Mon Sep 17 00:00:00 2001 From: nicehack Date: Tue, 21 Jul 2026 16:40:14 +0500 Subject: [PATCH 17/34] GL fyrox-impl shaders restored --- fyrox-impl/src/renderer/resources.rs | 121 +++----- .../shaders/opengl/ambient_light.shader | 159 ++++++++++ .../src/renderer/shaders/opengl/blit.shader | 69 +++++ .../src/renderer/shaders/opengl/bloom.shader | 75 +++++ .../src/renderer/shaders/opengl/blur.shader | 80 +++++ .../src/renderer/shaders/opengl/debug.shader | 64 ++++ .../src/renderer/shaders/opengl/decal.shader | 141 +++++++++ .../opengl/deferred_directional_light.shader | 155 ++++++++++ .../opengl/deferred_point_light.shader | 138 +++++++++ .../shaders/opengl/deferred_spot_light.shader | 158 ++++++++++ .../src/renderer/shaders/opengl/fxaa.shader | 276 ++++++++++++++++++ .../shaders/opengl/gaussian_blur.shader | 93 ++++++ .../shaders/opengl/hdr_adaptation.shader | 74 +++++ .../shaders/opengl/hdr_downscale.shader | 94 ++++++ .../shaders/opengl/hdr_luminance.shader | 75 +++++ .../renderer/shaders/opengl/hdr_map.shader | 128 ++++++++ .../renderer/shaders/opengl/irradiance.shader | 96 ++++++ .../shaders/opengl/pixel_counter.shader | 62 ++++ .../shaders/opengl/point_volumetric.shader | 111 +++++++ .../renderer/shaders/opengl/prefilter.shader | 128 ++++++++ .../src/renderer/shaders/opengl/skybox.shader | 68 +++++ .../shaders/opengl/spot_volumetric.shader | 135 +++++++++ .../src/renderer/shaders/opengl/ssao.shader | 112 +++++++ .../renderer/shaders/opengl/visibility.shader | 110 +++++++ .../opengl/visibility_optimizer.shader | 80 +++++ .../shaders/opengl/volume_marker_lit.shader | 39 +++ .../shaders/opengl/volume_marker_vol.shader | 62 ++++ .../shaders/{ => wgpu}/ambient_light.shader | 0 .../renderer/shaders/{ => wgpu}/blit.shader | 0 .../renderer/shaders/{ => wgpu}/bloom.shader | 0 .../renderer/shaders/{ => wgpu}/blur.shader | 0 .../renderer/shaders/{ => wgpu}/debug.shader | 0 .../renderer/shaders/{ => wgpu}/decal.shader | 0 .../deferred_directional_light.shader | 0 .../{ => wgpu}/deferred_point_light.shader | 0 .../{ => wgpu}/deferred_spot_light.shader | 0 .../renderer/shaders/{ => wgpu}/fxaa.shader | 0 .../shaders/{ => wgpu}/gaussian_blur.shader | 0 .../shaders/{ => wgpu}/hdr_adaptation.shader | 0 .../shaders/{ => wgpu}/hdr_downscale.shader | 0 .../shaders/{ => wgpu}/hdr_luminance.shader | 0 .../shaders/{ => wgpu}/hdr_map.shader | 0 .../shaders/{ => wgpu}/irradiance.shader | 0 .../shaders/{ => wgpu}/pixel_counter.shader | 0 .../{ => wgpu}/point_volumetric.shader | 0 .../shaders/{ => wgpu}/prefilter.shader | 0 .../renderer/shaders/{ => wgpu}/skybox.shader | 0 .../shaders/{ => wgpu}/spot_volumetric.shader | 0 .../renderer/shaders/{ => wgpu}/ssao.shader | 0 .../shaders/{ => wgpu}/visibility.shader | 0 .../{ => wgpu}/visibility_optimizer.shader | 0 .../{ => wgpu}/volume_marker_lit.shader | 0 .../{ => wgpu}/volume_marker_vol.shader | 0 53 files changed, 2824 insertions(+), 79 deletions(-) create mode 100644 fyrox-impl/src/renderer/shaders/opengl/ambient_light.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/blit.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/bloom.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/blur.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/debug.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/decal.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/deferred_directional_light.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/deferred_point_light.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/deferred_spot_light.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/fxaa.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/gaussian_blur.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/hdr_adaptation.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/hdr_downscale.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/hdr_luminance.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/hdr_map.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/irradiance.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/pixel_counter.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/point_volumetric.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/prefilter.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/skybox.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/spot_volumetric.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/ssao.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/visibility.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/visibility_optimizer.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/volume_marker_lit.shader create mode 100644 fyrox-impl/src/renderer/shaders/opengl/volume_marker_vol.shader rename fyrox-impl/src/renderer/shaders/{ => wgpu}/ambient_light.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/blit.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/bloom.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/blur.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/debug.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/decal.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/deferred_directional_light.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/deferred_point_light.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/deferred_spot_light.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/fxaa.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/gaussian_blur.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/hdr_adaptation.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/hdr_downscale.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/hdr_luminance.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/hdr_map.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/irradiance.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/pixel_counter.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/point_volumetric.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/prefilter.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/skybox.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/spot_volumetric.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/ssao.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/visibility.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/visibility_optimizer.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/volume_marker_lit.shader (100%) rename fyrox-impl/src/renderer/shaders/{ => wgpu}/volume_marker_vol.shader (100%) diff --git a/fyrox-impl/src/renderer/resources.rs b/fyrox-impl/src/renderer/resources.rs index 3a830b2049..87de6c73ba 100644 --- a/fyrox-impl/src/renderer/resources.rs +++ b/fyrox-impl/src/renderer/resources.rs @@ -98,79 +98,48 @@ pub struct ShadersContainer { pub environment_map_irradiance_convolution: RenderPassContainer, } +#[cfg(feature = "backend_opengl")] +macro_rules! include_shader { + ($file:literal) => { + include_str!(concat!("shaders/opengl/", $file)) + }; +} + +#[cfg(feature = "backend_wgpu")] +macro_rules! include_shader { + ($file:literal) => { + include_str!(concat!("shaders/wgpu/", $file)) + }; +} + impl ShadersContainer { - /// Creates a new shaders container. + /// Creates a new shaders' container. pub fn new(server: &dyn GraphicsServer) -> Result { Ok(Self { - decal: RenderPassContainer::from_str(server, include_str!("shaders/decal.shader"))?, - spot_light: RenderPassContainer::from_str( - server, - include_str!("shaders/deferred_spot_light.shader"), - )?, - point_light: RenderPassContainer::from_str( - server, - include_str!("shaders/deferred_point_light.shader"), - )?, - directional_light: RenderPassContainer::from_str( - server, - include_str!("shaders/deferred_directional_light.shader"), - )?, - ambient_light: RenderPassContainer::from_str( - server, - include_str!("shaders/ambient_light.shader"), - )?, - volume_marker_lit: RenderPassContainer::from_str( - server, - include_str!("shaders/volume_marker_lit.shader"), - )?, - pixel_counter: RenderPassContainer::from_str( - server, - include_str!("shaders/pixel_counter.shader"), - )?, - debug: RenderPassContainer::from_str(server, include_str!("shaders/debug.shader"))?, - fxaa: RenderPassContainer::from_str(server, include_str!("shaders/fxaa.shader"))?, - spot_light_volume: RenderPassContainer::from_str( - server, - include_str!("shaders/spot_volumetric.shader"), - )?, - point_light_volume: RenderPassContainer::from_str( - server, - include_str!("shaders/point_volumetric.shader"), - )?, - volume_marker_vol: RenderPassContainer::from_str( - server, - include_str!("shaders/volume_marker_vol.shader"), - )?, - visibility_optimizer: RenderPassContainer::from_str( - server, - include_str!("shaders/visibility_optimizer.shader"), - )?, - ssao: RenderPassContainer::from_str(server, include_str!("shaders/ssao.shader"))?, - visibility: RenderPassContainer::from_str( - server, - include_str!("shaders/visibility.shader"), - )?, - blit: RenderPassContainer::from_str(server, include_str!("shaders/blit.shader"))?, - hdr_adaptation: RenderPassContainer::from_str( - server, - include_str!("shaders/hdr_adaptation.shader"), - )?, - hdr_luminance: RenderPassContainer::from_str( - server, - include_str!("shaders/hdr_luminance.shader"), - )?, - hdr_downscale: RenderPassContainer::from_str( - server, - include_str!("shaders/hdr_downscale.shader"), - )?, - hdr_map: RenderPassContainer::from_str(server, include_str!("shaders/hdr_map.shader"))?, - bloom: RenderPassContainer::from_str(server, include_str!("shaders/bloom.shader"))?, - skybox: RenderPassContainer::from_str(server, include_str!("shaders/skybox.shader"))?, - gaussian_blur: RenderPassContainer::from_str( - server, - include_str!("shaders/gaussian_blur.shader"), - )?, - box_blur: RenderPassContainer::from_str(server, include_str!("shaders/blur.shader"))?, + decal: RenderPassContainer::from_str(server, include_shader!("decal.shader"))?, + spot_light: RenderPassContainer::from_str(server, include_shader!("deferred_spot_light.shader"))?, + point_light: RenderPassContainer::from_str(server, include_shader!("deferred_point_light.shader"))?, + directional_light: RenderPassContainer::from_str(server, include_shader!("deferred_directional_light.shader"))?, + ambient_light: RenderPassContainer::from_str(server, include_shader!("ambient_light.shader"))?, + volume_marker_lit: RenderPassContainer::from_str(server, include_shader!("volume_marker_lit.shader"))?, + pixel_counter: RenderPassContainer::from_str(server, include_shader!("pixel_counter.shader"))?, + debug: RenderPassContainer::from_str(server, include_shader!("debug.shader"))?, + fxaa: RenderPassContainer::from_str(server, include_shader!("fxaa.shader"))?, + spot_light_volume: RenderPassContainer::from_str(server, include_shader!("spot_volumetric.shader"))?, + point_light_volume: RenderPassContainer::from_str(server, include_shader!("point_volumetric.shader"))?, + volume_marker_vol: RenderPassContainer::from_str(server, include_shader!("volume_marker_vol.shader"))?, + visibility_optimizer: RenderPassContainer::from_str(server, include_shader!("visibility_optimizer.shader"))?, + ssao: RenderPassContainer::from_str(server, include_shader!("ssao.shader"))?, + visibility: RenderPassContainer::from_str(server, include_shader!("visibility.shader"))?, + blit: RenderPassContainer::from_str(server, include_shader!("blit.shader"))?, + hdr_adaptation: RenderPassContainer::from_str(server, include_shader!("hdr_adaptation.shader"))?, + hdr_luminance: RenderPassContainer::from_str(server, include_shader!("hdr_luminance.shader"))?, + hdr_downscale: RenderPassContainer::from_str(server, include_shader!("hdr_downscale.shader"))?, + hdr_map: RenderPassContainer::from_str(server, include_shader!("hdr_map.shader"))?, + bloom: RenderPassContainer::from_str(server, include_shader!("bloom.shader"))?, + skybox: RenderPassContainer::from_str(server, include_shader!("skybox.shader"))?, + gaussian_blur: RenderPassContainer::from_str(server, include_shader!("gaussian_blur.shader"))?, + box_blur: RenderPassContainer::from_str(server, include_shader!("blur.shader"))?, ui: RenderPassContainer::from_str( server, str::from_utf8( @@ -181,16 +150,10 @@ impl ShadersContainer { .bytes .as_ref(), ) - .unwrap(), - )?, - environment_map_specular_convolution: RenderPassContainer::from_str( - server, - include_str!("shaders/prefilter.shader"), - )?, - environment_map_irradiance_convolution: RenderPassContainer::from_str( - server, - include_str!("shaders/irradiance.shader"), + .unwrap(), )?, + environment_map_specular_convolution: RenderPassContainer::from_str(server, include_shader!("prefilter.shader"))?, + environment_map_irradiance_convolution: RenderPassContainer::from_str(server, include_shader!("irradiance.shader"))?, }) } } diff --git a/fyrox-impl/src/renderer/shaders/opengl/ambient_light.shader b/fyrox-impl/src/renderer/shaders/opengl/ambient_light.shader new file mode 100644 index 0000000000..6177cf25c5 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/ambient_light.shader @@ -0,0 +1,159 @@ +( + name: "AmbientLight", + resources: [ + ( + name: "diffuseTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "aoSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 1 + ), + ( + name: "bakedLightingTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 2 + ), + ( + name: "depthTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 3 + ), + ( + name: "normalTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 4 + ), + ( + name: "materialTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 5 + ), + ( + name: "prefilteredSpecularMap", + kind: Texture(kind: SamplerCube, fallback: White), + binding: 6 + ), + ( + name: "irradianceMap", + kind: Texture(kind: SamplerCube, fallback: White), + binding: 7 + ), + ( + name: "brdfLUT", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 8 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "ambientColor", kind: Vector4()), + (name: "cameraPosition", kind: Vector3()), + (name: "invViewProj", kind: Matrix4()), + (name: "skyboxLighting", kind: Bool()), + (name: "environmentLightingBrightness", kind: Float()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + in vec2 texCoord; + + void main() + { + float depth = texture(depthTexture, texCoord).r; + vec3 fragmentPosition = S_UnProject(vec3(texCoord, depth), properties.invViewProj); + + vec4 albedo = S_SRGBToLinear(texture(diffuseTexture, texCoord)); + + vec3 fragmentNormal = normalize(texture(normalTexture, texCoord).xyz * 2.0 - 1.0); + + vec3 material = texture(materialTexture, texCoord).rgb; + float metallic = material.x; + float roughness = material.y; + float materialAo = material.z; + + vec3 viewVector = normalize(properties.cameraPosition - fragmentPosition); + vec3 reflectionVector = -reflect(viewVector, fragmentNormal); + + float clampedCosViewAngle = max(dot(fragmentNormal, viewVector), 0.0); + + ivec2 cubeMapSize = textureSize(prefilteredSpecularMap, 0); + float mip = roughness * (floor(log2(float(cubeMapSize.x))) + 1.0); + vec3 reflection = properties.skyboxLighting ? S_SRGBToLinear(textureLod(prefilteredSpecularMap, reflectionVector, mip)).rgb : properties.ambientColor.rgb; + + vec3 F0 = mix(vec3(0.04), albedo.rgb, metallic); + vec3 F = S_FresnelSchlickRoughness(clampedCosViewAngle, F0, roughness); + vec3 kD = (vec3(1.0) - F) * (1.0 - metallic); + + vec2 envBRDF = texture(brdfLUT, vec2(clampedCosViewAngle, roughness)).rg; + vec3 specular = reflection * (F * envBRDF.x + envBRDF.y); + + float ambientOcclusion = texture(aoSampler, texCoord).r * materialAo; + vec4 bakedLighting = texture(bakedLightingTexture, texCoord); + + vec3 irradiance = S_SRGBToLinear(texture(irradianceMap, fragmentNormal)).rgb; + vec3 diffuse = (bakedLighting.rgb + properties.environmentLightingBrightness * (properties.skyboxLighting ? irradiance : properties.ambientColor.rgb)) * albedo.rgb; + + FragColor.rgb = (kD * diffuse + specular) * ambientOcclusion; + FragColor.a = bakedLighting.a; + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/blit.shader b/fyrox-impl/src/renderer/shaders/opengl/blit.shader new file mode 100644 index 0000000000..98a60fcd4d --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/blit.shader @@ -0,0 +1,69 @@ +( + name: "Blit", + resources: [ + ( + name: "diffuseTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec2 texCoord; + + void main() + { + FragColor = texture(diffuseTexture, texCoord); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/bloom.shader b/fyrox-impl/src/renderer/shaders/opengl/bloom.shader new file mode 100644 index 0000000000..3c9ef200f4 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/bloom.shader @@ -0,0 +1,75 @@ +( + name: "Bloom", + resources: [ + ( + name: "hdrSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "threshold", kind: Float(value: 1.01)), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + + out vec4 outBrightColor; + + void main() { + vec3 hdrPixel = texture(hdrSampler, texCoord).rgb; + + if (S_Luminance(hdrPixel) > properties.threshold) { + outBrightColor = vec4(hdrPixel, 0.0); + } else { + outBrightColor = vec4(0.0); + } + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/blur.shader b/fyrox-impl/src/renderer/shaders/opengl/blur.shader new file mode 100644 index 0000000000..8018ef25da --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/blur.shader @@ -0,0 +1,80 @@ +( + name: "Blur", + resources: [ + ( + name: "inputTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + // Simple 4x4 box blur. + out float FragColor; + + in vec2 texCoord; + + void main() + { + vec2 texelSize = 1.0 / vec2(textureSize(inputTexture, 0)); + float result = 0.0; + for (int y = -2; y < 2; ++y) + { + for (int x = -2; x < 2; ++x) + { + vec2 offset = vec2(float(x), float(y)) * texelSize; + result += texture(inputTexture, texCoord + offset).r; + } + } + FragColor = result / 16.0; + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/debug.shader b/fyrox-impl/src/renderer/shaders/opengl/debug.shader new file mode 100644 index 0000000000..dbe9791364 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/debug.shader @@ -0,0 +1,64 @@ +( + name: "Debug", + resources: [ + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: Some(LessOrEqual), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec4 vertexColor; + + out vec4 color; + + void main() + { + color = vertexColor; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec4 color; + + void main() + { + FragColor = color; + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/decal.shader b/fyrox-impl/src/renderer/shaders/opengl/decal.shader new file mode 100644 index 0000000000..12c3c560ab --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/decal.shader @@ -0,0 +1,141 @@ +( + name: "Decal", + resources: [ + ( + name: "sceneDepth", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "diffuseTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 1 + ), + ( + name: "normalTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 2 + ), + ( + name: "decalMask", + kind: Texture(kind: USampler2D, fallback: White), + binding: 3 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "invViewProj", kind: Matrix4()), + (name: "invWorldDecal", kind: Matrix4()), + (name: "resolution", kind: Vector2()), + (name: "color", kind: Vector4()), + (name: "layerIndex", kind: UInt()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + + out vec4 clipSpacePosition; + + void main() + { + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + clipSpacePosition = gl_Position; + } + "#, + + fragment_shader: + r#" + layout (location = 0) out vec4 outDiffuseMap; + layout (location = 1) out vec4 outNormalMap; + + in vec4 clipSpacePosition; + + void main() + { + vec2 screenPos = clipSpacePosition.xy / clipSpacePosition.w; + + vec2 texCoord = vec2( + (1.0 + screenPos.x) / 2.0 + (0.5 / properties.resolution.x), + (1.0 + screenPos.y) / 2.0 + (0.5 / properties.resolution.y) + ); + + uvec4 maskIndex = texture(decalMask, texCoord); + + // Masking. + if (maskIndex.r != properties.layerIndex) { + discard; + } + + float sceneDepth = texture(sceneDepth, texCoord).r; + + vec3 sceneWorldPosition = S_UnProject(vec3(texCoord, sceneDepth), properties.invViewProj); + + vec3 decalSpacePosition = (properties.invWorldDecal * vec4(sceneWorldPosition, 1.0)).xyz; + + // Check if scene pixel is not inside decal bounds. + vec3 dpos = vec3(0.5) - abs(decalSpacePosition.xyz); + if (dpos.x < 0.0 || dpos.y < 0.0 || dpos.z < 0.0) { + discard; + } + + vec2 decalTexCoord = decalSpacePosition.xz + 0.5; + + outDiffuseMap = properties.color * texture(diffuseTexture, decalTexCoord); + + vec3 fragmentTangent = dFdx(sceneWorldPosition); + vec3 fragmentBinormal = dFdy(sceneWorldPosition); + vec3 fragmentNormal = cross(fragmentTangent, fragmentBinormal); + + mat3 tangentToWorld; + tangentToWorld[0] = normalize(fragmentTangent); // Tangent + tangentToWorld[1] = normalize(fragmentBinormal); // Binormal + tangentToWorld[2] = normalize(fragmentNormal); // Normal + + vec3 rawNormal = (texture(normalTexture, decalTexCoord) * 2.0 - 1.0).xyz; + vec3 worldSpaceNormal = tangentToWorld * rawNormal; + outNormalMap = vec4(worldSpaceNormal * 0.5 + 0.5, outDiffuseMap.a); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/deferred_directional_light.shader b/fyrox-impl/src/renderer/shaders/opengl/deferred_directional_light.shader new file mode 100644 index 0000000000..3fd9e63a1b --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/deferred_directional_light.shader @@ -0,0 +1,155 @@ +( + name: "DeferredDirectionalLight", + resources: [ + ( + name: "depthTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "colorTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 1 + ), + ( + name: "normalTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 2 + ), + ( + name: "materialTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 3 + ), + ( + name: "shadowCascade0", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 4 + ), + ( + name: "shadowCascade1", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 5 + ), + ( + name: "shadowCascade2", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 6 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "viewMatrix", kind: Matrix4()), + (name: "invViewProj", kind: Matrix4()), + (name: "lightViewProjMatrices", kind: Matrix4Array(max_len: 3, value: [])), + (name: "lightColor", kind: Vector4()), + (name: "lightDirection", kind: Vector3()), + (name: "cameraPosition", kind: Vector3()), + (name: "lightIntensity", kind: Float()), + (name: "shadowsEnabled", kind: Bool()), + (name: "shadowBias", kind: Float()), + (name: "softShadows", kind: Bool()), + (name: "cascadeDistances", kind: FloatArray(max_len: 3, value: [])), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: One, + dfactor: One, + alpha_sfactor: One, + alpha_dfactor: One, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + out vec4 FragColor; + + // Returns **inverted** shadow factor where 1 - fully bright, 0 - fully in shadow. + float CsmGetShadow(in sampler2D sampler, in vec3 fragmentPosition, in mat4 lightViewProjMatrix) + { + float invSize = 1.0 / float(textureSize(sampler, 0).x); + return S_SpotShadowFactor(properties.shadowsEnabled, properties.softShadows, + properties.shadowBias, fragmentPosition, lightViewProjMatrix, invSize, sampler); + } + + void main() + { + vec3 material = texture(materialTexture, texCoord).rgb; + + vec3 fragmentPosition = S_UnProject(vec3(texCoord, texture(depthTexture, texCoord).r), properties.invViewProj); + vec4 diffuseColor = texture(colorTexture, texCoord); + + TPBRContext ctx; + ctx.albedo = S_SRGBToLinear(diffuseColor).rgb; + ctx.fragmentToLight = properties.lightDirection; + ctx.fragmentNormal = normalize(texture(normalTexture, texCoord).xyz * 2.0 - 1.0); + ctx.lightColor = properties.lightColor.rgb; + ctx.metallic = material.x; + ctx.roughness = material.y; + ctx.viewVector = normalize(properties.cameraPosition - fragmentPosition); + + vec3 lighting = S_PBR_CalculateLight(ctx); + + float fragmentZViewSpace = abs((properties.viewMatrix * vec4(fragmentPosition, 1.0)).z); + + float shadow = 1.0; + if (fragmentZViewSpace <= properties.cascadeDistances[0]) { + shadow = CsmGetShadow(shadowCascade0, fragmentPosition, properties.lightViewProjMatrices[0]); + } else if (fragmentZViewSpace <= properties.cascadeDistances[1]) { + shadow = CsmGetShadow(shadowCascade1, fragmentPosition, properties.lightViewProjMatrices[1]); + } else if (fragmentZViewSpace <= properties.cascadeDistances[2]) { + shadow = CsmGetShadow(shadowCascade2, fragmentPosition, properties.lightViewProjMatrices[2]); + } + + FragColor = shadow * vec4(properties.lightIntensity * lighting, diffuseColor.a); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/deferred_point_light.shader b/fyrox-impl/src/renderer/shaders/opengl/deferred_point_light.shader new file mode 100644 index 0000000000..c67a3c80fe --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/deferred_point_light.shader @@ -0,0 +1,138 @@ +( + name: "DeferredPointLight", + resources: [ + ( + name: "depthTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "colorTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 1 + ), + ( + name: "normalTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 2 + ), + ( + name: "materialTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 3 + ), + ( + name: "pointShadowTexture", + kind: Texture(kind: SamplerCube, fallback: White), + binding: 4 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "invViewProj", kind: Matrix4()), + (name: "lightColor", kind: Vector4()), + (name: "lightPos", kind: Vector3()), + (name: "cameraPosition", kind: Vector3()), + (name: "lightRadius", kind: Float()), + (name: "shadowBias", kind: Float()), + (name: "lightIntensity", kind: Float()), + (name: "shadowAlpha", kind: Float()), + (name: "softShadows", kind: Bool()), + (name: "shadowsEnabled", kind: Bool()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: Some(StencilFunc( + func: NotEqual, + ref_value: 0, + mask: 0xFFFF_FFFF + )), + depth_test: None, + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: One, + dfactor: One, + alpha_sfactor: One, + alpha_dfactor: One, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Zero, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + out vec4 FragColor; + + void main() + { + vec3 material = texture(materialTexture, texCoord).rgb; + + vec3 fragmentPosition = S_UnProject(vec3(texCoord, texture(depthTexture, texCoord).r), properties.invViewProj); + vec3 fragmentToLight = properties.lightPos - fragmentPosition; + float distance = length(fragmentToLight); + + vec4 diffuseColor = texture(colorTexture, texCoord); + + TPBRContext ctx; + ctx.albedo = S_SRGBToLinear(diffuseColor).rgb; + ctx.fragmentToLight = fragmentToLight / distance; + ctx.fragmentNormal = normalize(texture(normalTexture, texCoord).xyz * 2.0 - 1.0); + ctx.lightColor = properties.lightColor.rgb; + ctx.metallic = material.x; + ctx.roughness = material.y; + ctx.viewVector = normalize(properties.cameraPosition - fragmentPosition); + + vec3 lighting = S_PBR_CalculateLight(ctx); + + float distanceAttenuation = S_LightDistanceAttenuation(distance, properties.lightRadius); + + float shadow = S_PointShadow( + properties.shadowsEnabled, properties.softShadows, distance, properties.shadowBias, ctx.fragmentToLight, pointShadowTexture); + float finalShadow = mix(1.0, shadow, properties.shadowAlpha); + + FragColor = vec4(properties.lightIntensity * distanceAttenuation * finalShadow * lighting, diffuseColor.a); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/deferred_spot_light.shader b/fyrox-impl/src/renderer/shaders/opengl/deferred_spot_light.shader new file mode 100644 index 0000000000..9fc386e468 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/deferred_spot_light.shader @@ -0,0 +1,158 @@ +( + name: "DeferredSpotLight", + resources: [ + ( + name: "depthTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "colorTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 1 + ), + ( + name: "normalTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 2 + ), + ( + name: "materialTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 3 + ), + ( + name: "spotShadowTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 4 + ), + ( + name: "cookieTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 5 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "lightViewProjMatrix", kind: Matrix4()), + (name: "invViewProj", kind: Matrix4()), + (name: "lightPos", kind: Vector3()), + (name: "lightColor", kind: Vector4()), + (name: "cameraPosition", kind: Vector3()), + (name: "lightDirection", kind: Vector3()), + (name: "lightRadius", kind: Float()), + (name: "halfHotspotConeAngleCos", kind: Float()), + (name: "halfConeAngleCos", kind: Float()), + (name: "shadowMapInvSize", kind: Float()), + (name: "shadowBias", kind: Float()), + (name: "lightIntensity", kind: Float()), + (name: "shadowAlpha", kind: Float()), + (name: "cookieEnabled", kind: Bool()), + (name: "shadowsEnabled", kind: Bool()), + (name: "softShadows", kind: Bool()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: Some(StencilFunc( + func: NotEqual, + ref_value: 0, + mask: 0xFFFF_FFFF + )), + depth_test: None, + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: One, + dfactor: One, + alpha_sfactor: One, + alpha_dfactor: One, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Zero, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + out vec4 FragColor; + + void main() + { + vec3 material = texture(materialTexture, texCoord).rgb; + + vec3 fragmentPosition = S_UnProject(vec3(texCoord, texture(depthTexture, texCoord).r), properties.invViewProj); + vec3 fragmentToLight = properties.lightPos - fragmentPosition; + float distance = length(fragmentToLight); + vec4 diffuseColor = texture(colorTexture, texCoord); + + TPBRContext ctx; + ctx.albedo = S_SRGBToLinear(diffuseColor).rgb; + ctx.fragmentToLight = fragmentToLight / distance; + ctx.fragmentNormal = normalize(texture(normalTexture, texCoord).xyz * 2.0 - 1.0); + ctx.lightColor = properties.lightColor.rgb; + ctx.metallic = material.x; + ctx.roughness = material.y; + ctx.viewVector = normalize(properties.cameraPosition - fragmentPosition); + + vec3 lighting = S_PBR_CalculateLight(ctx); + + float distanceAttenuation = S_LightDistanceAttenuation(distance, properties.lightRadius); + + float spotAngleCos = dot(properties.lightDirection, ctx.fragmentToLight); + float coneFactor = smoothstep(properties.halfConeAngleCos, properties.halfHotspotConeAngleCos, spotAngleCos); + + float shadow = S_SpotShadowFactor( + properties.shadowsEnabled, properties.softShadows, properties.shadowBias, fragmentPosition, + properties.lightViewProjMatrix, properties.shadowMapInvSize, spotShadowTexture); + float finalShadow = mix(1.0, shadow, properties.shadowAlpha); + + vec4 cookieAttenuation = vec4(1.0); + if (properties.cookieEnabled) { + vec2 texCoords = S_Project(fragmentPosition, properties.lightViewProjMatrix).xy; + cookieAttenuation = texture(cookieTexture, texCoords); + } + + FragColor = cookieAttenuation * vec4(distanceAttenuation * properties.lightIntensity * coneFactor * finalShadow * lighting, diffuseColor.a); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/fxaa.shader b/fyrox-impl/src/renderer/shaders/opengl/fxaa.shader new file mode 100644 index 0000000000..c9492fe5c5 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/fxaa.shader @@ -0,0 +1,276 @@ +( + name: "FXAA", + resources: [ + ( + name: "screenTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "inverseScreenSize", kind: Vector2()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + // NVIDIA FXAA 3.11 + // Original source code by TIMOTHY LOTTES + // + // Cleaned version - https://github.com/kosua20/Rendu/blob/master/resources/common/shaders/screens/fxaa.frag + // + // Final tweaks by mrDIMAS + + in vec2 texCoord; + out vec4 fragColor; + + // Settings for FXAA. + #define EDGE_THRESHOLD_MIN 0.0312 + #define EDGE_THRESHOLD_MAX 0.125 + #define QUALITY(q) ((q) < 5 ? 1.0: ((q) > 5 ? ((q) < 10 ? 2.0: ((q) < 11 ? 4.0: 8.0)): 1.5)) + #define ITERATIONS 12 + #define SUBPIXEL_QUALITY 0.75 + + float rgb2luma(vec3 rgb) { + return sqrt(dot(rgb, vec3(0.299, 0.587, 0.114))); + } + + // Performs FXAA post-process anti-aliasing as described in the Nvidia FXAA white paper and the associated shader code. + void main() + { + vec4 colorCenter = texture(screenTexture, texCoord); + + // Luma at the current fragment + float lumaCenter = rgb2luma(colorCenter.rgb); + + // Luma at the four direct neighbours of the current fragment. + float lumaDown = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(0, -1)).rgb); + float lumaUp = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(0, 1)).rgb); + float lumaLeft = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(-1, 0)).rgb); + float lumaRight = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(1, 0)).rgb); + + // Find the maximum and minimum luma around the current fragment. + float lumaMin = min(lumaCenter, min(min(lumaDown, lumaUp), min(lumaLeft, lumaRight))); + float lumaMax = max(lumaCenter, max(max(lumaDown, lumaUp), max(lumaLeft, lumaRight))); + + // Compute the delta. + float lumaRange = lumaMax - lumaMin; + + // If the luma variation is lower that a threshold (or if we are in a really dark area), we are not on an edge, don't perform any AA. + if (lumaRange < max(EDGE_THRESHOLD_MIN, lumaMax * EDGE_THRESHOLD_MAX)) { + fragColor = colorCenter; + return; + } + + // Query the 4 remaining corners lumas. + float lumaDownLeft = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(-1, -1)).rgb); + float lumaUpRight = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(1, 1)).rgb); + float lumaUpLeft = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(-1, 1)).rgb); + float lumaDownRight = rgb2luma(textureLodOffset(screenTexture, texCoord, 0.0, ivec2(1, -1)).rgb); + + // Combine the four edges lumas (using intermediary variables for future computations with the same values). + float lumaDownUp = lumaDown + lumaUp; + float lumaLeftRight = lumaLeft + lumaRight; + + // Same for corners + float lumaLeftCorners = lumaDownLeft + lumaUpLeft; + float lumaDownCorners = lumaDownLeft + lumaDownRight; + float lumaRightCorners = lumaDownRight + lumaUpRight; + float lumaUpCorners = lumaUpRight + lumaUpLeft; + + // Compute an estimation of the gradient along the horizontal and vertical axis. + float edgeHorizontal = abs(-2.0 * lumaLeft + lumaLeftCorners) + abs(-2.0 * lumaCenter + lumaDownUp) * 2.0 + abs(-2.0 * lumaRight + lumaRightCorners); + float edgeVertical = abs(-2.0 * lumaUp + lumaUpCorners) + abs(-2.0 * lumaCenter + lumaLeftRight) * 2.0 + abs(-2.0 * lumaDown + lumaDownCorners); + + // Is the local edge horizontal or vertical ? + bool isHorizontal = (edgeHorizontal >= edgeVertical); + + // Choose the step size (one pixel) accordingly. + float stepLength = isHorizontal ? properties.inverseScreenSize.y : properties.inverseScreenSize.x; + + // Select the two neighboring texels lumas in the opposite direction to the local edge. + float luma1 = isHorizontal ? lumaDown : lumaLeft; + float luma2 = isHorizontal ? lumaUp : lumaRight; + // Compute gradients in this direction. + float gradient1 = luma1 - lumaCenter; + float gradient2 = luma2 - lumaCenter; + + // Which direction is the steepest ? + bool is1Steepest = abs(gradient1) >= abs(gradient2); + + // Gradient in the corresponding direction, normalized. + float gradientScaled = 0.25 * max(abs(gradient1), abs(gradient2)); + + // Average luma in the correct direction. + float lumaLocalAverage = 0.0; + if (is1Steepest) { + // Switch the direction + stepLength = -stepLength; + lumaLocalAverage = 0.5 * (luma1 + lumaCenter); + } else { + lumaLocalAverage = 0.5 * (luma2 + lumaCenter); + } + + // Shift UV in the correct direction by half a pixel. + vec2 currentUv = texCoord; + if (isHorizontal) { + currentUv.y += stepLength * 0.5; + } else { + currentUv.x += stepLength * 0.5; + } + + // Compute offset (for each iteration step) in the right direction. + vec2 offset = isHorizontal ? vec2(properties.inverseScreenSize.x, 0.0) : vec2(0.0, properties.inverseScreenSize.y); + // Compute UVs to explore on each side of the edge, orthogonally. The QUALITY allows us to step faster. + vec2 uv1 = currentUv - offset * QUALITY(0); + vec2 uv2 = currentUv + offset * QUALITY(0); + + // Read the lumas at both current extremities of the exploration segment, and compute the delta wrt to the local average luma. + float lumaEnd1 = rgb2luma(textureLod(screenTexture, uv1, 0.0).rgb); + float lumaEnd2 = rgb2luma(textureLod(screenTexture, uv2, 0.0).rgb); + lumaEnd1 -= lumaLocalAverage; + lumaEnd2 -= lumaLocalAverage; + + // If the luma deltas at the current extremities is larger than the local gradient, we have reached the side of the edge. + bool reached1 = abs(lumaEnd1) >= gradientScaled; + bool reached2 = abs(lumaEnd2) >= gradientScaled; + bool reachedBoth = reached1 && reached2; + + // If the side is not reached, we continue to explore in this direction. + if (!reached1) { + uv1 -= offset * QUALITY(1); + } + if (!reached2) { + uv2 += offset * QUALITY(1); + } + + // If both sides have not been reached, continue to explore. + if (!reachedBoth) + { + for (int i = 2; i < ITERATIONS; i++) + { + // If needed, read luma in 1st direction, compute delta. + if (!reached1) { + lumaEnd1 = rgb2luma(textureLod(screenTexture, uv1, 0.0).rgb); + lumaEnd1 = lumaEnd1 - lumaLocalAverage; + } + // If needed, read luma in opposite direction, compute delta. + if (!reached2) { + lumaEnd2 = rgb2luma(textureLod(screenTexture, uv2, 0.0).rgb); + lumaEnd2 = lumaEnd2 - lumaLocalAverage; + } + // If the luma deltas at the current extremities is larger than the local gradient, we have reached the side of the edge. + reached1 = abs(lumaEnd1) >= gradientScaled; + reached2 = abs(lumaEnd2) >= gradientScaled; + reachedBoth = reached1 && reached2; + + // If the side is not reached, we continue to explore in this direction, with a variable quality. + if (!reached1) { + uv1 -= offset * QUALITY(i); + } + if (!reached2) { + uv2 += offset * QUALITY(i); + } + + // If both sides have been reached, stop the exploration. + if (reachedBoth) { break; } + } + } + + // Compute the distances to each side edge of the edge (!). + float distance1 = isHorizontal ? (texCoord.x - uv1.x) : (texCoord.y - uv1.y); + float distance2 = isHorizontal ? (uv2.x - texCoord.x) : (uv2.y - texCoord.y); + + // In which direction is the side of the edge closer ? + bool isDirection1 = distance1 < distance2; + float distanceFinal = min(distance1, distance2); + + // Thickness of the edge. + float edgeThickness = (distance1 + distance2); + + // Is the luma at center smaller than the local average ? + bool isLumaCenterSmaller = lumaCenter < lumaLocalAverage; + + // If the luma at center is smaller than at its neighbour, the delta luma at each end should be positive (same variation). + bool correctVariation1 = (lumaEnd1 < 0.0) != isLumaCenterSmaller; + bool correctVariation2 = (lumaEnd2 < 0.0) != isLumaCenterSmaller; + + // Only keep the result in the direction of the closer side of the edge. + bool correctVariation = isDirection1 ? correctVariation1 : correctVariation2; + + // UV offset: read in the direction of the closest side of the edge. + float pixelOffset = -distanceFinal / edgeThickness + 0.5; + + // If the luma variation is incorrect, do not offset. + float finalOffset = correctVariation ? pixelOffset : 0.0; + + // Sub-pixel shifting + // Full weighted average of the luma over the 3x3 neighborhood. + float lumaAverage = (1.0 / 12.0) * (2.0 * (lumaDownUp + lumaLeftRight) + lumaLeftCorners + lumaRightCorners); + // Ratio of the delta between the global average and the center luma, over the luma range in the 3x3 neighborhood. + float subPixelOffset1 = clamp(abs(lumaAverage - lumaCenter) / lumaRange, 0.0, 1.0); + float subPixelOffset2 = (-2.0 * subPixelOffset1 + 3.0) * subPixelOffset1 * subPixelOffset1; + // Compute a sub-pixel offset based on this delta. + float subPixelOffsetFinal = subPixelOffset2 * subPixelOffset2 * SUBPIXEL_QUALITY; + + // Pick the biggest of the two offsets. + finalOffset = max(finalOffset, subPixelOffsetFinal); + + // Compute the final UV coordinates. + vec2 finalUv = texCoord; + if (isHorizontal) { + finalUv.y += finalOffset * stepLength; + } else { + finalUv.x += finalOffset * stepLength; + } + + // Read the color at the new UV coordinates, and use it. + vec3 finalColor = textureLod(screenTexture, finalUv, 0.0).rgb; + fragColor = vec4(finalColor, 1.0); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/gaussian_blur.shader b/fyrox-impl/src/renderer/shaders/opengl/gaussian_blur.shader new file mode 100644 index 0000000000..94abb25a53 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/gaussian_blur.shader @@ -0,0 +1,93 @@ +( + name: "GaussianBlur", + resources: [ + ( + name: "image", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "pixelSize", kind: Vector2()), + (name: "horizontal", kind: Bool()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + + out vec4 outColor; + + void main() + { + const float weights[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); + + vec4 center = texture(image, texCoord); + + vec3 result = center.rgb * weights[0]; + + if (properties.horizontal) { + for (int i = 1; i < 5; ++i) { + float fi = float(i); + + result += texture(image, texCoord + vec2(properties.pixelSize.x * fi, 0.0)).rgb * weights[i]; + result += texture(image, texCoord - vec2(properties.pixelSize.x * fi, 0.0)).rgb * weights[i]; + } + } else { + for (int i = 1; i < 5; ++i) { + float fi = float(i); + + result += texture(image, texCoord + vec2(0.0, properties.pixelSize.y * fi)).rgb * weights[i]; + result += texture(image, texCoord - vec2(0.0, properties.pixelSize.y * fi)).rgb * weights[i]; + } + } + + outColor = vec4(result, center.a); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/hdr_adaptation.shader b/fyrox-impl/src/renderer/shaders/opengl/hdr_adaptation.shader new file mode 100644 index 0000000000..3962732fe0 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/hdr_adaptation.shader @@ -0,0 +1,74 @@ +( + name: "HdrAdaptation", + resources: [ + ( + name: "oldLumSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "newLumSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 1 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "speed", kind: Float()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Zero, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out float outLum; + + void main() { + float oldLum = texture(oldLumSampler, vec2(0.5, 0.5)).r; + float newLum = texture(newLumSampler, vec2(0.5, 0.5)).r; + outLum = oldLum + (newLum - oldLum) * properties.speed; + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/hdr_downscale.shader b/fyrox-impl/src/renderer/shaders/opengl/hdr_downscale.shader new file mode 100644 index 0000000000..67b89ae4de --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/hdr_downscale.shader @@ -0,0 +1,94 @@ +( + name: "HdrDownscale", + resources: [ + ( + name: "lumSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "invSize", kind: Vector2()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + + out float outLum; + + void main() { + float x = properties.invSize.x; + float y = properties.invSize.y; + float twoX = 2.0 * x; + float twoY = 2.0 * y; + + float a = texture(lumSampler, vec2(texCoord.x - twoX, texCoord.y + twoY)).r; + float b = texture(lumSampler, vec2(texCoord.x, texCoord.y + twoY)).r; + float c = texture(lumSampler, vec2(texCoord.x + twoX, texCoord.y + twoY)).r; + + float d = texture(lumSampler, vec2(texCoord.x - twoX, texCoord.y)).r; + float e = texture(lumSampler, vec2(texCoord.x, texCoord.y)).r; + float f = texture(lumSampler, vec2(texCoord.x + twoX, texCoord.y)).r; + + float g = texture(lumSampler, vec2(texCoord.x - twoX, texCoord.y - twoY)).r; + float h = texture(lumSampler, vec2(texCoord.x, texCoord.y - twoY)).r; + float i = texture(lumSampler, vec2(texCoord.x + twoX, texCoord.y - twoY)).r; + + float j = texture(lumSampler, vec2(texCoord.x - x, texCoord.y + y)).r; + float k = texture(lumSampler, vec2(texCoord.x + x, texCoord.y + y)).r; + float l = texture(lumSampler, vec2(texCoord.x - x, texCoord.y - y)).r; + float m = texture(lumSampler, vec2(texCoord.x + x, texCoord.y - y)).r; + + outLum = e * 0.125; + outLum += (a + c + g + i) * 0.03125; + outLum += (b + d + f + h) * 0.0625; + outLum += (j + k + l + m) * 0.125; + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/hdr_luminance.shader b/fyrox-impl/src/renderer/shaders/opengl/hdr_luminance.shader new file mode 100644 index 0000000000..1dc7d4aae3 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/hdr_luminance.shader @@ -0,0 +1,75 @@ +( + name: "HdrLuminance", + resources: [ + ( + name: "frameSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "invSize", kind: Vector2()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + + out float outLum; + + void main() { + float totalLum = 0.0; + for (float y = -0.5; y < 0.5; y += 0.5) { + for (float x = -0.5; x < 0.5; x += 0.5) { + totalLum += S_Luminance(texture(frameSampler, texCoord - vec2(x, y) * properties.invSize).xyz); + } + } + outLum = totalLum / 9.0; + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/hdr_map.shader b/fyrox-impl/src/renderer/shaders/opengl/hdr_map.shader new file mode 100644 index 0000000000..deabbdda6e --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/hdr_map.shader @@ -0,0 +1,128 @@ +( + name: "HdrMap", + resources: [ + ( + name: "hdrSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "lumSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 1 + ), + ( + name: "bloomSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 2 + ), + ( + name: "colorMapSampler", + kind: Texture(kind: Sampler3D, fallback: White), + binding: 3 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "useColorGrading", kind: Bool()), + (name: "minLuminance", kind: Float()), + (name: "maxLuminance", kind: Float()), + (name: "autoExposure", kind: Bool()), + (name: "fixedExposure", kind: Float()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + + out vec4 outLdrColor; + + vec3 ColorGrading(vec3 color) { + const float lutSize = 16.0; + const float a = (lutSize - 1.0) / lutSize; + const float b = 1.0 / (2.0 * lutSize); + vec3 scale = vec3(a); + vec3 offset = vec3(b); + return texture(colorMapSampler, scale * color + offset).rgb; + } + + // Narkowicz 2015, "ACES Filmic Tone Mapping Curve" + float TonemapACES(float x) { + const float a = 2.51; + const float b = 0.03; + const float c = 2.43; + const float d = 0.59; + const float e = 0.14; + return (x * (a * x + b)) / (x * (c * x + d) + e); + } + + void main() { + vec4 hdrColor = texture(hdrSampler, texCoord) + texture(bloomSampler, texCoord); + + vec3 Yxy = S_ConvertRgbToYxy(hdrColor.rgb); + + float lp; + if (properties.autoExposure) { + float avgLum = texture(lumSampler, vec2(0.5, 0.5)).r; + float clampedAvgLum = clamp(avgLum, properties.minLuminance, properties.maxLuminance); + lp = Yxy.x / (9.6 * clampedAvgLum + 0.0001); + } else { + lp = Yxy.x * properties.fixedExposure; + } + + Yxy.x = TonemapACES(lp); + + vec4 ldrColor = vec4(S_ConvertYxyToRgb(Yxy), hdrColor.a); + + if (properties.useColorGrading) { + outLdrColor = vec4(ColorGrading(S_LinearToSRGB(ldrColor).rgb), ldrColor.a); + } else { + outLdrColor = S_LinearToSRGB(ldrColor); + } + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/irradiance.shader b/fyrox-impl/src/renderer/shaders/opengl/irradiance.shader new file mode 100644 index 0000000000..2f5d965141 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/irradiance.shader @@ -0,0 +1,96 @@ +( + name: "IrradianceShader", + resources: [ + ( + name: "environmentMap", + kind: Texture(kind: SamplerCube, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + + out vec3 localPos; + + void main() + { + localPos = vertexPosition; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + in vec3 localPos; + + void main() + { + vec3 N = normalize(localPos); + + vec3 irradiance = vec3(0.0); + + vec3 up = vec3(0.0, 1.0, 0.0); + vec3 right = normalize(cross(up, N)); + up = normalize(cross(N, right)); + + float sampleDelta = 0.1; + float nrSamples = 0.0; + for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta) + { + float cosPhi = cos(phi); + float sinPhi = sin(phi); + + for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta) + { + float cosTheta = cos(theta); + float sinTheta = sin(theta); + + vec3 tangentSample = vec3(sinTheta * cosPhi, sinTheta * sinPhi, cosTheta); + vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; + + irradiance += texture(environmentMap, sampleVec).rgb * cosTheta * sinTheta; + nrSamples++; + } + } + irradiance = PI * irradiance * (1.0 / float(nrSamples)); + + FragColor = vec4(irradiance, 1.0); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/pixel_counter.shader b/fyrox-impl/src/renderer/shaders/opengl/pixel_counter.shader new file mode 100644 index 0000000000..84f32fb9f9 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/pixel_counter.shader @@ -0,0 +1,62 @@ +( + name: "PixelCounter", + resources: [ + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: false, + green: false, + blue: false, + alpha: false, + ), + depth_write: false, + stencil_test: Some(StencilFunc ( + func: NotEqual, + ref_value: 0, + mask: 0xFFFF_FFFF, + )), + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + + void main() + { + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + void main() + { + FragColor = vec4(1.0); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/point_volumetric.shader b/fyrox-impl/src/renderer/shaders/opengl/point_volumetric.shader new file mode 100644 index 0000000000..60fe6ae8d1 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/point_volumetric.shader @@ -0,0 +1,111 @@ +( + name: "PointVolumetric", + resources: [ + ( + name: "depthSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "invProj", kind: Matrix4()), + (name: "lightPosition", kind: Vector3()), + (name: "lightColor", kind: Vector3()), + (name: "scatterFactor", kind: Vector3()), + (name: "intensity", kind: Float()), + (name: "lightRadius", kind: Float()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: Some(StencilFunc( + func: Equal, + ref_value: 0xFF, + mask: 0xFFFF_FFFF + )), + depth_test: None, + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: One, + dfactor: One, + alpha_sfactor: One, + alpha_dfactor: One, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Zero, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec2 texCoord; + + void main() + { + vec3 fragmentPosition = S_UnProject(vec3(texCoord, texture(depthSampler, texCoord).r), properties.invProj); + float fragmentDepth = length(fragmentPosition); + vec3 viewDirection = fragmentPosition / fragmentDepth; + + // Find intersection + vec3 scatter = vec3(0.0); + float minDepth, maxDepth; + if (S_RaySphereIntersection(vec3(0.0), viewDirection, properties.lightPosition, properties.lightRadius, minDepth, maxDepth)) + { + // Perform depth test. + if (minDepth > 0.0 || fragmentDepth > minDepth) + { + minDepth = max(minDepth, 0.0); + maxDepth = clamp(maxDepth, 0.0, fragmentDepth); + + vec3 closestPoint = viewDirection * minDepth; + + scatter = properties.scatterFactor * S_InScatter(closestPoint, viewDirection, properties.lightPosition, maxDepth - minDepth); + } + } + + FragColor = vec4(properties.lightColor.xyz * pow(clamp(properties.intensity * scatter, 0.0, 1.0), vec3(2.2)), 1.0); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/prefilter.shader b/fyrox-impl/src/renderer/shaders/opengl/prefilter.shader new file mode 100644 index 0000000000..5645bbd776 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/prefilter.shader @@ -0,0 +1,128 @@ +( + name: "ReflectionCubeMapPrefilter", + resources: [ + ( + name: "environmentMap", + kind: Texture(kind: SamplerCube, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "roughness", kind: Float()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + + out vec3 localPos; + + void main() + { + localPos = vertexPosition; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec3 localPos; + + float RadicalInverse_VdC(uint bits) + { + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return float(bits) * 2.3283064e-10; // / 0x100000000 + } + + vec2 Hammersley(uint i, uint N) + { + return vec2(float(i)/float(N), RadicalInverse_VdC(i)); + } + + vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) + { + float a = roughness*roughness; + + float phi = 2.0 * PI * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta*cosTheta); + + vec3 H; + H.x = cos(phi) * sinTheta; + H.y = sin(phi) * sinTheta; + H.z = cosTheta; + + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; + return normalize(sampleVec); + } + + + void main() + { + vec3 N = normalize(localPos); + vec3 R = N; + vec3 V = R; + + const uint SAMPLE_COUNT = 64u; + float totalWeight = 0.0; + vec3 prefilteredColor = vec3(0.0); + for(uint i = 0u; i < SAMPLE_COUNT; ++i) + { + vec2 Xi = Hammersley(i, SAMPLE_COUNT); + vec3 H = ImportanceSampleGGX(Xi, N, properties.roughness); + vec3 L = normalize(2.0 * dot(V, H) * H - V); + + float NdotL = max(dot(N, L), 0.0); + if(NdotL > 0.0) + { + prefilteredColor += texture(environmentMap, L).rgb * NdotL; + totalWeight += NdotL; + } + } + prefilteredColor = prefilteredColor / totalWeight; + + FragColor = vec4(prefilteredColor, 1.0); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/skybox.shader b/fyrox-impl/src/renderer/shaders/opengl/skybox.shader new file mode 100644 index 0000000000..1ba68ead61 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/skybox.shader @@ -0,0 +1,68 @@ +( + name: "SkyBox", + resources: [ + ( + name: "cubemapTexture", + kind: Texture(kind: SamplerCube, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + + out vec3 texCoord; + + void main() + { + texCoord = vertexPosition; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec3 texCoord; + + void main() + { + FragColor = S_SRGBToLinear(texture(cubemapTexture, texCoord)); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/spot_volumetric.shader b/fyrox-impl/src/renderer/shaders/opengl/spot_volumetric.shader new file mode 100644 index 0000000000..7e47b6ed11 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/spot_volumetric.shader @@ -0,0 +1,135 @@ +( + name: "SpotVolumetric", + resources: [ + ( + name: "depthSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "invProj", kind: Matrix4()), + (name: "lightPosition", kind: Vector3()), + (name: "lightDirection", kind: Vector3()), + (name: "lightColor", kind: Vector3()), + (name: "scatterFactor", kind: Vector3()), + (name: "intensity", kind: Float()), + (name: "coneAngleCos", kind: Float()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: Some(StencilFunc( + func: Equal, + ref_value: 0xFF, + mask: 0xFFFF_FFFF + )), + depth_test: None, + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: One, + dfactor: One, + alpha_sfactor: One, + alpha_dfactor: One, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Zero, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec2 texCoord; + + void main() + { + vec3 fragmentPosition = S_UnProject(vec3(texCoord, texture(depthSampler, texCoord).r), properties.invProj); + float fragmentDepth = length(fragmentPosition); + vec3 viewDirection = fragmentPosition / fragmentDepth; + + // Ray-cone intersection + float sqrConeAngleCos = properties.coneAngleCos * properties.coneAngleCos; + vec3 CO = -properties.lightPosition; + float DdotV = dot(viewDirection, properties.lightDirection); + float COdotV = dot(CO, properties.lightDirection); + float a = DdotV * DdotV - sqrConeAngleCos; + float b = 2.0 * (DdotV * COdotV - dot(viewDirection, CO) * sqrConeAngleCos); + float c = COdotV * COdotV - dot(CO, CO) * sqrConeAngleCos; + + // Find intersection + vec3 scatter = vec3(0.0); + float minDepth, maxDepth; + if (S_SolveQuadraticEq(a, b, c, minDepth, maxDepth)) + { + float dt1 = dot((minDepth * viewDirection) - properties.lightPosition, properties.lightDirection); + float dt2 = dot((maxDepth * viewDirection) - properties.lightPosition, properties.lightDirection); + + // Discard points on reflected cylinder and perform depth test. + if ((dt1 > 0.0 || dt2 > 0.0) && (minDepth > 0.0 || fragmentDepth > minDepth)) + { + if (dt1 > 0.0 && dt2 < 0.0) + { + // Closest point is on cylinder, farthest on reflected. + maxDepth = minDepth; + minDepth = 0.0; + } + else if (dt1 < 0.0 && dt2 > 0.0) + { + // Farthest point is on cylinder, closest on reflected. + minDepth = maxDepth; + maxDepth = fragmentDepth; + } + + minDepth = max(minDepth, 0.0); + maxDepth = clamp(maxDepth, 0.0, fragmentDepth); + + scatter = properties.scatterFactor * S_InScatter(viewDirection * minDepth, viewDirection, properties.lightPosition, maxDepth - minDepth); + } + } + + FragColor = vec4(properties.lightColor.xyz * pow(clamp(properties.intensity * scatter, 0.0, 1.0), vec3(2.2)), 1.0); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/ssao.shader b/fyrox-impl/src/renderer/shaders/opengl/ssao.shader new file mode 100644 index 0000000000..d57f315c99 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/ssao.shader @@ -0,0 +1,112 @@ +( + name: "SSAO", + resources: [ + ( + name: "depthSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "normalSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 1 + ), + ( + name: "noiseSampler", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 2 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + (name: "inverseProjectionMatrix", kind: Matrix4()), + (name: "projectionMatrix", kind: Matrix4()), + (name: "kernel", kind: Vector3Array(max_len: 32, value: [])), + (name: "noiseScale", kind: Vector2()), + (name: "viewMatrix", kind: Matrix3()), + (name: "radius", kind: Float()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + texCoord = vertexTexCoord; + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out float finalOcclusion; + + in vec2 texCoord; + + vec3 GetViewSpacePosition(vec2 screenCoord) { + return S_UnProject(vec3(screenCoord, texture(depthSampler, screenCoord).r), properties.inverseProjectionMatrix); + } + + void main() { + vec3 fragPos = GetViewSpacePosition(texCoord); + vec3 worldSpaceNormal = texture(normalSampler, texCoord).xyz * 2.0 - 1.0; + vec3 viewSpaceNormal = normalize(properties.viewMatrix * worldSpaceNormal); + vec3 randomVec = normalize(texture(noiseSampler, texCoord * properties.noiseScale).xyz * 2.0 - 1.0); + + vec3 tangent = normalize(randomVec - viewSpaceNormal * dot(randomVec, viewSpaceNormal)); + vec3 bitangent = normalize(cross(viewSpaceNormal, tangent)); + mat3 TBN = mat3(tangent, bitangent, viewSpaceNormal); + + float occlusion = 0.0; + const int kernelSize = 32; + for (int i = 0; i < kernelSize; ++i) { + vec3 samplePoint = fragPos.xyz + TBN * properties.kernel[i] * properties.radius; + + vec4 offset = properties.projectionMatrix * vec4(samplePoint, 1.0); + offset.xy /= offset.w; + offset.xy = offset.xy * 0.5 + 0.5; + + vec3 position = GetViewSpacePosition(offset.xy); + + float rangeCheck = smoothstep(0.0, 1.0, properties.radius / abs(fragPos.z - position.z)); + occlusion += rangeCheck * ((position.z > samplePoint.z + 0.04) ? 1.0 : 0.0); + } + + finalOcclusion = 1.0 - occlusion / float(kernelSize); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/visibility.shader b/fyrox-impl/src/renderer/shaders/opengl/visibility.shader new file mode 100644 index 0000000000..3a6aff1cae --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/visibility.shader @@ -0,0 +1,110 @@ +( + name: "Visibility", + resources: [ + ( + name: "matrices", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "tileBuffer", + kind: Texture(kind: USampler2D, fallback: White), + binding: 1 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "viewProjection", kind: Matrix4()), + (name: "tileSize", kind: Int()), + (name: "frameBufferHeight", kind: Float()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: Some(Back), + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: Some(LessOrEqual), + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: One, + dfactor: One, + alpha_sfactor: One, + alpha_dfactor: One, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Zero, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + + flat out uint objectIndex; + + void main() + { + objectIndex = uint(gl_InstanceID); + gl_Position = (properties.viewProjection * S_FetchMatrix(matrices, gl_InstanceID)) * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + flat in uint objectIndex; + + void main() + { + int x = int(gl_FragCoord.x) / properties.tileSize; + int y = int(properties.frameBufferHeight - gl_FragCoord.y) / properties.tileSize; + + int bitIndex = -1; + int tileDataIndex = x * 33; + int count = int(texelFetch(tileBuffer, ivec2(tileDataIndex, y), 0).x); + int objectsListStartIndex = tileDataIndex + 1; + for (int i = 0; i < count; ++i) { + uint pixelObjectIndex = uint(texelFetch(tileBuffer, ivec2(objectsListStartIndex + i, y), 0).x); + if (pixelObjectIndex == objectIndex) { + bitIndex = i; + break; + } + } + + if (bitIndex < 0) { + FragColor = vec4(0.0, 0.0, 0.0, 0.0); + } else { + uint outMask = uint(1 << bitIndex); + float r = float(outMask & 255u) / 255.0; + float g = float((outMask & 65280u) >> 8) / 255.0; + float b = float((outMask & 16711680u) >> 16) / 255.0; + float a = float((outMask & 4278190080u) >> 24) / 255.0; + FragColor = vec4(r, g, b, a); + } + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/visibility_optimizer.shader b/fyrox-impl/src/renderer/shaders/opengl/visibility_optimizer.shader new file mode 100644 index 0000000000..f75e519cc3 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/visibility_optimizer.shader @@ -0,0 +1,80 @@ +( + name: "VisibilityOptimizer", + resources: [ + ( + name: "visibilityBuffer", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "properties", + kind: PropertyGroup([ + (name: "viewProjection", kind: Matrix4()), + (name: "tileSize", kind: Int()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: Some(LessOrEqual), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Zero, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + + void main() + { + gl_Position = properties.viewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out uint optimizedVisibilityMask; + + void main() + { + int tileX = int(gl_FragCoord.x); + int tileY = int(gl_FragCoord.y); + + int beginX = tileX * properties.tileSize; + int beginY = tileY * properties.tileSize; + + int endX = (tileX + 1) * properties.tileSize; + int endY = (tileY + 1) * properties.tileSize; + + int visibilityMask = 0; + for (int y = beginY; y < endY; ++y) { + for (int x = beginX; x < endX; ++x) { + ivec4 mask = ivec4(texelFetch(visibilityBuffer, ivec2(x, y), 0) * 255.0); + visibilityMask |= (mask.a << 24) | (mask.b << 16) | (mask.g << 8) | mask.r; + } + } + optimizedVisibilityMask = uint(visibilityMask); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/volume_marker_lit.shader b/fyrox-impl/src/renderer/shaders/opengl/volume_marker_lit.shader new file mode 100644 index 0000000000..714a2d45e4 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/volume_marker_lit.shader @@ -0,0 +1,39 @@ +( + name: "VolumeMarkerLighting", + resources: [ + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + // Drawing params are dynamic. + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + + void main() + { + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + void main() + { + FragColor = vec4(1.0); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/opengl/volume_marker_vol.shader b/fyrox-impl/src/renderer/shaders/opengl/volume_marker_vol.shader new file mode 100644 index 0000000000..d8ec418888 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/opengl/volume_marker_vol.shader @@ -0,0 +1,62 @@ +( + name: "VolumeMarkerVolume", + resources: [ + ( + name: "properties", + kind: PropertyGroup([ + (name: "worldViewProjection", kind: Matrix4()), + ]), + binding: 0 + ), + ], + passes: [ + ( + name: "Primary", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: false, + green: false, + blue: false, + alpha: false, + ), + depth_write: false, + stencil_test: Some(StencilFunc ( + func: Equal, + ref_value: 0xFF, + mask: 0xFFFF_FFFF + )), + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Replace, + zfail: Keep, + zpass: Replace, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec3 vertexPosition; + + void main() + { + gl_Position = properties.worldViewProjection * vec4(vertexPosition, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + void main() + { + FragColor = vec4(1.0); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/ambient_light.shader b/fyrox-impl/src/renderer/shaders/wgpu/ambient_light.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/ambient_light.shader rename to fyrox-impl/src/renderer/shaders/wgpu/ambient_light.shader diff --git a/fyrox-impl/src/renderer/shaders/blit.shader b/fyrox-impl/src/renderer/shaders/wgpu/blit.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/blit.shader rename to fyrox-impl/src/renderer/shaders/wgpu/blit.shader diff --git a/fyrox-impl/src/renderer/shaders/bloom.shader b/fyrox-impl/src/renderer/shaders/wgpu/bloom.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/bloom.shader rename to fyrox-impl/src/renderer/shaders/wgpu/bloom.shader diff --git a/fyrox-impl/src/renderer/shaders/blur.shader b/fyrox-impl/src/renderer/shaders/wgpu/blur.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/blur.shader rename to fyrox-impl/src/renderer/shaders/wgpu/blur.shader diff --git a/fyrox-impl/src/renderer/shaders/debug.shader b/fyrox-impl/src/renderer/shaders/wgpu/debug.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/debug.shader rename to fyrox-impl/src/renderer/shaders/wgpu/debug.shader diff --git a/fyrox-impl/src/renderer/shaders/decal.shader b/fyrox-impl/src/renderer/shaders/wgpu/decal.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/decal.shader rename to fyrox-impl/src/renderer/shaders/wgpu/decal.shader diff --git a/fyrox-impl/src/renderer/shaders/deferred_directional_light.shader b/fyrox-impl/src/renderer/shaders/wgpu/deferred_directional_light.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/deferred_directional_light.shader rename to fyrox-impl/src/renderer/shaders/wgpu/deferred_directional_light.shader diff --git a/fyrox-impl/src/renderer/shaders/deferred_point_light.shader b/fyrox-impl/src/renderer/shaders/wgpu/deferred_point_light.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/deferred_point_light.shader rename to fyrox-impl/src/renderer/shaders/wgpu/deferred_point_light.shader diff --git a/fyrox-impl/src/renderer/shaders/deferred_spot_light.shader b/fyrox-impl/src/renderer/shaders/wgpu/deferred_spot_light.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/deferred_spot_light.shader rename to fyrox-impl/src/renderer/shaders/wgpu/deferred_spot_light.shader diff --git a/fyrox-impl/src/renderer/shaders/fxaa.shader b/fyrox-impl/src/renderer/shaders/wgpu/fxaa.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/fxaa.shader rename to fyrox-impl/src/renderer/shaders/wgpu/fxaa.shader diff --git a/fyrox-impl/src/renderer/shaders/gaussian_blur.shader b/fyrox-impl/src/renderer/shaders/wgpu/gaussian_blur.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/gaussian_blur.shader rename to fyrox-impl/src/renderer/shaders/wgpu/gaussian_blur.shader diff --git a/fyrox-impl/src/renderer/shaders/hdr_adaptation.shader b/fyrox-impl/src/renderer/shaders/wgpu/hdr_adaptation.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/hdr_adaptation.shader rename to fyrox-impl/src/renderer/shaders/wgpu/hdr_adaptation.shader diff --git a/fyrox-impl/src/renderer/shaders/hdr_downscale.shader b/fyrox-impl/src/renderer/shaders/wgpu/hdr_downscale.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/hdr_downscale.shader rename to fyrox-impl/src/renderer/shaders/wgpu/hdr_downscale.shader diff --git a/fyrox-impl/src/renderer/shaders/hdr_luminance.shader b/fyrox-impl/src/renderer/shaders/wgpu/hdr_luminance.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/hdr_luminance.shader rename to fyrox-impl/src/renderer/shaders/wgpu/hdr_luminance.shader diff --git a/fyrox-impl/src/renderer/shaders/hdr_map.shader b/fyrox-impl/src/renderer/shaders/wgpu/hdr_map.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/hdr_map.shader rename to fyrox-impl/src/renderer/shaders/wgpu/hdr_map.shader diff --git a/fyrox-impl/src/renderer/shaders/irradiance.shader b/fyrox-impl/src/renderer/shaders/wgpu/irradiance.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/irradiance.shader rename to fyrox-impl/src/renderer/shaders/wgpu/irradiance.shader diff --git a/fyrox-impl/src/renderer/shaders/pixel_counter.shader b/fyrox-impl/src/renderer/shaders/wgpu/pixel_counter.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/pixel_counter.shader rename to fyrox-impl/src/renderer/shaders/wgpu/pixel_counter.shader diff --git a/fyrox-impl/src/renderer/shaders/point_volumetric.shader b/fyrox-impl/src/renderer/shaders/wgpu/point_volumetric.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/point_volumetric.shader rename to fyrox-impl/src/renderer/shaders/wgpu/point_volumetric.shader diff --git a/fyrox-impl/src/renderer/shaders/prefilter.shader b/fyrox-impl/src/renderer/shaders/wgpu/prefilter.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/prefilter.shader rename to fyrox-impl/src/renderer/shaders/wgpu/prefilter.shader diff --git a/fyrox-impl/src/renderer/shaders/skybox.shader b/fyrox-impl/src/renderer/shaders/wgpu/skybox.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/skybox.shader rename to fyrox-impl/src/renderer/shaders/wgpu/skybox.shader diff --git a/fyrox-impl/src/renderer/shaders/spot_volumetric.shader b/fyrox-impl/src/renderer/shaders/wgpu/spot_volumetric.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/spot_volumetric.shader rename to fyrox-impl/src/renderer/shaders/wgpu/spot_volumetric.shader diff --git a/fyrox-impl/src/renderer/shaders/ssao.shader b/fyrox-impl/src/renderer/shaders/wgpu/ssao.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/ssao.shader rename to fyrox-impl/src/renderer/shaders/wgpu/ssao.shader diff --git a/fyrox-impl/src/renderer/shaders/visibility.shader b/fyrox-impl/src/renderer/shaders/wgpu/visibility.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/visibility.shader rename to fyrox-impl/src/renderer/shaders/wgpu/visibility.shader diff --git a/fyrox-impl/src/renderer/shaders/visibility_optimizer.shader b/fyrox-impl/src/renderer/shaders/wgpu/visibility_optimizer.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/visibility_optimizer.shader rename to fyrox-impl/src/renderer/shaders/wgpu/visibility_optimizer.shader diff --git a/fyrox-impl/src/renderer/shaders/volume_marker_lit.shader b/fyrox-impl/src/renderer/shaders/wgpu/volume_marker_lit.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/volume_marker_lit.shader rename to fyrox-impl/src/renderer/shaders/wgpu/volume_marker_lit.shader diff --git a/fyrox-impl/src/renderer/shaders/volume_marker_vol.shader b/fyrox-impl/src/renderer/shaders/wgpu/volume_marker_vol.shader similarity index 100% rename from fyrox-impl/src/renderer/shaders/volume_marker_vol.shader rename to fyrox-impl/src/renderer/shaders/wgpu/volume_marker_vol.shader From 58b5cd9826df6ff9611ef4971b392120222bad9e Mon Sep 17 00:00:00 2001 From: nicehack Date: Tue, 21 Jul 2026 17:00:55 +0500 Subject: [PATCH 18/34] GL fyrox-material shaders restored --- editor-standalone/Cargo.toml | 4 +- fyrox-impl/Cargo.toml | 4 +- fyrox-material/Cargo.toml | 4 + fyrox-material/src/shader/mod.rs | 30 +- .../standard/opengl/standard-two-sides.shader | 614 ++++++++++++++++++ .../shader/standard/opengl/standard.shader | 614 ++++++++++++++++++ .../shader/standard/opengl/standard2d.shader | 123 ++++ .../opengl/standard_particle_system.shader | 172 +++++ .../standard/opengl/standard_sprite.shader | 102 +++ .../src/shader/standard/opengl/terrain.shader | 511 +++++++++++++++ .../src/shader/standard/opengl/tile.shader | 123 ++++ .../src/shader/standard/opengl/widget.shader | 161 +++++ .../{ => wgpu}/standard-two-sides.shader | 0 .../standard/{ => wgpu}/standard.shader | 0 .../standard/{ => wgpu}/standard2d.shader | 0 .../standard_particle_system.shader | 0 .../{ => wgpu}/standard_sprite.shader | 0 .../shader/standard/{ => wgpu}/terrain.shader | 0 .../shader/standard/{ => wgpu}/tile.shader | 0 .../shader/standard/{ => wgpu}/widget.shader | 0 20 files changed, 2450 insertions(+), 12 deletions(-) create mode 100644 fyrox-material/src/shader/standard/opengl/standard-two-sides.shader create mode 100644 fyrox-material/src/shader/standard/opengl/standard.shader create mode 100644 fyrox-material/src/shader/standard/opengl/standard2d.shader create mode 100644 fyrox-material/src/shader/standard/opengl/standard_particle_system.shader create mode 100644 fyrox-material/src/shader/standard/opengl/standard_sprite.shader create mode 100644 fyrox-material/src/shader/standard/opengl/terrain.shader create mode 100644 fyrox-material/src/shader/standard/opengl/tile.shader create mode 100644 fyrox-material/src/shader/standard/opengl/widget.shader rename fyrox-material/src/shader/standard/{ => wgpu}/standard-two-sides.shader (100%) rename fyrox-material/src/shader/standard/{ => wgpu}/standard.shader (100%) rename fyrox-material/src/shader/standard/{ => wgpu}/standard2d.shader (100%) rename fyrox-material/src/shader/standard/{ => wgpu}/standard_particle_system.shader (100%) rename fyrox-material/src/shader/standard/{ => wgpu}/standard_sprite.shader (100%) rename fyrox-material/src/shader/standard/{ => wgpu}/terrain.shader (100%) rename fyrox-material/src/shader/standard/{ => wgpu}/tile.shader (100%) rename fyrox-material/src/shader/standard/{ => wgpu}/widget.shader (100%) diff --git a/editor-standalone/Cargo.toml b/editor-standalone/Cargo.toml index 2422685f35..4be2e5c940 100644 --- a/editor-standalone/Cargo.toml +++ b/editor-standalone/Cargo.toml @@ -19,5 +19,5 @@ clap = { version = "4", features = ["derive"] } [features] default = ["backend_opengl"] -backend_opengl = ["fyrox/backend_opengl", "fyroxed_base/backend_opengl"] -backend_wgpu = ["fyrox/backend_wgpu", "fyroxed_base/backend_wgpu"] +backend_opengl = ["fyroxed_base/backend_opengl"] +backend_wgpu = ["fyroxed_base/backend_wgpu"] diff --git a/fyrox-impl/Cargo.toml b/fyrox-impl/Cargo.toml index 0f6254420e..a0ca2d7027 100644 --- a/fyrox-impl/Cargo.toml +++ b/fyrox-impl/Cargo.toml @@ -94,8 +94,8 @@ opener = { version = "0.8", default-features = false, features = ["reveal"] } # === end of additional deps === [features] -backend_opengl = ["dep:fyrox-graphics-gl"] -backend_wgpu = ["dep:fyrox-graphics-wgpu"] +backend_opengl = ["dep:fyrox-graphics-gl", "fyrox-material/backend_opengl"] +backend_wgpu = ["dep:fyrox-graphics-wgpu", "fyrox-material/backend_wgpu"] mesh_analysis = [] [target.'cfg(target_os = "android")'.dependencies] diff --git a/fyrox-material/Cargo.toml b/fyrox-material/Cargo.toml index cf02b4c980..339d0de510 100644 --- a/fyrox-material/Cargo.toml +++ b/fyrox-material/Cargo.toml @@ -28,3 +28,7 @@ regex = "1" [dev-dependencies] fyrox-texture = { version = "2.0.0-rc.1", path = "../fyrox-texture" } + +[features] +backend_opengl = [] +backend_wgpu = [] diff --git a/fyrox-material/src/shader/mod.rs b/fyrox-material/src/shader/mod.rs index a6c530584f..ad94031832 100644 --- a/fyrox-material/src/shader/mod.rs +++ b/fyrox-material/src/shader/mod.rs @@ -966,11 +966,25 @@ impl ShaderResourceExtension for ShaderResource { } } +#[cfg(all(feature = "backend_wgpu", not(feature = "backend_opengl")))] +macro_rules! embedded_shader { + ($file:literal) => { + embedded_data_source!(concat!("standard/wgpu/", $file)) + }; +} + +#[cfg(feature = "backend_opengl")] +macro_rules! embedded_shader { + ($file:literal) => { + embedded_data_source!(concat!("standard/opengl/", $file)) + }; +} + /// Standard shader. pub static STANDARD: LazyLock> = LazyLock::new(|| { BuiltInResource::new( STANDARD_SHADER_NAME, - embedded_data_source!("standard/standard.shader"), + embedded_shader!("standard.shader"), |data| { ShaderResource::new_ok( uuid!("87195f6e-cba4-4c27-9f89-d0bf726db965"), @@ -985,7 +999,7 @@ pub static STANDARD: LazyLock> = LazyLock::new(|| { pub static STANDARD_2D: LazyLock> = LazyLock::new(|| { BuiltInResource::new( STANDARD_2D_SHADER_NAME, - embedded_data_source!("standard/standard2d.shader"), + embedded_shader!("standard2d.shader"), |data| { ShaderResource::new_ok( uuid!("55fa05b0-3c25-4e46-bae7-65f093185b75"), @@ -1000,7 +1014,7 @@ pub static STANDARD_2D: LazyLock> = LazyLock::new(|| { pub static STANDARD_PARTICLE_SYSTEM: LazyLock> = LazyLock::new(|| { BuiltInResource::new( STANDARD_PARTICLE_SYSTEM_SHADER_NAME, - embedded_data_source!("standard/standard_particle_system.shader"), + embedded_shader!("standard_particle_system.shader"), |data| { ShaderResource::new_ok( uuid!("eb474445-6a25-4481-bca9-f919699c300f"), @@ -1015,7 +1029,7 @@ pub static STANDARD_PARTICLE_SYSTEM: LazyLock> = LazyLoc pub static STANDARD_SPRITE: LazyLock> = LazyLock::new(|| { BuiltInResource::new( STANDARD_SPRITE_SHADER_NAME, - embedded_data_source!("standard/standard_sprite.shader"), + embedded_shader!("standard_sprite.shader"), |data| { ShaderResource::new_ok( uuid!("a135826a-4c1b-46d5-ba1f-0c9a226aa52c"), @@ -1030,7 +1044,7 @@ pub static STANDARD_SPRITE: LazyLock> = LazyLock::new(|| pub static STANDARD_TERRAIN: LazyLock> = LazyLock::new(|| { BuiltInResource::new( STANDARD_TERRAIN_SHADER_NAME, - embedded_data_source!("standard/terrain.shader"), + embedded_shader!("terrain.shader"), |data| { ShaderResource::new_ok( uuid!("4911aafe-9bb1-4115-a958-25b57b87b51e"), @@ -1045,7 +1059,7 @@ pub static STANDARD_TERRAIN: LazyLock> = LazyLock::new(| pub static STANDARD_TILE: LazyLock> = LazyLock::new(|| { BuiltInResource::new( STANDARD_TILE_SHADER_NAME, - embedded_data_source!("standard/tile.shader"), + embedded_shader!("tile.shader"), |data| { ShaderResource::new_ok( uuid!("5f29dd3a-ea99-480c-bb02-d2c6420843b1"), @@ -1060,7 +1074,7 @@ pub static STANDARD_TILE: LazyLock> = LazyLock::new(|| { pub static STANDARD_TWOSIDES: LazyLock> = LazyLock::new(|| { BuiltInResource::new( STANDARD_TWOSIDES_SHADER_NAME, - embedded_data_source!("standard/standard-two-sides.shader"), + embedded_shader!("standard-two-sides.shader"), |data| { ShaderResource::new_ok( uuid!("f7979409-5185-4e1c-a644-d53cea64af8f"), @@ -1075,7 +1089,7 @@ pub static STANDARD_TWOSIDES: LazyLock> = LazyLock::new( pub static STANDARD_WIDGET: LazyLock> = LazyLock::new(|| { BuiltInResource::new( STANDARD_WIDGET_SHADER_NAME, - embedded_data_source!("standard/widget.shader"), + embedded_shader!("widget.shader"), |data| { ShaderResource::new_ok( uuid!("f5908aa4-e187-42a8-95d2-dc6577f6def4"), diff --git a/fyrox-material/src/shader/standard/opengl/standard-two-sides.shader b/fyrox-material/src/shader/standard/opengl/standard-two-sides.shader new file mode 100644 index 0000000000..8d63fd15f3 --- /dev/null +++ b/fyrox-material/src/shader/standard/opengl/standard-two-sides.shader @@ -0,0 +1,614 @@ +( + name: "StandardTwoSidesShader", + + resources: [ + ( + name: "diffuseTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "normalTexture", + kind: Texture(kind: Sampler2D, fallback: Normal), + binding: 1 + ), + ( + name: "metallicTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 2 + ), + ( + name: "roughnessTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 3 + ), + ( + name: "heightTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 4 + ), + ( + name: "emissionTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 5 + ), + ( + name: "lightmapTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 6 + ), + ( + name: "aoTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 7 + ), + ( + name: "blendShapesStorage", + kind: Texture(kind: Sampler3D, fallback: Volume), + binding: 8 + ), + ( + name: "properties", + kind: PropertyGroup([ + ( + name: "texCoordScale", + kind: Vector2(value: (1.0, 1.0)), + ), + ( + name: "layerIndex", + kind: UInt(value: 0), + ), + ( + name: "emissionStrength", + kind: Vector3(value: (2.0, 2.0, 2.0)), + ), + ( + name: "diffuseColor", + kind: Color(r: 255, g: 255, b: 255, a: 255), + ), + ( + name: "parallaxCenter", + kind: Float(value: 0.0), + ), + ( + name: "parallaxScale", + kind: Float(value: 0.08), + ), + ]), + binding: 0 + ), + ( + name: "fyrox_instanceData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 1 + ), + ( + name: "fyrox_boneMatrices", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 2 + ), + ( + name: "fyrox_graphicsSettings", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 3 + ), + ( + name: "fyrox_cameraData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 4 + ), + ( + name: "fyrox_lightData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 5 + ), + ], + + passes: [ + ( + name: "GBuffer", + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 2) in vec3 vertexNormal; + layout(location = 3) in vec4 vertexTangent; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + layout(location = 6) in vec2 vertexSecondTexCoord; + + out vec3 position; + out vec3 normal; + out vec2 texCoord; + out vec3 tangent; + out vec3 binormal; + out vec2 secondTexCoord; + + void main() + { + vec4 localPosition = vec4(0); + vec3 localNormal = vec3(0); + vec3 localTangent = vec3(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + vec3 inputNormal = vertexNormal; + vec3 inputTangent = vertexTangent.xyz; + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + inputNormal += offsets.normal * weight; + inputTangent += offsets.tangent * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + int i0 = int(boneIndices.x); + int i1 = int(boneIndices.y); + int i2 = int(boneIndices.z); + int i3 = int(boneIndices.w); + + mat4 m0 = fyrox_boneMatrices.matrices[i0]; + mat4 m1 = fyrox_boneMatrices.matrices[i1]; + mat4 m2 = fyrox_boneMatrices.matrices[i2]; + mat4 m3 = fyrox_boneMatrices.matrices[i3]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + + localNormal += mat3(m0) * inputNormal * boneWeights.x; + localNormal += mat3(m1) * inputNormal * boneWeights.y; + localNormal += mat3(m2) * inputNormal * boneWeights.z; + localNormal += mat3(m3) * inputNormal * boneWeights.w; + + localTangent += mat3(m0) * inputTangent * boneWeights.x; + localTangent += mat3(m1) * inputTangent * boneWeights.y; + localTangent += mat3(m2) * inputTangent * boneWeights.z; + localTangent += mat3(m3) * inputTangent * boneWeights.w; + } + else + { + localPosition = inputPosition; + localNormal = inputNormal; + localTangent = inputTangent; + } + + mat3 nm = mat3(fyrox_instanceData.worldMatrix); + normal = normalize(nm * localNormal); + tangent = normalize(nm * localTangent); + binormal = normalize(vertexTangent.w * cross(normal, tangent)); + texCoord = vertexTexCoord; + position = vec3(fyrox_instanceData.worldMatrix * localPosition); + secondTexCoord = vertexSecondTexCoord; + + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + } + "#, + fragment_shader: + r#" + layout(location = 0) out vec4 outColor; + layout(location = 1) out vec4 outNormal; + layout(location = 2) out vec4 outAmbient; + layout(location = 3) out vec4 outMaterial; + layout(location = 4) out uint outDecalMask; + + in vec3 position; + in vec3 normal; + in vec2 texCoord; + in vec3 tangent; + in vec3 binormal; + in vec2 secondTexCoord; + + void main() + { + mat3 tangentSpace = mat3(tangent, binormal, normal); + vec3 toFragment = normalize(position - fyrox_cameraData.position); + + vec2 tc; + if (fyrox_graphicsSettings.usePOM) { + vec3 toFragmentTangentSpace = normalize(transpose(tangentSpace) * toFragment); + tc = S_ComputeParallaxTextureCoordinates( + heightTexture, + toFragmentTangentSpace, + texCoord * properties.texCoordScale, + properties.parallaxCenter, + properties.parallaxScale + ); + } else { + tc = texCoord * properties.texCoordScale; + } + + outColor = properties.diffuseColor * texture(diffuseTexture, tc); + + // Alpha test. + if (outColor.a < 0.5) { + discard; + } + outColor.a = 1.0; + + vec4 n = normalize(texture(normalTexture, tc) * 2.0 - 1.0); + outNormal = vec4(normalize(tangentSpace * n.xyz) * 0.5 + 0.5, 1.0); + + outMaterial.x = texture(metallicTexture, tc).r; + outMaterial.y = texture(roughnessTexture, tc).r; + outMaterial.z = texture(aoTexture, tc).r; + outMaterial.a = 1.0; + + outAmbient.xyz = properties.emissionStrength * texture(emissionTexture, tc).rgb + texture(lightmapTexture, secondTexCoord).rgb; + outAmbient.a = 1.0; + + outDecalMask = properties.layerIndex; + } + "#, + ), + ( + name: "Forward", + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + + out vec3 position; + out vec2 texCoord; + + void main() + { + vec4 localPosition = vec4(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + int i0 = int(boneIndices.x); + int i1 = int(boneIndices.y); + int i2 = int(boneIndices.z); + int i3 = int(boneIndices.w); + + mat4 m0 = fyrox_boneMatrices.matrices[i0]; + mat4 m1 = fyrox_boneMatrices.matrices[i1]; + mat4 m2 = fyrox_boneMatrices.matrices[i2]; + mat4 m3 = fyrox_boneMatrices.matrices[i3]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + } + else + { + localPosition = inputPosition; + } + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec2 texCoord; + + void main() + { + FragColor = properties.diffuseColor * S_SRGBToLinear(texture(diffuseTexture, texCoord)); + } + "#, + ), + ( + name: "DirectionalShadow", + + draw_parameters: DrawParameters ( + cull_face: None, + color_write: ColorMask( + red: false, + green: false, + blue: false, + alpha: false, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + + out vec2 texCoord; + + void main() + { + vec4 localPosition = vec4(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + vec4 vertex = vec4(vertexPosition, 1.0); + + mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; + mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; + mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; + mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + } + else + { + localPosition = inputPosition; + } + + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + + void main() + { + if (texture(diffuseTexture, texCoord).a < 0.2) discard; + } + "#, + ), + ( + name: "SpotShadow", + + draw_parameters: DrawParameters ( + cull_face: None, + color_write: ColorMask( + red: false, + green: false, + blue: false, + alpha: false, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + + out vec2 texCoord; + + void main() + { + vec4 localPosition = vec4(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + vec4 vertex = vec4(vertexPosition, 1.0); + + mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; + mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; + mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; + mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + } + else + { + localPosition = inputPosition; + } + + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + + void main() + { + if (texture(diffuseTexture, texCoord).a < 0.2) discard; + } + "#, + ), + ( + name: "PointShadow", + + draw_parameters: DrawParameters ( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + + out vec2 texCoord; + out vec3 worldPosition; + + void main() + { + vec4 localPosition = vec4(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + vec4 vertex = vec4(vertexPosition, 1.0); + + mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; + mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; + mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; + mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + } + else + { + localPosition = inputPosition; + } + + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + worldPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz; + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + in vec3 worldPosition; + + layout(location = 0) out float depth; + + void main() + { + if (texture(diffuseTexture, texCoord).a < 0.2) discard; + depth = length(fyrox_lightData.lightPosition - worldPosition); + } + "#, + ) + ], +) \ No newline at end of file diff --git a/fyrox-material/src/shader/standard/opengl/standard.shader b/fyrox-material/src/shader/standard/opengl/standard.shader new file mode 100644 index 0000000000..2b6642dc4e --- /dev/null +++ b/fyrox-material/src/shader/standard/opengl/standard.shader @@ -0,0 +1,614 @@ +( + name: "StandardShader", + + resources: [ + ( + name: "diffuseTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "normalTexture", + kind: Texture(kind: Sampler2D, fallback: Normal), + binding: 1 + ), + ( + name: "metallicTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 2 + ), + ( + name: "roughnessTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 3 + ), + ( + name: "heightTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 4 + ), + ( + name: "emissionTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 5 + ), + ( + name: "lightmapTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 6 + ), + ( + name: "aoTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 7 + ), + ( + name: "blendShapesStorage", + kind: Texture(kind: Sampler3D, fallback: Volume), + binding: 8 + ), + ( + name: "properties", + kind: PropertyGroup([ + ( + name: "texCoordScale", + kind: Vector2(value: (1.0, 1.0)), + ), + ( + name: "layerIndex", + kind: UInt(value: 0), + ), + ( + name: "emissionStrength", + kind: Vector3(value: (2.0, 2.0, 2.0)), + ), + ( + name: "diffuseColor", + kind: Color(r: 255, g: 255, b: 255, a: 255), + ), + ( + name: "parallaxCenter", + kind: Float(value: 0.0), + ), + ( + name: "parallaxScale", + kind: Float(value: 0.08), + ), + ]), + binding: 0 + ), + ( + name: "fyrox_instanceData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 1 + ), + ( + name: "fyrox_boneMatrices", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 2 + ), + ( + name: "fyrox_graphicsSettings", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 3 + ), + ( + name: "fyrox_cameraData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 4 + ), + ( + name: "fyrox_lightData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 5 + ), + ], + + passes: [ + ( + name: "GBuffer", + draw_parameters: DrawParameters( + cull_face: Some(Back), + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 2) in vec3 vertexNormal; + layout(location = 3) in vec4 vertexTangent; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + layout(location = 6) in vec2 vertexSecondTexCoord; + + out vec3 position; + out vec3 normal; + out vec2 texCoord; + out vec3 tangent; + out vec3 binormal; + out vec2 secondTexCoord; + + void main() + { + vec4 localPosition = vec4(0); + vec3 localNormal = vec3(0); + vec3 localTangent = vec3(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + vec3 inputNormal = vertexNormal; + vec3 inputTangent = vertexTangent.xyz; + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + inputNormal += offsets.normal * weight; + inputTangent += offsets.tangent * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + int i0 = int(boneIndices.x); + int i1 = int(boneIndices.y); + int i2 = int(boneIndices.z); + int i3 = int(boneIndices.w); + + mat4 m0 = fyrox_boneMatrices.matrices[i0]; + mat4 m1 = fyrox_boneMatrices.matrices[i1]; + mat4 m2 = fyrox_boneMatrices.matrices[i2]; + mat4 m3 = fyrox_boneMatrices.matrices[i3]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + + localNormal += mat3(m0) * inputNormal * boneWeights.x; + localNormal += mat3(m1) * inputNormal * boneWeights.y; + localNormal += mat3(m2) * inputNormal * boneWeights.z; + localNormal += mat3(m3) * inputNormal * boneWeights.w; + + localTangent += mat3(m0) * inputTangent * boneWeights.x; + localTangent += mat3(m1) * inputTangent * boneWeights.y; + localTangent += mat3(m2) * inputTangent * boneWeights.z; + localTangent += mat3(m3) * inputTangent * boneWeights.w; + } + else + { + localPosition = inputPosition; + localNormal = inputNormal; + localTangent = inputTangent; + } + + mat3 nm = mat3(fyrox_instanceData.worldMatrix); + normal = normalize(nm * localNormal); + tangent = normalize(nm * localTangent); + binormal = normalize(vertexTangent.w * cross(normal, tangent)); + texCoord = vertexTexCoord; + position = vec3(fyrox_instanceData.worldMatrix * localPosition); + secondTexCoord = vertexSecondTexCoord; + + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + } + "#, + fragment_shader: + r#" + layout(location = 0) out vec4 outColor; + layout(location = 1) out vec4 outNormal; + layout(location = 2) out vec4 outAmbient; + layout(location = 3) out vec4 outMaterial; + layout(location = 4) out uint outDecalMask; + + in vec3 position; + in vec3 normal; + in vec2 texCoord; + in vec3 tangent; + in vec3 binormal; + in vec2 secondTexCoord; + + void main() + { + mat3 tangentSpace = mat3(tangent, binormal, normal); + vec3 toFragment = normalize(position - fyrox_cameraData.position); + + vec2 tc; + if (fyrox_graphicsSettings.usePOM) { + vec3 toFragmentTangentSpace = normalize(transpose(tangentSpace) * toFragment); + tc = S_ComputeParallaxTextureCoordinates( + heightTexture, + toFragmentTangentSpace, + texCoord * properties.texCoordScale, + properties.parallaxCenter, + properties.parallaxScale + ); + } else { + tc = texCoord * properties.texCoordScale; + } + + outColor = properties.diffuseColor * texture(diffuseTexture, tc); + + // Alpha test. + if (outColor.a < 0.5) { + discard; + } + outColor.a = 1.0; + + vec4 n = normalize(texture(normalTexture, tc) * 2.0 - 1.0); + outNormal = vec4(normalize(tangentSpace * n.xyz) * 0.5 + 0.5, 1.0); + + outMaterial.x = texture(metallicTexture, tc).r; + outMaterial.y = texture(roughnessTexture, tc).r; + outMaterial.z = texture(aoTexture, tc).r; + outMaterial.a = 1.0; + + outAmbient.xyz = properties.emissionStrength * texture(emissionTexture, tc).rgb + texture(lightmapTexture, secondTexCoord).rgb; + outAmbient.a = 1.0; + + outDecalMask = properties.layerIndex; + } + "#, + ), + ( + name: "Forward", + draw_parameters: DrawParameters( + cull_face: Some(Back), + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + + out vec3 position; + out vec2 texCoord; + + void main() + { + vec4 localPosition = vec4(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + int i0 = int(boneIndices.x); + int i1 = int(boneIndices.y); + int i2 = int(boneIndices.z); + int i3 = int(boneIndices.w); + + mat4 m0 = fyrox_boneMatrices.matrices[i0]; + mat4 m1 = fyrox_boneMatrices.matrices[i1]; + mat4 m2 = fyrox_boneMatrices.matrices[i2]; + mat4 m3 = fyrox_boneMatrices.matrices[i3]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + } + else + { + localPosition = inputPosition; + } + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec2 texCoord; + + void main() + { + FragColor = properties.diffuseColor * S_SRGBToLinear(texture(diffuseTexture, texCoord)); + } + "#, + ), + ( + name: "DirectionalShadow", + + draw_parameters: DrawParameters ( + cull_face: Some(Back), + color_write: ColorMask( + red: false, + green: false, + blue: false, + alpha: false, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + + out vec2 texCoord; + + void main() + { + vec4 localPosition = vec4(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + vec4 vertex = vec4(vertexPosition, 1.0); + + mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; + mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; + mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; + mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + } + else + { + localPosition = inputPosition; + } + + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + + void main() + { + if (texture(diffuseTexture, texCoord).a < 0.2) discard; + } + "#, + ), + ( + name: "SpotShadow", + + draw_parameters: DrawParameters ( + cull_face: Some(Back), + color_write: ColorMask( + red: false, + green: false, + blue: false, + alpha: false, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + + out vec2 texCoord; + + void main() + { + vec4 localPosition = vec4(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + vec4 vertex = vec4(vertexPosition, 1.0); + + mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; + mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; + mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; + mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + } + else + { + localPosition = inputPosition; + } + + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + + void main() + { + if (texture(diffuseTexture, texCoord).a < 0.2) discard; + } + "#, + ), + ( + name: "PointShadow", + + draw_parameters: DrawParameters ( + cull_face: Some(Back), + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + + out vec2 texCoord; + out vec3 worldPosition; + + void main() + { + vec4 localPosition = vec4(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + vec4 vertex = vec4(vertexPosition, 1.0); + + mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; + mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; + mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; + mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + } + else + { + localPosition = inputPosition; + } + + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + worldPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz; + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + in vec3 worldPosition; + + layout(location = 0) out float depth; + + void main() + { + if (texture(diffuseTexture, texCoord).a < 0.2) discard; + depth = length(fyrox_lightData.lightPosition - worldPosition); + } + "#, + ) + ], +) \ No newline at end of file diff --git a/fyrox-material/src/shader/standard/opengl/standard2d.shader b/fyrox-material/src/shader/standard/opengl/standard2d.shader new file mode 100644 index 0000000000..b2e6121657 --- /dev/null +++ b/fyrox-material/src/shader/standard/opengl/standard2d.shader @@ -0,0 +1,123 @@ +( + name: "Standard2DShader", + + resources: [ + ( + name: "diffuseTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "fyrox_instanceData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 0 + ), + ( + name: "fyrox_lightData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 1 + ), + ( + name: "fyrox_lightsBlock", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 2 + ), + ], + + disabled_passes: ["GBuffer", "DirectionalShadow", "PointShadow", "SpotShadow"], + + passes: [ + ( + name: "Forward", + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(LessOrEqual), + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 2) in vec4 vertexColor; + + out vec2 texCoord; + out vec4 color; + out vec3 fragmentPosition; + + void main() + { + texCoord = vertexTexCoord; + fragmentPosition = (fyrox_instanceData.worldMatrix * vec4(vertexPosition, 1.0)).xyz; + gl_Position = fyrox_instanceData.worldViewProjection * vec4(vertexPosition, 1.0); + color = vertexColor; + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec2 texCoord; + in vec4 color; + in vec3 fragmentPosition; + + void main() + { + vec3 lighting = fyrox_lightData.ambientLightColor.xyz; + for(int i = 0; i < min(fyrox_lightsBlock.lightCount, 16); ++i) { + // "Unpack" light parameters. + float halfHotspotAngleCos = fyrox_lightsBlock.lightsParameters[i].x; + float halfConeAngleCos = fyrox_lightsBlock.lightsParameters[i].y; + vec3 lightColor = fyrox_lightsBlock.lightsColorRadius[i].xyz; + float radius = fyrox_lightsBlock.lightsColorRadius[i].w; + vec3 lightPosition = fyrox_lightsBlock.lightsPosition[i]; + vec3 direction = fyrox_lightsBlock.lightsDirection[i]; + + // Calculate lighting. + vec3 toFragment = fragmentPosition - lightPosition; + float distance = length(toFragment); + vec3 toFragmentNormalized = toFragment / distance; + float distanceAttenuation = S_LightDistanceAttenuation(distance, radius); + float spotAngleCos = dot(toFragmentNormalized, direction); + float directionalAttenuation = smoothstep(halfConeAngleCos, halfHotspotAngleCos, spotAngleCos); + lighting += lightColor * (distanceAttenuation * directionalAttenuation); + } + + FragColor = vec4(lighting, 1.0) * color * S_SRGBToLinear(texture(diffuseTexture, texCoord)); + } + "#, + ) + ], +) \ No newline at end of file diff --git a/fyrox-material/src/shader/standard/opengl/standard_particle_system.shader b/fyrox-material/src/shader/standard/opengl/standard_particle_system.shader new file mode 100644 index 0000000000..db5b558ab9 --- /dev/null +++ b/fyrox-material/src/shader/standard/opengl/standard_particle_system.shader @@ -0,0 +1,172 @@ +( + name: "StandardParticleSystemShader", + + resources: [ + ( + name: "diffuseTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "fyrox_sceneDepth", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 1 + ), + ( + name: "properties", + kind: PropertyGroup([ + ( + name: "softBoundarySharpnessFactor", + kind: Float(value: 100.0), + ), + ( + name: "useLighting", + kind: Bool(value: false), + ), + ]), + binding: 0 + ), + ( + name: "fyrox_instanceData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 1 + ), + ( + name: "fyrox_cameraData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 2 + ), + ( + name: "fyrox_lightsBlock", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 3 + ), + ( + name: "fyrox_lightData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 4 + ), + ], + + disabled_passes: ["GBuffer", "DirectionalShadow", "PointShadow", "SpotShadow"], + + passes: [ + ( + name: "Forward", + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: Some(Less), + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 2) in float particleSize; + layout(location = 3) in float particleRotation; + layout(location = 4) in vec4 vertexColor; + + out vec2 texCoord; + out vec4 color; + out vec3 fragmentPosition; + + void main() + { + color = S_SRGBToLinear(vertexColor); + texCoord = vertexTexCoord; + vec2 vertexOffset = S_RotateVec2(vertexTexCoord * 2.0 - 1.0, particleRotation); + vec4 worldPosition = fyrox_instanceData.worldMatrix * vec4(vertexPosition, 1.0); + vec3 offset = (vertexOffset.x * fyrox_cameraData.sideVector + vertexOffset.y * fyrox_cameraData.upVector) * particleSize; + vec4 finalPosition = worldPosition + vec4(offset.x, offset.y, offset.z, 0.0); + fragmentPosition = finalPosition.xyz; + gl_Position = fyrox_cameraData.viewProjectionMatrix * finalPosition; + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec2 texCoord; + in vec4 color; + in vec3 fragmentPosition; + + float toProjSpace(float z) + { + return (fyrox_cameraData.zFar * fyrox_cameraData.zNear) / (fyrox_cameraData.zFar - z * fyrox_cameraData.zRange); + } + + void main() + { + ivec2 depthTextureSize = textureSize(fyrox_sceneDepth, 0); + vec2 pixelSize = vec2(1.0 / float(depthTextureSize.x), 1.0 / float(depthTextureSize.y)); + float sceneDepth = toProjSpace(texture(fyrox_sceneDepth, gl_FragCoord.xy * pixelSize).r); + float fragmentDepth = toProjSpace(gl_FragCoord.z); + float depthOpacity = smoothstep((sceneDepth - fragmentDepth) * properties.softBoundarySharpnessFactor, 0.0, 1.0); + + vec3 lighting; + if (properties.useLighting) { + lighting = fyrox_lightData.ambientLightColor.xyz; + for(int i = 0; i < min(fyrox_lightsBlock.lightCount, 16); ++i) { + // "Unpack" light parameters. + float halfHotspotAngleCos = fyrox_lightsBlock.lightsParameters[i].x; + float halfConeAngleCos = fyrox_lightsBlock.lightsParameters[i].y; + vec3 lightColor = fyrox_lightsBlock.lightsColorRadius[i].xyz; + float radius = fyrox_lightsBlock.lightsColorRadius[i].w; + vec3 lightPosition = fyrox_lightsBlock.lightsPosition[i]; + vec3 direction = fyrox_lightsBlock.lightsDirection[i]; + + // Calculate lighting. + vec3 toFragment = fragmentPosition - lightPosition; + float distance = length(toFragment); + vec3 toFragmentNormalized = toFragment / distance; + float distanceAttenuation = S_LightDistanceAttenuation(distance, radius); + float spotAngleCos = dot(toFragmentNormalized, direction); + float directionalAttenuation = smoothstep(halfConeAngleCos, halfHotspotAngleCos, spotAngleCos); + lighting += lightColor * (distanceAttenuation * directionalAttenuation); + } + } else { + lighting = vec3(1.0); + } + + FragColor = vec4(lighting, 1.0) * color * S_SRGBToLinear(texture(diffuseTexture, texCoord)).r; + FragColor.a *= depthOpacity; + } + "#, + ) + ], +) \ No newline at end of file diff --git a/fyrox-material/src/shader/standard/opengl/standard_sprite.shader b/fyrox-material/src/shader/standard/opengl/standard_sprite.shader new file mode 100644 index 0000000000..428a5e5985 --- /dev/null +++ b/fyrox-material/src/shader/standard/opengl/standard_sprite.shader @@ -0,0 +1,102 @@ +( + name: "StandardSpriteShader", + + resources: [ + ( + name: "diffuseTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "fyrox_instanceData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 0 + ), + ( + name: "fyrox_cameraData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 1 + ), + ], + + disabled_passes: ["GBuffer", "DirectionalShadow", "PointShadow", "SpotShadow"], + + passes: [ + ( + name: "Forward", + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: false, + stencil_test: None, + depth_test: Some(Less), + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 2) in vec4 vertexParams; + layout(location = 3) in vec4 vertexColor; + + out vec2 texCoord; + out vec4 color; + + void main() + { + float size = vertexParams.x; + float rotation = vertexParams.y; + float dx = vertexParams.z; + float dy = vertexParams.w; + + texCoord = vertexTexCoord; + color = vertexColor; + vec2 vertexOffset = S_RotateVec2(vec2(dx, dy), rotation); + vec4 worldPosition = fyrox_instanceData.worldMatrix * vec4(vertexPosition, 1.0); + vec3 offset = (vertexOffset.x * fyrox_cameraData.sideVector + vertexOffset.y * fyrox_cameraData.upVector) * size; + gl_Position = fyrox_cameraData.viewProjectionMatrix * (worldPosition + vec4(offset.x, offset.y, offset.z, 0.0)); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec2 texCoord; + in vec4 color; + + void main() + { + FragColor = color * S_SRGBToLinear(texture(diffuseTexture, texCoord)); + } + "#, + ) + ], +) \ No newline at end of file diff --git a/fyrox-material/src/shader/standard/opengl/terrain.shader b/fyrox-material/src/shader/standard/opengl/terrain.shader new file mode 100644 index 0000000000..3b5e036627 --- /dev/null +++ b/fyrox-material/src/shader/standard/opengl/terrain.shader @@ -0,0 +1,511 @@ +( + name: "StandardTerrainShader", + + resources: [ + ( + name: "diffuseTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "normalTexture", + kind: Texture(kind: Sampler2D, fallback: Normal), + binding: 1 + ), + ( + name: "metallicTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 2 + ), + ( + name: "roughnessTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 3 + ), + ( + name: "heightTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 4 + ), + ( + name: "emissionTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 5 + ), + ( + name: "lightmapTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 6 + ), + ( + name: "aoTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 7 + ), + ( + name: "maskTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 8 + ), + ( + name: "heightMapTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 9 + ), + ( + name: "holeMaskTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 10 + ), + ( + name: "properties", + kind: PropertyGroup([ + ( + name: "nodeUvOffsets", + kind: Vector4(value: (0.0, 0.0, 0.0, 0.0)), + ), + ( + name: "texCoordScale", + kind: Vector2(value: (1.0, 1.0)), + ), + ( + name: "layerIndex", + kind: UInt(value: 0), + ), + ( + name: "emissionStrength", + kind: Vector3(value: (2.0, 2.0, 2.0)), + ), + ( + name: "diffuseColor", + kind: Color(r: 255, g: 255, b: 255, a: 255), + ), + ( + name: "parallaxCenter", + kind: Float(value: 0.0), + ), + ( + name: "parallaxScale", + kind: Float(value: 0.08), + ), + ]), + binding: 0 + ), + ( + name: "fyrox_instanceData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 1 + ), + ( + name: "fyrox_graphicsSettings", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 2 + ), + ( + name: "fyrox_cameraData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 3 + ), + ( + name: "fyrox_lightData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 4 + ), + ], + + passes: [ + ( + name: "GBuffer", + draw_parameters: DrawParameters( + cull_face: Some(Back), + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(LessOrEqual), + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Max + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 2) in vec3 vertexNormal; + layout(location = 3) in vec4 vertexTangent; + layout(location = 6) in vec2 vertexSecondTexCoord; + + out vec3 position; + out vec3 normal; + out vec2 texCoord; + out vec3 tangent; + out vec3 binormal; + out vec2 secondTexCoord; + + void main() + { + // Each node has tex coords in [0; 1] range, here we must scale and offset it + // to match the actual position. + vec2 actualTexCoords = vec2(vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy); + vec2 heightSize = vec2(textureSize(heightMapTexture, 0)); + vec2 innerSize = heightSize - 3.0; + vec2 pixelSize = 1.0 / heightSize; + vec2 heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; + float height = texture(heightMapTexture, heightCoords).r; + vec4 finalVertexPosition = vec4(vertexPosition.x, height, vertexPosition.z, 1.0); + float hx0 = texture(heightMapTexture, heightCoords + vec2(-1.0, 0.0) * pixelSize).r; + float hx1 = texture(heightMapTexture, heightCoords + vec2(1.0, 0.0) * pixelSize).r; + float hy0 = texture(heightMapTexture, heightCoords + vec2(0.0, -1.0) * pixelSize).r; + float hy1 = texture(heightMapTexture, heightCoords + vec2(0.0, 1.0) * pixelSize).r; + + vec3 n = vec3((hx0 - hx1) / 2.0, 1.0, (hy0 - hy1) / 2.0); + vec3 tan = vec3(n.y, -n.x, 0.0); + + mat3 nm = mat3(fyrox_instanceData.worldMatrix); + normal = normalize(nm * n); + tangent = normalize(nm * tan); + binormal = normalize(-1.0 * cross(normal, tangent)); + texCoord = actualTexCoords; + position = vec3(fyrox_instanceData.worldMatrix * finalVertexPosition); + secondTexCoord = vertexSecondTexCoord; + gl_Position = fyrox_instanceData.worldViewProjection * finalVertexPosition; + } + "#, + fragment_shader: + r#" + layout(location = 0) out vec4 outColor; + layout(location = 1) out vec4 outNormal; + layout(location = 2) out vec4 outAmbient; + layout(location = 3) out vec4 outMaterial; + layout(location = 4) out uint outDecalMask; + + in vec3 position; + in vec3 normal; + in vec2 texCoord; + in vec3 tangent; + in vec3 binormal; + in vec2 secondTexCoord; + + void main() + { + if (texture(holeMaskTexture, texCoord).r < 0.5) discard; + + mat3 tangentSpace = mat3(tangent, binormal, normal); + vec3 toFragment = normalize(position - fyrox_cameraData.position); + + vec2 tc; + if (fyrox_graphicsSettings.usePOM) { + vec3 toFragmentTangentSpace = normalize(transpose(tangentSpace) * toFragment); + tc = S_ComputeParallaxTextureCoordinates( + heightTexture, + toFragmentTangentSpace, + texCoord * properties.texCoordScale, + properties.parallaxCenter, + properties.parallaxScale + ); + } else { + tc = texCoord * properties.texCoordScale; + } + + outColor = properties.diffuseColor * texture(diffuseTexture, tc); + + vec3 n = normalize(texture(normalTexture, tc).xyz * 2.0 - 1.0); + outNormal = vec4(normalize(tangentSpace * n) * 0.5 + 0.5, 1.0); + + outMaterial.x = texture(metallicTexture, tc).r; + outMaterial.y = texture(roughnessTexture, tc).r; + outMaterial.z = texture(aoTexture, tc).r; + outMaterial.a = 1.0; + + outAmbient.xyz = properties.emissionStrength * texture(emissionTexture, tc).rgb + texture(lightmapTexture, secondTexCoord).rgb; + outAmbient.a = 1.0; + + outDecalMask = properties.layerIndex; + + float mask = texture(maskTexture, texCoord).r; + + outColor.a = mask; + outAmbient.a = mask; + outNormal.a = mask; + outMaterial.a = mask; + } + "#, + ), + ( + name: "Forward", + draw_parameters: DrawParameters( + cull_face: Some(Back), + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Max + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + + out vec3 position; + out vec2 texCoord; + + void main() + { + vec2 actualTexCoords = vec2(vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy); + vec2 heightSize = vec2(textureSize(heightMapTexture, 0)); + vec2 innerSize = heightSize - 3.0; + vec2 pixelSize = 1.0 / heightSize; + vec2 heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; + float height = texture(heightMapTexture, heightCoords).r; + vec4 finalVertexPosition = vec4(vertexPosition.x, height, vertexPosition.z, 1.0); + + gl_Position = fyrox_instanceData.worldViewProjection * finalVertexPosition; + texCoord = actualTexCoords; + } + "#, + + fragment_shader: + r#" + uniform vec4 diffuseColor; + + out vec4 FragColor; + + in vec2 texCoord; + + void main() + { + if (texture(holeMaskTexture, texCoord).r < 0.5) discard; + FragColor = properties.diffuseColor * S_SRGBToLinear(texture(diffuseTexture, texCoord)); + } + "#, + ), + ( + name: "SpotShadow", + + draw_parameters: DrawParameters ( + cull_face: Some(Back), + color_write: ColorMask( + red: false, + green: false, + blue: false, + alpha: false, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + vec2 actualTexCoords = vec2(vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy); + vec2 heightSize = vec2(textureSize(heightMapTexture, 0)); + vec2 innerSize = heightSize - 3.0; + vec2 pixelSize = 1.0 / heightSize; + vec2 heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; + float height = texture(heightMapTexture, heightCoords).r; + vec4 finalVertexPosition = vec4(vertexPosition.x, height, vertexPosition.z, 1.0); + + gl_Position = fyrox_instanceData.worldViewProjection * finalVertexPosition; + texCoord = actualTexCoords; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + + void main() + { + if (texture(holeMaskTexture, texCoord).r < 0.5) discard; + if (texture(diffuseTexture, texCoord).a < 0.2) discard; + } + "#, + ), + ( + name: "DirectionalShadow", + + draw_parameters: DrawParameters ( + cull_face: Some(Back), + color_write: ColorMask( + red: false, + green: false, + blue: false, + alpha: false, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + + void main() + { + vec2 actualTexCoords = vec2(vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy); + vec2 heightSize = vec2(textureSize(heightMapTexture, 0)); + vec2 innerSize = heightSize - 3.0; + vec2 pixelSize = 1.0 / heightSize; + vec2 heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; + float height = texture(heightMapTexture, heightCoords).r; + vec4 finalVertexPosition = vec4(vertexPosition.x, height, vertexPosition.z, 1.0); + + gl_Position = fyrox_instanceData.worldViewProjection * finalVertexPosition; + texCoord = actualTexCoords; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + + void main() + { + if (texture(holeMaskTexture, texCoord).r < 0.5) discard; + if (texture(diffuseTexture, texCoord).a < 0.2) discard; + } + "#, + ), + ( + name: "PointShadow", + + draw_parameters: DrawParameters ( + cull_face: Some(Back), + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + + out vec2 texCoord; + out vec3 worldPosition; + + void main() + { + vec2 actualTexCoords = vec2(vertexTexCoord * properties.nodeUvOffsets.zw + properties.nodeUvOffsets.xy); + vec2 heightSize = vec2(textureSize(heightMapTexture, 0)); + vec2 innerSize = heightSize - 3.0; + vec2 pixelSize = 1.0 / heightSize; + vec2 heightCoords = (actualTexCoords * innerSize + 1.5) * pixelSize; + float height = texture(heightMapTexture, heightCoords).r; + vec4 finalVertexPosition = vec4(vertexPosition.x, height, vertexPosition.z, 1.0); + + gl_Position = fyrox_instanceData.worldViewProjection * finalVertexPosition; + worldPosition = (fyrox_instanceData.worldMatrix * finalVertexPosition).xyz; + texCoord = actualTexCoords; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + in vec3 worldPosition; + + layout(location = 0) out float depth; + + void main() + { + if (texture(holeMaskTexture, texCoord).r < 0.5) discard; + if (texture(diffuseTexture, texCoord).a < 0.2) discard; + depth = length(fyrox_lightData.lightPosition - worldPosition); + } + "#, + ) + ], +) \ No newline at end of file diff --git a/fyrox-material/src/shader/standard/opengl/tile.shader b/fyrox-material/src/shader/standard/opengl/tile.shader new file mode 100644 index 0000000000..4d5f842433 --- /dev/null +++ b/fyrox-material/src/shader/standard/opengl/tile.shader @@ -0,0 +1,123 @@ +( + name: "Standard2DShader", + + resources: [ + ( + name: "diffuseTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "fyrox_instanceData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 0 + ), + ( + name: "fyrox_lightData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 1 + ), + ( + name: "fyrox_lightsBlock", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 2 + ), + ], + + disabled_passes: ["GBuffer", "DirectionalShadow", "PointShadow", "SpotShadow"], + + passes: [ + ( + name: "Forward", + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(LessOrEqual), + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 2) in vec4 vertexColor; + + out vec2 texCoord; + out vec4 color; + out vec3 fragmentPosition; + + void main() + { + texCoord = vertexTexCoord / vec2(textureSize(diffuseTexture, 0)); + fragmentPosition = (fyrox_instanceData.worldMatrix * vec4(vertexPosition, 1.0)).xyz; + gl_Position = fyrox_instanceData.worldViewProjection * vec4(vertexPosition, 1.0); + color = vertexColor; + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec2 texCoord; + in vec4 color; + in vec3 fragmentPosition; + + void main() + { + vec3 lighting = fyrox_lightData.ambientLightColor.xyz; + for(int i = 0; i < min(fyrox_lightsBlock.lightCount, 16); ++i) { + // "Unpack" light parameters. + float halfHotspotAngleCos = fyrox_lightsBlock.lightsParameters[i].x; + float halfConeAngleCos = fyrox_lightsBlock.lightsParameters[i].y; + vec3 lightColor = fyrox_lightsBlock.lightsColorRadius[i].xyz; + float radius = fyrox_lightsBlock.lightsColorRadius[i].w; + vec3 lightPosition = fyrox_lightsBlock.lightsPosition[i]; + vec3 direction = fyrox_lightsBlock.lightsDirection[i]; + + // Calculate lighting. + vec3 toFragment = fragmentPosition - lightPosition; + float distance = length(toFragment); + vec3 toFragmentNormalized = toFragment / distance; + float distanceAttenuation = S_LightDistanceAttenuation(distance, radius); + float spotAngleCos = dot(toFragmentNormalized, direction); + float directionalAttenuation = smoothstep(halfConeAngleCos, halfHotspotAngleCos, spotAngleCos); + lighting += lightColor * (distanceAttenuation * directionalAttenuation); + } + + FragColor = vec4(lighting, 1.0) * color * S_SRGBToLinear(texture(diffuseTexture, texCoord)); + } + "#, + ) + ], +) \ No newline at end of file diff --git a/fyrox-material/src/shader/standard/opengl/widget.shader b/fyrox-material/src/shader/standard/opengl/widget.shader new file mode 100644 index 0000000000..dfb41726b7 --- /dev/null +++ b/fyrox-material/src/shader/standard/opengl/widget.shader @@ -0,0 +1,161 @@ +( + name: "Widget", + resources: [ + ( + name: "fyrox_widgetTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "fyrox_widgetData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 0 + ), + ], + disabled_passes: ["GBuffer", "DirectionalShadow", "PointShadow", "SpotShadow"], + passes: [ + ( + name: "Forward", + + // Drawing parameters explicitly controlled from code. + + vertex_shader: + r#" + layout (location = 0) in vec2 vertexPosition; + layout (location = 1) in vec2 vertexTexCoord; + layout (location = 2) in vec4 vertexColor; + + out vec2 texCoord; + out vec4 color; + out vec2 localPosition; + + void main() + { + texCoord = vertexTexCoord; + color = vertexColor; + + localPosition = vertexPosition; + vec3 worldSpaceVertex = fyrox_widgetData.worldMatrix * vec3(vertexPosition, 1.0); + gl_Position = fyrox_widgetData.projectionMatrix * vec4(worldSpaceVertex, 1.0); + } + "#, + + fragment_shader: + r#" + // IMPORTANT: UI is rendered in sRGB color space! + out vec4 fragColor; + + in vec2 texCoord; + in vec4 color; + in vec2 localPosition; + + float project_point(vec2 a, vec2 b, vec2 p) { + vec2 ab = b - a; + return clamp(dot(p - a, ab) / dot(ab, ab), 0.0, 1.0); + } + + int find_stop_index(float t) { + int idx = 0; + + for (int i = 0; i < fyrox_widgetData.gradientPointCount; ++i) { + if (t > fyrox_widgetData.gradientStops[i]) { + idx = i; + } + } + + return idx; + } + + void main() + { + vec2 size = vec2(fyrox_widgetData.boundsMax.x - fyrox_widgetData.boundsMin.x, fyrox_widgetData.boundsMax.y - fyrox_widgetData.boundsMin.y); + vec2 normalizedPosition = (localPosition - fyrox_widgetData.boundsMin) / size; + + if (fyrox_widgetData.brushType == 0) { + // Solid color + fragColor = fyrox_widgetData.solidColor; + } else { + // Gradient brush + float t = 0.0; + + if (fyrox_widgetData.brushType == 1) { + // Linear gradient + t = project_point(fyrox_widgetData.gradientOrigin, fyrox_widgetData.gradientEnd, normalizedPosition); + } else if (fyrox_widgetData.brushType == 2) { + // Radial gradient + t = clamp(length(normalizedPosition - fyrox_widgetData.gradientOrigin), 0.0, 1.0); + } + + int current = find_stop_index(t); + int next = min(current + 1, fyrox_widgetData.gradientPointCount); + float delta = fyrox_widgetData.gradientStops[next] - fyrox_widgetData.gradientStops[current]; + float mix_factor = (t - fyrox_widgetData.gradientStops[current]) / delta; + fragColor = mix(fyrox_widgetData.gradientColors[current], fyrox_widgetData.gradientColors[next], mix_factor); + } + + vec4 diffuseColor = texture(fyrox_widgetTexture, texCoord); + + if (fyrox_widgetData.isFont) + { + fragColor.a *= diffuseColor.r; + } + else + { + fragColor *= diffuseColor; + } + + fragColor.a *= fyrox_widgetData.opacity; + + fragColor *= color; + } + "#, + ), + ( + name: "Clip", + + draw_parameters: DrawParameters( + cull_face: None, + color_write: ColorMask( + red: false, + green: false, + blue: false, + alpha: false, + ), + depth_write: false, + stencil_test: None, + depth_test: None, + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Incr, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout (location = 0) in vec2 vertexPosition; + + void main() + { + vec3 worldSpaceVertex = fyrox_widgetData.worldMatrix * vec3(vertexPosition, 1.0); + gl_Position = fyrox_widgetData.projectionMatrix * vec4(worldSpaceVertex, 1.0); + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + void main() + { + FragColor = vec4(1.0); + } + "#, + ) + ] +) \ No newline at end of file diff --git a/fyrox-material/src/shader/standard/standard-two-sides.shader b/fyrox-material/src/shader/standard/wgpu/standard-two-sides.shader similarity index 100% rename from fyrox-material/src/shader/standard/standard-two-sides.shader rename to fyrox-material/src/shader/standard/wgpu/standard-two-sides.shader diff --git a/fyrox-material/src/shader/standard/standard.shader b/fyrox-material/src/shader/standard/wgpu/standard.shader similarity index 100% rename from fyrox-material/src/shader/standard/standard.shader rename to fyrox-material/src/shader/standard/wgpu/standard.shader diff --git a/fyrox-material/src/shader/standard/standard2d.shader b/fyrox-material/src/shader/standard/wgpu/standard2d.shader similarity index 100% rename from fyrox-material/src/shader/standard/standard2d.shader rename to fyrox-material/src/shader/standard/wgpu/standard2d.shader diff --git a/fyrox-material/src/shader/standard/standard_particle_system.shader b/fyrox-material/src/shader/standard/wgpu/standard_particle_system.shader similarity index 100% rename from fyrox-material/src/shader/standard/standard_particle_system.shader rename to fyrox-material/src/shader/standard/wgpu/standard_particle_system.shader diff --git a/fyrox-material/src/shader/standard/standard_sprite.shader b/fyrox-material/src/shader/standard/wgpu/standard_sprite.shader similarity index 100% rename from fyrox-material/src/shader/standard/standard_sprite.shader rename to fyrox-material/src/shader/standard/wgpu/standard_sprite.shader diff --git a/fyrox-material/src/shader/standard/terrain.shader b/fyrox-material/src/shader/standard/wgpu/terrain.shader similarity index 100% rename from fyrox-material/src/shader/standard/terrain.shader rename to fyrox-material/src/shader/standard/wgpu/terrain.shader diff --git a/fyrox-material/src/shader/standard/tile.shader b/fyrox-material/src/shader/standard/wgpu/tile.shader similarity index 100% rename from fyrox-material/src/shader/standard/tile.shader rename to fyrox-material/src/shader/standard/wgpu/tile.shader diff --git a/fyrox-material/src/shader/standard/widget.shader b/fyrox-material/src/shader/standard/wgpu/widget.shader similarity index 100% rename from fyrox-material/src/shader/standard/widget.shader rename to fyrox-material/src/shader/standard/wgpu/widget.shader From 58c3293db29519e9953a34a6e6c9fd492f5d7ab8 Mon Sep 17 00:00:00 2001 From: nicehack Date: Tue, 21 Jul 2026 17:47:23 +0500 Subject: [PATCH 19/34] GL gltf shader restored and two backends support implemented --- fyrox-impl/src/resource/gltf/material.rs | 16 +- .../resource/gltf/opengl/gltf_standard.shader | 617 ++++++++++++++++++ .../gltf/{ => wgpu}/gltf_standard.shader | 0 3 files changed, 632 insertions(+), 1 deletion(-) create mode 100644 fyrox-impl/src/resource/gltf/opengl/gltf_standard.shader rename fyrox-impl/src/resource/gltf/{ => wgpu}/gltf_standard.shader (100%) diff --git a/fyrox-impl/src/resource/gltf/material.rs b/fyrox-impl/src/resource/gltf/material.rs index 6b9ae417a5..56e032428f 100644 --- a/fyrox-impl/src/resource/gltf/material.rs +++ b/fyrox-impl/src/resource/gltf/material.rs @@ -49,10 +49,24 @@ use crate::resource::texture::TextureMinificationFilter as FyroxMinFilter; use gltf::texture::MagFilter as GltfMagFilter; use gltf::texture::MinFilter as GltfMinFilter; +#[cfg(feature = "backend_wgpu")] +macro_rules! embedded_gltf_shader { + ($file:literal) => { + embedded_data_source!(concat!("wgpu/", $file)) + }; +} + +#[cfg(feature = "backend_opengl")] +macro_rules! embedded_gltf_shader { + ($file:literal) => { + embedded_data_source!(concat!("opengl/", $file)) + }; +} + pub static GLTF_SHADER: LazyLock> = LazyLock::new(|| { BuiltInResource::new( "GltfShader", - embedded_data_source!("gltf_standard.shader"), + embedded_gltf_shader!("gltf_standard.shader"), |data| { ShaderResource::new_ok( uuid!("33ee0142-f345-4c0a-9aca-d1f684a3485b"), diff --git a/fyrox-impl/src/resource/gltf/opengl/gltf_standard.shader b/fyrox-impl/src/resource/gltf/opengl/gltf_standard.shader new file mode 100644 index 0000000000..fd7446bee0 --- /dev/null +++ b/fyrox-impl/src/resource/gltf/opengl/gltf_standard.shader @@ -0,0 +1,617 @@ +( + name: "GLTFShader", + + resources: [ + ( + name: "diffuseTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 0 + ), + ( + name: "normalTexture", + kind: Texture(kind: Sampler2D, fallback: Normal), + binding: 1 + ), + ( + name: "metallicRoughnessTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 2 + ), + ( + name: "heightTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 3 + ), + ( + name: "emissionTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 4 + ), + ( + name: "lightmapTexture", + kind: Texture(kind: Sampler2D, fallback: Black), + binding: 5 + ), + ( + name: "aoTexture", + kind: Texture(kind: Sampler2D, fallback: White), + binding: 6 + ), + ( + name: "blendShapesStorage", + kind: Texture(kind: Sampler3D, fallback: Volume), + binding: 7 + ), + ( + name: "properties", + kind: PropertyGroup([ + ( + name: "texCoordScale", + kind: Vector2(value: (1.0, 1.0)), + ), + ( + name: "layerIndex", + kind: UInt(value: 0), + ), + ( + name: "emissionStrength", + kind: Vector3(value: (2.0, 2.0, 2.0)), + ), + ( + name: "diffuseColor", + kind: Color(r: 255, g: 255, b: 255, a: 255), + ), + ( + name: "parallaxCenter", + kind: Float(value: 0.0), + ), + ( + name: "parallaxScale", + kind: Float(value: 0.08), + ), + ( + name: "metallicFactor", + kind: Float(value: 1.0) + ), + ( + name: "roughnessFactor", + kind: Float(value: 1.0) + ) + ]), + binding: 0 + ), + ( + name: "fyrox_instanceData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 1 + ), + ( + name: "fyrox_boneMatrices", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 2 + ), + ( + name: "fyrox_graphicsSettings", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 3 + ), + ( + name: "fyrox_cameraData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 4 + ), + ( + name: "fyrox_lightData", + kind: PropertyGroup([ + // Autogenerated + ]), + binding: 5 + ), + ], + + passes: [ + ( + name: "GBuffer", + draw_parameters: DrawParameters( + cull_face: Some(Back), + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 2) in vec3 vertexNormal; + layout(location = 3) in vec4 vertexTangent; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + layout(location = 6) in vec2 vertexSecondTexCoord; + + out vec3 position; + out vec3 normal; + out vec2 texCoord; + out vec3 tangent; + out vec3 binormal; + out vec2 secondTexCoord; + + void main() + { + vec4 localPosition = vec4(0); + vec3 localNormal = vec3(0); + vec3 localTangent = vec3(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + vec3 inputNormal = vertexNormal; + vec3 inputTangent = vertexTangent.xyz; + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + inputNormal += offsets.normal * weight; + inputTangent += offsets.tangent * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + int i0 = int(boneIndices.x); + int i1 = int(boneIndices.y); + int i2 = int(boneIndices.z); + int i3 = int(boneIndices.w); + + mat4 m0 = fyrox_boneMatrices.matrices[i0]; + mat4 m1 = fyrox_boneMatrices.matrices[i1]; + mat4 m2 = fyrox_boneMatrices.matrices[i2]; + mat4 m3 = fyrox_boneMatrices.matrices[i3]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + + localNormal += mat3(m0) * inputNormal * boneWeights.x; + localNormal += mat3(m1) * inputNormal * boneWeights.y; + localNormal += mat3(m2) * inputNormal * boneWeights.z; + localNormal += mat3(m3) * inputNormal * boneWeights.w; + + localTangent += mat3(m0) * inputTangent * boneWeights.x; + localTangent += mat3(m1) * inputTangent * boneWeights.y; + localTangent += mat3(m2) * inputTangent * boneWeights.z; + localTangent += mat3(m3) * inputTangent * boneWeights.w; + } + else + { + localPosition = inputPosition; + localNormal = inputNormal; + localTangent = inputTangent; + } + + mat3 nm = mat3(fyrox_instanceData.worldMatrix); + normal = normalize(nm * localNormal); + tangent = normalize(nm * localTangent); + binormal = normalize(vertexTangent.w * cross(normal, tangent)); + texCoord = vertexTexCoord; + position = vec3(fyrox_instanceData.worldMatrix * localPosition); + secondTexCoord = vertexSecondTexCoord; + + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + } + "#, + fragment_shader: + r#" + layout(location = 0) out vec4 outColor; + layout(location = 1) out vec4 outNormal; + layout(location = 2) out vec4 outAmbient; + layout(location = 3) out vec4 outMaterial; + layout(location = 4) out uint outDecalMask; + + in vec3 position; + in vec3 normal; + in vec2 texCoord; + in vec3 tangent; + in vec3 binormal; + in vec2 secondTexCoord; + + void main() + { + mat3 tangentSpace = mat3(tangent, binormal, normal); + vec3 toFragment = normalize(position - fyrox_cameraData.position); + + vec2 tc; + if (fyrox_graphicsSettings.usePOM) { + vec3 toFragmentTangentSpace = normalize(transpose(tangentSpace) * toFragment); + tc = S_ComputeParallaxTextureCoordinates( + heightTexture, + toFragmentTangentSpace, + texCoord * properties.texCoordScale, + properties.parallaxCenter, + properties.parallaxScale + ); + } else { + tc = texCoord * properties.texCoordScale; + } + + outColor = properties.diffuseColor * texture(diffuseTexture, tc); + + // Alpha test. + if (outColor.a < 0.5) { + discard; + } + outColor.a = 1.0; + + vec4 n = normalize(texture(normalTexture, tc) * 2.0 - 1.0); + outNormal = vec4(normalize(tangentSpace * n.xyz) * 0.5 + 0.5, 1.0); + + outMaterial.x = properties.metallicFactor * texture(metallicRoughnessTexture, tc).b; // Metallic + outMaterial.y = properties.roughnessFactor * texture(metallicRoughnessTexture, tc).g; // Roughness + outMaterial.z = texture(aoTexture, tc).r; + outMaterial.a = 1.0; + + outAmbient.xyz = properties.emissionStrength * texture(emissionTexture, tc).rgb + texture(lightmapTexture, secondTexCoord).rgb; + outAmbient.a = 1.0; + + outDecalMask = properties.layerIndex; + } + "#, + ), + ( + name: "Forward", + draw_parameters: DrawParameters( + cull_face: Some(Back), + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: Some(BlendParameters( + func: BlendFunc( + sfactor: SrcAlpha, + dfactor: OneMinusSrcAlpha, + alpha_sfactor: SrcAlpha, + alpha_dfactor: OneMinusSrcAlpha, + ), + equation: BlendEquation( + rgb: Add, + alpha: Add + ) + )), + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + + out vec3 position; + out vec2 texCoord; + + void main() + { + vec4 localPosition = vec4(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + int i0 = int(boneIndices.x); + int i1 = int(boneIndices.y); + int i2 = int(boneIndices.z); + int i3 = int(boneIndices.w); + + mat4 m0 = fyrox_boneMatrices.matrices[i0]; + mat4 m1 = fyrox_boneMatrices.matrices[i1]; + mat4 m2 = fyrox_boneMatrices.matrices[i2]; + mat4 m3 = fyrox_boneMatrices.matrices[i3]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + } + else + { + localPosition = inputPosition; + } + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + out vec4 FragColor; + + in vec2 texCoord; + + void main() + { + FragColor = properties.diffuseColor * texture(diffuseTexture, texCoord); + } + "#, + ), + ( + name: "DirectionalShadow", + + draw_parameters: DrawParameters ( + cull_face: Some(Back), + color_write: ColorMask( + red: false, + green: false, + blue: false, + alpha: false, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + + out vec2 texCoord; + + void main() + { + vec4 localPosition = vec4(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + vec4 vertex = vec4(vertexPosition, 1.0); + + mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; + mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; + mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; + mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + } + else + { + localPosition = inputPosition; + } + + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + + void main() + { + if (texture(diffuseTexture, texCoord).a < 0.2) discard; + } + "#, + ), + ( + name: "SpotShadow", + + draw_parameters: DrawParameters ( + cull_face: Some(Back), + color_write: ColorMask( + red: false, + green: false, + blue: false, + alpha: false, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + + out vec2 texCoord; + + void main() + { + vec4 localPosition = vec4(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + vec4 vertex = vec4(vertexPosition, 1.0); + + mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; + mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; + mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; + mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + } + else + { + localPosition = inputPosition; + } + + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + + void main() + { + if (texture(diffuseTexture, texCoord).a < 0.2) discard; + } + "#, + ), + ( + name: "PointShadow", + + draw_parameters: DrawParameters ( + cull_face: Some(Back), + color_write: ColorMask( + red: true, + green: true, + blue: true, + alpha: true, + ), + depth_write: true, + stencil_test: None, + depth_test: Some(Less), + blend: None, + stencil_op: StencilOp( + fail: Keep, + zfail: Keep, + zpass: Keep, + write_mask: 0xFFFF_FFFF, + ), + scissor_box: None + ), + + vertex_shader: + r#" + layout(location = 0) in vec3 vertexPosition; + layout(location = 1) in vec2 vertexTexCoord; + layout(location = 4) in vec4 boneWeights; + layout(location = 5) in vec4 boneIndices; + + out vec2 texCoord; + out vec3 worldPosition; + + void main() + { + vec4 localPosition = vec4(0); + + vec4 inputPosition = vec4(vertexPosition, 1.0); + + for (int i = 0; i < fyrox_instanceData.blendShapesCount; ++i) { + TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(blendShapesStorage, gl_VertexID, i); + float weight = fyrox_instanceData.blendShapesWeights[i / 4][i % 4]; + inputPosition.xyz += offsets.position * weight; + } + + if (fyrox_instanceData.useSkeletalAnimation) + { + vec4 vertex = vec4(vertexPosition, 1.0); + + mat4 m0 = fyrox_boneMatrices.matrices[int(boneIndices.x)]; + mat4 m1 = fyrox_boneMatrices.matrices[int(boneIndices.y)]; + mat4 m2 = fyrox_boneMatrices.matrices[int(boneIndices.z)]; + mat4 m3 = fyrox_boneMatrices.matrices[int(boneIndices.w)]; + + localPosition += m0 * inputPosition * boneWeights.x; + localPosition += m1 * inputPosition * boneWeights.y; + localPosition += m2 * inputPosition * boneWeights.z; + localPosition += m3 * inputPosition * boneWeights.w; + } + else + { + localPosition = inputPosition; + } + + gl_Position = fyrox_instanceData.worldViewProjection * localPosition; + worldPosition = (fyrox_instanceData.worldMatrix * localPosition).xyz; + texCoord = vertexTexCoord; + } + "#, + + fragment_shader: + r#" + in vec2 texCoord; + in vec3 worldPosition; + + layout(location = 0) out float depth; + + void main() + { + if (texture(diffuseTexture, texCoord).a < 0.2) discard; + depth = length(fyrox_lightData.lightPosition - worldPosition); + } + "#, + ) + ], +) \ No newline at end of file diff --git a/fyrox-impl/src/resource/gltf/gltf_standard.shader b/fyrox-impl/src/resource/gltf/wgpu/gltf_standard.shader similarity index 100% rename from fyrox-impl/src/resource/gltf/gltf_standard.shader rename to fyrox-impl/src/resource/gltf/wgpu/gltf_standard.shader From e7b4bc8b524195c7db0f0244a1c15b74986cd483 Mon Sep 17 00:00:00 2001 From: nicehack Date: Wed, 22 Jul 2026 10:46:09 +0500 Subject: [PATCH 20/34] terrain crash fixed --- fyrox-graphics-wgpu/src/framebuffer.rs | 85 +++++++++++++++++++------- fyrox-graphics-wgpu/src/program.rs | 16 +++-- fyrox-graphics-wgpu/src/server.rs | 9 +++ 3 files changed, 85 insertions(+), 25 deletions(-) diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index 4ee5f09b94..3024697d0e 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -151,8 +151,9 @@ fn texture_format_for_attachment(tex: &GpuTexture) -> Option, } /// Wgpu implementation of [`GpuFrameBufferTrait`](fyrox_graphics::framebuffer::GpuFrameBufferTrait). @@ -237,6 +241,7 @@ impl WgpuFrameBuffer { pipeline_layout: &wgpu::PipelineLayout, element_kind: fyrox_graphics::ElementKind, has_color: bool, + texture_resource_formats: &[(usize, wgpu::TextureFormat)], ) -> wgpu::RenderPipeline { let needs_stencil = params.stencil_test.is_some() || params.stencil_op.zpass != fyrox_graphics::StencilAction::Keep @@ -245,6 +250,7 @@ impl WgpuFrameBuffer { let depth_fmt = df.unwrap_or(wgpu::TextureFormat::Depth32Float); let stencil_supported = format_has_stencil(depth_fmt); let effective_stencil = needs_stencil && stencil_supported; + let key = PipelineKey { program_ptr: program as *const WgpuProgram as usize, color_formats: color_formats.to_vec(), @@ -261,6 +267,7 @@ impl WgpuFrameBuffer { None => 0, }, extra_vert_count: all_layouts.len() as u8, + texture_resource_formats: texture_resource_formats.to_vec(), }; let key_hash = { let mut h = DefaultHasher::new(); @@ -563,7 +570,7 @@ impl WgpuFrameBuffer { } } } - let (_, pipeline_layout) = prog.get_or_create_layouts(&texture_formats); + let (_bind_group_layout, pipeline_layout) = prog.get_or_create_layouts(&texture_formats); let (all_layouts, extra_vert_count) = build_vertex_layouts(geo); let has_color = self.is_backbuffer || !self.color_attachments.is_empty(); @@ -577,6 +584,7 @@ impl WgpuFrameBuffer { &pipeline_layout, geo.element_kind(), has_color, + &texture_formats, ); let bind_group = create_bind_group(&server, prog, resources); @@ -1028,6 +1036,12 @@ fn create_bind_group( ) -> Option { let mut entries = Vec::new(); let mut texture_formats: Vec<(usize, wgpu::TextureFormat)> = Vec::new(); + + // Compute a cache key from all resource pointers and formats + let mut hasher = DefaultHasher::new(); + // Include program identity in the hash + hasher.write_usize(program as *const WgpuProgram as usize); + for group in groups { for binding in group.bindings { match binding { @@ -1039,6 +1053,10 @@ fn create_bind_group( let wt = texture.as_any().downcast_ref::()?; let ws = sampler.as_any().downcast_ref::()?; texture_formats.push((*loc, wt.format())); + // Hash the WgpuTexture and WgpuSampler thin pointers + format + binding + hasher.write_usize(wt as *const WgpuTexture as usize); + hasher.write_usize(ws as *const WgpuSampler as usize); + hasher.write_u32(*loc as u32); entries.push(wgpu::BindGroupEntry { binding: *loc as u32, resource: wgpu::BindingResource::TextureView(wt.wgpu_binding_view()), @@ -1059,16 +1077,24 @@ fn create_bind_group( data_usage, } => { let wb = buffer.as_any().downcast_ref::()?; + // Hash the WgpuBuffer thin pointer + binding + data usage + hasher.write_usize(wb as *const WgpuBuffer as usize); + hasher.write_u32(*loc as u32); match data_usage { - BufferDataUsage::UseEverything => entries.push(wgpu::BindGroupEntry { - binding: (*loc + UNIFORM_BINDING_OFFSET) as u32, - resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { - buffer: wb.wgpu_buffer(), - offset: 0, - size: None, - }), - }), + BufferDataUsage::UseEverything => { + hasher.write_u64(0); + entries.push(wgpu::BindGroupEntry { + binding: (*loc + UNIFORM_BINDING_OFFSET) as u32, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: wb.wgpu_buffer(), + offset: 0, + size: None, + }), + }); + } BufferDataUsage::UseSegment { offset, size } => { + hasher.write_u64(*offset as u64); + hasher.write_u64(*size as u64); let nonzero_size = std::num::NonZeroU64::new(*size as u64) .expect("BufferDataUsage::UseSegment size must be non-zero"); entries.push(wgpu::BindGroupEntry { @@ -1088,15 +1114,32 @@ fn create_bind_group( if entries.is_empty() { return None; } + + let key = hasher.finish(); + + // Check cache first + { + let cache = server.bind_group_cache.borrow(); + if let Some(bg) = cache.get(&key) { + return Some(bg.clone()); + } + } + let (bgl, _) = program.get_or_create_layouts(&texture_formats); - Some( - server - .state - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("BG"), - layout: &bgl, - entries: &entries, - }), - ) + let bind_group = server + .state + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("BG"), + layout: &bgl, + entries: &entries, + }); + + // Store in cache + server + .bind_group_cache + .borrow_mut() + .insert(key, bind_group.clone()); + + Some(bind_group) } diff --git a/fyrox-graphics-wgpu/src/program.rs b/fyrox-graphics-wgpu/src/program.rs index 20651b1d8c..0f3c9331e1 100644 --- a/fyrox-graphics-wgpu/src/program.rs +++ b/fyrox-graphics-wgpu/src/program.rs @@ -312,7 +312,13 @@ pub struct WgpuProgram { vertex_module: wgpu::ShaderModule, fragment_module: wgpu::ShaderModule, resources: Vec, - cached_layouts: RefCell>, + cached_layouts: RefCell< + Option<( + Vec<(usize, wgpu::TextureFormat)>, + wgpu::BindGroupLayout, + wgpu::PipelineLayout, + )>, + >, } impl GpuProgramTrait for WgpuProgram {} @@ -394,8 +400,10 @@ impl WgpuProgram { &self, texture_formats: &[(usize, wgpu::TextureFormat)], ) -> (wgpu::BindGroupLayout, wgpu::PipelineLayout) { - if let Some((ref bgl, ref pl)) = *self.cached_layouts.borrow() { - return (bgl.clone(), pl.clone()); + if let Some((ref cached_fmts, ref bgl, ref pl)) = *self.cached_layouts.borrow() { + if cached_fmts == texture_formats { + return (bgl.clone(), pl.clone()); + } } let server = self .server @@ -415,7 +423,7 @@ impl WgpuProgram { ..Default::default() }); let result = (bgl.clone(), pl.clone()); - *self.cached_layouts.borrow_mut() = Some(result.clone()); + *self.cached_layouts.borrow_mut() = Some((texture_formats.to_vec(), bgl, pl)); result } diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index 44f3de6c48..bb3c4e11da 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -75,6 +75,12 @@ pub struct WgpuState { /// identical pipelines across draw calls. The cache is stored in a [`RefCell`] /// and grows monotonically during the session. /// +/// # Bind Group Cache +/// +/// Bind groups are cached by a hash of resource pointers and texture formats to avoid +/// recreating identical bind groups across draw calls. This is critical for performance +/// since bind group creation is expensive. +/// /// # Backbuffer Lifecycle /// /// The backbuffer acquires a surface texture on the first draw call per frame. @@ -94,6 +100,8 @@ pub struct WgpuGraphicsServer { pub msaa_sample_count: u32, /// Hash-based cache of render pipelines, keyed by [`PipelineKey`]. pub pipeline_cache: RefCell>, + /// Hash-based cache of bind groups, keyed by resource pointers and texture formats. + pub bind_group_cache: RefCell>, weak_self: RefCell>>, /// Tracked GPU memory usage (buffers + textures). pub memory_usage: RefCell, @@ -259,6 +267,7 @@ impl WgpuGraphicsServer { named_objects, msaa_sample_count: msaa, pipeline_cache: RefCell::new(HashMap::new()), + bind_group_cache: RefCell::new(HashMap::new()), weak_self: RefCell::new(None), memory_usage: RefCell::new(ServerMemoryUsage::default()), pipeline_statistics: RefCell::new(PipelineStatistics::default()), From ffd24018e1821e5b0181263bf13f562f1b1dba3f Mon Sep 17 00:00:00 2001 From: nicehack Date: Fri, 24 Jul 2026 15:42:46 +0500 Subject: [PATCH 21/34] labels removed and layout caching implemented. --- fyrox-graphics-wgpu/src/framebuffer.rs | 4 ++-- fyrox-graphics-wgpu/src/program.rs | 27 ++++++++++++++++---------- fyrox-graphics-wgpu/src/read_buffer.rs | 2 +- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index 3024697d0e..13addf9882 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -595,7 +595,7 @@ impl WgpuFrameBuffer { .state .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("FrameEnc"), + label: None, }) }); @@ -849,7 +849,7 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { .state .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("ReadPxEnc"), + label: None, }); enc.copy_texture_to_buffer( wgpu::TexelCopyTextureInfo { diff --git a/fyrox-graphics-wgpu/src/program.rs b/fyrox-graphics-wgpu/src/program.rs index 0f3c9331e1..d970d67e33 100644 --- a/fyrox-graphics-wgpu/src/program.rs +++ b/fyrox-graphics-wgpu/src/program.rs @@ -32,6 +32,7 @@ use fyrox_graphics::{ }; use std::borrow::Cow; use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Weak; /// Returns the WGSL texture type for a given sampler kind. @@ -313,11 +314,10 @@ pub struct WgpuProgram { fragment_module: wgpu::ShaderModule, resources: Vec, cached_layouts: RefCell< - Option<( + HashMap< Vec<(usize, wgpu::TextureFormat)>, - wgpu::BindGroupLayout, - wgpu::PipelineLayout, - )>, + (wgpu::BindGroupLayout, wgpu::PipelineLayout), + >, >, } @@ -390,7 +390,7 @@ impl WgpuProgram { vertex_module: vert.module.clone(), fragment_module: frag.module.clone(), resources: resources.to_vec(), - cached_layouts: RefCell::new(None), + cached_layouts: RefCell::new(HashMap::new()), }) } @@ -400,20 +400,24 @@ impl WgpuProgram { &self, texture_formats: &[(usize, wgpu::TextureFormat)], ) -> (wgpu::BindGroupLayout, wgpu::PipelineLayout) { - if let Some((ref cached_fmts, ref bgl, ref pl)) = *self.cached_layouts.borrow() { - if cached_fmts == texture_formats { - return (bgl.clone(), pl.clone()); - } + + let mut cache = self.cached_layouts.borrow_mut(); + + if let Some((bgl, pl)) = cache.get(texture_formats) { + return (bgl.clone(), pl.clone()); } + let server = self .server .upgrade() .expect("WgpuGraphicsServer dropped before WgpuProgram"); + let bgl = create_bind_group_layout_with_formats( &server.state.device, &self.resources, texture_formats, ); + let pl = server .state .device @@ -422,8 +426,11 @@ impl WgpuProgram { bind_group_layouts: &[Some(&bgl)], ..Default::default() }); + let result = (bgl.clone(), pl.clone()); - *self.cached_layouts.borrow_mut() = Some((texture_formats.to_vec(), bgl, pl)); + + cache.insert(texture_formats.to_vec(), result.clone()); + result } diff --git a/fyrox-graphics-wgpu/src/read_buffer.rs b/fyrox-graphics-wgpu/src/read_buffer.rs index 01228f95a6..4600b9c69f 100644 --- a/fyrox-graphics-wgpu/src/read_buffer.rs +++ b/fyrox-graphics-wgpu/src/read_buffer.rs @@ -110,7 +110,7 @@ impl GpuAsyncReadBufferTrait for WgpuAsyncReadBuffer { .state .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("AsyncReadEnc"), + label: None }); encoder.copy_texture_to_buffer( wgpu::TexelCopyTextureInfo { From b7a2e719cf86abbb1158144b25331053ce70915b Mon Sep 17 00:00:00 2001 From: nicehack Date: Fri, 24 Jul 2026 16:16:42 +0500 Subject: [PATCH 22/34] optimize build vertex layouts --- fyrox-graphics-wgpu/src/framebuffer.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index 13addf9882..2e454df290 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -1008,16 +1008,22 @@ const EXTRA_VERTEX_LAYOUTS: &[(u32, &[wgpu::VertexAttribute])] = &[ /// on the server, so the shader reads valid (zeroed) data for missing attributes. fn build_vertex_layouts(geo: &WgpuGeometryBuffer) -> (Vec>, u32) { let geo_layouts = geo.vertex_buffer_layouts(); - let mut provided = std::collections::HashSet::new(); + + let mut provided_mask = 0u32; for layout in geo_layouts { for attr in layout.attributes { - provided.insert(attr.shader_location); + provided_mask |= 1 << attr.shader_location; } } - let mut all: Vec> = geo_layouts.to_vec(); + + let mut all: Vec> = + Vec::with_capacity(geo_layouts.len() + EXTRA_VERTEX_LAYOUTS.len()); + + all.extend_from_slice(geo_layouts); + let mut extra = 0u32; for &(loc, attrs) in EXTRA_VERTEX_LAYOUTS { - if !provided.contains(&loc) { + if (provided_mask & (1 << loc)) == 0 { all.push(wgpu::VertexBufferLayout { array_stride: 0, step_mode: wgpu::VertexStepMode::Vertex, @@ -1026,6 +1032,7 @@ fn build_vertex_layouts(geo: &WgpuGeometryBuffer) -> (Vec Date: Fri, 24 Jul 2026 16:33:42 +0500 Subject: [PATCH 23/34] all rendering is collected into one request and thus highly optimized --- fyrox-graphics-wgpu/src/framebuffer.rs | 402 +++++++------------------ fyrox-graphics-wgpu/src/server.rs | 82 +++++ 2 files changed, 196 insertions(+), 288 deletions(-) diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index 2e454df290..0dab647e04 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -454,164 +454,81 @@ impl WgpuFrameBuffer { resources: &[ResourceBindGroup], element_range: ElementRange, ) -> Result { - let server = self - .server - .upgrade() - .ok_or(FrameworkError::GraphicsServerUnavailable)?; - let geo = geometry - .as_any() - .downcast_ref::() - .ok_or_else(|| FrameworkError::Custom("Expected WgpuGeometryBuffer".into()))?; - let prog = program - .as_any() - .downcast_ref::() - .ok_or_else(|| FrameworkError::Custom("Expected WgpuProgram".into()))?; + let server = self.server.upgrade().ok_or(FrameworkError::GraphicsServerUnavailable)?; + let geo = geometry.as_any().downcast_ref::().ok_or_else(|| FrameworkError::Custom("Expected WgpuGeometryBuffer".into()))?; + let prog = program.as_any().downcast_ref::().ok_or_else(|| FrameworkError::Custom("Expected WgpuProgram".into()))?; let (offset, count) = match element_range { ElementRange::Full => (0, geo.element_count()), ElementRange::Specific { offset, count } => (offset, count), }; - if offset + count > geo.element_count() { - return Err(FrameworkError::InvalidElementRange { - start: offset, - end: offset + count, - total: geo.element_count(), - }); - } - if count == 0 { - return Ok(DrawCallStatistics { triangles: 0 }); - } - - let mut current_width = 0; - let mut current_height = 0; - - let surface_tex = if self.is_backbuffer { - if server.current_frame.borrow().is_none() { - match server.surface.get_current_texture() { - wgpu::CurrentSurfaceTexture::Success(t) - | wgpu::CurrentSurfaceTexture::Suboptimal(t) => { - *server.current_frame.borrow_mut() = Some(t); - } - wgpu::CurrentSurfaceTexture::Timeout => { - Log::warn("Surface texture timeout, skipping frame"); - return Ok(DrawCallStatistics { triangles: 0 }); - } - wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => { - let config = server.surface_config.read().unwrap(); - server.surface.configure(&server.state.device, &config); - Log::warn("Surface lost/outdated, reconfigured"); - return Ok(DrawCallStatistics { triangles: 0 }); - } - other => { - return Err(FrameworkError::Custom(format!( - "Surface texture error: {other:?}" - ))) - } - } - } + if offset + count > geo.element_count() { return Err(FrameworkError::InvalidElementRange { start: offset, end: offset + count, total: geo.element_count() }); } + if count == 0 { return Ok(DrawCallStatistics { triangles: 0 }); } - let frame = server.current_frame.borrow(); - let frame_ref = frame.as_ref().expect("frame should be Some after acquisition"); - - current_width = frame_ref.texture.size().width; - current_height = frame_ref.texture.size().height; - - Some( - frame_ref - .texture - .create_view(&wgpu::TextureViewDescriptor::default()), - ) - } else { - None - }; - - // Collect color formats from ALL attachments (MRT support). - // For backbuffer: single format from surface config. - // For offscreen FBOs: one format per color attachment (e.g. G-Buffer has 5). let color_formats: Vec = if self.is_backbuffer { vec![server.surface_config.read().unwrap().format] } else { - self.color_attachments - .iter() - .map(|a| { - texture_format_for_attachment(&a.texture) - .unwrap_or(wgpu::TextureFormat::Rgba8Unorm) - }) - .collect() - }; - - let df = if self.is_backbuffer { - Some(wgpu::TextureFormat::Depth24PlusStencil8) - } else { - self.depth_attachment - .as_ref() - .and_then(|a| texture_format_for_attachment(&a.texture)) + self.color_attachments.iter().map(|a| texture_format_for_attachment(&a.texture).unwrap_or(wgpu::TextureFormat::Rgba8Unorm)).collect() }; + let df = if self.is_backbuffer { Some(wgpu::TextureFormat::Depth24PlusStencil8) } else { self.depth_attachment.as_ref().and_then(|a| texture_format_for_attachment(&a.texture)) }; - // Collect actual texture formats from resources for layout creation let mut texture_formats: Vec<(usize, wgpu::TextureFormat)> = Vec::new(); for group in resources { for binding in group.bindings { - if let ResourceBinding::Texture { - texture, - binding: loc, - .. - } = binding - { - let wt = texture - .as_any() - .downcast_ref::() - .ok_or_else(|| { - FrameworkError::Custom( - "Expected WgpuTexture in resource binding".into(), - ) - })?; + if let ResourceBinding::Texture { texture, binding: loc, .. } = binding { + let wt = texture.as_any().downcast_ref::().ok_or_else(|| FrameworkError::Custom("Expected WgpuTexture".into()))?; texture_formats.push((*loc, wt.format())); } } } - let (_bind_group_layout, pipeline_layout) = prog.get_or_create_layouts(&texture_formats); + let (_bind_group_layout, pipeline_layout) = prog.get_or_create_layouts(&texture_formats); let (all_layouts, extra_vert_count) = build_vertex_layouts(geo); let has_color = self.is_backbuffer || !self.color_attachments.is_empty(); - let pipeline = self.get_or_create_pipeline( - &server, - prog, - params, - &all_layouts, - &color_formats, - df, - &pipeline_layout, - geo.element_kind(), - has_color, - &texture_formats, - ); + let pipeline = self.get_or_create_pipeline(&server, prog, params, &all_layouts, &color_formats, df, &pipeline_layout, geo.element_kind(), has_color, &texture_formats); let bind_group = create_bind_group(&server, prog, resources); - // Take or create frame encoder (batched: one encoder per frame, not per draw call). - let mut encoder = server.frame_encoder.borrow_mut().take().unwrap_or_else(|| { - server - .state - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: None, - }) - }); + let fb_id = self as *const _ as usize; + let requires_new_pass = { + let pass = server.active_pass.borrow(); + pass.is_none() + || pass.as_ref().unwrap().framebuffer_id != fb_id + || (self.is_backbuffer && server.backbuffer_needs_clear.get()) + || (!self.is_backbuffer && self.needs_clear.get()) + }; - // Create views for ALL color attachments (MRT). - let color_views: Vec = if self.is_backbuffer { - vec![surface_tex.expect("surface texture should be set for backbuffer")] - } else { - self.color_attachments - .iter() - .map(|att| { + if requires_new_pass { + server.flush_active_pass(); + + let mut current_width = 0; + let mut current_height = 0; + + let surface_tex = if self.is_backbuffer { + if server.current_frame.borrow().is_none() { + match server.surface.get_current_texture() { + wgpu::CurrentSurfaceTexture::Success(t) | wgpu::CurrentSurfaceTexture::Suboptimal(t) => { + *server.current_frame.borrow_mut() = Some(t); + } + wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => { + return Ok(DrawCallStatistics { triangles: 0 }); + } + other => return Err(FrameworkError::Custom(format!("Surface texture error: {other:?}"))) + } + } + let frame = server.current_frame.borrow(); + let frame_ref = frame.as_ref().unwrap(); + current_width = frame_ref.texture.size().width; + current_height = frame_ref.texture.size().height; + Some(frame_ref.texture.create_view(&wgpu::TextureViewDescriptor::default())) + } else { None }; + + let color_views: Vec = if self.is_backbuffer { + vec![surface_tex.unwrap()] + } else { + self.color_attachments.iter().map(|att| { if let Some(face) = att.cube_map_face() { - let wt = att - .texture - .as_any() - .downcast_ref::() - .expect("color attachment should be WgpuTexture"); + let wt = att.texture.as_any().downcast_ref::().unwrap(); wt.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { dimension: Some(wgpu::TextureViewDimension::D2), base_array_layer: cubemap_face_to_layer(face), @@ -620,171 +537,79 @@ impl WgpuFrameBuffer { ..Default::default() }) } else { - att.texture - .as_any() - .downcast_ref::() - .expect("color attachment should be WgpuTexture") - .wgpu_view() - .clone() + att.texture.as_any().downcast_ref::().unwrap().wgpu_view().clone() } - }) - .collect() - }; - - let depth_view = if self.is_backbuffer { - let mut backbuffer_depth_cache = self.backbuffer_depth_cache.borrow_mut(); - - let needs_recreate = match backbuffer_depth_cache.as_ref() { - Some((cw, ch, _)) => *cw != current_width || *ch != current_height, - None => true, + }).collect() }; - if needs_recreate && current_width > 0 && current_height > 0 { - let depth_texture = server.state.device.create_texture(&wgpu::TextureDescriptor { - label: Some("DynamicBackbufferDepth"), - size: wgpu::Extent3d { - width: current_width, - height: current_height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: server.msaa_sample_count, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth24PlusStencil8, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - view_formats: &[], - }); - *backbuffer_depth_cache = Some((current_width, current_height, depth_texture)); - } - - backbuffer_depth_cache - .as_ref() - .map(|(_, _, tex)| tex.create_view(&wgpu::TextureViewDescriptor::default())) - } else { - self.depth_attachment.as_ref().map(|a| { - let wt = a - .texture - .as_any() - .downcast_ref::() - .expect("depth attachment should be WgpuTexture"); - if let Some(face) = a.cube_map_face() { - wt.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { - dimension: Some(wgpu::TextureViewDimension::D2), - base_array_layer: cubemap_face_to_layer(face), - array_layer_count: Some(1), - mip_level_count: Some(1), - ..Default::default() - }) - } else { - wt.wgpu_view().clone() + let depth_view = if self.is_backbuffer { + let mut cache = self.backbuffer_depth_cache.borrow_mut(); + if match cache.as_ref() { + Some((cw, ch, _)) => *cw != current_width || *ch != current_height, + None => true, + } && current_width > 0 && current_height > 0 { + let depth_texture = server.state.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { width: current_width, height: current_height, depth_or_array_layers: 1 }, + mip_level_count: 1, + sample_count: server.msaa_sample_count, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth24PlusStencil8, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + *cache = Some((current_width, current_height, depth_texture)); } - }) - }; - - { - // Backbuffer clears once per frame (flag set by swap_buffers, consumed on first draw). - // Offscreen FBOs clear when their clear() was called. - let has_stencil_aspect = df.map(format_has_stencil).unwrap_or(false); - let (color_load, depth_load, stencil_load) = - if self.is_backbuffer && server.backbuffer_needs_clear.replace(false) { - ( - wgpu::LoadOp::Clear(wgpu::Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }), - wgpu::LoadOp::Clear(1.0), - if has_stencil_aspect { - wgpu::LoadOp::Clear(0) - } else { - wgpu::LoadOp::Load - }, - ) - } else if !self.is_backbuffer && self.needs_clear.replace(false) { - ( - wgpu::LoadOp::Clear(*self.pending_clear_color.borrow()), - wgpu::LoadOp::Clear(*self.pending_clear_depth.borrow()), - if has_stencil_aspect { - wgpu::LoadOp::Clear(0) - } else { - wgpu::LoadOp::Load - }, - ) - } else { - (wgpu::LoadOp::Load, wgpu::LoadOp::Load, wgpu::LoadOp::Load) - }; - - // Build color attachments for ALL render targets (MRT). - let color_attachments: Vec> = color_views - .iter() - .map(|view| { - Some(wgpu::RenderPassColorAttachment { - view, - resolve_target: None, - depth_slice: None, - ops: wgpu::Operations { - load: color_load, - store: wgpu::StoreOp::Store, - }, - }) + cache.as_ref().map(|(_, _, tex)| tex.create_view(&wgpu::TextureViewDescriptor::default())) + } else { + self.depth_attachment.as_ref().map(|a| { + let wt = a.texture.as_any().downcast_ref::().unwrap(); + if let Some(face) = a.cube_map_face() { + wt.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { + dimension: Some(wgpu::TextureViewDimension::D2), + base_array_layer: cubemap_face_to_layer(face), + array_layer_count: Some(1), + mip_level_count: Some(1), + ..Default::default() + }) + } else { wt.wgpu_view().clone() } }) - .collect(); - - let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("DrawRP"), - color_attachments: &color_attachments, - depth_stencil_attachment: depth_view.as_ref().map(|v| { - wgpu::RenderPassDepthStencilAttachment { - view: v, - depth_ops: Some(wgpu::Operations { - load: depth_load, - store: wgpu::StoreOp::Store, - }), - stencil_ops: Some(wgpu::Operations { - load: stencil_load, - store: wgpu::StoreOp::Store, - }), - } - }), - ..Default::default() - }); + }; - rp.set_viewport( - viewport.x() as f32, - viewport.y() as f32, - viewport.w() as f32, - viewport.h() as f32, - 0.0, - 1.0, - ); - rp.set_pipeline(&pipeline); - if let Some(st) = ¶ms.stencil_test { - rp.set_stencil_reference(st.ref_value); - } - if let Some(bg) = &bind_group { - rp.set_bind_group(0, bg, &[]); - } - let vbs = geo.vertex_buffers(); - for (i, vb) in vbs.iter().enumerate() { - rp.set_vertex_buffer(i as u32, vb.slice(..)); - } - let geo_buf_count = vbs.len() as u32; - for i in 0..extra_vert_count { - rp.set_vertex_buffer(geo_buf_count + i, server.dummy_vertex_buffer.slice(..)); - } - let eb = geo.element_buffer(); - rp.set_index_buffer(eb.slice(..), wgpu::IndexFormat::Uint32); + let has_stencil = df.map(format_has_stencil).unwrap_or(false); + let (color_load, depth_load, stencil_load) = if self.is_backbuffer && server.backbuffer_needs_clear.replace(false) { + (wgpu::LoadOp::Clear(wgpu::Color::BLACK), wgpu::LoadOp::Clear(1.0), if has_stencil { wgpu::LoadOp::Clear(0) } else { wgpu::LoadOp::Load }) + } else if !self.is_backbuffer && self.needs_clear.replace(false) { + (wgpu::LoadOp::Clear(*self.pending_clear_color.borrow()), wgpu::LoadOp::Clear(*self.pending_clear_depth.borrow()), if has_stencil { wgpu::LoadOp::Clear(0) } else { wgpu::LoadOp::Load }) + } else { + (wgpu::LoadOp::Load, wgpu::LoadOp::Load, wgpu::LoadOp::Load) + }; - let ipe = geo.element_kind().index_per_element(); - let start_idx = (offset * ipe) as u32; - let end_idx = ((offset + count) * ipe) as u32; - rp.draw_indexed(start_idx..end_idx, 0, 0..instance_count); + *server.active_pass.borrow_mut() = Some(crate::server::ActivePass { + framebuffer_id: fb_id, + color_views, + depth_view, + color_load, + depth_load, + stencil_load, + commands: Vec::new(), + }); } - // Render pass dropped here; encoder is free for next draw call. - // Return encoder to server for reuse by subsequent draw calls. - *server.frame_encoder.borrow_mut() = Some(encoder); + let ipe = geo.element_kind().index_per_element(); + + server.active_pass.borrow_mut().as_mut().unwrap().commands.push(crate::server::DrawCommand { + pipeline, + bind_group, + vertex_buffers: geo.vertex_buffers().iter().cloned().collect(), + extra_verts: extra_vert_count, + index_buffer: geo.element_buffer().clone(), + viewport, + stencil_ref: params.stencil_test.as_ref().map(|s| s.ref_value), + start_idx: (offset * ipe) as u32, + end_idx: ((offset + count) * ipe) as u32, + instances: instance_count, + }); Ok(DrawCallStatistics { triangles: count * instance_count as usize, @@ -824,6 +649,7 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { } fn read_pixels(&self, read_target: ReadTarget) -> Option> { let server = self.server.upgrade()?; + server.flush_active_pass(); // Flush any pending frame encoder so prior draws are submitted before readback. if let Some(encoder) = server.frame_encoder.borrow_mut().take() { server.state.queue.submit(std::iter::once(encoder.finish())); diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index bb3c4e11da..f8add9ee9d 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -47,6 +47,30 @@ use std::rc::{Rc, Weak}; use std::sync::{Arc, RwLock}; use winit::event_loop::ActiveEventLoop; use winit::window::{Window, WindowAttributes}; +use fyrox_core::math::Rect; + +pub struct DrawCommand { + pub pipeline: wgpu::RenderPipeline, + pub bind_group: Option, + pub vertex_buffers: Vec, + pub extra_verts: u32, + pub index_buffer: wgpu::Buffer, + pub viewport: Rect, + pub stencil_ref: Option, + pub start_idx: u32, + pub end_idx: u32, + pub instances: u32, +} + +pub struct ActivePass { + pub framebuffer_id: usize, + pub color_views: Vec, + pub depth_view: Option, + pub color_load: wgpu::LoadOp, + pub depth_load: wgpu::LoadOp, + pub stencil_load: wgpu::LoadOp, + pub commands: Vec, +} /// Core wgpu objects shared across the server. /// @@ -120,6 +144,7 @@ pub struct WgpuGraphicsServer { /// Storing it on the server (not per-framebuffer) because multiple framebuffers /// (G-Buffer, HDR, backbuffer) share the same frame and benefit from a single submit. pub frame_encoder: RefCell>, + pub active_pass: RefCell>, } impl WgpuGraphicsServer { @@ -277,6 +302,7 @@ impl WgpuGraphicsServer { backbuffer_needs_clear: Cell::new(true), backbuffer_depth_stencil: RefCell::new(None), frame_encoder: RefCell::new(None), + active_pass: RefCell::new(None), }); *server.weak_self.borrow_mut() = Some(Rc::downgrade(&server)); @@ -297,6 +323,59 @@ impl WgpuGraphicsServer { pub fn non_filtering_sampler(&self) -> &wgpu::Sampler { &self.non_filtering_sampler } + + pub fn flush_active_pass(&self) { + let Some(pass) = self.active_pass.borrow_mut().take() else { return; }; + + let mut encoder = self.frame_encoder.borrow_mut().take().unwrap_or_else(|| { + self.state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }) + }); + + let color_attachments: Vec<_> = pass.color_views.iter().map(|view| { + Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + depth_slice: None, + ops: wgpu::Operations { load: pass.color_load, store: wgpu::StoreOp::Store }, + }) + }).collect(); + + let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &color_attachments, + depth_stencil_attachment: pass.depth_view.as_ref().map(|v| { + wgpu::RenderPassDepthStencilAttachment { + view: v, + depth_ops: Some(wgpu::Operations { load: pass.depth_load, store: wgpu::StoreOp::Store }), + stencil_ops: Some(wgpu::Operations { load: pass.stencil_load, store: wgpu::StoreOp::Store }), + } + }), + ..Default::default() + }); + + for cmd in pass.commands { + rp.set_viewport(cmd.viewport.x() as f32, cmd.viewport.y() as f32, cmd.viewport.w() as f32, cmd.viewport.h() as f32, 0.0, 1.0); + rp.set_pipeline(&cmd.pipeline); + if let Some(bg) = &cmd.bind_group { + rp.set_bind_group(0, bg, &[]); + } + if let Some(st) = cmd.stencil_ref { + rp.set_stencil_reference(st); + } + for (i, vb) in cmd.vertex_buffers.iter().enumerate() { + rp.set_vertex_buffer(i as u32, vb.slice(..)); + } + let geo_buf_count = cmd.vertex_buffers.len() as u32; + for i in 0..cmd.extra_verts { + rp.set_vertex_buffer(geo_buf_count + i, self.dummy_vertex_buffer.slice(..)); + } + rp.set_index_buffer(cmd.index_buffer.slice(..), wgpu::IndexFormat::Uint32); + rp.draw_indexed(cmd.start_idx..cmd.end_idx, 0, 0..cmd.instances); + } + + drop(rp); + *self.frame_encoder.borrow_mut() = Some(encoder); + } } impl GraphicsServer for WgpuGraphicsServer { @@ -424,6 +503,7 @@ impl GraphicsServer for WgpuGraphicsServer { fn flush(&self) { // flush in Fyrox means "send the accumulated commands to the video card right now." // An empty submit is not needed here, just close and send the encoder, if there is one. + self.flush_active_pass(); if let Some(encoder) = self.frame_encoder.borrow_mut().take() { self.state.queue.submit(std::iter::once(encoder.finish())); } @@ -445,6 +525,7 @@ impl GraphicsServer for WgpuGraphicsServer { } fn swap_buffers(&self) -> Result<(), FrameworkError> { // Submit all batched draw commands from this frame + self.flush_active_pass(); if let Some(encoder) = self.frame_encoder.borrow_mut().take() { self.state.queue.submit(std::iter::once(encoder.finish())); } @@ -465,6 +546,7 @@ impl GraphicsServer for WgpuGraphicsServer { config.width = new_size.0; config.height = new_size.1; + self.flush_active_pass(); if let Some(encoder) = self.frame_encoder.borrow_mut().take() { self.state.queue.submit(std::iter::once(encoder.finish())); } From dd3578dbba3ad205f5818fc26f1ca3798ef5294e Mon Sep 17 00:00:00 2001 From: nicehack Date: Fri, 24 Jul 2026 16:51:52 +0500 Subject: [PATCH 24/34] texture storage optimization --- fyrox-graphics-wgpu/src/framebuffer.rs | 11 ++++++++--- fyrox-graphics-wgpu/src/program.rs | 10 +++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index 0dab647e04..6fd8d08e18 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -19,7 +19,7 @@ // SOFTWARE. use crate::buffer::WgpuBuffer; -use crate::format_helpers::{is_filterable_format, is_integer_format, SAMPLER_BINDING_OFFSET, UNIFORM_BINDING_OFFSET}; +use crate::format_helpers::{is_filterable_format, is_integer_format, sample_type_for_format, SAMPLER_BINDING_OFFSET, UNIFORM_BINDING_OFFSET}; use crate::geometry_buffer::WgpuGeometryBuffer; use crate::program::WgpuProgram; use crate::sampler::WgpuSampler; @@ -169,7 +169,7 @@ pub struct PipelineKey { extra_vert_count: u8, /// Resource texture formats that determine the bind group layout. /// Ensures pipeline is recreated when texture formats change (e.g., R32Float is non-filterable). - texture_resource_formats: Vec<(usize, wgpu::TextureFormat)>, + texture_resource_sample_types: Vec<(usize, wgpu::TextureSampleType)>, } /// Wgpu implementation of [`GpuFrameBufferTrait`](fyrox_graphics::framebuffer::GpuFrameBufferTrait). @@ -251,6 +251,11 @@ impl WgpuFrameBuffer { let stencil_supported = format_has_stencil(depth_fmt); let effective_stencil = needs_stencil && stencil_supported; + let sample_types: Vec<_> = texture_resource_formats + .iter() + .map(|(loc, fmt)| (*loc, sample_type_for_format(*fmt))) + .collect(); + let key = PipelineKey { program_ptr: program as *const WgpuProgram as usize, color_formats: color_formats.to_vec(), @@ -267,7 +272,7 @@ impl WgpuFrameBuffer { None => 0, }, extra_vert_count: all_layouts.len() as u8, - texture_resource_formats: texture_resource_formats.to_vec(), + texture_resource_sample_types: sample_types, }; let key_hash = { let mut h = DefaultHasher::new(); diff --git a/fyrox-graphics-wgpu/src/program.rs b/fyrox-graphics-wgpu/src/program.rs index d970d67e33..0c00db017b 100644 --- a/fyrox-graphics-wgpu/src/program.rs +++ b/fyrox-graphics-wgpu/src/program.rs @@ -315,7 +315,7 @@ pub struct WgpuProgram { resources: Vec, cached_layouts: RefCell< HashMap< - Vec<(usize, wgpu::TextureFormat)>, + Vec<(usize, wgpu::TextureSampleType)>, (wgpu::BindGroupLayout, wgpu::PipelineLayout), >, >, @@ -400,10 +400,14 @@ impl WgpuProgram { &self, texture_formats: &[(usize, wgpu::TextureFormat)], ) -> (wgpu::BindGroupLayout, wgpu::PipelineLayout) { + let sample_types: Vec<_> = texture_formats + .iter() + .map(|(loc, fmt)| (*loc, sample_type_for_format(*fmt))) + .collect(); let mut cache = self.cached_layouts.borrow_mut(); - if let Some((bgl, pl)) = cache.get(texture_formats) { + if let Some((bgl, pl)) = cache.get(&sample_types) { return (bgl.clone(), pl.clone()); } @@ -429,7 +433,7 @@ impl WgpuProgram { let result = (bgl.clone(), pl.clone()); - cache.insert(texture_formats.to_vec(), result.clone()); + cache.insert(sample_types, result.clone()); result } From e535c3014705f57e1192febba9ce37980272cdc1 Mon Sep 17 00:00:00 2001 From: nicehack Date: Fri, 24 Jul 2026 17:23:54 +0500 Subject: [PATCH 25/34] fix point light shader --- .../src/renderer/shaders/wgpu/deferred_point_light.shader | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fyrox-impl/src/renderer/shaders/wgpu/deferred_point_light.shader b/fyrox-impl/src/renderer/shaders/wgpu/deferred_point_light.shader index f7a9a9ccbb..12d4cbf271 100644 --- a/fyrox-impl/src/renderer/shaders/wgpu/deferred_point_light.shader +++ b/fyrox-impl/src/renderer/shaders/wgpu/deferred_point_light.shader @@ -23,7 +23,7 @@ ), ( name: "pointShadowTexture", - kind: Texture(kind: DepthSamplerCube, fallback: White), + kind: Texture(kind: SamplerCube, fallback: White), binding: 4 ), ( @@ -128,7 +128,7 @@ let distance_attenuation = S_LightDistanceAttenuation(dist, properties.lightRadius); - let shadow = S_PointShadow_Depth( + let shadow = S_PointShadow( properties.shadowsEnabled != 0u, properties.softShadows != 0u, dist, properties.shadowBias, ctx.fragmentToLight, pointShadowTexture_tex, pointShadowTexture_samp); let final_shadow = mix(1.0, shadow, properties.shadowAlpha); From c1ee1c76bd83a1608a1466ccbc0e60a368245043 Mon Sep 17 00:00:00 2001 From: nicehack Date: Sun, 26 Jul 2026 14:03:59 +0500 Subject: [PATCH 26/34] optimize dependencies --- fyrox-graphics-wgpu/Cargo.toml | 2 -- fyrox-graphics-wgpu/src/buffer.rs | 5 +++-- fyrox-graphics-wgpu/src/framebuffer.rs | 2 +- fyrox-graphics-wgpu/src/server.rs | 9 +++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/fyrox-graphics-wgpu/Cargo.toml b/fyrox-graphics-wgpu/Cargo.toml index 3caa6f806b..5ed146b524 100644 --- a/fyrox-graphics-wgpu/Cargo.toml +++ b/fyrox-graphics-wgpu/Cargo.toml @@ -17,8 +17,6 @@ winit = { version = "0.30" } fyrox-graphics = { version = "2.0.0-rc.1", path = "../fyrox-graphics" } fyrox-core = { version = "2.0.0-rc.1", path = "../fyrox-core" } wgpu = { version = "29.0.3" } -bytemuck = { version = "1", features = ["derive"] } -log = "0.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] raw-window-handle = "0.6" diff --git a/fyrox-graphics-wgpu/src/buffer.rs b/fyrox-graphics-wgpu/src/buffer.rs index d038abc075..0f8489e050 100644 --- a/fyrox-graphics-wgpu/src/buffer.rs +++ b/fyrox-graphics-wgpu/src/buffer.rs @@ -19,6 +19,7 @@ // SOFTWARE. use crate::server::WgpuGraphicsServer; +use fyrox_core::log::Log; use fyrox_graphics::{ buffer::{BufferKind, BufferUsage, GpuBufferDescriptor, GpuBufferTrait}, error::FrameworkError, @@ -123,11 +124,11 @@ impl GpuBufferTrait for WgpuBuffer { if data.len() <= self.size.get() { server.state.queue.write_buffer(&self.buffer, 0, data); } else { - log::warn!( + Log::warn(format!( "WgpuBuffer::write_data: data ({} bytes) exceeds buffer ({} bytes)", data.len(), self.size.get() - ); + )); server .state .queue diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index 6fd8d08e18..6932932614 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -650,7 +650,7 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { _d: bool, _s: bool, ) { - log::warn!("blit_to not yet implemented for wgpu"); + Log::warn("blit_to not yet implemented for wgpu"); } fn read_pixels(&self, read_target: ReadTarget) -> Option> { let server = self.server.upgrade()?; diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index f8add9ee9d..84ddf07432 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -27,6 +27,8 @@ use crate::read_buffer::WgpuAsyncReadBuffer; use crate::sampler::WgpuSampler; use crate::texture::WgpuTexture; use fyrox_core::futures::executor::block_on; +use fyrox_core::log::Log; +use fyrox_core::math::Rect; use fyrox_graphics::buffer::{GpuBuffer, GpuBufferDescriptor}; use fyrox_graphics::error::FrameworkError; use fyrox_graphics::framebuffer::{Attachment, GpuFrameBuffer}; @@ -47,7 +49,6 @@ use std::rc::{Rc, Weak}; use std::sync::{Arc, RwLock}; use winit::event_loop::ActiveEventLoop; use winit::window::{Window, WindowAttributes}; -use fyrox_core::math::Rect; pub struct DrawCommand { pub pipeline: wgpu::RenderPipeline, @@ -419,7 +420,7 @@ impl GraphicsServer for WgpuGraphicsServer { *cache = Some((w, h, tex)); } Err(e) => { - log::warn!("Failed to create backbuffer depth-stencil: {e}"); + Log::warn("Failed to create backbuffer depth-stencil: {e}"); *cache = None; } } @@ -565,10 +566,10 @@ impl GraphicsServer for WgpuGraphicsServer { } } fn set_polygon_fill_mode(&self, _face: PolygonFace, _mode: PolygonFillMode) { - log::warn!("set_polygon_fill_mode: wgpu requires pipeline recreation"); + Log::warn("set_polygon_fill_mode: wgpu requires pipeline recreation"); } fn generate_mipmap(&self, _texture: &GpuTexture) { - log::warn!("generate_mipmap: not yet fully implemented"); + Log::warn("generate_mipmap: not yet fully implemented"); } fn memory_usage(&self) -> ServerMemoryUsage { self.memory_usage.borrow().clone() From f264ff99072cd30cda67f079ed67c76b3a47c825 Mon Sep 17 00:00:00 2001 From: nicehack Date: Sun, 26 Jul 2026 14:29:25 +0500 Subject: [PATCH 27/34] update wgpu version from 29.0.3 to 30.0.0 --- fyrox-graphics-wgpu/Cargo.toml | 2 +- fyrox-graphics-wgpu/src/buffer.rs | 11 ++++++++++- fyrox-graphics-wgpu/src/framebuffer.rs | 17 ++++++++++++----- fyrox-graphics-wgpu/src/read_buffer.rs | 18 +++++++++++------- fyrox-graphics-wgpu/src/server.rs | 14 +++++++++++--- .../renderer/shaders/wgpu/visibility.shader | 4 ++-- 6 files changed, 47 insertions(+), 19 deletions(-) diff --git a/fyrox-graphics-wgpu/Cargo.toml b/fyrox-graphics-wgpu/Cargo.toml index 5ed146b524..f1c626b693 100644 --- a/fyrox-graphics-wgpu/Cargo.toml +++ b/fyrox-graphics-wgpu/Cargo.toml @@ -16,7 +16,7 @@ rust-version = "1.87" winit = { version = "0.30" } fyrox-graphics = { version = "2.0.0-rc.1", path = "../fyrox-graphics" } fyrox-core = { version = "2.0.0-rc.1", path = "../fyrox-core" } -wgpu = { version = "29.0.3" } +wgpu = { version = "30.0.0" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] raw-window-handle = "0.6" diff --git a/fyrox-graphics-wgpu/src/buffer.rs b/fyrox-graphics-wgpu/src/buffer.rs index 0f8489e050..eca4c952de 100644 --- a/fyrox-graphics-wgpu/src/buffer.rs +++ b/fyrox-graphics-wgpu/src/buffer.rs @@ -141,11 +141,14 @@ impl GpuBufferTrait for WgpuBuffer { let Some(server) = self.server.upgrade() else { return Err(FrameworkError::GraphicsServerUnavailable); }; + let buffer_slice = self.buffer.slice(..data.len() as u64); let (tx, rx) = std::sync::mpsc::channel(); + buffer_slice.map_async(wgpu::MapMode::Read, move |result| { tx.send(result).ok(); }); + server .state .device @@ -154,13 +157,19 @@ impl GpuBufferTrait for WgpuBuffer { timeout: None, }) .ok(); + rx.recv() .map_err(|_| FrameworkError::Custom("Channel closed".into()))? .map_err(|e| FrameworkError::Custom(format!("Buffer map failed: {e}")))?; - let mapped = buffer_slice.get_mapped_range(); + + let mapped = buffer_slice + .get_mapped_range() + .map_err(|e| FrameworkError::Custom(format!("Failed to get mapped range: {e}")))?; + data.copy_from_slice(&mapped); drop(mapped); self.buffer.unmap(); + Ok(()) } } diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index 6932932614..e7a4dbd291 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -411,6 +411,13 @@ impl WgpuFrameBuffer { } else { None }; + + let optional_layouts: Vec>> = all_layouts + .iter() + .cloned() + .map(Some) + .collect(); + let pipeline = server .state @@ -421,7 +428,7 @@ impl WgpuFrameBuffer { vertex: wgpu::VertexState { module: program.vertex_module(), entry_point: Some("vs_main"), - buffers: all_layouts, + buffers: &optional_layouts, compilation_options: Default::default(), }, fragment: fragment_state, @@ -583,11 +590,11 @@ impl WgpuFrameBuffer { let has_stencil = df.map(format_has_stencil).unwrap_or(false); let (color_load, depth_load, stencil_load) = if self.is_backbuffer && server.backbuffer_needs_clear.replace(false) { - (wgpu::LoadOp::Clear(wgpu::Color::BLACK), wgpu::LoadOp::Clear(1.0), if has_stencil { wgpu::LoadOp::Clear(0) } else { wgpu::LoadOp::Load }) + (wgpu::LoadOp::Clear(wgpu::Color::BLACK), wgpu::LoadOp::Clear(1.0), if has_stencil { Some(wgpu::LoadOp::Clear(0)) } else { None }) } else if !self.is_backbuffer && self.needs_clear.replace(false) { - (wgpu::LoadOp::Clear(*self.pending_clear_color.borrow()), wgpu::LoadOp::Clear(*self.pending_clear_depth.borrow()), if has_stencil { wgpu::LoadOp::Clear(0) } else { wgpu::LoadOp::Load }) + (wgpu::LoadOp::Clear(*self.pending_clear_color.borrow()), wgpu::LoadOp::Clear(*self.pending_clear_depth.borrow()), if has_stencil { Some(wgpu::LoadOp::Clear(0)) } else { None }) } else { - (wgpu::LoadOp::Load, wgpu::LoadOp::Load, wgpu::LoadOp::Load) + (wgpu::LoadOp::Load, wgpu::LoadOp::Load, if has_stencil { Some(wgpu::LoadOp::Load) } else { None }) }; *server.active_pass.borrow_mut() = Some(crate::server::ActivePass { @@ -718,7 +725,7 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { }) .ok(); rx.recv().ok()?.ok()?; - let mapped = slice.get_mapped_range(); + let mapped = slice.get_mapped_range().ok()?; let unpadded_row = width * bps; let mut result = vec![0u8; unpadded_row * height]; if bytes_per_row == unpadded_row { diff --git a/fyrox-graphics-wgpu/src/read_buffer.rs b/fyrox-graphics-wgpu/src/read_buffer.rs index 4600b9c69f..d50f2812b5 100644 --- a/fyrox-graphics-wgpu/src/read_buffer.rs +++ b/fyrox-graphics-wgpu/src/read_buffer.rs @@ -162,13 +162,17 @@ impl GpuAsyncReadBufferTrait for WgpuAsyncReadBuffer { .ok(); match rx.recv() { Ok(Ok(())) => { - let mapped = slice.get_mapped_range(); - let mut result = vec![0u8; self.size_bytes]; - result.copy_from_slice(&mapped); - drop(mapped); - self.buffer.unmap(); - self.request_pending.set(false); - Some(result) + if let Ok(mapped) = slice.get_mapped_range() { + let mut result = vec![0u8; self.size_bytes]; + result.copy_from_slice(&mapped); + drop(mapped); + self.buffer.unmap(); + self.request_pending.set(false); + Some(result) + } else { + self.request_pending.set(false); + None + } } _ => { self.request_pending.set(false); diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index 84ddf07432..25c25b5009 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -47,6 +47,7 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::rc::{Rc, Weak}; use std::sync::{Arc, RwLock}; +use wgpu::hal::DynQueue; use winit::event_loop::ActiveEventLoop; use winit::window::{Window, WindowAttributes}; @@ -69,7 +70,7 @@ pub struct ActivePass { pub depth_view: Option, pub color_load: wgpu::LoadOp, pub depth_load: wgpu::LoadOp, - pub stencil_load: wgpu::LoadOp, + pub stencil_load: Option>, pub commands: Vec, } @@ -218,6 +219,7 @@ impl WgpuGraphicsServer { power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface), force_fallback_adapter: false, + apply_limit_buckets: false, })) .map_err(|e| FrameworkError::Custom(format!("No suitable WGPU adapter found: {e}")))?; @@ -254,6 +256,7 @@ impl WgpuGraphicsServer { let surface_config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: surface_format, + color_space: Default::default(), width: size.width, height: size.height, present_mode, @@ -348,7 +351,10 @@ impl WgpuGraphicsServer { wgpu::RenderPassDepthStencilAttachment { view: v, depth_ops: Some(wgpu::Operations { load: pass.depth_load, store: wgpu::StoreOp::Store }), - stencil_ops: Some(wgpu::Operations { load: pass.stencil_load, store: wgpu::StoreOp::Store }), + stencil_ops: pass.stencil_load.map(|load| wgpu::Operations { + load, + store: wgpu::StoreOp::Store, + }), } }), ..Default::default() @@ -530,9 +536,11 @@ impl GraphicsServer for WgpuGraphicsServer { if let Some(encoder) = self.frame_encoder.borrow_mut().take() { self.state.queue.submit(std::iter::once(encoder.finish())); } + if let Some(frame) = self.current_frame.borrow_mut().take() { - frame.present(); + self.state.queue.present(frame); } + self.backbuffer_needs_clear.replace(true); Ok(()) } diff --git a/fyrox-impl/src/renderer/shaders/wgpu/visibility.shader b/fyrox-impl/src/renderer/shaders/wgpu/visibility.shader index de27a08bff..e4e749d64c 100644 --- a/fyrox-impl/src/renderer/shaders/wgpu/visibility.shader +++ b/fyrox-impl/src/renderer/shaders/wgpu/visibility.shader @@ -65,7 +65,7 @@ struct VertexOutput { @builtin(position) position: vec4f, - @location(0) objectIndex: u32, + @location(0) @interpolate(flat) objectIndex: u32, }; @vertex @@ -80,7 +80,7 @@ fragment_shader: r#" @fragment - fn fs_main(@location(0) objectIndex: u32, @builtin(position) fragCoord: vec4f) -> @location(0) vec4f { + fn fs_main(@location(0) @interpolate(flat) objectIndex: u32, @builtin(position) fragCoord: vec4f) -> @location(0) vec4f { var x = i32(fragCoord.x) / properties.tileSize; var y = i32(properties.frameBufferHeight - fragCoord.y) / properties.tileSize; From ee61911782de55ca1e2bc15a81eefa35f7fa86ed Mon Sep 17 00:00:00 2001 From: nicehack Date: Sun, 26 Jul 2026 15:03:04 +0500 Subject: [PATCH 28/34] implemented generate_mipmap() and copy_attachment_texture() --- fyrox-graphics-wgpu/src/framebuffer.rs | 138 ++++++++++++++-- fyrox-graphics-wgpu/src/server.rs | 214 ++++++++++++++++++++++++- fyrox-graphics-wgpu/src/texture.rs | 2 +- 3 files changed, 337 insertions(+), 17 deletions(-) diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index e7a4dbd291..e43fc255b9 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -629,6 +629,52 @@ impl WgpuFrameBuffer { } } +fn copy_attachment_texture( + encoder: &mut wgpu::CommandEncoder, + src: &Attachment, + dst: &Attachment, + src_x: u32, + src_y: u32, + dst_x: u32, + dst_y: u32, + width: u32, + height: u32, +) { + let Some(src_tex) = src.texture.as_any().downcast_ref::() else { + return; + }; + let Some(dst_tex) = dst.texture.as_any().downcast_ref::() else { + return; + }; + encoder.copy_texture_to_texture( + wgpu::TexelCopyTextureInfo { + texture: src_tex.wgpu_texture(), + mip_level: src.level() as u32, + origin: wgpu::Origin3d { + x: src_x, + y: src_y, + z: 0, + }, + aspect: wgpu::TextureAspect::All, + }, + wgpu::TexelCopyTextureInfo { + texture: dst_tex.wgpu_texture(), + mip_level: dst.level() as u32, + origin: wgpu::Origin3d { + x: dst_x, + y: dst_y, + z: 0, + }, + aspect: wgpu::TextureAspect::All, + }, + wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + ); +} + impl GpuFrameBufferTrait for WgpuFrameBuffer { fn color_attachments(&self) -> &[Attachment] { &self.color_attachments @@ -644,20 +690,86 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { } fn blit_to( &self, - _dest: &GpuFrameBuffer, - _sx0: i32, - _sy0: i32, - _sx1: i32, - _sy1: i32, - _dx0: i32, - _dy0: i32, - _dx1: i32, - _dy1: i32, - _c: bool, - _d: bool, - _s: bool, + dest: &GpuFrameBuffer, + src_x0: i32, + src_y0: i32, + src_x1: i32, + src_y1: i32, + dst_x0: i32, + dst_y0: i32, + dst_x1: i32, + dst_y1: i32, + copy_color: bool, + copy_depth: bool, + copy_stencil: bool, ) { - Log::warn("blit_to not yet implemented for wgpu"); + let Some(server) = self.server.upgrade() else { + return; + }; + let Some(dest) = dest.as_any().downcast_ref::() else { + return; + }; + + let src_w = (src_x1 - src_x0) as u32; + let src_h = (src_y1 - src_y0) as u32; + let dst_w = (dst_x1 - dst_x0) as u32; + let dst_h = (dst_y1 - dst_y0) as u32; + + if src_w != dst_w || src_h != dst_h { + Log::warn("blit_to: scaling not supported in wgpu backend, skipping"); + return; + } + + server.flush_active_pass(); + + let mut encoder = server + .frame_encoder + .borrow_mut() + .take() + .unwrap_or_else(|| { + server + .state + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }) + }); + + if copy_color { + for (src_att, dst_att) in + self.color_attachments.iter().zip(&dest.color_attachments) + { + copy_attachment_texture( + &mut encoder, + src_att, + dst_att, + src_x0 as u32, + src_y0 as u32, + dst_x0 as u32, + dst_y0 as u32, + src_w, + src_h, + ); + } + } + + if copy_depth || copy_stencil { + if let (Some(src_att), Some(dst_att)) = + (&self.depth_attachment, &dest.depth_attachment) + { + copy_attachment_texture( + &mut encoder, + src_att, + dst_att, + src_x0 as u32, + src_y0 as u32, + dst_x0 as u32, + dst_y0 as u32, + src_w, + src_h, + ); + } + } + + *server.frame_encoder.borrow_mut() = Some(encoder); } fn read_pixels(&self, read_target: ReadTarget) -> Option> { let server = self.server.upgrade()?; diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index 25c25b5009..8ab3e6f277 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -19,13 +19,14 @@ // SOFTWARE. use crate::buffer::WgpuBuffer; +use crate::format_helpers::is_filterable_format; use crate::framebuffer::WgpuFrameBuffer; use crate::geometry_buffer::WgpuGeometryBuffer; use crate::program::{WgpuProgram, WgpuShader}; use crate::query::WgpuQuery; use crate::read_buffer::WgpuAsyncReadBuffer; use crate::sampler::WgpuSampler; -use crate::texture::WgpuTexture; +use crate::texture::{texture_size, WgpuTexture}; use fyrox_core::futures::executor::block_on; use fyrox_core::log::Log; use fyrox_core::math::Rect; @@ -51,6 +52,26 @@ use wgpu::hal::DynQueue; use winit::event_loop::ActiveEventLoop; use winit::window::{Window, WindowAttributes}; +const MIPMAP_SHADER_SRC: &str = r#" +@vertex +fn vs_main(@builtin(vertex_index) idx: u32) -> @builtin(position) vec4 { + var pos = array, 3>( + vec2(-1.0, -1.0), + vec2( 3.0, -1.0), + vec2(-1.0, 3.0) + ); + return vec4(pos[idx], 0.0, 1.0); +} + +@group(0) @binding(0) var src_tex: texture_2d; +@group(0) @binding(1) var src_sampler: sampler; + +@fragment +fn fs_main(@builtin(position) pos: vec4) -> @location(0) vec4 { + return textureLoad(src_tex, vec2(pos.xy), 0); +} +"#; + pub struct DrawCommand { pub pipeline: wgpu::RenderPipeline, pub bind_group: Option, @@ -147,6 +168,12 @@ pub struct WgpuGraphicsServer { /// (G-Buffer, HDR, backbuffer) share the same frame and benefit from a single submit. pub frame_encoder: RefCell>, pub active_pass: RefCell>, + /// Sampler used for mipmap generation (linear filtering). + mipmap_sampler: wgpu::Sampler, + /// Bind group layout for the mipmap generation shader. + mipmap_bind_group_layout: wgpu::BindGroupLayout, + /// Cached mipmap render pipelines, keyed by texture format. + mipmap_pipeline_cache: RefCell>, } impl WgpuGraphicsServer { @@ -277,6 +304,37 @@ impl WgpuGraphicsServer { ..Default::default() }); + let mipmap_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("MipmapSampler"), + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::MipmapFilterMode::Nearest, + ..Default::default() + }); + + let mipmap_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("MipmapBGL"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + let dummy_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("DummyVB"), size: 16, // enough for vec4f @@ -307,6 +365,9 @@ impl WgpuGraphicsServer { backbuffer_depth_stencil: RefCell::new(None), frame_encoder: RefCell::new(None), active_pass: RefCell::new(None), + mipmap_sampler, + mipmap_bind_group_layout, + mipmap_pipeline_cache: RefCell::new(HashMap::new()), }); *server.weak_self.borrow_mut() = Some(Rc::downgrade(&server)); @@ -576,8 +637,155 @@ impl GraphicsServer for WgpuGraphicsServer { fn set_polygon_fill_mode(&self, _face: PolygonFace, _mode: PolygonFillMode) { Log::warn("set_polygon_fill_mode: wgpu requires pipeline recreation"); } - fn generate_mipmap(&self, _texture: &GpuTexture) { - Log::warn("generate_mipmap: not yet fully implemented"); + fn generate_mipmap(&self, texture: &GpuTexture) { + let Some(wtex) = texture.as_any().downcast_ref::() else { + return; + }; + let format = wtex.format(); + if !is_filterable_format(format) { + Log::warn("generate_mipmap: format is not filterable, skipping"); + return; + } + + let (width, height, _depth) = texture_size(texture.kind()); + let mip_count = wtex.wgpu_texture().mip_level_count(); + if mip_count <= 1 || width <= 1 || height <= 1 { + return; + } + + self.flush_active_pass(); + + let has_pipeline = self.mipmap_pipeline_cache.borrow().contains_key(&format); + if !has_pipeline { + let shader = + self.state + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("MipmapShader"), + source: wgpu::ShaderSource::Wgsl(MIPMAP_SHADER_SRC.into()), + }); + let layout = + self.state + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("MipmapPL"), + bind_group_layouts: &[Some(&self.mipmap_bind_group_layout)], + ..Default::default() + }); + let pipeline = + self.state + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("MipmapPipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview_mask: None, + cache: None, + }); + self.mipmap_pipeline_cache + .borrow_mut() + .insert(format, pipeline); + } + + let pipeline_cache = self.mipmap_pipeline_cache.borrow(); + let pipeline = pipeline_cache.get(&format).unwrap(); + + let mut encoder = self + .frame_encoder + .borrow_mut() + .take() + .unwrap_or_else(|| { + self.state + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }) + }); + + let mut mip_w = width; + let mut mip_h = height; + + for level in 1..mip_count { + mip_w = (mip_w / 2).max(1); + mip_h = (mip_h / 2).max(1); + + let src_view = + wtex.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { + dimension: Some(wgpu::TextureViewDimension::D2), + base_mip_level: level - 1, + mip_level_count: Some(1), + ..Default::default() + }); + + let dst_view = + wtex.wgpu_texture().create_view(&wgpu::TextureViewDescriptor { + dimension: Some(wgpu::TextureViewDimension::D2), + base_mip_level: level, + mip_level_count: Some(1), + ..Default::default() + }); + + let bind_group = self.state.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.mipmap_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&src_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&self.mipmap_sampler), + }, + ], + }); + + let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &dst_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + depth_stencil_attachment: None, + ..Default::default() + }); + + rp.set_viewport(0.0, 0.0, mip_w as f32, mip_h as f32, 0.0, 1.0); + rp.set_pipeline(&pipeline); + rp.set_bind_group(0, &bind_group, &[]); + rp.draw(0..3, 0..1); + drop(rp); + } + + *self.frame_encoder.borrow_mut() = Some(encoder); } fn memory_usage(&self) -> ServerMemoryUsage { self.memory_usage.borrow().clone() diff --git a/fyrox-graphics-wgpu/src/texture.rs b/fyrox-graphics-wgpu/src/texture.rs index 26eb0c4fdd..5ece68d2a2 100644 --- a/fyrox-graphics-wgpu/src/texture.rs +++ b/fyrox-graphics-wgpu/src/texture.rs @@ -118,7 +118,7 @@ fn texture_dimension(kind: GpuTextureKind) -> wgpu::TextureDimension { } } -fn texture_size(kind: GpuTextureKind) -> (u32, u32, u32) { +pub(crate) fn texture_size(kind: GpuTextureKind) -> (u32, u32, u32) { match kind { GpuTextureKind::Line { length } => (length as u32, 1, 1), GpuTextureKind::Rectangle { width, height } => (width as u32, height as u32, 1), From 3b68e9514a4957e559f22f67b92be6671c80ec80 Mon Sep 17 00:00:00 2001 From: nicehack Date: Sun, 26 Jul 2026 15:37:23 +0500 Subject: [PATCH 29/34] implemented set_polygon_fill_mode() --- fyrox-graphics-wgpu/src/framebuffer.rs | 20 +++++++++++++++++++- fyrox-graphics-wgpu/src/server.rs | 20 +++++++++++++++++--- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index e43fc255b9..c7665c45db 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -167,6 +167,7 @@ pub struct PipelineKey { has_color: bool, cull: u8, extra_vert_count: u8, + polygon_fill_mode: u8, /// Resource texture formats that determine the bind group layout. /// Ensures pipeline is recreated when texture formats change (e.g., R32Float is non-filterable). texture_resource_sample_types: Vec<(usize, wgpu::TextureSampleType)>, @@ -256,6 +257,16 @@ impl WgpuFrameBuffer { .map(|(loc, fmt)| (*loc, sample_type_for_format(*fmt))) .collect(); + let device_features = server.state.device.features(); + let supports_line = device_features.contains(wgpu::Features::POLYGON_MODE_LINE); + let supports_point = device_features.contains(wgpu::Features::POLYGON_MODE_POINT); + + let actual_polygon_mode = match server.polygon_fill_mode() { + fyrox_graphics::PolygonFillMode::Line if supports_line => wgpu::PolygonMode::Line, + fyrox_graphics::PolygonFillMode::Point if supports_point => wgpu::PolygonMode::Point, + _ => wgpu::PolygonMode::Fill, + }; + let key = PipelineKey { program_ptr: program as *const WgpuProgram as usize, color_formats: color_formats.to_vec(), @@ -272,6 +283,11 @@ impl WgpuFrameBuffer { None => 0, }, extra_vert_count: all_layouts.len() as u8, + polygon_fill_mode: match actual_polygon_mode { + wgpu::PolygonMode::Point => 0, + wgpu::PolygonMode::Line => 1, + wgpu::PolygonMode::Fill => 2, + }, texture_resource_sample_types: sample_types, }; let key_hash = { @@ -437,7 +453,9 @@ impl WgpuFrameBuffer { strip_index_format: None, front_face: wgpu::FrontFace::Ccw, cull_mode: cull, - ..Default::default() + polygon_mode: actual_polygon_mode, + unclipped_depth: false, + conservative: false, }, depth_stencil, multisample: wgpu::MultisampleState { diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index 8ab3e6f277..3aa265cc1e 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -174,6 +174,8 @@ pub struct WgpuGraphicsServer { mipmap_bind_group_layout: wgpu::BindGroupLayout, /// Cached mipmap render pipelines, keyed by texture format. mipmap_pipeline_cache: RefCell>, + /// Current polygon fill mode (Fill, Line, or Point). Baked into new pipelines. + polygon_fill_mode: Cell, } impl WgpuGraphicsServer { @@ -250,9 +252,16 @@ impl WgpuGraphicsServer { })) .map_err(|e| FrameworkError::Custom(format!("No suitable WGPU adapter found: {e}")))?; + let adapter_features = adapter.features(); + let mut required_features = wgpu::Features::empty(); + + if adapter_features.contains(wgpu::Features::POLYGON_MODE_LINE) { + required_features |= wgpu::Features::POLYGON_MODE_LINE; + } + let (device, queue) = block_on(adapter.request_device(&wgpu::DeviceDescriptor { label: None, - required_features: wgpu::Features::empty(), + required_features, required_limits: if cfg!(target_arch = "wasm32") { wgpu::Limits::downlevel_webgl2_defaults() } else { @@ -368,6 +377,7 @@ impl WgpuGraphicsServer { mipmap_sampler, mipmap_bind_group_layout, mipmap_pipeline_cache: RefCell::new(HashMap::new()), + polygon_fill_mode: Cell::new(PolygonFillMode::Fill), }); *server.weak_self.borrow_mut() = Some(Rc::downgrade(&server)); @@ -388,6 +398,10 @@ impl WgpuGraphicsServer { pub fn non_filtering_sampler(&self) -> &wgpu::Sampler { &self.non_filtering_sampler } + /// Returns the current polygon fill mode. Baked into new render pipelines. + pub fn polygon_fill_mode(&self) -> PolygonFillMode { + self.polygon_fill_mode.get() + } pub fn flush_active_pass(&self) { let Some(pass) = self.active_pass.borrow_mut().take() else { return; }; @@ -634,8 +648,8 @@ impl GraphicsServer for WgpuGraphicsServer { max_lod_bias: 16.0, } } - fn set_polygon_fill_mode(&self, _face: PolygonFace, _mode: PolygonFillMode) { - Log::warn("set_polygon_fill_mode: wgpu requires pipeline recreation"); + fn set_polygon_fill_mode(&self, _face: PolygonFace, mode: PolygonFillMode) { + self.polygon_fill_mode.set(mode); } fn generate_mipmap(&self, texture: &GpuTexture) { let Some(wtex) = texture.as_any().downcast_ref::() else { From d171620f19b30984ec4069c9e29193ef00f02298 Mon Sep 17 00:00:00 2001 From: nicehack Date: Sun, 26 Jul 2026 16:27:05 +0500 Subject: [PATCH 30/34] add scissor box handling --- fyrox-graphics-wgpu/src/framebuffer.rs | 11 +++++++++-- fyrox-graphics-wgpu/src/server.rs | 25 ++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index c7665c45db..297d000908 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -192,6 +192,7 @@ pub struct WgpuFrameBuffer { needs_clear: Cell, pending_clear_color: RefCell, pending_clear_depth: RefCell, + pending_clear_stencil: RefCell, backbuffer_depth_cache: RefCell>, } @@ -210,6 +211,7 @@ impl WgpuFrameBuffer { needs_clear: Cell::new(false), pending_clear_color: RefCell::new(wgpu::Color::BLACK), pending_clear_depth: RefCell::new(1.0), + pending_clear_stencil: RefCell::new(0), backbuffer_depth_cache: RefCell::new(None), }) } @@ -227,6 +229,7 @@ impl WgpuFrameBuffer { needs_clear: Cell::new(false), pending_clear_color: RefCell::new(wgpu::Color::BLACK), pending_clear_depth: RefCell::new(1.0), + pending_clear_stencil: RefCell::new(0), backbuffer_depth_cache: RefCell::new(None), } } @@ -610,7 +613,7 @@ impl WgpuFrameBuffer { let (color_load, depth_load, stencil_load) = if self.is_backbuffer && server.backbuffer_needs_clear.replace(false) { (wgpu::LoadOp::Clear(wgpu::Color::BLACK), wgpu::LoadOp::Clear(1.0), if has_stencil { Some(wgpu::LoadOp::Clear(0)) } else { None }) } else if !self.is_backbuffer && self.needs_clear.replace(false) { - (wgpu::LoadOp::Clear(*self.pending_clear_color.borrow()), wgpu::LoadOp::Clear(*self.pending_clear_depth.borrow()), if has_stencil { Some(wgpu::LoadOp::Clear(0)) } else { None }) + (wgpu::LoadOp::Clear(*self.pending_clear_color.borrow()), wgpu::LoadOp::Clear(*self.pending_clear_depth.borrow()), if has_stencil { Some(wgpu::LoadOp::Clear(*self.pending_clear_stencil.borrow())) } else { None }) } else { (wgpu::LoadOp::Load, wgpu::LoadOp::Load, if has_stencil { Some(wgpu::LoadOp::Load) } else { None }) }; @@ -636,6 +639,7 @@ impl WgpuFrameBuffer { index_buffer: geo.element_buffer().clone(), viewport, stencil_ref: params.stencil_test.as_ref().map(|s| s.ref_value), + scissor_box: params.scissor_box, start_idx: (offset * ipe) as u32, end_idx: ((offset + count) * ipe) as u32, instances: instance_count, @@ -880,7 +884,7 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { _viewport: Rect, color: Option, depth: Option, - _stencil: Option, + stencil: Option, ) { if let Some(c) = color { *self.pending_clear_color.borrow_mut() = wgpu::Color { @@ -893,6 +897,9 @@ impl GpuFrameBufferTrait for WgpuFrameBuffer { if let Some(d) = depth { *self.pending_clear_depth.borrow_mut() = d; } + if let Some(s) = stencil { + *self.pending_clear_stencil.borrow_mut() = s as u32; + } self.needs_clear.set(true); } fn draw( diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index 3aa265cc1e..3cb4795a92 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -43,7 +43,7 @@ use fyrox_graphics::server::{ GraphicsServer, ServerCapabilities, ServerMemoryUsage, SharedGraphicsServer, }; use fyrox_graphics::stats::PipelineStatistics; -use fyrox_graphics::{PolygonFace, PolygonFillMode}; +use fyrox_graphics::{PolygonFace, PolygonFillMode, ScissorBox}; use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::rc::{Rc, Weak}; @@ -80,6 +80,7 @@ pub struct DrawCommand { pub index_buffer: wgpu::Buffer, pub viewport: Rect, pub stencil_ref: Option, + pub scissor_box: Option, pub start_idx: u32, pub end_idx: u32, pub instances: u32, @@ -444,6 +445,28 @@ impl WgpuGraphicsServer { if let Some(st) = cmd.stencil_ref { rp.set_stencil_reference(st); } + match cmd.scissor_box { + Some(sb) => { + // The ScissorBox Y is computed for OpenGL (origin at bottom-left): + // y_gl = viewport_h - (pos_y + size_h) + // wgpu uses top-left origin (same as UI coords), so convert: + // y_wgpu = viewport_h - y_gl - height = pos_y + let rt_h = cmd.viewport.h(); + let wgpu_y = (rt_h - sb.y - sb.height).max(0); + rp.set_scissor_rect( + sb.x.max(0) as u32, + wgpu_y as u32, + sb.width.max(0) as u32, + sb.height.max(0) as u32, + ); + } + None => rp.set_scissor_rect( + cmd.viewport.x().max(0) as u32, + cmd.viewport.y().max(0) as u32, + cmd.viewport.w().max(0) as u32, + cmd.viewport.h().max(0) as u32, + ), + } for (i, vb) in cmd.vertex_buffers.iter().enumerate() { rp.set_vertex_buffer(i as u32, vb.slice(..)); } From e903dc16156a52266664d2ee9997c64ea39e4e2a Mon Sep 17 00:00:00 2001 From: nicehack Date: Sun, 26 Jul 2026 17:03:10 +0500 Subject: [PATCH 31/34] minor bug fixes, micro-optimization, and documentation --- editor/resources/shaders/wgpu/grid.shader | 2 +- fyrox-graphics-wgpu/src/framebuffer.rs | 21 +++-- fyrox-graphics-wgpu/src/server.rs | 88 +++++++++++++++------ fyrox-graphics-wgpu/src/shaders/shared.wgsl | 2 +- 4 files changed, 79 insertions(+), 34 deletions(-) diff --git a/editor/resources/shaders/wgpu/grid.shader b/editor/resources/shaders/wgpu/grid.shader index 827d327a93..60508580b3 100644 --- a/editor/resources/shaders/wgpu/grid.shader +++ b/editor/resources/shaders/wgpu/grid.shader @@ -195,7 +195,7 @@ var depth = computeDepth(fragPos3D); var output: FragOutput; - output.depth = (depth + 1.0) / 2.0; + output.depth = depth; output.color = grid(fragPos3D); if (properties.isPerspective != 0u) { diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index 297d000908..9d640227da 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -156,17 +156,29 @@ fn texture_format_for_attachment(tex: &GpuTexture) -> Option, + /// Depth-stencil attachment format, if present. depth_format: Option, + /// MSAA sample count. sample_count: u32, + /// Whether alpha blending is enabled. blend: bool, + /// Whether depth testing is enabled. depth_test: bool, + /// Whether depth writes are enabled. depth_write: bool, + /// Whether stencil operations are configured. stencil: bool, + /// Whether the pipeline has at least one color target. has_color: bool, + /// Cull mode encoded as u8 (0=None, 1=Front, 2=Back). cull: u8, + /// Number of extra vertex buffer slots (filled with dummy buffer). extra_vert_count: u8, + /// Polygon fill mode encoded as u8 (0=Point, 1=Line, 2=Fill). polygon_fill_mode: u8, /// Resource texture formats that determine the bind group layout. /// Ensures pipeline is recreated when texture formats change (e.g., R32Float is non-filterable). @@ -293,14 +305,9 @@ impl WgpuFrameBuffer { }, texture_resource_sample_types: sample_types, }; - let key_hash = { - let mut h = DefaultHasher::new(); - key.hash(&mut h); - h.finish() - }; { let cache = server.pipeline_cache.borrow(); - if let Some(p) = cache.get(&key_hash) { + if let Some(p) = cache.get(&key) { return p.clone(); } } @@ -473,7 +480,7 @@ impl WgpuFrameBuffer { server .pipeline_cache .borrow_mut() - .insert(key_hash, pipeline.clone()); + .insert(key, pipeline.clone()); pipeline } diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index 3cb4795a92..f61243c2f5 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -20,7 +20,7 @@ use crate::buffer::WgpuBuffer; use crate::format_helpers::is_filterable_format; -use crate::framebuffer::WgpuFrameBuffer; +use crate::framebuffer::{PipelineKey, WgpuFrameBuffer}; use crate::geometry_buffer::WgpuGeometryBuffer; use crate::program::{WgpuProgram, WgpuShader}; use crate::query::WgpuQuery; @@ -44,11 +44,10 @@ use fyrox_graphics::server::{ }; use fyrox_graphics::stats::PipelineStatistics; use fyrox_graphics::{PolygonFace, PolygonFillMode, ScissorBox}; -use std::cell::{Cell, RefCell}; +use std::cell::{Cell, OnceCell, RefCell}; use std::collections::HashMap; use std::rc::{Rc, Weak}; use std::sync::{Arc, RwLock}; -use wgpu::hal::DynQueue; use winit::event_loop::ActiveEventLoop; use winit::window::{Window, WindowAttributes}; @@ -72,27 +71,51 @@ fn fs_main(@builtin(position) pos: vec4) -> @location(0) vec4 { } "#; +/// A single draw call recorded into an [`ActivePass`]. Captures all GPU state +/// needed to replay the call when the pass is flushed. pub struct DrawCommand { + /// The compiled render pipeline for this draw. pub pipeline: wgpu::RenderPipeline, + /// Resource bind group (textures, samplers, uniforms). pub bind_group: Option, + /// Vertex buffers bound for this draw. pub vertex_buffers: Vec, + /// Number of extra vertex buffer slots filled with the dummy buffer. pub extra_verts: u32, + /// Index buffer for indexed drawing. pub index_buffer: wgpu::Buffer, + /// Viewport rectangle. pub viewport: Rect, + /// Stencil reference value, if stencil testing is enabled. pub stencil_ref: Option, + /// Optional scissor clipping rectangle. pub scissor_box: Option, + /// Start index in the index buffer. pub start_idx: u32, + /// End index in the index buffer. pub end_idx: u32, + /// Number of instances to draw. pub instances: u32, } +/// A batched render pass that accumulates draw commands for a single framebuffer. +/// +/// Created when the first draw call targets a framebuffer, and flushed +/// (encoded into a `wgpu::RenderPass`) when the target changes or the frame ends. pub struct ActivePass { + /// Identity of the target framebuffer (pointer-based). pub framebuffer_id: usize, + /// Color attachment views for the render pass. pub color_views: Vec, + /// Depth-stencil attachment view, if present. pub depth_view: Option, + /// Load operation for color attachments. pub color_load: wgpu::LoadOp, + /// Load operation for the depth attachment. pub depth_load: wgpu::LoadOp, + /// Load operation for the stencil attachment, if present. pub stencil_load: Option>, + /// Accumulated draw commands to replay when the pass is flushed. pub commands: Vec, } @@ -147,7 +170,7 @@ pub struct WgpuGraphicsServer { /// MSAA sample count (currently forced to 1). pub msaa_sample_count: u32, /// Hash-based cache of render pipelines, keyed by [`PipelineKey`]. - pub pipeline_cache: RefCell>, + pub pipeline_cache: RefCell>, /// Hash-based cache of bind groups, keyed by resource pointers and texture formats. pub bind_group_cache: RefCell>, weak_self: RefCell>>, @@ -168,6 +191,8 @@ pub struct WgpuGraphicsServer { /// Storing it on the server (not per-framebuffer) because multiple framebuffers /// (G-Buffer, HDR, backbuffer) share the same frame and benefit from a single submit. pub frame_encoder: RefCell>, + /// Currently accumulating render pass. Draw commands are batched here and + /// flushed when the target changes or the frame ends. pub active_pass: RefCell>, /// Sampler used for mipmap generation (linear filtering). mipmap_sampler: wgpu::Sampler, @@ -175,6 +200,10 @@ pub struct WgpuGraphicsServer { mipmap_bind_group_layout: wgpu::BindGroupLayout, /// Cached mipmap render pipelines, keyed by texture format. mipmap_pipeline_cache: RefCell>, + /// Shared shader module for mipmap generation (created once). + mipmap_shader: OnceCell, + /// Shared pipeline layout for mipmap generation (created once). + mipmap_pipeline_layout: OnceCell, /// Current polygon fill mode (Fill, Line, or Point). Baked into new pipelines. polygon_fill_mode: Cell, } @@ -378,6 +407,8 @@ impl WgpuGraphicsServer { mipmap_sampler, mipmap_bind_group_layout, mipmap_pipeline_cache: RefCell::new(HashMap::new()), + mipmap_shader: OnceCell::new(), + mipmap_pipeline_layout: OnceCell::new(), polygon_fill_mode: Cell::new(PolygonFillMode::Fill), }); @@ -404,6 +435,11 @@ impl WgpuGraphicsServer { self.polygon_fill_mode.get() } + /// Encodes the current [`ActivePass`] into the frame command encoder and clears it. + /// + /// All buffered draw commands are replayed into a `wgpu::RenderPass`, which is + /// then dropped (ending the pass). The encoder is stored back for subsequent + /// operations. Does nothing if there is no active pass. pub fn flush_active_pass(&self) { let Some(pass) = self.active_pass.borrow_mut().take() else { return; }; @@ -524,7 +560,7 @@ impl GraphicsServer for WgpuGraphicsServer { *cache = Some((w, h, tex)); } Err(e) => { - Log::warn("Failed to create backbuffer depth-stencil: {e}"); + Log::warn(format!("Failed to create backbuffer depth-stencil: {e}")); *cache = None; } } @@ -692,37 +728,39 @@ impl GraphicsServer for WgpuGraphicsServer { self.flush_active_pass(); - let has_pipeline = self.mipmap_pipeline_cache.borrow().contains_key(&format); - if !has_pipeline { - let shader = - self.state - .device - .create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("MipmapShader"), - source: wgpu::ShaderSource::Wgsl(MIPMAP_SHADER_SRC.into()), - }); - let layout = - self.state - .device - .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("MipmapPL"), - bind_group_layouts: &[Some(&self.mipmap_bind_group_layout)], - ..Default::default() - }); + let shader = self.mipmap_shader.get_or_init(|| { + self.state + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("MipmapShader"), + source: wgpu::ShaderSource::Wgsl(MIPMAP_SHADER_SRC.into()), + }) + }); + let layout = self.mipmap_pipeline_layout.get_or_init(|| { + self.state + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("MipmapPL"), + bind_group_layouts: &[Some(&self.mipmap_bind_group_layout)], + ..Default::default() + }) + }); + + if !self.mipmap_pipeline_cache.borrow().contains_key(&format) { let pipeline = self.state .device .create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("MipmapPipeline"), - layout: Some(&layout), + layout: Some(layout), vertex: wgpu::VertexState { - module: &shader, + module: shader, entry_point: Some("vs_main"), buffers: &[], compilation_options: Default::default(), }, fragment: Some(wgpu::FragmentState { - module: &shader, + module: shader, entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format, diff --git a/fyrox-graphics-wgpu/src/shaders/shared.wgsl b/fyrox-graphics-wgpu/src/shaders/shared.wgsl index 7c18d1a32d..0e2f80e995 100644 --- a/fyrox-graphics-wgpu/src/shaders/shared.wgsl +++ b/fyrox-graphics-wgpu/src/shaders/shared.wgsl @@ -40,7 +40,7 @@ fn inverse_mat4(m: mat4x4f) -> mat4x4f { inv[3][2] = m31 * b01 - m30 * b03 - m32 * b00; inv[3][3] = m20 * b03 - m21 * b01 + m22 * b00; - let det = m00 * b00 + m01 * b08 + m02 * b06 + m03 * b09; + let det = m00 * b00 - m01 * b01 + m02 * b02 - m03 * b03; let invDet = 1.0 / det; return inv * invDet; } From 59b65b41b88f298b47f05ce81da184c7fd2a340c Mon Sep 17 00:00:00 2001 From: nicehack Date: Fri, 31 Jul 2026 11:02:20 +0500 Subject: [PATCH 32/34] fyrox project manager support wgpu --- project-manager/Cargo.toml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/project-manager/Cargo.toml b/project-manager/Cargo.toml index afb71e925f..d7d211e547 100644 --- a/project-manager/Cargo.toml +++ b/project-manager/Cargo.toml @@ -24,4 +24,9 @@ serde_json = "1.0.133" directories = "5.0.1" [build-dependencies] -toml_edit = "0.23.5" \ No newline at end of file +toml_edit = "0.23.5" + +[features] +default = ["backend_opengl"] +backend_opengl = ["fyrox/backend_opengl"] +backend_wgpu = ["fyrox/backend_wgpu"] \ No newline at end of file From 90040ca85d7e21a406a980579a5bd907e1b9646f Mon Sep 17 00:00:00 2001 From: nicehack Date: Fri, 31 Jul 2026 12:43:54 +0500 Subject: [PATCH 33/34] fix uniform buffer size > 16320 --- fyrox-graphics-gl/src/server.rs | 2 +- fyrox-graphics-wgpu/src/buffer.rs | 58 ++++++++++++++++-------- fyrox-graphics-wgpu/src/framebuffer.rs | 7 ++- fyrox-graphics-wgpu/src/server.rs | 2 +- fyrox-graphics/src/server.rs | 6 ++- fyrox-impl/src/renderer/cache/uniform.rs | 29 ++++++++---- fyrox-impl/src/renderer/mod.rs | 9 ++-- fyrox-impl/src/renderer/resources.rs | 6 +-- 8 files changed, 75 insertions(+), 44 deletions(-) diff --git a/fyrox-graphics-gl/src/server.rs b/fyrox-graphics-gl/src/server.rs index d62fbfece4..1202ce8520 100644 --- a/fyrox-graphics-gl/src/server.rs +++ b/fyrox-graphics-gl/src/server.rs @@ -1494,7 +1494,7 @@ impl GraphicsServer for GlGraphicsServer { let gl = &self.gl; unsafe { ServerCapabilities { - max_uniform_block_size: gl.get_parameter_i32(glow::MAX_UNIFORM_BLOCK_SIZE) as usize, + max_uniform_buffer_binding_size: gl.get_parameter_i32(glow::MAX_UNIFORM_BLOCK_SIZE) as usize, uniform_buffer_offset_alignment: gl .get_parameter_i32(glow::UNIFORM_BUFFER_OFFSET_ALIGNMENT) as usize, diff --git a/fyrox-graphics-wgpu/src/buffer.rs b/fyrox-graphics-wgpu/src/buffer.rs index eca4c952de..e6b8e0eace 100644 --- a/fyrox-graphics-wgpu/src/buffer.rs +++ b/fyrox-graphics-wgpu/src/buffer.rs @@ -19,12 +19,11 @@ // SOFTWARE. use crate::server::WgpuGraphicsServer; -use fyrox_core::log::Log; use fyrox_graphics::{ buffer::{BufferKind, BufferUsage, GpuBufferDescriptor, GpuBufferTrait}, error::FrameworkError, }; -use std::cell::Cell; +use std::cell::{Cell, RefCell}; use std::rc::Weak; /// Maps a Fyrox [`BufferKind`] to the corresponding wgpu [`BufferUsages`] flags. @@ -49,11 +48,12 @@ fn buffer_usage_to_wgpu(kind: BufferKind) -> wgpu::BufferUsages { /// Wgpu implementation of [`GpuBufferTrait`](fyrox_graphics::buffer::GpuBufferTrait). /// /// Wraps a [`wgpu::Buffer`] and tracks its memory usage via [`ServerMemoryUsage`]. -/// Supports writing data larger than the buffer capacity (truncated with a warning) -/// and synchronous GPU readback via mapped buffers. +/// When `write_data` receives data larger than the current buffer capacity, the buffer +/// is transparently reallocated to fit — matching the OpenGL backend behavior. Memory +/// accounting is updated accordingly. pub struct WgpuBuffer { server: Weak, - buffer: wgpu::Buffer, + buffer: RefCell, size: Cell, kind: BufferKind, usage: BufferUsage, @@ -82,7 +82,7 @@ impl WgpuBuffer { server.memory_usage.borrow_mut().buffers += desc.size; Ok(Self { server: server.weak_ref(), - buffer, + buffer: RefCell::new(buffer), size: Cell::new(desc.size), kind: desc.kind, usage: desc.usage, @@ -90,8 +90,15 @@ impl WgpuBuffer { } /// Returns a reference to the underlying [`wgpu::Buffer`]. - pub fn wgpu_buffer(&self) -> &wgpu::Buffer { - &self.buffer + /// + /// # Safety + /// + /// The returned reference must not be held across a call to `write_data`, + /// which may replace the inner buffer. In practice this is always satisfied + /// because `write_data` takes `&self` and completes before external access. + pub unsafe fn wgpu_buffer_raw(&self) -> &wgpu::Buffer { + // SAFETY: The caller guarantees no mutable borrow is active. + unsafe { &*self.buffer.as_ptr() } } } @@ -122,17 +129,27 @@ impl GpuBufferTrait for WgpuBuffer { return Err(FrameworkError::GraphicsServerUnavailable); }; if data.len() <= self.size.get() { - server.state.queue.write_buffer(&self.buffer, 0, data); + server.state.queue.write_buffer(&self.buffer.borrow(), 0, data); } else { - Log::warn(format!( - "WgpuBuffer::write_data: data ({} bytes) exceeds buffer ({} bytes)", - data.len(), - self.size.get() - )); - server - .state - .queue - .write_buffer(&self.buffer, 0, &data[..self.size.get()]); + // Reallocate the buffer to fit the larger data, matching the GL backend + // behavior. This prevents silent data truncation that caused rendering + // artifacts (e.g. broken skeletal animation when bone count changes). + let new_size = data.len(); + let wgpu_usage = buffer_usage_to_wgpu(self.kind); + let new_buffer = server.state.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: new_size as u64, + usage: wgpu_usage, + mapped_at_creation: false, + }); + server.state.queue.write_buffer(&new_buffer, 0, data); + + let mut mem = server.memory_usage.borrow_mut(); + mem.buffers -= self.size.get(); + mem.buffers += new_size; + + *self.buffer.borrow_mut() = new_buffer; + self.size.set(new_size); } Ok(()) } @@ -142,7 +159,8 @@ impl GpuBufferTrait for WgpuBuffer { return Err(FrameworkError::GraphicsServerUnavailable); }; - let buffer_slice = self.buffer.slice(..data.len() as u64); + let buf = self.buffer.borrow(); + let buffer_slice = buf.slice(..data.len() as u64); let (tx, rx) = std::sync::mpsc::channel(); buffer_slice.map_async(wgpu::MapMode::Read, move |result| { @@ -168,7 +186,7 @@ impl GpuBufferTrait for WgpuBuffer { data.copy_from_slice(&mapped); drop(mapped); - self.buffer.unmap(); + buf.unmap(); Ok(()) } diff --git a/fyrox-graphics-wgpu/src/framebuffer.rs b/fyrox-graphics-wgpu/src/framebuffer.rs index 9d640227da..ac57e83470 100644 --- a/fyrox-graphics-wgpu/src/framebuffer.rs +++ b/fyrox-graphics-wgpu/src/framebuffer.rs @@ -1069,13 +1069,16 @@ fn create_bind_group( // Hash the WgpuBuffer thin pointer + binding + data usage hasher.write_usize(wb as *const WgpuBuffer as usize); hasher.write_u32(*loc as u32); + // SAFETY: No write_data call is active at this point (we're building + // bind groups between draw calls), so the buffer reference is stable. + let wb_buf = unsafe { wb.wgpu_buffer_raw() }; match data_usage { BufferDataUsage::UseEverything => { hasher.write_u64(0); entries.push(wgpu::BindGroupEntry { binding: (*loc + UNIFORM_BINDING_OFFSET) as u32, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { - buffer: wb.wgpu_buffer(), + buffer: wb_buf, offset: 0, size: None, }), @@ -1089,7 +1092,7 @@ fn create_bind_group( entries.push(wgpu::BindGroupEntry { binding: (*loc + UNIFORM_BINDING_OFFSET) as u32, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { - buffer: wb.wgpu_buffer(), + buffer: wb_buf, offset: *offset as u64, size: Some(nonzero_size), }), diff --git a/fyrox-graphics-wgpu/src/server.rs b/fyrox-graphics-wgpu/src/server.rs index f61243c2f5..4ebb7ecffe 100644 --- a/fyrox-graphics-wgpu/src/server.rs +++ b/fyrox-graphics-wgpu/src/server.rs @@ -702,7 +702,7 @@ impl GraphicsServer for WgpuGraphicsServer { fn capabilities(&self) -> ServerCapabilities { let limits = self.state.device.limits(); ServerCapabilities { - max_uniform_block_size: limits.max_uniform_buffer_binding_size as usize, + max_uniform_buffer_binding_size: limits.max_uniform_buffer_binding_size as usize, uniform_buffer_offset_alignment: limits.min_uniform_buffer_offset_alignment as usize, max_lod_bias: 16.0, } diff --git a/fyrox-graphics/src/server.rs b/fyrox-graphics/src/server.rs index 19150cbac2..cdd260ff6f 100644 --- a/fyrox-graphics/src/server.rs +++ b/fyrox-graphics/src/server.rs @@ -43,8 +43,10 @@ use std::rc::{Rc, Weak}; /// Graphics server capabilities. #[derive(Debug)] pub struct ServerCapabilities { - /// The maximum size in basic machine units of a uniform block, which must be at least 16384. - pub max_uniform_block_size: usize, + /// The maximum size in bytes of a single uniform buffer binding in a bind group. + /// This corresponds to `GL_MAX_UNIFORM_BLOCK_SIZE` (OpenGL) and + /// `max_uniform_buffer_binding_size` (wgpu). Must be at least 16384. + pub max_uniform_buffer_binding_size: usize, /// The minimum required alignment for uniform buffer sizes and offset. The initial value is 1. pub uniform_buffer_offset_alignment: usize, /// The maximum, absolute value of the texture level-of-detail bias. The value must be at least diff --git a/fyrox-impl/src/renderer/cache/uniform.rs b/fyrox-impl/src/renderer/cache/uniform.rs index 50b882cc19..8d23393cc8 100644 --- a/fyrox-impl/src/renderer/cache/uniform.rs +++ b/fyrox-impl/src/renderer/cache/uniform.rs @@ -88,7 +88,15 @@ impl UniformBufferCache { pub fn get_or_create(&self, size: usize) -> Result { let mut cache = self.cache.borrow_mut(); let set = cache.entry(size).or_default(); - set.get_or_create(size, &*self.server) + let buffer = set.get_or_create(size, &*self.server)?; + debug_assert_eq!( + buffer.size(), + size, + "UniformBufferCache returned buffer of size {} but {} was requested", + buffer.size(), + size + ); + Ok(buffer) } /// Fetches a suitable (or creates new one) GPU uniform buffer for the given CPU uniform buffer @@ -140,17 +148,20 @@ pub struct UniformBlockLocation { pub struct UniformMemoryAllocator { gpu_buffers: Vec, block_alignment: usize, - max_uniform_buffer_size: usize, + /// Maximum size of a single uniform buffer binding. This is the GPU's + /// `max_uniform_buffer_binding_size` and is used as both the page capacity + /// and the per-allocation size limit. + max_uniform_buffer_binding_size: usize, pages: Vec, blocks: Vec, } impl UniformMemoryAllocator { - pub fn new(max_uniform_buffer_size: usize, block_alignment: usize) -> Self { + pub fn new(max_uniform_buffer_binding_size: usize, block_alignment: usize) -> Self { Self { gpu_buffers: Default::default(), block_alignment, - max_uniform_buffer_size, + max_uniform_buffer_binding_size, pages: Default::default(), blocks: Default::default(), } @@ -170,20 +181,20 @@ impl UniformMemoryAllocator { { let data = buffer.finish(); assert!(data.bytes_count() > 0); - assert!(data.bytes_count() < self.max_uniform_buffer_size); + assert!(data.bytes_count() < self.max_uniform_buffer_binding_size); let page_index = match self.pages.iter().position(|page| { let write_position = page .dynamic .next_write_aligned_position(self.block_alignment); - self.max_uniform_buffer_size - write_position >= data.bytes_count() + self.max_uniform_buffer_binding_size - write_position >= data.bytes_count() }) { Some(page_index) => page_index, None => { let page_index = self.pages.len(); self.pages.push(Page { dynamic: UniformBuffer::with_storage(Vec::with_capacity( - self.max_uniform_buffer_size, + self.max_uniform_buffer_binding_size, )), is_submitted: false, }); @@ -211,7 +222,7 @@ impl UniformMemoryAllocator { for _ in 0..(self.pages.len() - self.gpu_buffers.len()) { let buffer = server.create_buffer(GpuBufferDescriptor { name: &format!("UniformMemoryPage{}", self.gpu_buffers.len()), - size: self.max_uniform_buffer_size, + size: self.max_uniform_buffer_binding_size, kind: BufferKind::Uniform, usage: BufferUsage::StreamCopy, })?; @@ -222,7 +233,7 @@ impl UniformMemoryAllocator { for (page, gpu_buffer) in self.pages.iter_mut().zip(self.gpu_buffers.iter()) { if !page.is_submitted { let bytes = page.dynamic.storage().bytes(); - assert!(bytes.len() <= self.max_uniform_buffer_size); + assert!(bytes.len() <= self.max_uniform_buffer_binding_size); gpu_buffer.write_data(bytes)?; page.is_submitted = true; } diff --git a/fyrox-impl/src/renderer/mod.rs b/fyrox-impl/src/renderer/mod.rs index 455e682aa2..acb37014e0 100644 --- a/fyrox-impl/src/renderer/mod.rs +++ b/fyrox-impl/src/renderer/mod.rs @@ -644,12 +644,11 @@ impl Renderer { let shader_cache = ShaderCache::default(); - let one_megabyte = 1024 * 1024; let uniform_memory_allocator = UniformMemoryAllocator::new( - // Clamp max uniform block size from the upper bound, to prevent allocating huge - // uniform buffers when GPU supports it. Some AMD GPUs are able to allocate ~500 Mb - // uniform buffers, which will lead to ridiculous VRAM consumption. - caps.max_uniform_block_size.min(one_megabyte), + // Use the GPU's max uniform buffer binding size as the page size limit. + // This ensures individual bindings never exceed the limit enforced by + // create_bind_group(). On most GPUs this is 64KB. + caps.max_uniform_buffer_binding_size, caps.uniform_buffer_offset_alignment, ); diff --git a/fyrox-impl/src/renderer/resources.rs b/fyrox-impl/src/renderer/resources.rs index 87de6c73ba..4e92a53a75 100644 --- a/fyrox-impl/src/renderer/resources.rs +++ b/fyrox-impl/src/renderer/resources.rs @@ -21,7 +21,7 @@ //! A set of textures of certain kinds. See [`RendererResources`] docs for more info. use crate::{ - core::{algebra::Matrix4, array_as_u8_slice}, + core::algebra::Matrix4, graphics::{ buffer::GpuBufferDescriptor, buffer::{BufferKind, BufferUsage, GpuBuffer}, @@ -272,9 +272,7 @@ impl RendererResources { kind: BufferKind::Uniform, usage: BufferUsage::StaticDraw, })?; - const SIZE: usize = ShaderDefinition::MAX_BONE_MATRICES * size_of::>(); - let zeros = [0.0; SIZE]; - buffer.write_data(array_as_u8_slice(&zeros))?; + buffer.write_data(&vec![0u8; ShaderDefinition::MAX_BONE_MATRICES * size_of::>()])?; buffer }, linear_clamp_sampler: server.create_sampler(GpuSamplerDescriptor { From 27ae1eb9692040d41f1b7ae9d93ad9ff6328c8ee Mon Sep 17 00:00:00 2001 From: nicehack Date: Fri, 31 Jul 2026 17:04:55 +0500 Subject: [PATCH 34/34] tmp fix displaying models in deferred rendering --- fyrox-impl/src/renderer/light.rs | 5 +++-- fyrox-impl/src/renderer/mod.rs | 33 +++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/fyrox-impl/src/renderer/light.rs b/fyrox-impl/src/renderer/light.rs index 2bc4704690..4d45348737 100644 --- a/fyrox-impl/src/renderer/light.rs +++ b/fyrox-impl/src/renderer/light.rs @@ -48,7 +48,6 @@ use crate::{ framework::GeometryBufferExt, gbuffer::GBuffer, light_volume::LightVolumeRenderer, - make_viewport_matrix, observer::Observer, resources::RendererResources, shadow::{ @@ -70,6 +69,7 @@ use crate::{ EnvironmentLightingSource, Scene, }, }; +use crate::renderer::make_deferred_viewport_matrix; pub struct DeferredLightRenderer { sphere: GpuGeometryBuffer, @@ -291,7 +291,8 @@ impl DeferredLightRenderer { Frustum::from_view_projection_matrix(observer.position.view_projection_matrix) .unwrap_or_default(); - let frame_matrix = make_viewport_matrix(viewport); + // TODO: A temporary fix until we figure out what's going on. For some reason, the deferred light renderer requires an inverted viewport matrix. + let frame_matrix = make_deferred_viewport_matrix(viewport); let inv_projection = observer .position diff --git a/fyrox-impl/src/renderer/mod.rs b/fyrox-impl/src/renderer/mod.rs index acb37014e0..e0925f7a37 100644 --- a/fyrox-impl/src/renderer/mod.rs +++ b/fyrox-impl/src/renderer/mod.rs @@ -363,7 +363,8 @@ impl RenderDataContainer { } } -/// Creates a view-projection matrix that projects unit quad a screen with the specified viewport. +/// Creates a view-projection matrix that projects unit quad to screen with the specified viewport. +/// Uses OpenGL-style orthographic parameters (bottom=h, top=0). pub fn make_viewport_matrix(viewport: Rect) -> Matrix4 { Matrix4::new_orthographic( 0.0, @@ -379,6 +380,36 @@ pub fn make_viewport_matrix(viewport: Rect) -> Matrix4 { )) } +/// Viewport matrix for deferred passes that reconstruct world positions from G-Buffer depth. +/// +/// In wgpu, the viewport Y-axis is flipped relative to OpenGL (Y=0 at top vs bottom). +/// Deferred passes that use `S_UnProject` with depth need the orthographic bottom/top +/// swapped so that texture coordinates match the G-Buffer's pixel layout. +/// +/// Non-deferred passes (bloom, FXAA, HDR) should use [`make_viewport_matrix`] instead. +#[cfg(feature = "backend_wgpu")] +pub fn make_deferred_viewport_matrix(viewport: Rect) -> Matrix4 { + Matrix4::new_orthographic( + 0.0, + viewport.w() as f32, + 0.0, + viewport.h() as f32, + -1.0, + 1.0, + ) * Matrix4::new_nonuniform_scaling(&Vector3::new( + viewport.w() as f32, + viewport.h() as f32, + 0.0, + )) +} + +/// Viewport matrix for deferred passes that reconstruct world positions from G-Buffer depth. +/// On OpenGL this is identical to [`make_viewport_matrix`]. +#[cfg(not(feature = "backend_wgpu"))] +pub fn make_deferred_viewport_matrix(viewport: Rect) -> Matrix4 { + make_viewport_matrix(viewport) +} + /// See module docs. pub struct Renderer { backbuffer: GpuFrameBuffer,