experimental renderer for widgets with custom materials

- rename `shader` to `material` in `fyrox-ui`
This commit is contained in:
Dmitry Stepanov
2025-05-23 22:41:09 +03:00
parent d2a0ba9452
commit 65d2e8937b
6 changed files with 315 additions and 195 deletions
+51 -36
View File
@@ -22,6 +22,7 @@
#![allow(missing_docs)] // TODO
use crate::material::Material;
use crate::scene::camera::Camera;
use crate::{
core::{
@@ -76,6 +77,7 @@ use crate::{
use fxhash::{FxBuildHasher, FxHashMap, FxHasher};
use fyrox_core::algebra::Point3;
use fyrox_graph::{SceneGraph, SceneGraphNode};
use fyrox_graphics::gpu_program::{SamplerFallback, ShaderResourceDefinition};
use std::{
fmt::{Debug, Formatter},
hash::{Hash, Hasher},
@@ -363,6 +365,49 @@ pub fn write_shader_values<T: ByteStorage>(
}
}
pub fn make_texture_binding(
server: &dyn GraphicsServer,
material: &Material,
resource_definition: &ShaderResourceDefinition,
fallback_resources: &FallbackResources,
fallback: SamplerFallback,
texture_cache: &mut TextureCache,
) -> ResourceBinding {
let fallback = fallback_resources.sampler_fallback(fallback);
let fallback = (fallback, &fallback_resources.linear_wrap_sampler);
let texture_sampler_pair =
if let Some(binding) = material.binding_ref(resource_definition.name.clone()) {
if let material::MaterialResourceBinding::Texture(binding) = binding {
binding
.value
.as_ref()
.and_then(|t| {
texture_cache
.get(server, t)
.map(|t| (&t.gpu_texture, &t.gpu_sampler))
})
.unwrap_or(fallback)
} else {
Log::err(format!(
"Unable to use texture binding {}, types mismatch! Expected \
{:?} got {:?}",
resource_definition.name, resource_definition.kind, binding
));
fallback
}
} else {
fallback
};
ResourceBinding::texture(
texture_sampler_pair.0,
texture_sampler_pair.1,
resource_definition.binding,
)
}
impl RenderDataBundle {
/// Writes all the required uniform data of the bundle to uniform memory allocator.
pub fn write_uniforms(
@@ -597,43 +642,13 @@ impl RenderDataBundle {
}
_ => match resource_definition.kind {
ShaderResourceKind::Texture { fallback, .. } => {
let fallback = render_context.fallback_resources.sampler_fallback(fallback);
let fallback = (
material_bindings.push(make_texture_binding(
server,
material,
resource_definition,
render_context.fallback_resources,
fallback,
&render_context.fallback_resources.linear_wrap_sampler,
);
let texture_sampler_pair = if let Some(binding) =
material.binding_ref(resource_definition.name.clone())
{
if let material::MaterialResourceBinding::Texture(binding) = binding {
binding
.value
.as_ref()
.and_then(|t| {
render_context
.texture_cache
.get(server, t)
.map(|t| (&t.gpu_texture, &t.gpu_sampler))
})
.unwrap_or(fallback)
} else {
Log::err(format!(
"Unable to use texture binding {}, types mismatch! Expected \
{:?} got {:?}",
resource_definition.name, resource_definition.kind, binding
));
fallback
}
} else {
fallback
};
material_bindings.push(ResourceBinding::texture(
texture_sampler_pair.0,
texture_sampler_pair.1,
resource_definition.binding,
render_context.texture_cache,
));
}
ShaderResourceKind::PropertyGroup(_) => {
+2
View File
@@ -1369,6 +1369,7 @@ impl Renderer {
fallback_resources: &self.fallback_resources,
texture_cache: &mut self.texture_cache,
uniform_buffer_cache: &mut self.uniform_buffer_cache,
render_pass_cache: &mut self.shader_cache,
})?;
// Finally register texture in the cache so it will become available as texture in deferred/forward
@@ -1856,6 +1857,7 @@ impl Renderer {
fallback_resources: &self.fallback_resources,
texture_cache: &mut self.texture_cache,
uniform_buffer_cache: &mut self.uniform_buffer_cache,
render_pass_cache: &mut self.shader_cache,
})?;
}
+239 -136
View File
@@ -32,9 +32,13 @@ use crate::{
brush::Brush,
draw::{CommandTexture, DrawingContext},
},
material::{Material, MaterialResource},
renderer::{
bundle::{self, make_texture_binding},
cache::{
shader::{binding, property, PropertyGroup, RenderMaterial, RenderPassContainer},
shader::{
binding, property, PropertyGroup, RenderMaterial, RenderPassContainer, ShaderCache,
},
uniform::UniformBufferCache,
},
framework::{
@@ -53,6 +57,13 @@ use crate::{
},
resource::texture::{Texture, TextureKind, TexturePixelKind, TextureResource},
};
use fyrox_core::arrayvec::ArrayVec;
use fyrox_graphics::{
framebuffer::{ResourceBindGroup, ResourceBinding},
gpu_program::ShaderResourceKind,
uniform::StaticUniformBuffer,
};
use fyrox_resource::untyped::UntypedResource;
use uuid::Uuid;
/// User interface renderer allows you to render drawing context in specified render target.
@@ -82,6 +93,26 @@ pub struct UiRenderContext<'a, 'b, 'c> {
pub texture_cache: &'a mut TextureCache,
/// A reference to the cache of uniform buffers.
pub uniform_buffer_cache: &'a mut UniformBufferCache,
/// A reference to the render pass cache.
pub render_pass_cache: &'a mut ShaderCache,
}
fn try_get_render_pass<'a>(
server: &dyn GraphicsServer,
material: Option<&UntypedResource>,
render_pass_cache: &'a mut ShaderCache,
) -> Option<(MaterialResource, &'a RenderPassContainer)> {
if let Some(material) = material {
if let Some(material) = material.try_cast::<Material>() {
let material_data_guard = material.data_ref();
if let Some(material_data) = material_data_guard.as_loaded_ref() {
let rp = render_pass_cache.get(server, material_data.shader())?;
drop(material_data_guard);
return Some((material, rp));
}
}
}
None
}
impl UiRenderer {
@@ -163,6 +194,7 @@ impl UiRenderer {
fallback_resources,
texture_cache,
uniform_buffer_cache,
render_pass_cache,
} = args;
let mut statistics = RenderPassStatistics::default();
@@ -176,12 +208,6 @@ impl UiRenderer {
let resolution = Vector2::new(frame_width, frame_height);
for cmd in drawing_context.get_commands() {
let mut diffuse_texture = (
&fallback_resources.white_dummy,
&fallback_resources.linear_wrap_sampler,
);
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();
@@ -231,72 +257,6 @@ impl UiRenderer {
});
}
match &cmd.texture {
CommandTexture::Font {
font,
page_index,
height,
} => {
if let Some(font) = font.state().data() {
let page_size = font.page_size() as u32;
if let Some(page) = font
.atlases
.get_mut(height)
.and_then(|atlas| atlas.pages.get_mut(*page_index))
{
if page.texture.is_none() || page.modified {
if let Some(details) = Texture::from_bytes(
TextureKind::Rectangle {
width: page_size,
height: page_size,
},
TexturePixelKind::R8,
page.pixels.clone(),
) {
page.texture = Some(
TextureResource::new_ok(
Uuid::new_v4(),
ResourceKind::Embedded,
details,
)
.into(),
);
page.modified = false;
}
}
if let Some(texture) = texture_cache.get(
server,
&page
.texture
.as_ref()
.unwrap()
.try_cast::<Texture>()
.unwrap(),
) {
diffuse_texture = (&texture.gpu_texture, &texture.gpu_sampler);
}
is_font_texture = true;
}
}
}
CommandTexture::Texture(texture) => {
if let Some(texture) = texture_cache.get(server, texture) {
diffuse_texture = (&texture.gpu_texture, &texture.gpu_sampler);
}
}
_ => (),
}
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),
@@ -311,76 +271,219 @@ impl UiRenderer {
scissor_box,
};
let solid_color = match cmd.brush {
Brush::Solid(color) => color,
_ => Color::WHITE,
let element_range = ElementRange::Specific {
offset: cmd.triangles.start,
count: cmd.triangles.end - cmd.triangles.start,
};
let gradient_colors = match cmd.brush {
Brush::Solid(_) => &raw_colors,
Brush::LinearGradient { ref stops, .. }
| Brush::RadialGradient { ref stops, .. } => {
for (i, point) in stops.iter().enumerate() {
raw_colors[i] = point.color.as_frgba();
if let Some((material, render_pass_container)) =
try_get_render_pass(server, cmd.material.as_ref(), render_pass_cache)
{
let render_pass = render_pass_container.get(&ImmutableString::new("Forward"))?;
let mut resource_bindings = ArrayVec::<ResourceBinding, 32>::new();
let material = material.data_ref();
let shader = material.shader();
let shader = shader.data_ref();
for resource in shader.definition.resources.iter() {
// Ignore built-in groups.
if resource.is_built_in() {
continue;
}
&raw_colors
}
};
let gradient_stops = match cmd.brush {
Brush::Solid(_) => &raw_stops,
Brush::LinearGradient { ref stops, .. }
| Brush::RadialGradient { ref stops, .. } => {
for (i, point) in stops.iter().enumerate() {
raw_stops[i] = point.stop;
match resource.kind {
ShaderResourceKind::Texture { fallback, .. } => {
resource_bindings.push(make_texture_binding(
server,
&material,
resource,
fallback_resources,
fallback,
texture_cache,
))
}
ShaderResourceKind::PropertyGroup(ref shader_property_group) => {
let mut buf = StaticUniformBuffer::<16384>::new();
if let Some(material_property_group) =
material.property_group_ref(resource.name.clone())
{
bundle::write_with_material(
shader_property_group,
material_property_group,
|c, n| c.property_ref(n.clone()).map(|p| p.as_ref()),
&mut buf,
);
} else {
// No respective resource bound in the material, use shader defaults. This is very
// important, because some drivers will crash if uniform buffer has insufficient
// data.
bundle::write_shader_values(shader_property_group, &mut buf)
}
resource_bindings.push(ResourceBinding::buffer(
&uniform_buffer_cache.write(buf)?,
resource.binding,
Default::default(),
));
}
}
&raw_stops
}
};
let brush_type = match cmd.brush {
Brush::Solid(_) => 0,
Brush::LinearGradient { .. } => 1,
Brush::RadialGradient { .. } => 2,
};
let gradient_point_count = match cmd.brush {
Brush::Solid(_) => 0,
Brush::LinearGradient { ref stops, .. }
| Brush::RadialGradient { ref stops, .. } => stops.len() as i32,
};
let properties = PropertyGroup::from([
property("worldViewProjection", &ortho),
property("solidColor", &solid_color),
property("gradientColors", gradient_colors.as_slice()),
property("gradientStops", gradient_stops.as_slice()),
property("gradientOrigin", &gradient_origin),
property("gradientEnd", &gradient_end),
property("resolution", &resolution),
property("boundsMin", &cmd.bounds.position),
property("boundsMax", &bounds_max),
property("isFont", &is_font_texture),
property("opacity", &cmd.opacity),
property("brushType", &brush_type),
property("gradientPointCount", &gradient_point_count),
]);
statistics += frame_buffer.draw(
&self.geometry_buffer,
viewport,
&render_pass.program,
&params,
&[ResourceBindGroup {
bindings: &resource_bindings,
}],
element_range,
)?;
} else {
// Render with standard material.
let mut diffuse_texture = (
&fallback_resources.white_dummy,
&fallback_resources.linear_wrap_sampler,
);
let mut is_font_texture = false;
let material = RenderMaterial::from([
binding("diffuseTexture", diffuse_texture),
binding("properties", &properties),
]);
match &cmd.texture {
CommandTexture::Font {
font,
page_index,
height,
} => {
if let Some(font) = font.state().data() {
let page_size = font.page_size() as u32;
if let Some(page) = font
.atlases
.get_mut(height)
.and_then(|atlas| atlas.pages.get_mut(*page_index))
{
if page.texture.is_none() || page.modified {
if let Some(details) = Texture::from_bytes(
TextureKind::Rectangle {
width: page_size,
height: page_size,
},
TexturePixelKind::R8,
page.pixels.clone(),
) {
page.texture = Some(
TextureResource::new_ok(
Uuid::new_v4(),
ResourceKind::Embedded,
details,
)
.into(),
);
page.modified = false;
}
}
if let Some(texture) = texture_cache.get(
server,
&page
.texture
.as_ref()
.unwrap()
.try_cast::<Texture>()
.unwrap(),
) {
diffuse_texture = (&texture.gpu_texture, &texture.gpu_sampler);
}
is_font_texture = true;
}
}
}
CommandTexture::Texture(texture) => {
if let Some(texture) = texture_cache.get(server, texture) {
diffuse_texture = (&texture.gpu_texture, &texture.gpu_sampler);
}
}
_ => (),
}
statistics += self.render_passes.run_pass(
1,
&ImmutableString::new("Primary"),
frame_buffer,
&self.geometry_buffer,
viewport,
&material,
uniform_buffer_cache,
ElementRange::Specific {
offset: cmd.triangles.start,
count: cmd.triangles.end - cmd.triangles.start,
},
Some(&params),
)?;
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 solid_color = match cmd.brush {
Brush::Solid(color) => color,
_ => Color::WHITE,
};
let gradient_colors = match cmd.brush {
Brush::Solid(_) => &raw_colors,
Brush::LinearGradient { ref stops, .. }
| Brush::RadialGradient { ref stops, .. } => {
for (i, point) in stops.iter().enumerate() {
raw_colors[i] = point.color.as_frgba();
}
&raw_colors
}
};
let gradient_stops = match cmd.brush {
Brush::Solid(_) => &raw_stops,
Brush::LinearGradient { ref stops, .. }
| Brush::RadialGradient { ref stops, .. } => {
for (i, point) in stops.iter().enumerate() {
raw_stops[i] = point.stop;
}
&raw_stops
}
};
let brush_type = match cmd.brush {
Brush::Solid(_) => 0,
Brush::LinearGradient { .. } => 1,
Brush::RadialGradient { .. } => 2,
};
let gradient_point_count = match cmd.brush {
Brush::Solid(_) => 0,
Brush::LinearGradient { ref stops, .. }
| Brush::RadialGradient { ref stops, .. } => stops.len() as i32,
};
let properties = PropertyGroup::from([
property("worldViewProjection", &ortho),
property("solidColor", &solid_color),
property("gradientColors", gradient_colors.as_slice()),
property("gradientStops", gradient_stops.as_slice()),
property("gradientOrigin", &gradient_origin),
property("gradientEnd", &gradient_end),
property("resolution", &resolution),
property("boundsMin", &cmd.bounds.position),
property("boundsMax", &bounds_max),
property("isFont", &is_font_texture),
property("opacity", &cmd.opacity),
property("brushType", &brush_type),
property("gradientPointCount", &gradient_point_count),
]);
let material = RenderMaterial::from([
binding("diffuseTexture", diffuse_texture),
binding("properties", &properties),
]);
statistics += self.render_passes.run_pass(
1,
&ImmutableString::new("Primary"),
frame_buffer,
&self.geometry_buffer,
viewport,
&material,
uniform_buffer_cache,
element_range,
Some(&params),
)?;
}
}
Ok(statistics)
+10 -10
View File
@@ -132,7 +132,7 @@ pub struct Command {
pub brush: Brush,
pub texture: CommandTexture,
pub triangles: Range<usize>,
pub shader: Option<UntypedResource>,
pub material: Option<UntypedResource>,
pub opacity: f32,
/// A set of triangles that defines clipping region.
pub clipping_geometry: Option<ClippingGeometry>,
@@ -786,7 +786,7 @@ pub struct DrawingContext {
command_buffer: Vec<Command>,
pub transform_stack: TransformStack,
opacity_stack: Vec<f32>,
shader_stack: Vec<UntypedResource>,
material_stack: Vec<UntypedResource>,
triangles_to_commit: usize,
pub style: StyleResource,
/// Amount of time (in seconds) that passed from creation of the engine. Keep in mind, that
@@ -838,7 +838,7 @@ impl DrawingContext {
transform_stack: Default::default(),
style,
elapsed_time: 0.0,
shader_stack: Default::default(),
material_stack: Default::default(),
}
}
@@ -848,7 +848,7 @@ impl DrawingContext {
self.triangle_buffer.clear();
self.command_buffer.clear();
self.opacity_stack.clear();
self.shader_stack.clear();
self.material_stack.clear();
self.opacity_stack.push(1.0);
self.triangles_to_commit = 0;
}
@@ -876,12 +876,12 @@ impl DrawingContext {
self.opacity_stack.pop().unwrap();
}
pub fn push_shader(&mut self, shader: UntypedResource) {
self.shader_stack.push(shader)
pub fn push_material(&mut self, shader: UntypedResource) {
self.material_stack.push(shader)
}
pub fn pop_shader(&mut self) {
self.shader_stack.pop().unwrap();
pub fn pop_material(&mut self) {
self.material_stack.pop().unwrap();
}
pub fn triangle_points(
@@ -938,14 +938,14 @@ impl DrawingContext {
let bounds = self.bounds_of(triangles.clone());
let opacity = *self.opacity_stack.last().unwrap();
let shader = self.shader_stack.last().cloned();
let shader = self.material_stack.last().cloned();
self.command_buffer.push(Command {
clip_bounds,
bounds,
brush,
texture,
triangles,
shader,
material: shader,
opacity,
clipping_geometry,
});
+4 -4
View File
@@ -976,8 +976,8 @@ fn draw_node(
return;
}
if let Some(shader) = node.shader.as_ref() {
drawing_context.push_shader(shader.clone());
if let Some(shader) = node.material.as_ref() {
drawing_context.push_material(shader.clone());
}
let pushed = if !is_node_enabled(nodes, node_handle) {
@@ -1022,8 +1022,8 @@ fn draw_node(
drawing_context.transform_stack.pop();
if node.shader.as_ref().is_some() {
drawing_context.pop_shader();
if node.material.as_ref().is_some() {
drawing_context.pop_material();
}
if pushed {
+9 -9
View File
@@ -892,8 +892,8 @@ pub struct Widget {
/// handle to get a corresponding widget from resource.
#[reflect(read_only)]
pub resource: Option<Resource<UserInterface>>,
/// A shader, that should be used when rendering the widget.
pub shader: InheritableVariable<Option<UntypedResource>>,
/// A material, that should be used when rendering the widget.
pub material: InheritableVariable<Option<UntypedResource>>,
/// Handle to a widget in a user interface resource from which this node was instantiated from.
#[reflect(hidden)]
pub original_handle_in_resource: Handle<UiNode>,
@@ -1894,8 +1894,8 @@ pub struct WidgetBuilder {
pub tab_stop: bool,
/// A flag, that indicates that the widget accepts user input.
pub accepts_input: bool,
/// A shader that will be used for rendering.
pub shader: Option<UntypedResource>,
/// A material that will be used for rendering.
pub material: Option<UntypedResource>,
}
impl Default for WidgetBuilder {
@@ -1945,7 +1945,7 @@ impl WidgetBuilder {
tab_index: None,
tab_stop: false,
accepts_input: false,
shader: None,
material: None,
}
}
@@ -2208,9 +2208,9 @@ impl WidgetBuilder {
self
}
/// Sets a shader which will be used for rendering of this widget.
pub fn with_shader(mut self, shader: UntypedResource) -> Self {
self.shader = Some(shader);
/// Sets a material which will be used for rendering of this widget.
pub fn with_material(mut self, material: UntypedResource) -> Self {
self.material = Some(material);
self
}
@@ -2282,7 +2282,7 @@ impl WidgetBuilder {
id: self.id,
is_resource_instance_root: false,
resource: None,
shader: self.shader.into(),
material: self.material.into(),
original_handle_in_resource: Default::default(),
}
}