Spinning Cube
This commit is contained in:
182
mini-game/src/post_process.rs
Normal file
182
mini-game/src/post_process.rs
Normal file
@@ -0,0 +1,182 @@
|
||||
use bevy::core_pipeline::core_3d::graph::Core3d;
|
||||
use bevy::ecs::query::QueryItem;
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::extract_component::{
|
||||
DynamicUniformIndex, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
|
||||
};
|
||||
use bevy::render::render_graph::{
|
||||
NodeRunError, RenderGraphContext, RenderLabel, ViewNode, ViewNodeRunner,
|
||||
};
|
||||
use bevy::render::render_resource::{
|
||||
BindGroupEntries, PipelineCache, RenderPipelineDescriptor, ShaderType, TextureFormat,
|
||||
};
|
||||
use bevy::render::renderer::RenderContext;
|
||||
use bevy::render::view::ViewTarget;
|
||||
use bevy::render::{RenderApp, RenderStartup};
|
||||
|
||||
use bevy::{
|
||||
core_pipeline::{FullscreenShader, core_3d::graph::Node3d},
|
||||
render::{
|
||||
extract_component::ComponentUniforms,
|
||||
render_graph::RenderGraphExt,
|
||||
render_resource::{
|
||||
binding_types::{sampler, texture_2d, uniform_buffer},
|
||||
*,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
},
|
||||
};
|
||||
|
||||
const SHADER_ASSET_PATH: &str = "shaders/post_processing.wgsl";
|
||||
|
||||
/// It is generally encouraged to set up post processing effects as a plugin
|
||||
pub struct PostProcessPlugin;
|
||||
|
||||
impl Plugin for PostProcessPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins((
|
||||
ExtractComponentPlugin::<PostProcessSettings>::default(),
|
||||
UniformComponentPlugin::<PostProcessSettings>::default(),
|
||||
));
|
||||
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app.add_systems(RenderStartup, init_post_process_pipeline);
|
||||
|
||||
render_app
|
||||
.add_render_graph_node::<ViewNodeRunner<PostProcessNode>>(Core3d, PostProcessLabel)
|
||||
.add_render_graph_edges(
|
||||
Core3d,
|
||||
(
|
||||
Node3d::Tonemapping,
|
||||
PostProcessLabel,
|
||||
Node3d::EndMainPassPostProcessing,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
|
||||
struct PostProcessLabel;
|
||||
|
||||
#[derive(Default)]
|
||||
struct PostProcessNode;
|
||||
|
||||
impl ViewNode for PostProcessNode {
|
||||
type ViewQuery = (
|
||||
&'static ViewTarget,
|
||||
&'static PostProcessSettings,
|
||||
&'static DynamicUniformIndex<PostProcessSettings>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext,
|
||||
(view_target, _post_process_settings, settings_index): QueryItem<Self::ViewQuery>,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let post_process_pipeline = world.resource::<PostProcessPipeline>();
|
||||
let pipeline_cache = world.resource::<PipelineCache>();
|
||||
let Some(pipeline) = pipeline_cache.get_render_pipeline(post_process_pipeline.pipeline_id)
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let settings_uniforms = world.resource::<ComponentUniforms<PostProcessSettings>>();
|
||||
let Some(settings_binding) = settings_uniforms.uniforms().binding() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let post_process = view_target.post_process_write();
|
||||
|
||||
let bind_group = render_context.render_device().create_bind_group(
|
||||
"post_process_bind_group",
|
||||
&pipeline_cache.get_bind_group_layout(&post_process_pipeline.layout),
|
||||
&BindGroupEntries::sequential((
|
||||
post_process.source,
|
||||
&post_process_pipeline.sampler,
|
||||
settings_binding.clone(),
|
||||
)),
|
||||
);
|
||||
|
||||
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
|
||||
label: Some("post_process_pass"),
|
||||
color_attachments: &[Some(RenderPassColorAttachment {
|
||||
view: post_process.destination,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: Operations::default(),
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
|
||||
render_pass.set_render_pipeline(pipeline);
|
||||
render_pass.set_bind_group(0, &bind_group, &[settings_index.index()]);
|
||||
render_pass.draw(0..3, 0..1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct PostProcessPipeline {
|
||||
layout: BindGroupLayoutDescriptor,
|
||||
sampler: Sampler,
|
||||
pipeline_id: CachedRenderPipelineId,
|
||||
}
|
||||
|
||||
fn init_post_process_pipeline(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
asset_server: Res<AssetServer>,
|
||||
fullscreen_shader: Res<FullscreenShader>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
) {
|
||||
let layout = BindGroupLayoutDescriptor::new(
|
||||
"post_process_bind_group_layout",
|
||||
&BindGroupLayoutEntries::sequential(
|
||||
ShaderStages::FRAGMENT,
|
||||
(
|
||||
texture_2d(TextureSampleType::Float { filterable: true }),
|
||||
sampler(SamplerBindingType::Filtering),
|
||||
uniform_buffer::<PostProcessSettings>(true),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
|
||||
|
||||
let shader = asset_server.load(SHADER_ASSET_PATH);
|
||||
let vertex_state = fullscreen_shader.to_vertex_state();
|
||||
let pipeline_id = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
|
||||
label: Some("post_process_pipeline".into()),
|
||||
layout: vec![layout.clone()],
|
||||
vertex: vertex_state,
|
||||
fragment: Some(FragmentState {
|
||||
shader,
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format: TextureFormat::bevy_default(),
|
||||
blend: None,
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
});
|
||||
|
||||
commands.insert_resource(PostProcessPipeline {
|
||||
layout,
|
||||
sampler,
|
||||
pipeline_id,
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Component, Default, Clone, Copy, ExtractComponent, ShaderType)]
|
||||
pub struct PostProcessSettings {
|
||||
pub scale: f32,
|
||||
}
|
||||
8
mini-game/src/renderer/mod.rs
Normal file
8
mini-game/src/renderer/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
mod node;
|
||||
mod plugin;
|
||||
|
||||
#[allow(unused)]
|
||||
pub use plugin::{
|
||||
G13Resource, GpuImageExportSource, ImageExport, ImageExportPlugin, ImageExportSource,
|
||||
ImageExportSystems,
|
||||
};
|
||||
49
mini-game/src/renderer/node.rs
Normal file
49
mini-game/src/renderer/node.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use super::GpuImageExportSource;
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
render::{
|
||||
render_asset::RenderAssets,
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext, RenderLabel},
|
||||
renderer::RenderContext,
|
||||
texture::GpuImage,
|
||||
},
|
||||
};
|
||||
use wgpu::{TexelCopyBufferInfo, TexelCopyBufferLayout};
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
|
||||
pub struct ImageExportLabel;
|
||||
|
||||
pub struct ImageExportNode;
|
||||
impl Node for ImageExportNode {
|
||||
fn run(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
for (_, source) in world
|
||||
.resource::<RenderAssets<GpuImageExportSource>>()
|
||||
.iter()
|
||||
{
|
||||
if let Some(gpu_image) = world
|
||||
.resource::<RenderAssets<GpuImage>>()
|
||||
.get(&source.source_handle)
|
||||
{
|
||||
render_context.command_encoder().copy_texture_to_buffer(
|
||||
gpu_image.texture.as_image_copy(),
|
||||
TexelCopyBufferInfo {
|
||||
buffer: &source.buffer,
|
||||
layout: TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(source.padded_bytes_per_row),
|
||||
rows_per_image: None,
|
||||
},
|
||||
},
|
||||
source.source_size,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
214
mini-game/src/renderer/plugin.rs
Normal file
214
mini-game/src/renderer/plugin.rs
Normal file
@@ -0,0 +1,214 @@
|
||||
use super::node::{ImageExportLabel, ImageExportNode};
|
||||
use bevy::{
|
||||
asset::RenderAssetUsages,
|
||||
ecs::system::{SystemParamItem, lifetimeless::SRes},
|
||||
prelude::*,
|
||||
render::{
|
||||
Render, RenderApp, RenderSystems,
|
||||
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
||||
graph::CameraDriverLabel,
|
||||
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
|
||||
render_graph::RenderGraph,
|
||||
render_resource::{Buffer, BufferDescriptor, BufferUsages, Extent3d, MapMode},
|
||||
renderer::RenderDevice,
|
||||
texture::GpuImage,
|
||||
},
|
||||
};
|
||||
use embedded_graphics::pixelcolor::BinaryColor;
|
||||
use futures::channel::oneshot;
|
||||
use g13_driver::{G13, G13_LCD_BUF_SIZE};
|
||||
use wgpu::PollType;
|
||||
|
||||
#[derive(Resource, Clone)]
|
||||
pub struct G13Resource {
|
||||
pub g13: G13,
|
||||
}
|
||||
|
||||
#[derive(Asset, Reflect, Clone, Default)]
|
||||
pub struct ImageExportSource(pub Handle<Image>);
|
||||
|
||||
impl From<Handle<Image>> for ImageExportSource {
|
||||
fn from(value: Handle<Image>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GpuImageExportSource {
|
||||
pub buffer: Buffer,
|
||||
pub source_handle: Handle<Image>,
|
||||
pub source_size: Extent3d,
|
||||
pub bytes_per_row: u32,
|
||||
pub padded_bytes_per_row: u32,
|
||||
}
|
||||
|
||||
impl RenderAsset for GpuImageExportSource {
|
||||
type SourceAsset = ImageExportSource;
|
||||
type Param = (SRes<RenderDevice>, SRes<RenderAssets<GpuImage>>);
|
||||
|
||||
fn asset_usage(_: &Self::SourceAsset) -> RenderAssetUsages {
|
||||
RenderAssetUsages::default()
|
||||
}
|
||||
|
||||
fn prepare_asset(
|
||||
source_asset: Self::SourceAsset,
|
||||
_asset_id: AssetId<Self::SourceAsset>,
|
||||
(device, images): &mut SystemParamItem<Self::Param>,
|
||||
_previous_asset: Option<&Self>,
|
||||
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
|
||||
let Some(gpu_image) = images.get(&source_asset.0) else {
|
||||
return Err(PrepareAssetError::RetryNextUpdate(source_asset));
|
||||
};
|
||||
|
||||
let size = gpu_image.texture.size();
|
||||
let format = &gpu_image.texture_format;
|
||||
let bytes_per_row =
|
||||
(size.width / format.block_dimensions().0) * format.block_copy_size(None).unwrap();
|
||||
let padded_bytes_per_row =
|
||||
RenderDevice::align_copy_bytes_per_row(bytes_per_row as usize) as u32;
|
||||
|
||||
let source_size = gpu_image.texture.size();
|
||||
|
||||
Ok(GpuImageExportSource {
|
||||
buffer: device.create_buffer(&BufferDescriptor {
|
||||
label: Some("Image Export Buffer"),
|
||||
size: (source_size.height * padded_bytes_per_row) as u64,
|
||||
usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
|
||||
mapped_at_creation: false,
|
||||
}),
|
||||
source_handle: source_asset.0,
|
||||
source_size,
|
||||
bytes_per_row,
|
||||
padded_bytes_per_row,
|
||||
})
|
||||
}
|
||||
|
||||
fn byte_len(_: &Self::SourceAsset) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, ExtractComponent, Clone, Default, Debug)]
|
||||
pub struct ImageExport(pub Handle<ImageExportSource>);
|
||||
|
||||
fn save_buffer_to_disk(
|
||||
export_bundles: Query<&ImageExport>,
|
||||
sources: Res<RenderAssets<GpuImageExportSource>>,
|
||||
render_device: Res<RenderDevice>,
|
||||
mut g13: ResMut<G13Resource>,
|
||||
) {
|
||||
for export in &export_bundles {
|
||||
if let Some(gpu_source) = sources.get(&export.0) {
|
||||
let mut image_bytes = {
|
||||
let slice = gpu_source.buffer.slice(..);
|
||||
|
||||
{
|
||||
let (mapping_tx, mapping_rx) = oneshot::channel();
|
||||
|
||||
render_device.map_buffer(&slice, MapMode::Read, move |res| {
|
||||
mapping_tx.send(res).unwrap();
|
||||
});
|
||||
|
||||
if render_device
|
||||
.poll(PollType::Wait {
|
||||
submission_index: None,
|
||||
timeout: None,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
futures_lite::future::block_on(mapping_rx).unwrap().unwrap();
|
||||
}
|
||||
|
||||
slice.get_mapped_range().to_vec()
|
||||
};
|
||||
|
||||
gpu_source.buffer.unmap();
|
||||
|
||||
let bytes_per_row = gpu_source.bytes_per_row as usize;
|
||||
let padded_bytes_per_row = gpu_source.padded_bytes_per_row as usize;
|
||||
let source_size = gpu_source.source_size;
|
||||
|
||||
if bytes_per_row != padded_bytes_per_row {
|
||||
let mut unpadded_bytes =
|
||||
Vec::<u8>::with_capacity(source_size.height as usize * bytes_per_row);
|
||||
|
||||
for padded_row in image_bytes.chunks(padded_bytes_per_row) {
|
||||
unpadded_bytes.extend_from_slice(&padded_row[..bytes_per_row]);
|
||||
}
|
||||
|
||||
image_bytes = unpadded_bytes;
|
||||
}
|
||||
|
||||
let mut img_buffer = [0u8; G13_LCD_BUF_SIZE as usize];
|
||||
|
||||
for (i, b) in image_bytes
|
||||
.iter()
|
||||
.step_by(4)
|
||||
.take(G13_LCD_BUF_SIZE as usize * 8)
|
||||
.enumerate()
|
||||
{
|
||||
let offset = i / 8;
|
||||
let mask = 1 << (7 - (i % 8));
|
||||
|
||||
if *b == 0xFF {
|
||||
img_buffer[offset] |= mask;
|
||||
} else {
|
||||
img_buffer[offset] &= !mask;
|
||||
}
|
||||
}
|
||||
|
||||
use embedded_graphics::image::ImageDrawable;
|
||||
embedded_graphics::image::ImageRaw::<BinaryColor>::new(&img_buffer, 160)
|
||||
.draw(&mut g13.g13)
|
||||
.expect("G13 to be connected");
|
||||
|
||||
let _ = g13.g13.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin enabling the generation of image sequences.
|
||||
#[derive(Default)]
|
||||
pub struct ImageExportPlugin;
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||
pub enum ImageExportSystems {
|
||||
ImageExportSetup,
|
||||
}
|
||||
|
||||
impl Plugin for ImageExportPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
use ImageExportSystems::*;
|
||||
|
||||
let g13 = G13::new().expect("G13 to be connected");
|
||||
|
||||
app.configure_sets(PostUpdate, ImageExportSetup)
|
||||
.register_type::<ImageExportSource>()
|
||||
.init_asset::<ImageExportSource>()
|
||||
.register_asset_reflect::<ImageExportSource>()
|
||||
.add_plugins((
|
||||
RenderAssetPlugin::<GpuImageExportSource>::default(),
|
||||
ExtractComponentPlugin::<ImageExport>::default(),
|
||||
));
|
||||
// .add_systems(PostUpdate, setup_exporters.in_set(ImageExportSetup));
|
||||
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
|
||||
render_app.insert_resource(G13Resource { g13 }).add_systems(
|
||||
Render,
|
||||
save_buffer_to_disk
|
||||
.after(RenderSystems::Render)
|
||||
.before(RenderSystems::Cleanup),
|
||||
);
|
||||
|
||||
let mut graph = render_app
|
||||
.world_mut()
|
||||
.get_resource_mut::<RenderGraph>()
|
||||
.unwrap();
|
||||
|
||||
graph.add_node(ImageExportLabel, ImageExportNode);
|
||||
graph.add_node_edge(CameraDriverLabel, ImageExportLabel);
|
||||
}
|
||||
}
|
||||
47
mini-game/src/shared.rs
Normal file
47
mini-game/src/shared.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Component)]
|
||||
pub struct Spinner;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Resource, Default)]
|
||||
pub struct Flags {
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn spawn_3d_scene(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Cuboid::default())),
|
||||
MeshMaterial3d(materials.add(Color::from(bevy::color::palettes::css::WHITE))),
|
||||
Transform::from_xyz(0.0, 0.0, 0.0),
|
||||
Spinner,
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
PointLight {
|
||||
intensity: 6_000_000.,
|
||||
shadows_enabled: true,
|
||||
..Default::default()
|
||||
},
|
||||
Transform::from_xyz(3., 4., 6.),
|
||||
));
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn spawn_2d_scene(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
||||
) {
|
||||
commands.spawn((
|
||||
Spinner,
|
||||
Mesh2d(meshes.add(RegularPolygon::new(66.0, 8))),
|
||||
MeshMaterial2d(materials.add(Color::srgb(0.4, 0.4, 0.6))),
|
||||
));
|
||||
}
|
||||
Reference in New Issue
Block a user