use crate::core::sstorage::ImmutableString;
use crate::{
asset::Resource,
core::{
algebra::{Matrix4, Vector2, Vector4},
color::Color,
math::Rect,
parking_lot::Mutex,
scope_profile,
},
gui::{
brush::Brush,
draw::{CommandTexture, DrawingContext, SharedTexture},
},
renderer::{
framework::{
error::FrameworkError,
framebuffer::{DrawParameters, FrameBuffer},
geometry_buffer::{
AttributeDefinition, AttributeKind, BufferBuilder, ElementKind, GeometryBuffer,
GeometryBufferBuilder, GeometryBufferKind,
},
gpu_program::{GpuProgram, UniformLocation},
gpu_texture::GpuTexture,
state::{
BlendFactor, BlendFunc, ColorMask, CompareFunc, PipelineState, StencilAction,
StencilFunc, StencilOp,
},
},
RenderPassStatistics, TextureCache,
},
resource::texture::{Texture, TextureData, TextureKind, TexturePixelKind, TextureState},
};
use std::{cell::RefCell, rc::Rc, sync::Arc};
struct UiShader {
program: GpuProgram,
wvp_matrix: UniformLocation,
diffuse_texture: UniformLocation,
is_font: UniformLocation,
solid_color: UniformLocation,
brush_type: UniformLocation,
gradient_point_count: UniformLocation,
gradient_colors: UniformLocation,
gradient_stops: UniformLocation,
gradient_origin: UniformLocation,
gradient_end: UniformLocation,
resolution: UniformLocation,
bounds_min: UniformLocation,
bounds_max: UniformLocation,
opacity: UniformLocation,
}
impl UiShader {
pub fn new(state: &mut PipelineState) -> Result<Self, FrameworkError> {
let fragment_source = include_str!("shaders/ui_fs.glsl");
let vertex_source = include_str!("shaders/ui_vs.glsl");
let program = GpuProgram::from_source(state, "UIShader", vertex_source, fragment_source)?;
Ok(Self {
wvp_matrix: program
.uniform_location(state, &ImmutableString::new("worldViewProjection"))?,
diffuse_texture: program
.uniform_location(state, &ImmutableString::new("diffuseTexture"))?,
is_font: program.uniform_location(state, &ImmutableString::new("isFont"))?,
solid_color: program.uniform_location(state, &ImmutableString::new("solidColor"))?,
brush_type: program.uniform_location(state, &ImmutableString::new("brushType"))?,
gradient_point_count: program
.uniform_location(state, &ImmutableString::new("gradientPointCount"))?,
gradient_colors: program
.uniform_location(state, &ImmutableString::new("gradientColors"))?,
gradient_stops: program
.uniform_location(state, &ImmutableString::new("gradientStops"))?,
gradient_origin: program
.uniform_location(state, &ImmutableString::new("gradientOrigin"))?,
gradient_end: program.uniform_location(state, &ImmutableString::new("gradientEnd"))?,
bounds_min: program.uniform_location(state, &ImmutableString::new("boundsMin"))?,
bounds_max: program.uniform_location(state, &ImmutableString::new("boundsMax"))?,
resolution: program.uniform_location(state, &ImmutableString::new("resolution"))?,
opacity: program.uniform_location(state, &ImmutableString::new("opacity"))?,
program,
})
}
}
pub struct UiRenderer {
shader: UiShader,
geometry_buffer: GeometryBuffer,
clipping_geometry_buffer: GeometryBuffer,
}
pub(in crate) struct UiRenderContext<'a, 'b, 'c> {
pub state: &'a mut PipelineState,
pub viewport: Rect<i32>,
pub frame_buffer: &'b mut FrameBuffer,
pub frame_width: f32,
pub frame_height: f32,
pub drawing_context: &'c DrawingContext,
pub white_dummy: Rc<RefCell<GpuTexture>>,
pub texture_cache: &'a mut TextureCache,
}
impl UiRenderer {
pub(in crate::renderer) fn new(state: &mut PipelineState) -> Result<Self, FrameworkError> {
let geometry_buffer = GeometryBufferBuilder::new(ElementKind::Triangle)
.with_buffer_builder(
BufferBuilder::new::<crate::gui::draw::Vertex>(
GeometryBufferKind::DynamicDraw,
None,
)
.with_attribute(AttributeDefinition {
location: 0,
kind: AttributeKind::Float2,
normalized: false,
divisor: 0,
})
.with_attribute(AttributeDefinition {
location: 1,
kind: AttributeKind::Float2,
normalized: false,
divisor: 0,
})
.with_attribute(AttributeDefinition {
location: 2,
kind: AttributeKind::UnsignedByte4,
normalized: true, divisor: 0,
}),
)
.build(state)?;
let clipping_geometry_buffer = GeometryBufferBuilder::new(ElementKind::Triangle)
.with_buffer_builder(
BufferBuilder::new::<crate::gui::draw::Vertex>(
GeometryBufferKind::DynamicDraw,
None,
)
.with_attribute(AttributeDefinition {
location: 0,
kind: AttributeKind::Float2,
normalized: false,
divisor: 0,
}),
)
.build(state)?;
Ok(Self {
geometry_buffer,
clipping_geometry_buffer,
shader: UiShader::new(state)?,
})
}
pub(in crate::renderer) fn render(
&mut self,
args: UiRenderContext,
) -> Result<RenderPassStatistics, FrameworkError> {
scope_profile!();
let UiRenderContext {
state,
viewport,
frame_buffer: backbuffer,
frame_width,
frame_height,
drawing_context,
white_dummy,
texture_cache,
} = args;
let mut statistics = RenderPassStatistics::default();
self.geometry_buffer
.set_buffer_data(state, 0, drawing_context.get_vertices());
let geometry_buffer = self.geometry_buffer.bind(state);
geometry_buffer.set_triangles(drawing_context.get_triangles());
let ortho = Matrix4::new_orthographic(0.0, frame_width, frame_height, 0.0, -1.0, 1.0);
let resolution = Vector2::new(frame_width, frame_height);
state.set_scissor_test(true);
for cmd in drawing_context.get_commands() {
let mut diffuse_texture = white_dummy.clone();
let mut is_font_texture = false;
let mut clip_bounds = cmd.clip_bounds;
clip_bounds.position.x = clip_bounds.position.x.floor();
clip_bounds.position.y = clip_bounds.position.y.floor();
clip_bounds.size.x = clip_bounds.size.x.ceil();
clip_bounds.size.y = clip_bounds.size.y.ceil();
state.set_scissor_box(
clip_bounds.position.x as i32,
viewport.size.y - (clip_bounds.position.y + clip_bounds.size.y) as i32,
clip_bounds.size.x as i32,
clip_bounds.size.y as i32,
);
let mut stencil_test = None;
if let Some(clipping_geometry) = cmd.clipping_geometry.as_ref() {
backbuffer.clear(state, viewport, None, None, Some(0));
self.clipping_geometry_buffer.set_buffer_data(
state,
0,
&clipping_geometry.vertex_buffer,
);
self.clipping_geometry_buffer
.bind(state)
.set_triangles(&clipping_geometry.triangle_buffer);
statistics += backbuffer.draw(
&self.clipping_geometry_buffer,
state,
viewport,
&self.shader.program,
&DrawParameters {
cull_face: None,
color_write: ColorMask::all(false),
depth_write: false,
stencil_test: None,
depth_test: false,
blend: None,
stencil_op: StencilOp {
zpass: StencilAction::Incr,
..Default::default()
},
},
|mut program_binding| {
program_binding.set_matrix4(&self.shader.wvp_matrix, &ortho);
},
);
stencil_test = Some(StencilFunc {
func: CompareFunc::Equal,
ref_value: 1,
..Default::default()
});
}
match &cmd.texture {
CommandTexture::Font(font_arc) => {
let mut font = font_arc.0.lock().unwrap();
if font.texture.is_none() {
let size = font.atlas_size() as u32;
if let Some(details) = TextureData::from_bytes(
TextureKind::Rectangle {
width: size,
height: size,
},
TexturePixelKind::R8,
font.atlas_pixels().to_vec(),
false,
) {
font.texture = Some(SharedTexture(Arc::new(Mutex::new(
TextureState::Ok(details),
))));
}
}
let tex = font
.texture
.clone()
.unwrap()
.0
.downcast::<Mutex<TextureState>>()
.unwrap();
if let Some(texture) = texture_cache.get(state, &Texture(Resource::from(tex))) {
diffuse_texture = texture;
}
is_font_texture = true;
}
CommandTexture::Texture(texture) => {
if let Ok(texture) = texture.clone().0.downcast::<Mutex<TextureState>>() {
let resource = Resource::from(texture);
if let Some(texture) = texture_cache.get(state, &Texture(resource)) {
diffuse_texture = texture;
}
}
}
_ => (),
}
let mut raw_stops = [0.0; 16];
let mut raw_colors = [Vector4::default(); 16];
let bounds_max = cmd.bounds.right_bottom_corner();
let (gradient_origin, gradient_end) = match cmd.brush {
Brush::Solid(_) => (Vector2::default(), Vector2::default()),
Brush::LinearGradient { from, to, .. } => (from, to),
Brush::RadialGradient { center, .. } => (center, Vector2::default()),
};
let params = DrawParameters {
cull_face: None,
color_write: ColorMask::all(true),
depth_write: false,
stencil_test,
depth_test: false,
blend: Some(BlendFunc {
sfactor: BlendFactor::SrcAlpha,
dfactor: BlendFactor::OneMinusSrcAlpha,
}),
stencil_op: Default::default(),
};
let shader = &self.shader;
statistics += backbuffer.draw_part(
&self.geometry_buffer,
state,
viewport,
&self.shader.program,
params,
cmd.triangles.start,
cmd.triangles.end - cmd.triangles.start,
|mut program_binding| {
program_binding
.set_texture(&shader.diffuse_texture, &diffuse_texture)
.set_matrix4(&shader.wvp_matrix, &ortho)
.set_vector2(&shader.resolution, &resolution)
.set_vector2(&shader.bounds_min, &cmd.bounds.position)
.set_vector2(&shader.bounds_max, &bounds_max)
.set_bool(&shader.is_font, is_font_texture)
.set_i32(
&shader.brush_type,
match cmd.brush {
Brush::Solid(_) => 0,
Brush::LinearGradient { .. } => 1,
Brush::RadialGradient { .. } => 2,
},
)
.set_srgb_color(
&shader.solid_color,
&match cmd.brush {
Brush::Solid(color) => color,
_ => Color::WHITE,
},
)
.set_vector2(&shader.gradient_origin, &gradient_origin)
.set_vector2(&shader.gradient_end, &gradient_end)
.set_i32(
&shader.gradient_point_count,
match &cmd.brush {
Brush::Solid(_) => 0,
Brush::LinearGradient { stops, .. }
| Brush::RadialGradient { stops, .. } => stops.len() as i32,
},
)
.set_f32_slice(
&shader.gradient_stops,
match &cmd.brush {
Brush::Solid(_) => &raw_stops,
Brush::LinearGradient { stops, .. }
| Brush::RadialGradient { stops, .. } => {
for (i, point) in stops.iter().enumerate() {
raw_stops[i] = point.stop;
}
&raw_stops
}
},
)
.set_vector4_slice(
&shader.gradient_colors,
match &cmd.brush {
Brush::Solid(_) => &raw_colors,
Brush::LinearGradient { stops, .. }
| Brush::RadialGradient { stops, .. } => {
for (i, point) in stops.iter().enumerate() {
raw_colors[i] = point.color.as_frgba();
}
&raw_colors
}
},
)
.set_f32(&shader.opacity, cmd.opacity);
},
)?;
}
state.set_scissor_test(false);
Ok(statistics)
}
}