use bytemuck::Pod;
use bytemuck::Zeroable;
use lyon::geom::Point;
use wgpu::BufferUsages;
use wgpu::IndexFormat;
use crate::Box2D;
use crate::Transform2D;
use std::collections::HashMap;
use std::rc::{Rc, Weak};
use wgpu::util::DeviceExt;
use wgpu::TextureFormat;
use super::stencil::StencilRenderer;
pub struct TextureRenderer {
#[allow(dead_code)]
vertices_buffer: wgpu::Buffer,
indices_buffer: wgpu::Buffer,
texture_sampler: Rc<wgpu::Sampler>,
texture_pipeline: Rc<wgpu::RenderPipeline>,
texture_bind_group_layout: Rc<wgpu::BindGroupLayout>,
}
#[derive(Clone)]
pub(crate) struct TexturePipelineResources {
texture_sampler: Rc<wgpu::Sampler>,
texture_pipeline: Rc<wgpu::RenderPipeline>,
texture_bind_group_layout: Rc<wgpu::BindGroupLayout>,
}
pub(crate) struct CachedTextureResource {
pub bind_group: wgpu::BindGroup,
pub width: u32,
pub height: u32,
_image: Rc<ImageTexture>,
}
#[derive(Hash, PartialEq, Eq)]
struct ImageTextureKey {
identity: String,
version: u64,
width: u32,
height: u32,
}
struct ImageTexture {
_texture: wgpu::Texture,
view: wgpu::TextureView,
width: u32,
height: u32,
}
#[derive(Default)]
pub(super) struct ImageTextureCache {
entries: HashMap<ImageTextureKey, Weak<ImageTexture>>,
}
impl ImageTextureCache {
fn get_or_upload(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
identity: &str,
version: u64,
rgba: &[u8],
width: u32,
height: u32,
) -> (Rc<ImageTexture>, bool) {
let key = ImageTextureKey {
identity: identity.into(),
version,
width,
height,
};
if let Some(image) = self.entries.get(&key).and_then(Weak::upgrade) {
return (image, false);
}
self.entries.retain(|_, image| image.strong_count() > 0);
let image = Rc::new(ImageTexture::upload(device, queue, rgba, width, height));
self.entries.insert(key, Rc::downgrade(&image));
(image, true)
}
#[cfg(test)]
pub(super) fn live_count(&self) -> usize {
self.entries
.values()
.filter(|image| image.strong_count() > 0)
.count()
}
}
impl ImageTexture {
fn upload(
device: &wgpu::Device,
queue: &wgpu::Queue,
rgba: &[u8],
width: u32,
height: u32,
) -> Self {
let size = wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Shared Retained Image"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
aspect: wgpu::TextureAspect::All,
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
rgba,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * width),
rows_per_image: Some(height),
},
size,
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
Self {
_texture: texture,
view,
width,
height,
}
}
}
pub(crate) struct RetainedImageResource {
pub image_key: String,
pub vertices_buffer: wgpu::Buffer,
}
impl TextureRenderer {
#[allow(dead_code)]
pub fn new(device: &wgpu::Device, target_format: TextureFormat, sample_count: u32) -> Self {
let alpha_layout = super::alpha_mask::sampling_layout(device);
let resources =
Self::create_pipeline_resources(device, target_format, sample_count, &alpha_layout);
Self::with_pipeline_resources(device, resources)
}
pub(crate) fn with_pipeline_resources(
device: &wgpu::Device,
resources: TexturePipelineResources,
) -> Self {
let vertices = [TextureVertex::default(); 6];
let vertices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(&vertices),
usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
});
let indices: &[u16] = &[1, 0, 2, 1, 2, 3];
let indices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(&indices),
usage: BufferUsages::INDEX,
});
Self {
vertices_buffer,
indices_buffer,
texture_sampler: resources.texture_sampler,
texture_pipeline: resources.texture_pipeline,
texture_bind_group_layout: resources.texture_bind_group_layout,
}
}
pub(crate) fn create_pipeline_resources(
device: &wgpu::Device,
target_format: TextureFormat,
sample_count: u32,
alpha_layout: &wgpu::BindGroupLayout,
) -> TexturePipelineResources {
let texture_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Texture Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("textures.wgsl").into()),
});
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
label: Some("texture_bind_group_layout"),
});
let texture_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Texture Pipeline Layout"),
bind_group_layouts: &[&texture_bind_group_layout, alpha_layout],
immediate_size: 0,
});
let texture_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Texture Pipeline"),
layout: Some(&texture_pipeline_layout),
vertex: wgpu::VertexState {
module: &texture_shader,
entry_point: Some("vs_main"),
buffers: &[TextureVertex::desc()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &texture_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: target_format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Stencil8,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::Always,
stencil: wgpu::StencilState {
front: wgpu::StencilFaceState {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
},
back: wgpu::StencilFaceState::IGNORE,
read_mask: !0,
write_mask: !0,
},
bias: Default::default(),
}),
multisample: wgpu::MultisampleState {
count: sample_count,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
});
let texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default()
});
TexturePipelineResources {
texture_sampler: Rc::new(texture_sampler),
texture_pipeline: Rc::new(texture_pipeline),
texture_bind_group_layout: Rc::new(texture_bind_group_layout),
}
}
#[allow(dead_code)]
pub fn render_image(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
target: &wgpu::TextureView,
resolve_target: Option<&wgpu::TextureView>,
globals: &wgpu::Buffer,
stencil_renderer: &StencilRenderer,
clear_target: bool,
clear_color: wgpu::Color,
rgba: &[u8],
rgba_width: u32,
transform: Transform2D,
location: Box2D,
) {
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Texture Encoder"),
});
let points = get_transformed_corners(&location, &transform);
let height = rgba.len() as u32 / (rgba_width * 4);
let verts = [
TextureVertex {
position: points[0].to_array(),
texture_coord: [0.0, 0.0],
opacity: 1.0,
},
TextureVertex {
position: points[1].to_array(),
texture_coord: [1.0, 0.0],
opacity: 1.0,
},
TextureVertex {
position: points[2].to_array(),
texture_coord: [0.0, 1.0],
opacity: 1.0,
},
TextureVertex {
position: points[3].to_array(),
texture_coord: [1.0, 1.0],
opacity: 1.0,
},
];
queue.write_buffer(&self.vertices_buffer, 0, bytemuck::cast_slice(&verts));
let size = wgpu::Extent3d {
width: rgba_width,
height,
depth_or_array_layers: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Texture Creation"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
aspect: wgpu::TextureAspect::All,
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
&rgba,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * rgba_width),
rows_per_image: Some(height),
},
size,
);
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: self.texture_bind_group_layout.as_ref(),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: globals.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&texture_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(self.texture_sampler.as_ref()),
},
],
label: Some("texture_bind_group"),
});
{
let (stencil_texture, stencil_index) = stencil_renderer.get_stencil();
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Texture Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
depth_slice: None,
resolve_target,
ops: wgpu::Operations {
load: if clear_target {
wgpu::LoadOp::Clear(clear_color)
} else {
wgpu::LoadOp::Load
},
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: stencil_texture,
depth_ops: None,
stencil_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
}),
}),
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
render_pass.set_pipeline(self.texture_pipeline.as_ref());
render_pass.set_bind_group(0, &bind_group, &[]);
render_pass.set_stencil_reference(stencil_index);
render_pass.set_vertex_buffer(0, self.vertices_buffer.slice(..));
render_pass.set_index_buffer(self.indices_buffer.slice(..), IndexFormat::Uint16);
render_pass.draw_indexed(0..6, 0, 0..1);
}
queue.submit(std::iter::once(encoder.finish()));
}
pub(super) fn create_cached_texture(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
globals: &wgpu::Buffer,
cache: &mut ImageTextureCache,
identity: &str,
version: u64,
rgba: &[u8],
rgba_width: u32,
rgba_height: u32,
) -> (CachedTextureResource, bool) {
let (image, uploaded) = cache.get_or_upload(
device,
queue,
identity,
version,
rgba,
rgba_width,
rgba_height,
);
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: self.texture_bind_group_layout.as_ref(),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: globals.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&image.view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(self.texture_sampler.as_ref()),
},
],
label: Some("retained_texture_bind_group"),
});
(
CachedTextureResource {
bind_group,
width: image.width,
height: image.height,
_image: image,
},
uploaded,
)
}
pub fn create_retained_image_resource(
&self,
device: &wgpu::Device,
image_key: String,
verts: [TextureVertex; 4],
) -> RetainedImageResource {
let vertices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Retained Image Vertex Buffer"),
contents: bytemuck::cast_slice(&verts),
usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
});
RetainedImageResource {
image_key,
vertices_buffer,
}
}
#[allow(dead_code)]
pub fn update_retained_image_resource(
&self,
queue: &wgpu::Queue,
resource: &RetainedImageResource,
verts: [TextureVertex; 4],
) {
queue.write_buffer(&resource.vertices_buffer, 0, bytemuck::cast_slice(&verts));
}
#[allow(dead_code)]
pub fn draw_retained_image(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
target: &wgpu::TextureView,
resolve_target: Option<&wgpu::TextureView>,
stencil_renderer: &StencilRenderer,
clear_target: bool,
clear_color: wgpu::Color,
texture: &CachedTextureResource,
resource: &RetainedImageResource,
) {
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Retained Texture Encoder"),
});
{
let (stencil_texture, stencil_index) = stencil_renderer.get_stencil();
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Retained Texture Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
depth_slice: None,
resolve_target,
ops: wgpu::Operations {
load: if clear_target {
wgpu::LoadOp::Clear(clear_color)
} else {
wgpu::LoadOp::Load
},
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: stencil_texture,
depth_ops: None,
stencil_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
}),
}),
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
render_pass.set_pipeline(self.texture_pipeline.as_ref());
render_pass.set_bind_group(0, &texture.bind_group, &[]);
render_pass.set_stencil_reference(stencil_index);
render_pass.set_vertex_buffer(0, resource.vertices_buffer.slice(..));
render_pass.set_index_buffer(self.indices_buffer.slice(..), IndexFormat::Uint16);
render_pass.draw_indexed(0..6, 0, 0..1);
}
queue.submit(std::iter::once(encoder.finish()));
}
pub fn draw_retained_image_in_pass<'a>(
&'a self,
render_pass: &mut wgpu::RenderPass<'a>,
texture: &'a CachedTextureResource,
resource: &'a RetainedImageResource,
) {
render_pass.set_pipeline(self.texture_pipeline.as_ref());
render_pass.set_bind_group(0, &texture.bind_group, &[]);
render_pass.set_vertex_buffer(0, resource.vertices_buffer.slice(..));
render_pass.set_index_buffer(self.indices_buffer.slice(..), IndexFormat::Uint16);
render_pass.draw_indexed(0..6, 0, 0..1);
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, Pod, Zeroable)]
pub(crate) struct TextureVertex {
pub position: [f32; 2],
pub texture_coord: [f32; 2],
pub opacity: f32,
}
impl TextureVertex {
pub(crate) fn desc() -> wgpu::VertexBufferLayout<'static> {
const ATTRIBS: [wgpu::VertexAttribute; 3] =
wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Float32];
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<TextureVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &ATTRIBS,
}
}
}
pub(crate) fn corners_to_texture_vertices(
points: [[f32; 2]; 4],
opacity: f32,
) -> [TextureVertex; 4] {
[
TextureVertex {
position: points[0],
texture_coord: [0.0, 0.0],
opacity,
},
TextureVertex {
position: points[1],
texture_coord: [1.0, 0.0],
opacity,
},
TextureVertex {
position: points[2],
texture_coord: [0.0, 1.0],
opacity,
},
TextureVertex {
position: points[3],
texture_coord: [1.0, 1.0],
opacity,
},
]
}
#[allow(dead_code)]
fn get_transformed_corners(box2d: &Box2D, transform: &Transform2D) -> [Point<f32>; 4] {
let min = box2d.min;
let max = box2d.max;
let corners = [
Point::new(min.x, min.y), Point::new(max.x, min.y), Point::new(min.x, max.y), Point::new(max.x, max.y), ];
[
transform.transform_point(corners[0]),
transform.transform_point(corners[1]),
transform.transform_point(corners[2]),
transform.transform_point(corners[3]),
]
}