Replace FULLSCREEN_SHADER_HANDLE
with a FullscreenShader
resource. (#19426)
# Objective - Related to #19024. ## Solution - Remove the `FULLSCREEN_SHADER_HANDLE` `weak_handle` with a resource holding the shader handle. - This also changes us from using `load_internal_asset` to `embedded_asset`/`load_embedded_asset`. - All uses have been migrated to clone the `FullscreenShader` resource and use its `to_vertex_state` method. ## Testing - `anti_aliasing` example still works. - `bloom_3d` example still works. --------- Co-authored-by: charlotte 🌸 <charlotte.c.mcelwain@gmail.com>
This commit is contained in:
parent
9f376e2537
commit
a7fdd6fc6f
@ -3,7 +3,7 @@ use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
core_2d::graph::{Core2d, Node2d},
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
|
||||
FullscreenShader,
|
||||
};
|
||||
use bevy_ecs::{prelude::*, query::QueryItem};
|
||||
use bevy_image::BevyDefault as _;
|
||||
@ -163,7 +163,8 @@ impl Plugin for CasPlugin {
|
||||
pub struct CasPipeline {
|
||||
texture_bind_group: BindGroupLayout,
|
||||
sampler: Sampler,
|
||||
shader: Handle<Shader>,
|
||||
fullscreen_shader: FullscreenShader,
|
||||
fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for CasPipeline {
|
||||
@ -187,7 +188,11 @@ impl FromWorld for CasPipeline {
|
||||
CasPipeline {
|
||||
texture_bind_group,
|
||||
sampler,
|
||||
shader: load_embedded_asset!(render_world, "robust_contrast_adaptive_sharpening.wgsl"),
|
||||
fullscreen_shader: render_world.resource::<FullscreenShader>().clone(),
|
||||
fragment_shader: load_embedded_asset!(
|
||||
render_world,
|
||||
"robust_contrast_adaptive_sharpening.wgsl"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,9 +214,9 @@ impl SpecializedRenderPipeline for CasPipeline {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("contrast_adaptive_sharpening".into()),
|
||||
layout: vec![self.texture_bind_group.clone()],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone(),
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
|
@ -3,7 +3,7 @@ use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
core_2d::graph::{Core2d, Node2d},
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
|
||||
FullscreenShader,
|
||||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_image::BevyDefault as _;
|
||||
@ -130,7 +130,8 @@ impl Plugin for FxaaPlugin {
|
||||
pub struct FxaaPipeline {
|
||||
texture_bind_group: BindGroupLayout,
|
||||
sampler: Sampler,
|
||||
shader: Handle<Shader>,
|
||||
fullscreen_shader: FullscreenShader,
|
||||
fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for FxaaPipeline {
|
||||
@ -157,7 +158,8 @@ impl FromWorld for FxaaPipeline {
|
||||
FxaaPipeline {
|
||||
texture_bind_group,
|
||||
sampler,
|
||||
shader: load_embedded_asset!(render_world, "fxaa.wgsl"),
|
||||
fullscreen_shader: render_world.resource::<FullscreenShader>().clone(),
|
||||
fragment_shader: load_embedded_asset!(render_world, "fxaa.wgsl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -181,9 +183,9 @@ impl SpecializedRenderPipeline for FxaaPipeline {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("fxaa".into()),
|
||||
layout: vec![self.texture_bind_group.clone()],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone(),
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs: vec![
|
||||
format!("EDGE_THRESH_{}", key.edge_threshold.get_str()).into(),
|
||||
format!("EDGE_THRESH_MIN_{}", key.edge_threshold_min.get_str()).into(),
|
||||
|
@ -2,9 +2,9 @@ use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
|
||||
prelude::Camera3d,
|
||||
prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures},
|
||||
FullscreenShader,
|
||||
};
|
||||
use bevy_diagnostic::FrameCount;
|
||||
use bevy_ecs::{
|
||||
@ -238,7 +238,8 @@ struct TaaPipeline {
|
||||
taa_bind_group_layout: BindGroupLayout,
|
||||
nearest_sampler: Sampler,
|
||||
linear_sampler: Sampler,
|
||||
shader: Handle<Shader>,
|
||||
fullscreen_shader: FullscreenShader,
|
||||
fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for TaaPipeline {
|
||||
@ -283,7 +284,8 @@ impl FromWorld for TaaPipeline {
|
||||
taa_bind_group_layout,
|
||||
nearest_sampler,
|
||||
linear_sampler,
|
||||
shader: load_embedded_asset!(world, "taa.wgsl"),
|
||||
fullscreen_shader: world.resource::<FullscreenShader>().clone(),
|
||||
fragment_shader: load_embedded_asset!(world, "taa.wgsl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -314,9 +316,9 @@ impl SpecializedRenderPipeline for TaaPipeline {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("taa_pipeline".into()),
|
||||
layout: vec![self.taa_bind_group_layout.clone()],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone(),
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: "taa".into(),
|
||||
targets: vec![
|
||||
|
@ -10,7 +10,7 @@ use bevy_render::{
|
||||
RenderApp,
|
||||
};
|
||||
|
||||
use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
|
||||
use crate::FullscreenShader;
|
||||
|
||||
/// Adds support for specialized "blit pipelines", which can be used to write one texture to another.
|
||||
pub struct BlitPlugin;
|
||||
@ -38,7 +38,8 @@ impl Plugin for BlitPlugin {
|
||||
pub struct BlitPipeline {
|
||||
pub texture_bind_group: BindGroupLayout,
|
||||
pub sampler: Sampler,
|
||||
pub shader: Handle<Shader>,
|
||||
pub fullscreen_shader: FullscreenShader,
|
||||
pub fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for BlitPipeline {
|
||||
@ -61,7 +62,8 @@ impl FromWorld for BlitPipeline {
|
||||
BlitPipeline {
|
||||
texture_bind_group,
|
||||
sampler,
|
||||
shader: load_embedded_asset!(render_world, "blit.wgsl"),
|
||||
fullscreen_shader: render_world.resource::<FullscreenShader>().clone(),
|
||||
fragment_shader: load_embedded_asset!(render_world, "blit.wgsl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,9 +82,9 @@ impl SpecializedRenderPipeline for BlitPipeline {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("blit pipeline".into()),
|
||||
layout: vec![self.texture_bind_group.clone()],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone(),
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs: vec![],
|
||||
entry_point: "fs_main".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::FullscreenShader;
|
||||
|
||||
use super::{Bloom, BLOOM_TEXTURE_FORMAT};
|
||||
use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
|
||||
use bevy_asset::{load_embedded_asset, Handle};
|
||||
use bevy_ecs::{
|
||||
prelude::{Component, Entity},
|
||||
@ -27,8 +28,10 @@ pub struct BloomDownsamplingPipeline {
|
||||
/// Layout with a texture, a sampler, and uniforms
|
||||
pub bind_group_layout: BindGroupLayout,
|
||||
pub sampler: Sampler,
|
||||
/// The shader asset handle.
|
||||
pub shader: Handle<Shader>,
|
||||
/// The asset handle for the fullscreen vertex shader.
|
||||
pub fullscreen_shader: FullscreenShader,
|
||||
/// The fragment shader asset handle.
|
||||
pub fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
@ -81,7 +84,8 @@ impl FromWorld for BloomDownsamplingPipeline {
|
||||
BloomDownsamplingPipeline {
|
||||
bind_group_layout,
|
||||
sampler,
|
||||
shader: load_embedded_asset!(world, "bloom.wgsl"),
|
||||
fullscreen_shader: world.resource::<FullscreenShader>().clone(),
|
||||
fragment_shader: load_embedded_asset!(world, "bloom.wgsl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,9 +126,9 @@ impl SpecializedRenderPipeline for BloomDownsamplingPipeline {
|
||||
.into(),
|
||||
),
|
||||
layout,
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone(),
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs,
|
||||
entry_point,
|
||||
targets: vec![Some(ColorTargetState {
|
||||
|
@ -1,7 +1,8 @@
|
||||
use crate::FullscreenShader;
|
||||
|
||||
use super::{
|
||||
downsampling_pipeline::BloomUniforms, Bloom, BloomCompositeMode, BLOOM_TEXTURE_FORMAT,
|
||||
};
|
||||
use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
|
||||
use bevy_asset::{load_embedded_asset, Handle};
|
||||
use bevy_ecs::{
|
||||
prelude::{Component, Entity},
|
||||
@ -27,8 +28,10 @@ pub struct UpsamplingPipelineIds {
|
||||
#[derive(Resource)]
|
||||
pub struct BloomUpsamplingPipeline {
|
||||
pub bind_group_layout: BindGroupLayout,
|
||||
/// The shader asset handle.
|
||||
pub shader: Handle<Shader>,
|
||||
/// The asset handle for the fullscreen vertex shader.
|
||||
pub fullscreen_shader: FullscreenShader,
|
||||
/// The fragment shader asset handle.
|
||||
pub fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
@ -58,7 +61,8 @@ impl FromWorld for BloomUpsamplingPipeline {
|
||||
|
||||
BloomUpsamplingPipeline {
|
||||
bind_group_layout,
|
||||
shader: load_embedded_asset!(world, "bloom.wgsl"),
|
||||
fullscreen_shader: world.resource::<FullscreenShader>().clone(),
|
||||
fragment_shader: load_embedded_asset!(world, "bloom.wgsl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,9 +112,9 @@ impl SpecializedRenderPipeline for BloomUpsamplingPipeline {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("bloom_upsampling_pipeline".into()),
|
||||
layout: vec![self.bind_group_layout.clone()],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone(),
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs: vec![],
|
||||
entry_point: "upsample".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
|
||||
prepass::{DeferredPrepass, ViewPrepassTextures},
|
||||
FullscreenShader,
|
||||
};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset};
|
||||
@ -130,6 +130,7 @@ impl FromWorld for CopyDeferredLightingIdPipeline {
|
||||
),
|
||||
);
|
||||
|
||||
let vertex_state = world.resource::<FullscreenShader>().to_vertex_state();
|
||||
let shader = load_embedded_asset!(world, "copy_deferred_lighting_id.wgsl");
|
||||
|
||||
let pipeline_id =
|
||||
@ -138,7 +139,7 @@ impl FromWorld for CopyDeferredLightingIdPipeline {
|
||||
.queue_render_pipeline(RenderPipelineDescriptor {
|
||||
label: Some("copy_deferred_lighting_id_pipeline".into()),
|
||||
layout: vec![layout.clone()],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: vertex_state,
|
||||
fragment: Some(FragmentState {
|
||||
shader,
|
||||
shader_defs: vec![],
|
||||
|
@ -66,7 +66,7 @@ use crate::{
|
||||
graph::{Core3d, Node3d},
|
||||
Camera3d, DEPTH_TEXTURE_SAMPLING_SUPPORTED,
|
||||
},
|
||||
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
|
||||
FullscreenShader,
|
||||
};
|
||||
|
||||
/// A plugin that adds support for the depth of field effect to Bevy.
|
||||
@ -325,8 +325,10 @@ pub struct DepthOfFieldPipeline {
|
||||
/// The bind group layout shared among all invocations of the depth of field
|
||||
/// shader.
|
||||
global_bind_group_layout: BindGroupLayout,
|
||||
/// The shader asset handle.
|
||||
shader: Handle<Shader>,
|
||||
/// The asset handle for the fullscreen vertex shader.
|
||||
fullscreen_shader: FullscreenShader,
|
||||
/// The fragment shader asset handle.
|
||||
fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl ViewNode for DepthOfFieldNode {
|
||||
@ -678,13 +680,15 @@ pub fn prepare_depth_of_field_pipelines(
|
||||
&ViewDepthOfFieldBindGroupLayouts,
|
||||
&Msaa,
|
||||
)>,
|
||||
fullscreen_shader: Res<FullscreenShader>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
for (entity, view, depth_of_field, view_bind_group_layouts, msaa) in view_targets.iter() {
|
||||
let dof_pipeline = DepthOfFieldPipeline {
|
||||
view_bind_group_layouts: view_bind_group_layouts.clone(),
|
||||
global_bind_group_layout: global_bind_group_layout.layout.clone(),
|
||||
shader: load_embedded_asset!(asset_server.as_ref(), "dof.wgsl"),
|
||||
fullscreen_shader: fullscreen_shader.clone(),
|
||||
fragment_shader: load_embedded_asset!(asset_server.as_ref(), "dof.wgsl"),
|
||||
};
|
||||
|
||||
// We'll need these two flags to create the `DepthOfFieldPipelineKey`s.
|
||||
@ -797,12 +801,12 @@ impl SpecializedRenderPipeline for DepthOfFieldPipeline {
|
||||
label: Some("depth of field pipeline".into()),
|
||||
layout,
|
||||
push_constant_ranges: vec![],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
primitive: default(),
|
||||
depth_stencil: None,
|
||||
multisample: default(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone(),
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: match key.pass {
|
||||
DofPass::GaussianHorizontal => "gaussian_horizontal".into(),
|
||||
|
@ -1,25 +1,40 @@
|
||||
use bevy_asset::{weak_handle, Handle};
|
||||
use bevy_asset::{load_embedded_asset, Handle};
|
||||
use bevy_ecs::{resource::Resource, world::FromWorld};
|
||||
use bevy_render::{prelude::Shader, render_resource::VertexState};
|
||||
|
||||
pub const FULLSCREEN_SHADER_HANDLE: Handle<Shader> =
|
||||
weak_handle!("481fb759-d0b1-4175-8319-c439acde30a2");
|
||||
/// A shader that renders to the whole screen. Useful for post-processing.
|
||||
#[derive(Resource, Clone)]
|
||||
pub struct FullscreenShader(Handle<Shader>);
|
||||
|
||||
/// uses the [`FULLSCREEN_SHADER_HANDLE`] to output a
|
||||
/// ```wgsl
|
||||
/// struct FullscreenVertexOutput {
|
||||
/// [[builtin(position)]]
|
||||
/// position: vec4<f32>;
|
||||
/// [[location(0)]]
|
||||
/// uv: vec2<f32>;
|
||||
/// };
|
||||
/// ```
|
||||
/// from the vertex shader.
|
||||
/// The draw call should render one triangle: `render_pass.draw(0..3, 0..1);`
|
||||
pub fn fullscreen_shader_vertex_state() -> VertexState {
|
||||
VertexState {
|
||||
shader: FULLSCREEN_SHADER_HANDLE,
|
||||
shader_defs: Vec::new(),
|
||||
entry_point: "fullscreen_vertex_shader".into(),
|
||||
buffers: Vec::new(),
|
||||
impl FromWorld for FullscreenShader {
|
||||
fn from_world(world: &mut bevy_ecs::world::World) -> Self {
|
||||
Self(load_embedded_asset!(world, "fullscreen.wgsl"))
|
||||
}
|
||||
}
|
||||
|
||||
impl FullscreenShader {
|
||||
/// Gets the raw shader handle.
|
||||
pub fn shader(&self) -> Handle<Shader> {
|
||||
self.0.clone()
|
||||
}
|
||||
|
||||
/// Creates a [`VertexState`] that uses the [`FullscreenShader`] to output a
|
||||
/// ```wgsl
|
||||
/// struct FullscreenVertexOutput {
|
||||
/// @builtin(position)
|
||||
/// position: vec4<f32>;
|
||||
/// @location(0)
|
||||
/// uv: vec2<f32>;
|
||||
/// };
|
||||
/// ```
|
||||
/// from the vertex shader.
|
||||
/// The draw call should render one triangle: `render_pass.draw(0..3, 0..1);`
|
||||
pub fn to_vertex_state(&self) -> VertexState {
|
||||
VertexState {
|
||||
shader: self.0.clone(),
|
||||
shader_defs: Vec::new(),
|
||||
entry_point: "fullscreen_vertex_shader".into(),
|
||||
buffers: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,18 +14,20 @@ pub mod core_3d;
|
||||
pub mod deferred;
|
||||
pub mod dof;
|
||||
pub mod experimental;
|
||||
pub mod fullscreen_vertex_shader;
|
||||
pub mod motion_blur;
|
||||
pub mod msaa_writeback;
|
||||
pub mod oit;
|
||||
pub mod post_process;
|
||||
pub mod prepass;
|
||||
mod skybox;
|
||||
pub mod tonemapping;
|
||||
pub mod upscaling;
|
||||
|
||||
pub use fullscreen_vertex_shader::FullscreenShader;
|
||||
pub use skybox::Skybox;
|
||||
|
||||
mod fullscreen_vertex_shader;
|
||||
mod skybox;
|
||||
|
||||
/// The core pipeline prelude.
|
||||
///
|
||||
/// This includes the most common types in this crate, re-exported for your convenience.
|
||||
@ -42,7 +44,6 @@ use crate::{
|
||||
deferred::copy_lighting_id::CopyDeferredLightingIdPlugin,
|
||||
dof::DepthOfFieldPlugin,
|
||||
experimental::mip_generation::MipGenerationPlugin,
|
||||
fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE,
|
||||
motion_blur::MotionBlurPlugin,
|
||||
msaa_writeback::MsaaWritebackPlugin,
|
||||
post_process::PostProcessingPlugin,
|
||||
@ -51,8 +52,8 @@ use crate::{
|
||||
upscaling::UpscalingPlugin,
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::load_internal_asset;
|
||||
use bevy_render::prelude::Shader;
|
||||
use bevy_asset::embedded_asset;
|
||||
use bevy_render::RenderApp;
|
||||
use oit::OrderIndependentTransparencyPlugin;
|
||||
|
||||
#[derive(Default)]
|
||||
@ -60,17 +61,13 @@ pub struct CorePipelinePlugin;
|
||||
|
||||
impl Plugin for CorePipelinePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
load_internal_asset!(
|
||||
app,
|
||||
FULLSCREEN_SHADER_HANDLE,
|
||||
"fullscreen_vertex_shader/fullscreen.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
embedded_asset!(app, "fullscreen_vertex_shader/fullscreen.wgsl");
|
||||
|
||||
app.register_type::<DepthPrepass>()
|
||||
.register_type::<NormalPrepass>()
|
||||
.register_type::<MotionVectorPrepass>()
|
||||
.register_type::<DeferredPrepass>()
|
||||
.init_resource::<FullscreenShader>()
|
||||
.add_plugins((Core2dPlugin, Core3dPlugin, CopyDeferredLightingIdPlugin))
|
||||
.add_plugins((
|
||||
BlitPlugin,
|
||||
@ -85,4 +82,11 @@ impl Plugin for CorePipelinePlugin {
|
||||
MipGenerationPlugin,
|
||||
));
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
render_app.init_resource::<FullscreenShader>();
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ use bevy_render::{
|
||||
view::{ExtractedView, Msaa, ViewTarget},
|
||||
};
|
||||
|
||||
use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
|
||||
use crate::FullscreenShader;
|
||||
|
||||
use super::MotionBlurUniform;
|
||||
|
||||
@ -34,11 +34,16 @@ pub struct MotionBlurPipeline {
|
||||
pub(crate) sampler: Sampler,
|
||||
pub(crate) layout: BindGroupLayout,
|
||||
pub(crate) layout_msaa: BindGroupLayout,
|
||||
pub(crate) shader: Handle<Shader>,
|
||||
pub(crate) fullscreen_shader: FullscreenShader,
|
||||
pub(crate) fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl MotionBlurPipeline {
|
||||
pub(crate) fn new(render_device: &RenderDevice, shader: Handle<Shader>) -> Self {
|
||||
pub(crate) fn new(
|
||||
render_device: &RenderDevice,
|
||||
fullscreen_shader: FullscreenShader,
|
||||
fragment_shader: Handle<Shader>,
|
||||
) -> Self {
|
||||
let mb_layout = &BindGroupLayoutEntries::sequential(
|
||||
ShaderStages::FRAGMENT,
|
||||
(
|
||||
@ -84,7 +89,8 @@ impl MotionBlurPipeline {
|
||||
sampler,
|
||||
layout,
|
||||
layout_msaa,
|
||||
shader,
|
||||
fullscreen_shader,
|
||||
fragment_shader,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,8 +99,9 @@ impl FromWorld for MotionBlurPipeline {
|
||||
fn from_world(render_world: &mut bevy_ecs::world::World) -> Self {
|
||||
let render_device = render_world.resource::<RenderDevice>().clone();
|
||||
|
||||
let shader = load_embedded_asset!(render_world, "motion_blur.wgsl");
|
||||
MotionBlurPipeline::new(&render_device, shader)
|
||||
let fullscreen_shader = render_world.resource::<FullscreenShader>().clone();
|
||||
let fragment_shader = load_embedded_asset!(render_world, "motion_blur.wgsl");
|
||||
MotionBlurPipeline::new(&render_device, fullscreen_shader, fragment_shader)
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,9 +135,9 @@ impl SpecializedRenderPipeline for MotionBlurPipeline {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("motion_blur_pipeline".into()),
|
||||
layout,
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone(),
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
|
@ -1,7 +1,4 @@
|
||||
use crate::{
|
||||
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
|
||||
oit::OrderIndependentTransparencySettings,
|
||||
};
|
||||
use crate::{oit::OrderIndependentTransparencySettings, FullscreenShader};
|
||||
use bevy_app::Plugin;
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer};
|
||||
use bevy_derive::Deref;
|
||||
@ -156,6 +153,7 @@ pub fn queue_oit_resolve_pipeline(
|
||||
),
|
||||
With<OrderIndependentTransparencySettings>,
|
||||
>,
|
||||
fullscreen_shader: Res<FullscreenShader>,
|
||||
asset_server: Res<AssetServer>,
|
||||
// Store the key with the id to make the clean up logic easier.
|
||||
// This also means it will always replace the entry if the key changes so nothing to clean up.
|
||||
@ -176,7 +174,12 @@ pub fn queue_oit_resolve_pipeline(
|
||||
}
|
||||
}
|
||||
|
||||
let desc = specialize_oit_resolve_pipeline(key, &resolve_pipeline, &asset_server);
|
||||
let desc = specialize_oit_resolve_pipeline(
|
||||
key,
|
||||
&resolve_pipeline,
|
||||
&fullscreen_shader,
|
||||
&asset_server,
|
||||
);
|
||||
|
||||
let pipeline_id = pipeline_cache.queue_render_pipeline(desc);
|
||||
commands.entity(e).insert(OitResolvePipelineId(pipeline_id));
|
||||
@ -194,6 +197,7 @@ pub fn queue_oit_resolve_pipeline(
|
||||
fn specialize_oit_resolve_pipeline(
|
||||
key: OitResolvePipelineKey,
|
||||
resolve_pipeline: &OitResolvePipeline,
|
||||
fullscreen_shader: &FullscreenShader,
|
||||
asset_server: &AssetServer,
|
||||
) -> RenderPipelineDescriptor {
|
||||
let format = if key.hdr {
|
||||
@ -224,7 +228,7 @@ fn specialize_oit_resolve_pipeline(
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: fullscreen_shader.to_vertex_state(),
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
|
@ -44,7 +44,7 @@ use bevy_utils::prelude::default;
|
||||
use crate::{
|
||||
core_2d::graph::{Core2d, Node2d},
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
fullscreen_vertex_shader,
|
||||
FullscreenShader,
|
||||
};
|
||||
|
||||
/// The handle to the default chromatic aberration lookup texture.
|
||||
@ -130,8 +130,10 @@ pub struct PostProcessingPipeline {
|
||||
source_sampler: Sampler,
|
||||
/// Specifies how to sample the chromatic aberration gradient.
|
||||
chromatic_aberration_lut_sampler: Sampler,
|
||||
/// The shader asset handle.
|
||||
shader: Handle<Shader>,
|
||||
/// The asset handle for the fullscreen vertex shader.
|
||||
fullscreen_shader: FullscreenShader,
|
||||
/// The fragment shader asset handle.
|
||||
fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
/// A key that uniquely identifies a built-in postprocessing pipeline.
|
||||
@ -308,7 +310,8 @@ impl FromWorld for PostProcessingPipeline {
|
||||
bind_group_layout,
|
||||
source_sampler,
|
||||
chromatic_aberration_lut_sampler,
|
||||
shader: load_embedded_asset!(world, "post_process.wgsl"),
|
||||
fullscreen_shader: world.resource::<FullscreenShader>().clone(),
|
||||
fragment_shader: load_embedded_asset!(world, "post_process.wgsl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -320,9 +323,9 @@ impl SpecializedRenderPipeline for PostProcessingPipeline {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("postprocessing".into()),
|
||||
layout: vec![self.bind_group_layout.clone()],
|
||||
vertex: fullscreen_vertex_shader::fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone(),
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs: vec![],
|
||||
entry_point: "fragment_main".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
|
@ -27,7 +27,7 @@ use crate::{
|
||||
prepass_target_descriptors, MotionVectorPrepass, NormalPrepass, PreviousViewData,
|
||||
PreviousViewUniforms,
|
||||
},
|
||||
Skybox,
|
||||
FullscreenShader, Skybox,
|
||||
};
|
||||
|
||||
/// This pipeline writes motion vectors to the prepass for all [`Skybox`]es.
|
||||
@ -38,7 +38,8 @@ use crate::{
|
||||
#[derive(Resource)]
|
||||
pub struct SkyboxPrepassPipeline {
|
||||
bind_group_layout: BindGroupLayout,
|
||||
shader: Handle<Shader>,
|
||||
fullscreen_shader: FullscreenShader,
|
||||
fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
/// Used to specialize the [`SkyboxPrepassPipeline`].
|
||||
@ -73,7 +74,8 @@ impl FromWorld for SkyboxPrepassPipeline {
|
||||
),
|
||||
),
|
||||
),
|
||||
shader: load_embedded_asset!(world, "skybox_prepass.wgsl"),
|
||||
fullscreen_shader: world.resource::<FullscreenShader>().clone(),
|
||||
fragment_shader: load_embedded_asset!(world, "skybox_prepass.wgsl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,7 +88,7 @@ impl SpecializedRenderPipeline for SkyboxPrepassPipeline {
|
||||
label: Some("skybox_prepass_pipeline".into()),
|
||||
layout: vec![self.bind_group_layout.clone()],
|
||||
push_constant_ranges: vec![],
|
||||
vertex: crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
primitive: default(),
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: CORE_3D_DEPTH_FORMAT,
|
||||
@ -101,7 +103,7 @@ impl SpecializedRenderPipeline for SkyboxPrepassPipeline {
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone(),
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs: vec![],
|
||||
entry_point: "fragment".into(),
|
||||
targets: prepass_target_descriptors(key.normal_prepass, true, false),
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset, Assets, Handle};
|
||||
use bevy_ecs::prelude::*;
|
||||
@ -28,6 +27,8 @@ mod node;
|
||||
use bevy_utils::default;
|
||||
pub use node::TonemappingNode;
|
||||
|
||||
use crate::FullscreenShader;
|
||||
|
||||
/// 3D LUT (look up table) textures used for tonemapping
|
||||
#[derive(Resource, Clone, ExtractResource)]
|
||||
pub struct TonemappingLuts {
|
||||
@ -112,7 +113,8 @@ impl Plugin for TonemappingPlugin {
|
||||
pub struct TonemappingPipeline {
|
||||
texture_bind_group: BindGroupLayout,
|
||||
sampler: Sampler,
|
||||
shader: Handle<Shader>,
|
||||
fullscreen_shader: FullscreenShader,
|
||||
fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
/// Optionally enables a tonemapping shader that attempts to map linear input stimulus into a perceptually uniform image for a given [`Camera`] entity.
|
||||
@ -273,9 +275,9 @@ impl SpecializedRenderPipeline for TonemappingPipeline {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("tonemapping pipeline".into()),
|
||||
layout: vec![self.texture_bind_group.clone()],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone(),
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
@ -319,7 +321,8 @@ impl FromWorld for TonemappingPipeline {
|
||||
TonemappingPipeline {
|
||||
texture_bind_group: tonemap_texture_bind_group,
|
||||
sampler,
|
||||
shader: load_embedded_asset!(render_world, "tonemapping.wgsl"),
|
||||
fullscreen_shader: render_world.resource::<FullscreenShader>().clone(),
|
||||
fragment_shader: load_embedded_asset!(render_world, "tonemapping.wgsl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
use bevy_asset::{load_embedded_asset, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
core_3d::Camera3d, fullscreen_vertex_shader::fullscreen_shader_vertex_state,
|
||||
};
|
||||
use bevy_core_pipeline::{core_3d::Camera3d, FullscreenShader};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
@ -36,7 +34,8 @@ pub(crate) struct AtmosphereBindGroupLayouts {
|
||||
pub(crate) struct RenderSkyBindGroupLayouts {
|
||||
pub render_sky: BindGroupLayout,
|
||||
pub render_sky_msaa: BindGroupLayout,
|
||||
pub shader: Handle<Shader>,
|
||||
pub fullscreen_shader: FullscreenShader,
|
||||
pub fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for AtmosphereBindGroupLayouts {
|
||||
@ -205,7 +204,8 @@ impl FromWorld for RenderSkyBindGroupLayouts {
|
||||
Self {
|
||||
render_sky,
|
||||
render_sky_msaa,
|
||||
shader: load_embedded_asset!(world, "render_sky.wgsl"),
|
||||
fullscreen_shader: world.resource::<FullscreenShader>().clone(),
|
||||
fragment_shader: load_embedded_asset!(world, "render_sky.wgsl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -358,7 +358,7 @@ impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts {
|
||||
self.render_sky_msaa.clone()
|
||||
}],
|
||||
push_constant_ranges: vec![],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: MultisampleState {
|
||||
@ -368,7 +368,7 @@ impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts {
|
||||
},
|
||||
zero_initialize_workgroup_memory: false,
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone(),
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: "main".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
|
@ -2,7 +2,7 @@ use super::resource_manager::ResourceManager;
|
||||
use bevy_asset::{weak_handle, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
core_3d::CORE_3D_DEPTH_FORMAT, experimental::mip_generation::DOWNSAMPLE_DEPTH_SHADER_HANDLE,
|
||||
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
|
||||
FullscreenShader,
|
||||
};
|
||||
use bevy_ecs::{
|
||||
resource::Resource,
|
||||
@ -80,6 +80,8 @@ impl FromWorld for MeshletPipelines {
|
||||
let remap_1d_to_2d_dispatch_layout = resource_manager
|
||||
.remap_1d_to_2d_dispatch_bind_group_layout
|
||||
.clone();
|
||||
|
||||
let vertex_state = world.resource::<FullscreenShader>().to_vertex_state();
|
||||
let pipeline_cache = world.resource_mut::<PipelineCache>();
|
||||
|
||||
Self {
|
||||
@ -400,7 +402,7 @@ impl FromWorld for MeshletPipelines {
|
||||
label: Some("meshlet_resolve_depth_pipeline".into()),
|
||||
layout: vec![resolve_depth_layout],
|
||||
push_constant_ranges: vec![],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: vertex_state.clone(),
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: CORE_3D_DEPTH_FORMAT,
|
||||
@ -424,7 +426,7 @@ impl FromWorld for MeshletPipelines {
|
||||
label: Some("meshlet_resolve_depth_pipeline".into()),
|
||||
layout: vec![resolve_depth_shadow_view_layout],
|
||||
push_constant_ranges: vec![],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: vertex_state.clone(),
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: CORE_3D_DEPTH_FORMAT,
|
||||
@ -449,7 +451,7 @@ impl FromWorld for MeshletPipelines {
|
||||
label: Some("meshlet_resolve_material_depth_pipeline".into()),
|
||||
layout: vec![resolve_material_depth_layout],
|
||||
push_constant_ranges: vec![],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: vertex_state,
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: TextureFormat::Depth16Unorm,
|
||||
|
@ -7,8 +7,8 @@ use bevy_core_pipeline::{
|
||||
graph::{Core3d, Node3d},
|
||||
DEPTH_TEXTURE_SAMPLING_SUPPORTED,
|
||||
},
|
||||
fullscreen_vertex_shader,
|
||||
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
|
||||
FullscreenShader,
|
||||
};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
@ -155,7 +155,8 @@ pub struct ScreenSpaceReflectionsPipeline {
|
||||
depth_nearest_sampler: Sampler,
|
||||
bind_group_layout: BindGroupLayout,
|
||||
binding_arrays_are_usable: bool,
|
||||
shader: Handle<Shader>,
|
||||
fullscreen_shader: FullscreenShader,
|
||||
fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
/// A GPU buffer that stores the screen space reflection settings for each view.
|
||||
@ -397,9 +398,10 @@ impl FromWorld for ScreenSpaceReflectionsPipeline {
|
||||
depth_nearest_sampler,
|
||||
bind_group_layout,
|
||||
binding_arrays_are_usable: binding_arrays_are_usable(render_device, render_adapter),
|
||||
fullscreen_shader: world.resource::<FullscreenShader>().clone(),
|
||||
// Even though ssr was loaded using load_shader_library, we can still access it like a
|
||||
// normal embedded asset (so we can use it as both a library or a kernel).
|
||||
shader: load_embedded_asset!(world, "ssr.wgsl"),
|
||||
fragment_shader: load_embedded_asset!(world, "ssr.wgsl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -536,9 +538,9 @@ impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("SSR pipeline".into()),
|
||||
layout: vec![mesh_view_layout.clone(), self.bind_group_layout.clone()],
|
||||
vertex: fullscreen_vertex_shader::fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone(),
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
|
@ -9,7 +9,7 @@
|
||||
use bevy::{
|
||||
core_pipeline::{
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
|
||||
FullscreenShader,
|
||||
},
|
||||
ecs::query::QueryItem,
|
||||
prelude::*,
|
||||
@ -259,6 +259,8 @@ impl FromWorld for PostProcessPipeline {
|
||||
|
||||
// Get the shader handle
|
||||
let shader = world.load_asset(SHADER_ASSET_PATH);
|
||||
// This will setup a fullscreen triangle for the vertex state.
|
||||
let vertex_state = world.resource::<FullscreenShader>().to_vertex_state();
|
||||
|
||||
let pipeline_id = world
|
||||
.resource_mut::<PipelineCache>()
|
||||
@ -266,8 +268,7 @@ impl FromWorld for PostProcessPipeline {
|
||||
.queue_render_pipeline(RenderPipelineDescriptor {
|
||||
label: Some("post_process_pipeline".into()),
|
||||
layout: vec![layout.clone()],
|
||||
// This will setup a fullscreen triangle for the vertex state
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: vertex_state,
|
||||
fragment: Some(FragmentState {
|
||||
shader,
|
||||
shader_defs: vec![],
|
||||
|
@ -0,0 +1,68 @@
|
||||
---
|
||||
title: `FULLSCREEN_SHADER_HANDLE` replaced with `FullscreenShader`
|
||||
pull_requests: [19426]
|
||||
---
|
||||
|
||||
`FULLSCREEN_SHADER_HANDLE` and `fullscreen_shader_vertex_state` have been replaced by the
|
||||
`FullscreenShader` resource. Users of either of these will need to call `FullscreenShader::shader`
|
||||
or `FullscreenShader::to_vertex_state` respectively. You may need to clone `FullscreenShader` out of
|
||||
the render world to store an instance that you can use later (e.g., if you are attempting to use the
|
||||
fullscreen shader inside a `SpecializedRenderPipeline` implementation).
|
||||
|
||||
For example, if your previous code looked like this:
|
||||
|
||||
```rust
|
||||
struct MyPipeline {
|
||||
some_bind_group: BindGroupLayout,
|
||||
}
|
||||
|
||||
impl FromWorld for MyPipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
let some_bind_group = /* ... RenderDevice stuff */;
|
||||
Self {
|
||||
some_bind_group,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedRenderPipeline for MyPipeline {
|
||||
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
|
||||
RenderPipelineDescriptor {
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
// ... other stuff
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can migrate your code to:
|
||||
|
||||
```rust
|
||||
struct MyPipeline {
|
||||
some_bind_group: BindGroupLayout,
|
||||
fullscreen_shader: FullscreenShader,
|
||||
}
|
||||
|
||||
impl FromWorld for MyPipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
let some_bind_group = /* ... RenderDevice stuff */;
|
||||
Self {
|
||||
some_bind_group,
|
||||
fullscreen_shader: render_world.resource::<FullscreenShader>().clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedRenderPipeline for MyPipeline {
|
||||
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
|
||||
RenderPipelineDescriptor {
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
// ... other stuff
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is just one example. Pipelines may be initialized in different ways, but the primary strategy
|
||||
is clone out the `FullscreenShader` resource from the render world, and call `to_vertex_state` to
|
||||
use it as the vertex shader.
|
Loading…
Reference in New Issue
Block a user