Skip to content
Snippets Groups Projects
Commit b8172906 authored by StarArawn's avatar StarArawn
Browse files

Delete old renderer.

parent 1915878f
No related branches found
No related tags found
No related merge requests found
use bevy::{
prelude::{Assets, HandleUntyped, Plugin},
reflect::TypeUuid,
render2::{render_phase::DrawFunctions, render_resource::Shader, RenderApp, RenderStage},
};
use crate::render::{
quad::pipeline::{DrawQuad, QuadMeta, QuadPipeline},
ui_pass::TransparentUI,
};
mod pipeline;
pub const QUAD_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7604018236855288450);
#[derive(Default)]
pub struct QuadRendererPlugin;
impl Plugin for QuadRendererPlugin {
fn build(&self, app: &mut bevy::prelude::App) {
let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().unwrap();
let quad_shader = Shader::from_wgsl(include_str!("shader.wgsl"));
shaders.set_untracked(QUAD_SHADER_HANDLE, quad_shader);
let render_app = app.sub_app(RenderApp);
render_app
.init_resource::<QuadPipeline>()
.init_resource::<QuadMeta>()
.add_system_to_stage(RenderStage::Extract, pipeline::extract_quads)
.add_system_to_stage(RenderStage::Prepare, pipeline::prepare_quads)
.add_system_to_stage(RenderStage::Queue, pipeline::queue_quads);
let draw_quad = DrawQuad::new(&mut render_app.world);
render_app
.world
.get_resource::<DrawFunctions<TransparentUI>>()
.unwrap()
.write()
.add(draw_quad);
}
}
use bevy::{
core::FloatOrd,
ecs::system::{
lifetimeless::{Read, SQuery, SRes},
SystemState,
},
math::{const_vec3, Mat4, Quat, Vec2, Vec3},
prelude::{Bundle, Color, Commands, Entity, FromWorld, Query, Res, ResMut, World},
render2::{
render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass},
render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendComponent,
BlendFactor, BlendOperation, BlendState, BufferBindingType, BufferSize, BufferUsages,
BufferVec, CachedPipelineId, ColorTargetState, ColorWrites, CompareFunction,
DepthBiasState, DepthStencilState, FragmentState, FrontFace, MultisampleState,
PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineCache,
RenderPipelineDescriptor, Shader, ShaderStages, StencilFaceState, StencilState,
TextureFormat, VertexAttribute, VertexBufferLayout, VertexFormat, VertexState,
VertexStepMode,
},
renderer::{RenderDevice, RenderQueue},
texture::BevyDefault,
view::{ViewUniformOffset, ViewUniforms},
},
sprite2::Rect,
};
use bytemuck::{Pod, Zeroable};
use kayak_core::render_primitive::RenderPrimitive;
use crate::{
render::{quad::QUAD_SHADER_HANDLE, ui_pass::TransparentUI},
to_bevy_color, BevyContext,
};
pub struct QuadPipeline {
view_layout: BindGroupLayout,
pipeline: CachedPipelineId,
}
const QUAD_VERTEX_POSITIONS: &[Vec3] = &[
const_vec3!([0.0, 0.0, 0.0]),
const_vec3!([1.0, 1.0, 0.0]),
const_vec3!([0.0, 1.0, 0.0]),
const_vec3!([0.0, 0.0, 0.0]),
const_vec3!([1.0, 0.0, 0.0]),
const_vec3!([1.0, 1.0, 0.0]),
];
impl FromWorld for QuadPipeline {
fn from_world(world: &mut World) -> Self {
let world = world.cell();
let render_device = world.get_resource::<RenderDevice>().unwrap();
let mut pipeline_cache = world.get_resource_mut::<RenderPipelineCache>().unwrap();
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
// TODO: change this to ViewUniform::std140_size_static once crevice fixes this!
// Context: https://github.com/LPGhatguy/crevice/issues/29
min_binding_size: BufferSize::new(144),
},
count: None,
}],
label: Some("quad_view_layout"),
});
let vertex_buffer_layout = VertexBufferLayout {
array_stride: 28,
step_mode: VertexStepMode::Vertex,
attributes: vec![
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 0,
shader_location: 0,
},
VertexAttribute {
format: VertexFormat::Float32x4,
offset: 12,
shader_location: 1,
},
],
};
let pipeline_desc = RenderPipelineDescriptor {
vertex: VertexState {
shader: QUAD_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(),
shader_defs: vec![],
buffers: vec![vertex_buffer_layout],
},
fragment: Some(FragmentState {
shader: QUAD_SHADER_HANDLE.typed::<Shader>(),
shader_defs: vec![],
entry_point: "fragment".into(),
targets: vec![ColorTargetState {
format: TextureFormat::bevy_default(),
blend: Some(BlendState {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::One,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
}),
write_mask: ColorWrites::ALL,
}],
}),
layout: Some(vec![view_layout.clone()]),
primitive: PrimitiveState {
front_face: FrontFace::Ccw,
cull_mode: None,
polygon_mode: PolygonMode::Fill,
clamp_depth: false,
conservative: false,
topology: PrimitiveTopology::TriangleList,
strip_index_format: None,
},
depth_stencil: Some(DepthStencilState {
format: TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: CompareFunction::Greater,
stencil: StencilState {
front: StencilFaceState::IGNORE,
back: StencilFaceState::IGNORE,
read_mask: 0,
write_mask: 0,
},
bias: DepthBiasState {
constant: 0,
slope_scale: 0.0,
clamp: 0.0,
},
}),
multisample: MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("quad_pipeline".into()),
};
QuadPipeline {
pipeline: pipeline_cache.queue(pipeline_desc),
view_layout,
}
}
}
#[derive(Bundle)]
pub struct ExtractQuadBundle {
extracted_quad: ExtractedQuad,
}
pub struct ExtractedQuad {
rect: Rect,
background_color: Color,
vertex_index: usize,
}
pub fn extract_quads(mut commands: Commands, context: Res<BevyContext>) {
let render_commands = if let Ok(context) = context.kayak_context.read() {
context.widget_manager.build_render_primitives()
} else {
vec![]
};
let quad_commands: Vec<&RenderPrimitive> = render_commands
.iter()
.filter(|command| matches!(command, RenderPrimitive::Quad { .. }))
.collect::<Vec<_>>();
let mut extracted_quads = Vec::new();
for render_primitive in quad_commands {
let (background_color, layout) = match render_primitive {
RenderPrimitive::Quad {
background_color,
layout,
} => (background_color, layout),
_ => panic!(""),
};
extracted_quads.push(ExtractQuadBundle {
extracted_quad: ExtractedQuad {
rect: Rect {
min: Vec2::new(layout.posx, layout.posy),
max: Vec2::new(layout.width, layout.height),
},
background_color: to_bevy_color(background_color),
vertex_index: 0,
},
});
}
commands.spawn_batch(extracted_quads);
}
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct QuadVertex {
pub position: [f32; 3],
pub color: [f32; 4],
}
pub struct QuadMeta {
vertices: BufferVec<QuadVertex>,
view_bind_group: Option<BindGroup>,
}
impl Default for QuadMeta {
fn default() -> Self {
Self {
vertices: BufferVec::new(BufferUsages::VERTEX),
view_bind_group: None,
}
}
}
pub fn prepare_quads(
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut sprite_meta: ResMut<QuadMeta>,
mut extracted_sprites: Query<&mut ExtractedQuad>,
) {
let extracted_sprite_len = extracted_sprites.iter_mut().len();
// dont create buffers when there are no quads
if extracted_sprite_len == 0 {
return;
}
sprite_meta.vertices.clear();
sprite_meta.vertices.reserve(
extracted_sprite_len * QUAD_VERTEX_POSITIONS.len(),
&render_device,
);
for (i, mut extracted_sprite) in extracted_sprites.iter_mut().enumerate() {
let sprite_rect = extracted_sprite.rect;
let color = extracted_sprite.background_color.as_linear_rgba_f32();
extracted_sprite.vertex_index = i;
for vertex_position in QUAD_VERTEX_POSITIONS.iter() {
let world = Mat4::from_scale_rotation_translation(
sprite_rect.size().extend(1.0),
Quat::default(),
sprite_rect.min.extend(0.0),
);
let final_position = (world * Vec3::from(*vertex_position).extend(1.0)).truncate();
sprite_meta.vertices.push(QuadVertex {
position: final_position.into(),
color,
});
}
}
sprite_meta
.vertices
.write_buffer(&render_device, &render_queue);
}
pub fn queue_quads(
draw_functions: Res<DrawFunctions<TransparentUI>>,
render_device: Res<RenderDevice>,
mut sprite_meta: ResMut<QuadMeta>,
view_uniforms: Res<ViewUniforms>,
quad_pipeline: Res<QuadPipeline>,
mut extracted_sprites: Query<(Entity, &ExtractedQuad)>,
mut views: Query<&mut RenderPhase<TransparentUI>>,
) {
if let Some(view_binding) = view_uniforms.uniforms.binding() {
sprite_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: view_binding,
}],
label: Some("quad_view_bind_group"),
layout: &quad_pipeline.view_layout,
}));
let draw_quad = draw_functions.read().get_id::<DrawQuad>().unwrap();
for mut transparent_phase in views.iter_mut() {
for (entity, quad) in extracted_sprites.iter_mut() {
transparent_phase.add(TransparentUI {
draw_function: draw_quad,
pipeline: quad_pipeline.pipeline,
entity,
sort_key: FloatOrd(0.0),
});
}
}
}
}
pub struct DrawQuad {
params: SystemState<(
SRes<QuadMeta>,
SRes<RenderPipelineCache>,
SQuery<Read<ViewUniformOffset>>,
SQuery<Read<ExtractedQuad>>,
)>,
}
impl DrawQuad {
pub fn new(world: &mut World) -> Self {
Self {
params: SystemState::new(world),
}
}
}
impl Draw<TransparentUI> for DrawQuad {
fn draw<'w>(
&mut self,
world: &'w World,
pass: &mut TrackedRenderPass<'w>,
view: Entity,
item: &TransparentUI,
) {
let (quad_meta, pipelines, views, quads) = self.params.get(world);
let view_uniform = views.get(view).unwrap();
let quad_meta = quad_meta.into_inner();
let extracted_quad = quads.get(item.entity).unwrap();
if let Some(pipeline) = pipelines.into_inner().get(item.pipeline) {
pass.set_render_pipeline(pipeline);
pass.set_vertex_buffer(0, quad_meta.vertices.buffer().unwrap().slice(..));
pass.set_bind_group(
0,
quad_meta.view_bind_group.as_ref().unwrap(),
&[view_uniform.offset],
);
pass.draw(
(extracted_quad.vertex_index * QUAD_VERTEX_POSITIONS.len()) as u32
..((extracted_quad.vertex_index + 1) * QUAD_VERTEX_POSITIONS.len()) as u32,
0..1,
);
}
}
}
[[block]]
struct View {
view_proj: mat4x4<f32>;
world_position: vec3<f32>;
};
[[group(0), binding(0)]]
var<uniform> view: View;
struct VertexOutput {
[[builtin(position)]] position: vec4<f32>;
[[location(0)]] color: vec4<f32>;
[[location(1)]] uv: vec3<f32>;
};
[[stage(vertex)]]
fn vertex(
[[location(0)]] vertex_position: vec3<f32>,
[[location(1)]] vertex_color: vec4<f32>,
[[location(2)]] vertex_uv: vec3<f32>;
) -> VertexOutput {
var out: VertexOutput;
out.color = vertex_color;
out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
return out;
}
[[stage(fragment)]]
fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> {
return in.color;
}
\ No newline at end of file
use bevy::reflect::TypeUuid;
use kayak_font::Font;
#[derive(Debug, Clone, TypeUuid)]
#[uuid = "4fe4732c-6731-49bb-bafc-4690d636b848"]
pub struct KayakFont {
pub font: Font,
}
use bevy::{prelude::Handle, utils::HashMap};
use super::font::KayakFont;
pub struct FontMapping {
count: u16,
font_ids: HashMap<Handle<KayakFont>, u16>,
font_handles: HashMap<u16, Handle<KayakFont>>,
}
impl Default for FontMapping {
fn default() -> Self {
Self {
count: 0,
font_ids: HashMap::default(),
font_handles: HashMap::default(),
}
}
}
impl FontMapping {
pub(crate) fn add(&mut self, handle: Handle<KayakFont>) -> u16 {
if !self.font_ids.contains_key(&handle) {
let id = self.count;
self.font_ids.insert(handle.clone(), id);
self.font_handles.insert(id, handle);
self.count += 1;
id
} else {
*self.font_ids.get(&handle).unwrap()
}
}
pub(crate) fn get_handle(&self, id: u16) -> Option<Handle<KayakFont>> {
self.font_handles
.get(&id)
.and_then(|item| Some(item.clone()))
}
}
use bevy::{
prelude::Handle,
render2::{
render_resource::{
AddressMode, BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, Extent3d,
FilterMode, ImageCopyTexture, ImageDataLayout, Origin3d, SamplerDescriptor,
TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
TextureViewDescriptor, TextureViewDimension,
},
renderer::{RenderDevice, RenderQueue},
texture::{GpuImage, TextureFormatPixelInfo},
},
utils::HashMap,
};
use super::{font::KayakFont, pipeline::TextPipeline};
pub const MAX_CHARACTERS: u32 = 100;
pub struct FontTextureCache {
images: HashMap<Handle<KayakFont>, GpuImage>,
pub(crate) bind_groups: HashMap<Handle<KayakFont>, BindGroup>,
fonts: HashMap<Handle<KayakFont>, KayakFont>,
new_fonts: Vec<Handle<KayakFont>>,
updated_fonts: Vec<Handle<KayakFont>>,
}
impl Default for FontTextureCache {
fn default() -> Self {
Self::new()
}
}
impl FontTextureCache {
pub fn new() -> Self {
Self {
images: HashMap::default(),
bind_groups: HashMap::default(),
fonts: HashMap::default(),
new_fonts: Vec::new(),
updated_fonts: Vec::new(),
}
}
pub fn add(&mut self, kayak_font_handle: Handle<KayakFont>, font: KayakFont) {
if !self.fonts.contains_key(&kayak_font_handle) {
self.fonts.insert(kayak_font_handle.clone(), font);
self.new_fonts.push(kayak_font_handle);
} else {
if let Some(old_font) = self.fonts.get_mut(&kayak_font_handle) {
*old_font = font;
self.updated_fonts.push(kayak_font_handle);
}
}
}
pub fn process_new(&mut self, device: &RenderDevice, pipeline: &TextPipeline) {
let new_fonts = self.new_fonts.drain(..);
for kayak_font_handle in new_fonts {
if let Some(font) = self.fonts.get(&kayak_font_handle) {
Self::create_texture(
&mut self.images,
kayak_font_handle.clone_weak(),
font,
device,
);
let gpu_image = self.images.get(&kayak_font_handle).unwrap();
// create bind group
let binding = device.create_bind_group(&BindGroupDescriptor {
label: Some("text_image_bind_group"),
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&gpu_image.texture_view),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&gpu_image.sampler),
},
],
layout: &pipeline.image_layout,
});
self.bind_groups.insert(kayak_font_handle, binding);
}
}
}
pub fn process_updated(&mut self, queue: &RenderQueue) {
let updated_fonts = self.updated_fonts.drain(..);
for kayak_font_handle in updated_fonts {
if let Some(font) = self.fonts.get_mut(&kayak_font_handle) {
Self::process_new_chars_into_texture(
&mut self.images,
kayak_font_handle,
font,
queue,
);
}
}
}
fn create_texture(
images: &mut HashMap<Handle<KayakFont>, GpuImage>,
font_handle: Handle<KayakFont>,
font: &KayakFont,
device: &RenderDevice,
) {
let texture_descriptor = TextureDescriptor {
label: Some("font_texture_array"),
size: Extent3d {
width: font.font.cache.dimensions,
height: font.font.cache.dimensions,
depth_or_array_layers: MAX_CHARACTERS,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba32Float,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
};
let sampler_descriptor = SamplerDescriptor {
label: Some("font_texture_array_sampler"),
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
address_mode_w: AddressMode::ClampToEdge,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
mipmap_filter: FilterMode::Nearest,
lod_min_clamp: 0.0,
lod_max_clamp: std::f32::MAX,
compare: None,
anisotropy_clamp: None,
border_color: None,
};
let texture = device.create_texture(&texture_descriptor);
let sampler = device.create_sampler(&sampler_descriptor);
let texture_view = texture.create_view(&TextureViewDescriptor {
label: Some("font_texture_array_view"),
format: None,
dimension: Some(TextureViewDimension::D2Array),
aspect: bevy::render2::render_resource::TextureAspect::All,
base_mip_level: 0,
base_array_layer: 0,
mip_level_count: None,
array_layer_count: std::num::NonZeroU32::new(MAX_CHARACTERS),
});
let image = GpuImage {
texture,
sampler,
texture_view,
};
images.insert(font_handle, image);
}
pub fn process_new_chars_into_texture(
images: &mut HashMap<Handle<KayakFont>, GpuImage>,
kayak_font_handle: Handle<KayakFont>,
font: &mut KayakFont,
queue: &RenderQueue,
) {
let size = font.font.cache.dimensions;
if let Some(gpu_image) = images.get_mut(&kayak_font_handle) {
for (_, id, pixels) in font.font.get_data_to_process() {
let format_size = TextureFormat::Rgba32Float.pixel_size();
queue.write_texture(
ImageCopyTexture {
texture: &gpu_image.texture,
mip_level: 0,
origin: Origin3d {
x: 0,
y: 0,
z: id as u32,
},
aspect: TextureAspect::All,
},
&pixels,
ImageDataLayout {
offset: 0,
bytes_per_row: Some(
std::num::NonZeroU32::new(size * format_size as u32).unwrap(),
),
rows_per_image: None,
},
Extent3d {
width: size,
height: size,
depth_or_array_layers: 1,
},
);
}
}
}
}
use bevy::{
prelude::{
AddAsset, AssetEvent, Assets, Commands, EventReader, Handle, HandleUntyped, Plugin, Res,
ResMut,
},
reflect::TypeUuid,
render2::{
render_phase::DrawFunctions,
render_resource::Shader,
renderer::{RenderDevice, RenderQueue},
RenderApp, RenderStage,
},
utils::HashSet,
};
use crate::render::{
text::{
font_mapping::FontMapping,
pipeline::{DrawText, TextMeta, TextPipeline},
},
ui_pass::TransparentUI,
};
use self::{font::KayakFont, font_texture_cache::FontTextureCache};
mod font;
mod font_mapping;
mod font_texture_cache;
mod pipeline;
pub const TEXT_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1561765330850392701);
#[derive(Default)]
pub struct TextRendererPlugin;
impl Plugin for TextRendererPlugin {
fn build(&self, app: &mut bevy::prelude::App) {
let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().unwrap();
let text_shader = Shader::from_wgsl(include_str!("shader.wgsl"));
shaders.set_untracked(TEXT_SHADER_HANDLE, text_shader);
let render_app = app.sub_app(RenderApp);
render_app
.init_resource::<TextPipeline>()
.init_resource::<TextMeta>()
.add_system_to_stage(RenderStage::Extract, pipeline::extract_texts)
.add_system_to_stage(RenderStage::Prepare, pipeline::prepare_texts)
.add_system_to_stage(RenderStage::Queue, pipeline::queue_texts);
let draw_text = DrawText::new(&mut render_app.world);
render_app
.world
.get_resource::<DrawFunctions<TransparentUI>>()
.unwrap()
.write()
.add(draw_text);
render_app
.init_resource::<FontTextureCache>()
.init_resource::<ExtractedFonts>()
.add_system_to_stage(RenderStage::Extract, extract_fonts)
.add_system_to_stage(RenderStage::Prepare, prepare_fonts)
.add_system_to_stage(RenderStage::Queue, create_and_update_font_cache_texture);
app.add_asset::<KayakFont>()
.init_resource::<FontMapping>()
.add_startup_system(load_fonts);
}
}
#[derive(Default)]
pub struct ExtractedFonts {
pub fonts: Vec<(Handle<KayakFont>, KayakFont)>,
}
fn load_fonts(mut font_assets: ResMut<Assets<KayakFont>>, mut font_mapping: ResMut<FontMapping>) {
let font_bytes = include_bytes!("../../../../resources/Roboto-Regular.ttf");
let font = kayak_font::Font::new(font_bytes, 128);
let handle = font_assets.add(KayakFont { font });
font_mapping.add(handle);
dbg!("Loaded base font!");
}
fn extract_fonts(
mut commands: Commands,
font_assets: Res<Assets<KayakFont>>,
mut events: EventReader<AssetEvent<KayakFont>>,
) {
let mut extracted_fonts = ExtractedFonts { fonts: Vec::new() };
let mut changed_assets = HashSet::default();
let mut removed = Vec::new();
for event in events.iter() {
match event {
AssetEvent::Created { handle } => {
changed_assets.insert(handle);
dbg!("New font added!");
}
AssetEvent::Modified { handle } => {
changed_assets.insert(handle);
dbg!("Font changed!");
}
AssetEvent::Removed { handle } => {
if !changed_assets.remove(handle) {
removed.push(handle.clone_weak());
}
}
}
}
for handle in changed_assets {
let font_asset = font_assets.get(handle).unwrap();
let font = font_asset.clone();
extracted_fonts.fonts.push((handle.clone_weak(), font));
}
commands.insert_resource(extracted_fonts);
}
fn prepare_fonts(
mut extracted_fonts: ResMut<ExtractedFonts>,
mut font_texture_cache: ResMut<FontTextureCache>,
) {
for (handle, font) in extracted_fonts.fonts.drain(..) {
font_texture_cache.add(handle, font);
}
}
fn create_and_update_font_cache_texture(
device: Res<RenderDevice>,
queue: Res<RenderQueue>,
pipeline: Res<TextPipeline>,
mut font_texture_cache: ResMut<FontTextureCache>,
) {
font_texture_cache.process_new(&device, &pipeline);
font_texture_cache.process_updated(&queue);
}
use bevy::{
core::FloatOrd,
ecs::system::{
lifetimeless::{Read, SQuery, SRes},
SystemState,
},
math::{const_vec3, Mat4, Quat, Vec2, Vec3},
prelude::{
Assets, Bundle, Color, Commands, Entity, FromWorld, Handle, Query, Res, ResMut, World,
},
render2::{
render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass},
render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendComponent,
BlendFactor, BlendOperation, BlendState, BufferBindingType, BufferSize, BufferUsages,
BufferVec, CachedPipelineId, ColorTargetState, ColorWrites, CompareFunction,
DepthBiasState, DepthStencilState, FragmentState, FrontFace, MultisampleState,
PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineCache,
RenderPipelineDescriptor, Shader, ShaderStages, StencilFaceState, StencilState,
TextureFormat, TextureSampleType, TextureViewDimension, VertexAttribute,
VertexBufferLayout, VertexFormat, VertexState, VertexStepMode,
},
renderer::{RenderDevice, RenderQueue},
texture::BevyDefault,
view::{ViewUniformOffset, ViewUniforms},
},
sprite2::Rect,
};
use bytemuck::{Pod, Zeroable};
use kayak_core::render_primitive::RenderPrimitive;
use crate::{
render::{text::TEXT_SHADER_HANDLE, ui_pass::TransparentUI},
to_bevy_color, BevyContext,
};
use super::{font::KayakFont, font_mapping::FontMapping, font_texture_cache::FontTextureCache};
pub struct TextPipeline {
view_layout: BindGroupLayout,
pub(crate) image_layout: BindGroupLayout,
pipeline: CachedPipelineId,
}
const QUAD_VERTEX_POSITIONS: &[Vec3] = &[
const_vec3!([0.0, 0.0, 0.0]),
const_vec3!([1.0, 1.0, 0.0]),
const_vec3!([0.0, 1.0, 0.0]),
const_vec3!([0.0, 0.0, 0.0]),
const_vec3!([1.0, 0.0, 0.0]),
const_vec3!([1.0, 1.0, 0.0]),
];
impl FromWorld for TextPipeline {
fn from_world(world: &mut World) -> Self {
let world = world.cell();
let render_device = world.get_resource::<RenderDevice>().unwrap();
let mut pipeline_cache = world.get_resource_mut::<RenderPipelineCache>().unwrap();
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
// TODO: change this to ViewUniform::std140_size_static once crevice fixes this!
// Context: https://github.com/LPGhatguy/crevice/issues/29
min_binding_size: BufferSize::new(144),
},
count: None,
}],
label: Some("text_view_layout"),
});
let image_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Float { filterable: false },
view_dimension: TextureViewDimension::D2Array,
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler {
comparison: false,
filtering: true,
},
count: None,
},
],
label: Some("text_image_layout"),
});
let vertex_buffer_layout = VertexBufferLayout {
array_stride: 40,
step_mode: VertexStepMode::Vertex,
attributes: vec![
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 0,
shader_location: 0,
},
VertexAttribute {
format: VertexFormat::Float32x4,
offset: 12,
shader_location: 1,
},
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 28,
shader_location: 2,
},
],
};
let pipeline_desc = RenderPipelineDescriptor {
vertex: VertexState {
shader: TEXT_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(),
shader_defs: vec![],
buffers: vec![vertex_buffer_layout],
},
fragment: Some(FragmentState {
shader: TEXT_SHADER_HANDLE.typed::<Shader>(),
shader_defs: vec![],
entry_point: "fragment".into(),
targets: vec![ColorTargetState {
format: TextureFormat::bevy_default(),
blend: Some(BlendState {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::One,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
}),
write_mask: ColorWrites::ALL,
}],
}),
layout: Some(vec![view_layout.clone(), image_layout.clone()]),
primitive: PrimitiveState {
front_face: FrontFace::Ccw,
cull_mode: None,
polygon_mode: PolygonMode::Fill,
clamp_depth: false,
conservative: false,
topology: PrimitiveTopology::TriangleList,
strip_index_format: None,
},
depth_stencil: Some(DepthStencilState {
format: TextureFormat::Depth32Float,
depth_write_enabled: false,
depth_compare: CompareFunction::Greater,
stencil: StencilState {
front: StencilFaceState::IGNORE,
back: StencilFaceState::IGNORE,
read_mask: 0,
write_mask: 0,
},
bias: DepthBiasState {
constant: 0,
slope_scale: 0.0,
clamp: 0.0,
},
}),
multisample: MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("text_pipeline".into()),
};
TextPipeline {
pipeline: pipeline_cache.queue(pipeline_desc),
view_layout,
image_layout,
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct TextVertex {
pub position: [f32; 3],
pub color: [f32; 4],
pub uv: [f32; 3],
}
pub struct TextMeta {
vertices: BufferVec<TextVertex>,
view_bind_group: Option<BindGroup>,
}
impl Default for TextMeta {
fn default() -> Self {
Self {
vertices: BufferVec::new(BufferUsages::VERTEX),
view_bind_group: None,
}
}
}
pub fn prepare_texts(
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut sprite_meta: ResMut<TextMeta>,
mut extracted_sprites: Query<&mut ExtractedText>,
) {
let extracted_sprite_len = extracted_sprites.iter_mut().len();
// dont create buffers when there are no texts
if extracted_sprite_len == 0 {
return;
}
sprite_meta.vertices.clear();
sprite_meta.vertices.reserve(
extracted_sprite_len * QUAD_VERTEX_POSITIONS.len(),
&render_device,
);
for (i, mut extracted_sprite) in extracted_sprites.iter_mut().enumerate() {
let sprite_rect = extracted_sprite.rect;
let color = extracted_sprite.background_color.as_linear_rgba_f32();
let bottom_left = Vec3::new(0.0, 1.0, extracted_sprite.char_id as f32);
let top_left = Vec3::new(0.0, 0.0, extracted_sprite.char_id as f32);
let top_right = Vec3::new(1.0, 0.0, extracted_sprite.char_id as f32);
let bottom_right = Vec3::new(1.0, 1.0, extracted_sprite.char_id as f32);
let uvs: [[f32; 3]; 6] = [
bottom_left.into(),
top_right.into(),
top_left.into(),
bottom_left.into(),
bottom_right.into(),
top_right.into(),
];
extracted_sprite.vertex_index = i;
for (index, vertex_position) in QUAD_VERTEX_POSITIONS.iter().enumerate() {
let world = Mat4::from_scale_rotation_translation(
sprite_rect.size().extend(1.0),
Quat::default(),
sprite_rect.min.extend(extracted_sprite.z_index),
);
let final_position = (world * Vec3::from(*vertex_position).extend(1.0)).truncate();
sprite_meta.vertices.push(TextVertex {
position: final_position.into(),
color,
uv: uvs[index],
});
}
}
sprite_meta
.vertices
.write_buffer(&render_device, &render_queue);
}
pub fn queue_texts(
draw_functions: Res<DrawFunctions<TransparentUI>>,
render_device: Res<RenderDevice>,
mut sprite_meta: ResMut<TextMeta>,
view_uniforms: Res<ViewUniforms>,
text_pipeline: Res<TextPipeline>,
mut extracted_sprites: Query<(Entity, &ExtractedText)>,
mut views: Query<&mut RenderPhase<TransparentUI>>,
) {
if let Some(view_binding) = view_uniforms.uniforms.binding() {
sprite_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: view_binding,
}],
label: Some("text_view_bind_group"),
layout: &text_pipeline.view_layout,
}));
let draw_text = draw_functions.read().get_id::<DrawText>().unwrap();
for mut transparent_phase in views.iter_mut() {
for (entity, _) in extracted_sprites.iter_mut() {
transparent_phase.add(TransparentUI {
draw_function: draw_text,
pipeline: text_pipeline.pipeline,
entity,
sort_key: FloatOrd(0.0),
});
}
}
}
}
pub struct DrawText {
params: SystemState<(
SRes<TextMeta>,
SRes<RenderPipelineCache>,
SRes<FontTextureCache>,
SQuery<Read<ViewUniformOffset>>,
SQuery<Read<ExtractedText>>,
)>,
}
impl DrawText {
pub fn new(world: &mut World) -> Self {
Self {
params: SystemState::new(world),
}
}
}
impl Draw<TransparentUI> for DrawText {
fn draw<'w>(
&mut self,
world: &'w World,
pass: &mut TrackedRenderPass<'w>,
view: Entity,
item: &TransparentUI,
) {
let (text_meta, pipelines, font_texture_cache, views, texts) = self.params.get(world);
let view_uniform = views.get(view).unwrap();
let text_meta = text_meta.into_inner();
let extracted_text = texts.get(item.entity).unwrap();
if let Some(pipeline) = pipelines.into_inner().get(item.pipeline) {
pass.set_render_pipeline(pipeline);
pass.set_vertex_buffer(0, text_meta.vertices.buffer().unwrap().slice(..));
pass.set_bind_group(
0,
text_meta.view_bind_group.as_ref().unwrap(),
&[view_uniform.offset],
);
if let Some(image_bindings) = font_texture_cache
.into_inner()
.bind_groups
.get(&extracted_text.font_handle)
{
pass.set_bind_group(1, image_bindings, &[]);
pass.draw(
(extracted_text.vertex_index * QUAD_VERTEX_POSITIONS.len()) as u32
..((extracted_text.vertex_index + 1) * QUAD_VERTEX_POSITIONS.len()) as u32,
0..1,
);
}
}
}
}
[[block]]
struct View {
view_proj: mat4x4<f32>;
world_position: vec3<f32>;
};
[[group(0), binding(0)]]
var<uniform> view: View;
struct VertexOutput {
[[builtin(position)]] position: vec4<f32>;
[[location(0)]] color: vec4<f32>;
[[location(1)]] uv: vec3<f32>;
};
[[stage(vertex)]]
fn vertex(
[[location(0)]] vertex_position: vec3<f32>,
[[location(1)]] vertex_color: vec4<f32>,
[[location(2)]] vertex_uv: vec3<f32>,
) -> VertexOutput {
var out: VertexOutput;
out.color = vertex_color;
out.uv = vertex_uv;
out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
return out;
}
[[group(1), binding(0)]]
var sprite_texture: texture_2d_array<f32>;
[[group(1), binding(1)]]
var sprite_sampler: sampler;
let RADIUS: f32 = 0.5;
[[stage(fragment)]]
fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> {
var x = textureSample(sprite_texture, sprite_sampler, in.uv.xy, i32(in.uv.z));
var v = max(min(x.r, x.g), min(max(x.r, x.g), x.b));
var c = v; //remap(v);
var v2 = c / fwidth( c );
var a = v2 + RADIUS; //clamp( v2 + RADIUS, 0.0, 1.0 );
return vec4<f32>(in.color.rgb, a);
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment