Skip to content

Commit 9afe666

Browse files
ickshonpemockersf
authored andcommitted
UI texture atlas slice shader (#14990)
Fixes #14183 Reimplement the UI texture atlas slicer using a shader. The problems with #14183 could be fixed more simply by hacking around with the coordinates and scaling but that way is very fragile and might get broken again the next time we make changes to the layout calculations. A shader based solution is more robust, it's impossible for gaps to appear between the image slices with these changes as we're only drawing a single quad. I've not tried any benchmarks yet but it should much more efficient as well, in the worst cases even hundreds or thousands of times faster. Maybe could have used the UiMaterialPipeline. I wrote the shader first and used fat vertices and then realised it wouldn't work that way with a UiMaterial. If it's rewritten it so it puts all the slice geometry in uniform buffer, then it might work? Adding the uniform buffer would probably make the shader more complicated though, so don't know if it's even worth it. Instancing is another alternative. The examples are working and it seems to match the old API correctly but I've not used the texture atlas slicing API for anything before, I reviewed the PR but that was back in January. Needs a review by someone who knows the rendering pipeline and wgsl really well because I don't really have any idea what I'm doing.
1 parent 489c1e1 commit 9afe666

File tree

5 files changed

+908
-252
lines changed

5 files changed

+908
-252
lines changed

crates/bevy_ui/src/lib.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ mod geometry;
2626
mod layout;
2727
mod render;
2828
mod stack;
29-
mod texture_slice;
3029
mod ui_node;
3130

3231
pub use focus::*;
@@ -163,11 +162,6 @@ impl Plugin for UiPlugin {
163162
.before(UiSystem::Layout)
164163
.in_set(AmbiguousWithTextSystem)
165164
.in_set(AmbiguousWithUpdateText2DLayout),
166-
(
167-
texture_slice::compute_slices_on_asset_event,
168-
texture_slice::compute_slices_on_image_change,
169-
)
170-
.after(UiSystem::Layout),
171165
),
172166
);
173167

crates/bevy_ui/src/render/mod.rs

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod pipeline;
22
mod render_pass;
33
mod ui_material_pipeline;
4+
pub mod ui_texture_slice_pipeline;
45

56
use bevy_color::{Alpha, ColorToComponents, LinearRgba};
67
use bevy_core_pipeline::core_2d::graph::{Core2d, Node2d};
@@ -14,16 +15,16 @@ use bevy_render::{
1415
view::ViewVisibility,
1516
ExtractSchedule, Render,
1617
};
17-
use bevy_sprite::{SpriteAssetEvents, TextureAtlas};
18+
use bevy_sprite::{ImageScaleMode, SpriteAssetEvents, TextureAtlas};
1819
pub use pipeline::*;
1920
pub use render_pass::*;
2021
pub use ui_material_pipeline::*;
22+
use ui_texture_slice_pipeline::UiTextureSlicerPlugin;
2123

2224
use crate::graph::{NodeUi, SubGraphUi};
2325
use crate::{
24-
texture_slice::ComputedTextureSlices, BackgroundColor, BorderColor, BorderRadius,
25-
CalculatedClip, ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiImage,
26-
UiScale, Val,
26+
BackgroundColor, BorderColor, BorderRadius, CalculatedClip, ContentSize, DefaultUiCamera, Node,
27+
Outline, Style, TargetCamera, UiImage, UiScale, Val,
2728
};
2829

2930
use bevy_app::prelude::*;
@@ -139,6 +140,8 @@ pub fn build_ui_render(app: &mut App) {
139140
graph_3d.add_node_edge(Node3d::EndMainPassPostProcessing, NodeUi::UiPass);
140141
graph_3d.add_node_edge(NodeUi::UiPass, Node3d::Upscaling);
141142
}
143+
144+
app.add_plugins(UiTextureSlicerPlugin);
142145
}
143146

144147
fn get_ui_graph(render_app: &mut SubApp) -> RenderGraph {
@@ -298,19 +301,21 @@ pub fn extract_uinode_images(
298301
ui_scale: Extract<Res<UiScale>>,
299302
default_ui_camera: Extract<DefaultUiCamera>,
300303
uinode_query: Extract<
301-
Query<(
302-
&Node,
303-
&GlobalTransform,
304-
&ViewVisibility,
305-
Option<&CalculatedClip>,
306-
Option<&TargetCamera>,
307-
&UiImage,
308-
Option<&TextureAtlas>,
309-
Option<&ComputedTextureSlices>,
310-
Option<&BorderRadius>,
311-
Option<&Parent>,
312-
&Style,
313-
)>,
304+
Query<
305+
(
306+
&Node,
307+
&GlobalTransform,
308+
&ViewVisibility,
309+
Option<&CalculatedClip>,
310+
Option<&TargetCamera>,
311+
&UiImage,
312+
Option<&TextureAtlas>,
313+
Option<&BorderRadius>,
314+
Option<&Parent>,
315+
&Style,
316+
),
317+
Without<ImageScaleMode>,
318+
>,
314319
>,
315320
node_query: Extract<Query<&Node>>,
316321
) {
@@ -322,7 +327,6 @@ pub fn extract_uinode_images(
322327
camera,
323328
image,
324329
atlas,
325-
slices,
326330
border_radius,
327331
parent,
328332
style,
@@ -338,15 +342,6 @@ pub fn extract_uinode_images(
338342
continue;
339343
}
340344

341-
if let Some(slices) = slices {
342-
extracted_uinodes.uinodes.extend(
343-
slices
344-
.extract_ui_nodes(transform, uinode, image, clip, camera_entity)
345-
.map(|e| (commands.spawn_empty().id(), e)),
346-
);
347-
continue;
348-
}
349-
350345
let (rect, atlas_size) = match atlas {
351346
Some(atlas) => {
352347
let Some(layout) = texture_atlases.get(&atlas.layout) else {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#import bevy_render::view::View;
2+
#import bevy_render::globals::Globals;
3+
4+
@group(0) @binding(0)
5+
var<uniform> view: View;
6+
@group(0) @binding(1)
7+
var<uniform> globals: Globals;
8+
9+
@group(1) @binding(0) var sprite_texture: texture_2d<f32>;
10+
@group(1) @binding(1) var sprite_sampler: sampler;
11+
12+
struct UiVertexOutput {
13+
@location(0) uv: vec2<f32>,
14+
@location(1) color: vec4<f32>,
15+
16+
// Defines the dividing line that are used to split the texture atlas rect into corner, side and center slices
17+
// The distances are normalized and from the top left corner of the texture atlas rect
18+
// x = distance of the left vertical dividing line
19+
// y = distance of the top horizontal dividing line
20+
// z = distance of the right vertical dividing line
21+
// w = distance of the bottom horizontal dividing line
22+
@location(2) @interpolate(flat) texture_slices: vec4<f32>,
23+
24+
// Defines the dividing line that are used to split the render target into into corner, side and center slices
25+
// The distances are normalized and from the top left corner of the render target
26+
// x = distance of left vertical dividing line
27+
// y = distance of top horizontal dividing line
28+
// z = distance of right vertical dividing line
29+
// w = distance of bottom horizontal dividing line
30+
@location(3) @interpolate(flat) target_slices: vec4<f32>,
31+
32+
// The number of times the side or center texture slices should be repeated when mapping them to the border slices
33+
// x = number of times to repeat along the horizontal axis for the side textures
34+
// y = number of times to repeat along the vertical axis for the side textures
35+
// z = number of times to repeat along the horizontal axis for the center texture
36+
// w = number of times to repeat along the vertical axis for the center texture
37+
@location(4) @interpolate(flat) repeat: vec4<f32>,
38+
39+
// normalized texture atlas rect coordinates
40+
// x, y = top, left corner of the atlas rect
41+
// z, w = bottom, right corner of the atlas rect
42+
@location(5) @interpolate(flat) atlas_rect: vec4<f32>,
43+
@builtin(position) position: vec4<f32>,
44+
}
45+
46+
@vertex
47+
fn vertex(
48+
@location(0) vertex_position: vec3<f32>,
49+
@location(1) vertex_uv: vec2<f32>,
50+
@location(2) vertex_color: vec4<f32>,
51+
@location(3) texture_slices: vec4<f32>,
52+
@location(4) target_slices: vec4<f32>,
53+
@location(5) repeat: vec4<f32>,
54+
@location(6) atlas_rect: vec4<f32>,
55+
) -> UiVertexOutput {
56+
var out: UiVertexOutput;
57+
out.uv = vertex_uv;
58+
out.color = vertex_color;
59+
out.position = view.clip_from_world * vec4<f32>(vertex_position, 1.0);
60+
out.texture_slices = texture_slices;
61+
out.target_slices = target_slices;
62+
out.repeat = repeat;
63+
out.atlas_rect = atlas_rect;
64+
return out;
65+
}
66+
67+
/// maps a point along the axis of the render target to slice coordinates
68+
fn map_axis_with_repeat(
69+
// normalized distance along the axis
70+
p: f32,
71+
// target min dividing point
72+
il: f32,
73+
// target max dividing point
74+
ih: f32,
75+
// slice min dividing point
76+
tl: f32,
77+
// slice max dividing point
78+
th: f32,
79+
// number of times to repeat the slice for sides and the center
80+
r: f32,
81+
) -> f32 {
82+
if p < il {
83+
// inside one of the two left (horizontal axis) or top (vertical axis) corners
84+
return (p / il) * tl;
85+
} else if ih < p {
86+
// inside one of the two (horizontal axis) or top (vertical axis) corners
87+
return th + ((p - ih) / (1 - ih)) * (1 - th);
88+
} else {
89+
// not inside a corner, repeat the texture
90+
return tl + fract((r * (p - il)) / (ih - il)) * (th - tl);
91+
}
92+
}
93+
94+
fn map_uvs_to_slice(
95+
uv: vec2<f32>,
96+
target_slices: vec4<f32>,
97+
texture_slices: vec4<f32>,
98+
repeat: vec4<f32>,
99+
) -> vec2<f32> {
100+
var r: vec2<f32>;
101+
if target_slices.x <= uv.x && uv.x <= target_slices.z && target_slices.y <= uv.y && uv.y <= target_slices.w {
102+
// use the center repeat values if the uv coords are inside the center slice of the target
103+
r = repeat.zw;
104+
} else {
105+
// use the side repeat values if the uv coords are outside the center slice
106+
r = repeat.xy;
107+
}
108+
109+
// map horizontal axis
110+
let x = map_axis_with_repeat(uv.x, target_slices.x, target_slices.z, texture_slices.x, texture_slices.z, r.x);
111+
112+
// map vertical axis
113+
let y = map_axis_with_repeat(uv.y, target_slices.y, target_slices.w, texture_slices.y, texture_slices.w, r.y);
114+
115+
return vec2(x, y);
116+
}
117+
118+
@fragment
119+
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
120+
// map the target uvs to slice coords
121+
let uv = map_uvs_to_slice(in.uv, in.target_slices, in.texture_slices, in.repeat);
122+
123+
// map the slice coords to texture coords
124+
let atlas_uv = in.atlas_rect.xy + uv * (in.atlas_rect.zw - in.atlas_rect.xy);
125+
126+
return in.color * textureSample(sprite_texture, sprite_sampler, atlas_uv);
127+
}

0 commit comments

Comments
 (0)