summaryrefslogtreecommitdiffstats
path: root/wgpu/src
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-04-05 00:40:39 +0200
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-04-05 00:40:39 +0200
commit394e599c3a096b036aabdd6f850c4a7c518d44fa (patch)
treebe925d4f26d7f1e30a289297ccf50ce968d6e86c /wgpu/src
parentcc05cb9be4a1de5f0427f93ce64e658be0703f8b (diff)
downloadiced-394e599c3a096b036aabdd6f850c4a7c518d44fa.tar.gz
iced-394e599c3a096b036aabdd6f850c4a7c518d44fa.tar.bz2
iced-394e599c3a096b036aabdd6f850c4a7c518d44fa.zip
Fix layer transformations
Diffstat (limited to 'wgpu/src')
-rw-r--r--wgpu/src/geometry.rs39
-rw-r--r--wgpu/src/layer.rs65
-rw-r--r--wgpu/src/lib.rs158
-rw-r--r--wgpu/src/text.rs36
-rw-r--r--wgpu/src/triangle.rs72
5 files changed, 206 insertions, 164 deletions
diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs
index d153c764..611e81f1 100644
--- a/wgpu/src/geometry.rs
+++ b/wgpu/src/geometry.rs
@@ -106,23 +106,28 @@ impl Frame {
.buffers
.stack
.into_iter()
- .map(|buffer| match buffer {
- Buffer::Solid(buffer) => Mesh::Solid {
- buffers: mesh::Indexed {
- vertices: buffer.vertices,
- indices: buffer.indices,
- },
- transformation: Transformation::IDENTITY,
- size: self.size,
- },
- Buffer::Gradient(buffer) => Mesh::Gradient {
- buffers: mesh::Indexed {
- vertices: buffer.vertices,
- indices: buffer.indices,
- },
- transformation: Transformation::IDENTITY,
- size: self.size,
- },
+ .filter_map(|buffer| match buffer {
+ Buffer::Solid(buffer) if !buffer.indices.is_empty() => {
+ Some(Mesh::Solid {
+ buffers: mesh::Indexed {
+ vertices: buffer.vertices,
+ indices: buffer.indices,
+ },
+ transformation: Transformation::IDENTITY,
+ size: self.size,
+ })
+ }
+ Buffer::Gradient(buffer) if !buffer.indices.is_empty() => {
+ Some(Mesh::Gradient {
+ buffers: mesh::Indexed {
+ vertices: buffer.vertices,
+ indices: buffer.indices,
+ },
+ transformation: Transformation::IDENTITY,
+ size: self.size,
+ })
+ }
+ _ => None,
})
.collect();
diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs
index 5c23669a..1c9638b0 100644
--- a/wgpu/src/layer.rs
+++ b/wgpu/src/layer.rs
@@ -13,17 +13,17 @@ use std::rc::Rc;
pub enum Layer<'a> {
Live(&'a Live),
- Cached(cell::Ref<'a, Cached>),
+ Cached(Transformation, cell::Ref<'a, Cached>),
}
pub enum LayerMut<'a> {
Live(&'a mut Live),
- Cached(cell::RefMut<'a, Cached>),
+ Cached(Transformation, cell::RefMut<'a, Cached>),
}
pub struct Stack {
live: Vec<Live>,
- cached: Vec<Rc<RefCell<Cached>>>,
+ cached: Vec<(Transformation, Rc<RefCell<Cached>>)>,
order: Vec<Kind>,
transformations: Vec<Transformation>,
previous: Vec<usize>,
@@ -92,7 +92,7 @@ impl Stack {
position,
color,
clip_bounds,
- transformation: self.transformations.last().copied().unwrap(),
+ transformation: self.transformation(),
};
self.live[self.current].text.push(paragraph);
@@ -178,36 +178,31 @@ impl Stack {
}
pub fn draw_cached_layer(&mut self, layer: &Rc<RefCell<Cached>>) {
- {
- let mut layer = layer.borrow_mut();
- layer.transformation = self.transformation() * layer.transformation;
- }
-
- self.cached.push(layer.clone());
+ self.cached.push((self.transformation(), layer.clone()));
self.order.push(Kind::Cache);
}
pub fn push_clip(&mut self, bounds: Option<Rectangle>) {
- self.previous.push(self.current);
- self.order.push(Kind::Live);
-
- self.current = self.live_count;
- self.live_count += 1;
-
- let bounds = bounds.map(|bounds| bounds * self.transformation());
-
- if self.current == self.live.len() {
- self.live.push(Live {
- bounds,
- ..Live::default()
- });
- } else {
- self.live[self.current].bounds = bounds;
- }
+ // self.previous.push(self.current);
+ // self.order.push(Kind::Live);
+
+ // self.current = self.live_count;
+ // self.live_count += 1;
+
+ // let bounds = bounds.map(|bounds| bounds * self.transformation());
+
+ // if self.current == self.live.len() {
+ // self.live.push(Live {
+ // bounds,
+ // ..Live::default()
+ // });
+ // } else {
+ // self.live[self.current].bounds = bounds;
+ // }
}
pub fn pop_clip(&mut self) {
- self.current = self.previous.pop().unwrap();
+ // self.current = self.previous.pop().unwrap();
}
pub fn push_transformation(&mut self, transformation: Transformation) {
@@ -224,13 +219,17 @@ impl Stack {
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = LayerMut<'_>> {
+ dbg!(self.order.len());
let mut live = self.live.iter_mut();
let mut cached = self.cached.iter_mut();
self.order.iter().map(move |kind| match kind {
Kind::Live => LayerMut::Live(live.next().unwrap()),
Kind::Cache => {
- LayerMut::Cached(cached.next().unwrap().borrow_mut())
+ let (transformation, layer) = cached.next().unwrap();
+ let layer = layer.borrow_mut();
+
+ LayerMut::Cached(*transformation * layer.transformation, layer)
}
})
}
@@ -241,7 +240,12 @@ impl Stack {
self.order.iter().map(move |kind| match kind {
Kind::Live => Layer::Live(live.next().unwrap()),
- Kind::Cache => Layer::Cached(cached.next().unwrap().borrow()),
+ Kind::Cache => {
+ let (transformation, layer) = cached.next().unwrap();
+ let layer = layer.borrow();
+
+ Layer::Cached(*transformation * layer.transformation, layer)
+ }
})
}
@@ -288,7 +292,6 @@ impl Live {
Cached {
bounds: self.bounds,
transformation: self.transformation,
- last_transformation: None,
quads: quad::Cache::Staged(self.quads),
meshes: triangle::Cache::Staged(self.meshes),
text: text::Cache::Staged(self.text),
@@ -301,7 +304,6 @@ impl Live {
pub struct Cached {
pub bounds: Option<Rectangle>,
pub transformation: Transformation,
- pub last_transformation: Option<Transformation>,
pub quads: quad::Cache,
pub meshes: triangle::Cache,
pub text: text::Cache,
@@ -311,7 +313,6 @@ pub struct Cached {
impl Cached {
pub fn update(&mut self, live: Live) {
self.bounds = live.bounds;
- self.transformation = live.transformation;
self.quads.update(live.quads);
self.meshes.update(live.meshes);
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index 2d023d8b..4705cfa0 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -116,32 +116,10 @@ impl Renderer {
viewport: &Viewport,
overlay: &[T],
) {
- let target_size = viewport.physical_size();
- let scale_factor = viewport.scale_factor() as f32;
- let transformation = viewport.projection();
-
self.draw_overlay(overlay, viewport);
- self.prepare(
- engine,
- device,
- queue,
- format,
- encoder,
- scale_factor,
- target_size,
- transformation,
- );
-
- self.render(
- engine,
- device,
- encoder,
- frame,
- clear_color,
- scale_factor,
- target_size,
- );
+ self.prepare(engine, device, queue, format, encoder, viewport);
+ self.render(engine, device, encoder, frame, clear_color, viewport);
}
fn prepare(
@@ -151,10 +129,10 @@ impl Renderer {
queue: &wgpu::Queue,
_format: wgpu::TextureFormat,
encoder: &mut wgpu::CommandEncoder,
- scale_factor: f32,
- target_size: Size<u32>,
- transformation: Transformation,
+ viewport: &Viewport,
) {
+ let scale_factor = viewport.scale_factor() as f32;
+
for layer in self.layers.iter_mut() {
match layer {
LayerMut::Live(live) => {
@@ -164,7 +142,7 @@ impl Renderer {
encoder,
&mut engine.staging_belt,
&live.quads,
- transformation,
+ viewport.projection(),
scale_factor,
);
}
@@ -175,7 +153,7 @@ impl Renderer {
encoder,
&mut engine.staging_belt,
&live.meshes,
- transformation
+ viewport.projection()
* Transformation::scale(scale_factor),
);
}
@@ -187,10 +165,11 @@ impl Renderer {
encoder,
&live.text,
live.bounds.unwrap_or(Rectangle::with_size(
- Size::INFINITY,
+ viewport.logical_size(),
)),
- scale_factor,
- target_size,
+ live.transformation
+ * Transformation::scale(scale_factor),
+ viewport.physical_size(),
);
}
@@ -201,38 +180,46 @@ impl Renderer {
encoder,
&mut engine.staging_belt,
&live.images,
- transformation,
+ viewport.projection(),
scale_factor,
);
}
}
- LayerMut::Cached(mut cached) => {
+ LayerMut::Cached(layer_transformation, mut cached) => {
if !cached.quads.is_empty() {
engine.quad_pipeline.prepare_cache(
device,
encoder,
&mut engine.staging_belt,
&mut cached.quads,
- transformation,
+ viewport.projection(),
scale_factor,
);
}
if !cached.meshes.is_empty() {
+ let transformation =
+ Transformation::scale(scale_factor)
+ * layer_transformation;
+
engine.triangle_pipeline.prepare_cache(
device,
encoder,
&mut engine.staging_belt,
&mut cached.meshes,
- transformation
- * Transformation::scale(scale_factor),
+ viewport.projection(),
+ transformation,
);
}
if !cached.text.is_empty() {
- let bounds = cached
- .bounds
- .unwrap_or(Rectangle::with_size(Size::INFINITY));
+ let bounds = cached.bounds.unwrap_or(
+ Rectangle::with_size(viewport.logical_size()),
+ );
+
+ let transformation =
+ Transformation::scale(scale_factor)
+ * layer_transformation;
engine.text_pipeline.prepare_cache(
device,
@@ -240,8 +227,8 @@ impl Renderer {
encoder,
&mut cached.text,
bounds,
- scale_factor,
- target_size,
+ transformation,
+ viewport.physical_size(),
);
}
@@ -252,7 +239,7 @@ impl Renderer {
encoder,
&mut engine.staging_belt,
&cached.images,
- transformation,
+ viewport.projection(),
scale_factor,
);
}
@@ -268,8 +255,7 @@ impl Renderer {
encoder: &mut wgpu::CommandEncoder,
frame: &wgpu::TextureView,
clear_color: Option<Color>,
- scale_factor: f32,
- target_size: Size<u32>,
+ viewport: &Viewport,
) {
use std::mem::ManuallyDrop;
@@ -312,22 +298,37 @@ impl Renderer {
let mut image_layer = 0;
// TODO: Can we avoid collecting here?
+ let scale_factor = viewport.scale_factor() as f32;
+ let screen_bounds = Rectangle::with_size(viewport.logical_size());
+ let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
+ viewport.physical_size(),
+ ));
+
let layers: Vec<_> = self.layers.iter().collect();
let mut i = 0;
+ // println!("RENDER");
+
while i < layers.len() {
match layers[i] {
Layer::Live(live) => {
- let bounds = live
- .bounds
- .map(|bounds| bounds * scale_factor)
+ let layer_transformation =
+ Transformation::scale(scale_factor)
+ * live.transformation;
+
+ let layer_bounds = live.bounds.unwrap_or(screen_bounds);
+
+ let Some(physical_bounds) = physical_bounds
+ .intersection(&(layer_bounds * layer_transformation))
.map(Rectangle::snap)
- .unwrap_or(Rectangle::with_size(target_size));
+ else {
+ continue;
+ };
if !live.quads.is_empty() {
engine.quad_pipeline.render_batch(
quad_layer,
- bounds,
+ physical_bounds,
&live.quads,
&mut render_pass,
);
@@ -336,6 +337,7 @@ impl Renderer {
}
if !live.meshes.is_empty() {
+ // println!("LIVE PASS");
let _ = ManuallyDrop::into_inner(render_pass);
engine.triangle_pipeline.render_batch(
@@ -343,10 +345,10 @@ impl Renderer {
encoder,
frame,
mesh_layer,
- target_size,
+ viewport.physical_size(),
&live.meshes,
- bounds,
- scale_factor,
+ physical_bounds,
+ &layer_transformation,
);
mesh_layer += 1;
@@ -375,7 +377,7 @@ impl Renderer {
if !live.text.is_empty() {
engine.text_pipeline.render_batch(
text_layer,
- bounds,
+ physical_bounds,
&mut render_pass,
);
@@ -386,7 +388,7 @@ impl Renderer {
if !live.images.is_empty() {
engine.image_pipeline.render(
image_layer,
- bounds,
+ physical_bounds,
&mut render_pass,
);
@@ -395,25 +397,35 @@ impl Renderer {
i += 1;
}
- Layer::Cached(_) => {
+ Layer::Cached(_, _) => {
let group_len = layers[i..]
.iter()
.position(|layer| matches!(layer, Layer::Live(_)))
- .unwrap_or(layers.len());
-
- let group = layers[i..i + group_len].iter().map(|layer| {
- let Layer::Cached(cached) = layer else {
- unreachable!()
- };
-
- let bounds = cached
- .bounds
- .map(|bounds| bounds * scale_factor)
- .map(Rectangle::snap)
- .unwrap_or(Rectangle::with_size(target_size));
-
- (cached, bounds)
- });
+ .unwrap_or(layers.len() - i);
+
+ let group =
+ layers[i..i + group_len].iter().filter_map(|layer| {
+ let Layer::Cached(transformation, cached) = layer
+ else {
+ unreachable!()
+ };
+
+ let physical_bounds = cached
+ .bounds
+ .and_then(|bounds| {
+ physical_bounds.intersection(
+ &(bounds
+ * *transformation
+ * Transformation::scale(
+ scale_factor,
+ )),
+ )
+ })
+ .unwrap_or(physical_bounds)
+ .snap();
+
+ Some((cached, physical_bounds))
+ });
for (cached, bounds) in group.clone() {
if !cached.quads.is_empty() {
@@ -430,17 +442,17 @@ impl Renderer {
.any(|(cached, _)| !cached.meshes.is_empty());
if group_has_meshes {
+ // println!("CACHE PASS");
let _ = ManuallyDrop::into_inner(render_pass);
engine.triangle_pipeline.render_cache_group(
device,
encoder,
frame,
- target_size,
+ viewport.physical_size(),
group.clone().map(|(cached, bounds)| {
(&cached.meshes, bounds)
}),
- scale_factor,
);
render_pass =
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs
index 016ac92a..e0a5355c 100644
--- a/wgpu/src/text.rs
+++ b/wgpu/src/text.rs
@@ -26,7 +26,7 @@ pub enum Cache {
renderer: glyphon::TextRenderer,
atlas: Option<glyphon::TextAtlas>,
buffer_cache: Option<BufferCache>,
- scale_factor: f32,
+ transformation: Transformation,
target_size: Size<u32>,
needs_reupload: bool,
},
@@ -95,7 +95,7 @@ impl Pipeline {
encoder: &mut wgpu::CommandEncoder,
sections: &Batch,
layer_bounds: Rectangle,
- scale_factor: f32,
+ layer_transformation: Transformation,
target_size: Size<u32>,
) {
if self.renderers.len() <= self.prepare_layer {
@@ -117,7 +117,7 @@ impl Pipeline {
&mut self.cache,
sections,
layer_bounds,
- scale_factor,
+ layer_transformation,
target_size,
);
@@ -140,7 +140,7 @@ impl Pipeline {
encoder: &mut wgpu::CommandEncoder,
cache: &mut Cache,
layer_bounds: Rectangle,
- new_scale_factor: f32,
+ new_transformation: Transformation,
new_target_size: Size<u32>,
) {
match cache {
@@ -186,7 +186,7 @@ impl Pipeline {
buffer_cache.as_mut().unwrap_or(&mut self.cache),
&batch,
layer_bounds,
- new_scale_factor,
+ new_transformation,
new_target_size,
);
@@ -196,7 +196,7 @@ impl Pipeline {
renderer,
atlas,
buffer_cache,
- scale_factor: new_scale_factor,
+ transformation: new_transformation,
target_size: new_target_size,
}
}
@@ -206,13 +206,13 @@ impl Pipeline {
renderer,
atlas,
buffer_cache,
- scale_factor,
+ transformation,
target_size,
} => {
if *needs_reupload
|| atlas.is_none()
|| buffer_cache.is_none()
- || new_scale_factor != *scale_factor
+ || new_transformation != *transformation
|| new_target_size != *target_size
{
let _ = prepare(
@@ -224,11 +224,11 @@ impl Pipeline {
buffer_cache.as_mut().unwrap_or(&mut self.cache),
batch,
layer_bounds,
- *scale_factor,
- *target_size,
+ new_transformation,
+ new_target_size,
);
- *scale_factor = new_scale_factor;
+ *transformation = new_transformation;
*target_size = new_target_size;
}
}
@@ -297,7 +297,7 @@ fn prepare(
buffer_cache: &mut BufferCache,
sections: &Batch,
layer_bounds: Rectangle,
- scale_factor: f32,
+ layer_transformation: Transformation,
target_size: Size<u32>,
) -> Result<(), glyphon::PrepareError> {
let mut font_system = font_system().write().expect("Write font system");
@@ -349,7 +349,7 @@ fn prepare(
})
.collect();
- let layer_bounds = layer_bounds * scale_factor;
+ let layer_bounds = layer_bounds * layer_transformation;
let text_areas = sections.iter().zip(allocations.iter()).filter_map(
|(section, allocation)| {
@@ -456,7 +456,7 @@ fn prepare(
}
};
- let bounds = bounds * transformation * scale_factor;
+ let bounds = bounds * transformation * layer_transformation;
let left = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
@@ -470,14 +470,16 @@ fn prepare(
alignment::Vertical::Bottom => bounds.y - bounds.height,
};
- let clip_bounds = layer_bounds
- .intersection(&(clip_bounds * transformation * scale_factor))?;
+ let clip_bounds = layer_bounds.intersection(
+ &(clip_bounds * transformation * layer_transformation),
+ )?;
Some(glyphon::TextArea {
buffer,
left,
top,
- scale: scale_factor * transformation.scale_factor(),
+ scale: transformation.scale_factor()
+ * layer_transformation.scale_factor(),
bounds: glyphon::TextBounds {
left: clip_bounds.x as i32,
top: clip_bounds.y as i32,
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index be7bb867..3a184da2 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -68,8 +68,11 @@ impl Pipeline {
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
cache: &mut Cache,
- transformation: Transformation,
+ new_projection: Transformation,
+ new_transformation: Transformation,
) {
+ let new_projection = new_projection * new_transformation;
+
match cache {
Cache::Staged(_) => {
let Cache::Staged(batch) =
@@ -86,21 +89,25 @@ impl Pipeline {
&self.solid,
&self.gradient,
&batch,
- transformation,
+ new_projection,
);
*cache = Cache::Uploaded {
layer,
batch,
+ transformation: new_transformation,
+ projection: new_projection,
needs_reupload: false,
}
}
Cache::Uploaded {
batch,
layer,
+ transformation,
+ projection,
needs_reupload,
} => {
- if *needs_reupload {
+ if *needs_reupload || new_projection != *projection {
layer.prepare(
device,
encoder,
@@ -108,9 +115,11 @@ impl Pipeline {
&self.solid,
&self.gradient,
batch,
- transformation,
+ new_projection,
);
+ *transformation = new_transformation;
+ *projection = new_projection;
*needs_reupload = false;
}
}
@@ -126,7 +135,7 @@ impl Pipeline {
target_size: Size<u32>,
meshes: &Batch,
bounds: Rectangle<u32>,
- scale_factor: f32,
+ transformation: &Transformation,
) {
Self::render(
device,
@@ -136,8 +145,12 @@ impl Pipeline {
&self.solid,
&self.gradient,
target_size,
- std::iter::once((&self.layers[layer], meshes, bounds)),
- scale_factor,
+ std::iter::once((
+ &self.layers[layer],
+ meshes,
+ transformation,
+ bounds,
+ )),
);
}
@@ -150,9 +163,14 @@ impl Pipeline {
target_size: Size<u32>,
cache: &Cache,
bounds: Rectangle<u32>,
- scale_factor: f32,
) {
- let Cache::Uploaded { batch, layer, .. } = cache else {
+ let Cache::Uploaded {
+ batch,
+ layer,
+ transformation,
+ ..
+ } = cache
+ else {
return;
};
@@ -164,8 +182,7 @@ impl Pipeline {
&self.solid,
&self.gradient,
target_size,
- std::iter::once((layer, batch, bounds)),
- scale_factor,
+ std::iter::once((layer, batch, transformation, bounds)),
);
}
@@ -176,11 +193,16 @@ impl Pipeline {
target: &wgpu::TextureView,
target_size: Size<u32>,
group: impl Iterator<Item = (&'a Cache, Rectangle<u32>)>,
- scale_factor: f32,
) {
let group = group.filter_map(|(cache, bounds)| {
- if let Cache::Uploaded { batch, layer, .. } = cache {
- Some((layer, batch, bounds))
+ if let Cache::Uploaded {
+ batch,
+ layer,
+ transformation,
+ ..
+ } = cache
+ {
+ Some((layer, batch, transformation, bounds))
} else {
None
}
@@ -195,7 +217,6 @@ impl Pipeline {
&self.gradient,
target_size,
group,
- scale_factor,
);
}
@@ -207,8 +228,9 @@ impl Pipeline {
solid: &solid::Pipeline,
gradient: &gradient::Pipeline,
target_size: Size<u32>,
- group: impl Iterator<Item = (&'a Layer, &'a Batch, Rectangle<u32>)>,
- scale_factor: f32,
+ group: impl Iterator<
+ Item = (&'a Layer, &'a Batch, &'a Transformation, Rectangle<u32>),
+ >,
) {
{
let (attachment, resolve_target, load) = if let Some(blit) =
@@ -244,13 +266,13 @@ impl Pipeline {
occlusion_query_set: None,
});
- for (layer, meshes, bounds) in group {
+ for (layer, meshes, transformation, bounds) in group {
layer.render(
solid,
gradient,
meshes,
bounds,
- scale_factor,
+ *transformation,
&mut render_pass,
);
}
@@ -272,6 +294,8 @@ pub enum Cache {
Uploaded {
batch: Batch,
layer: Layer,
+ transformation: Transformation,
+ projection: Transformation,
needs_reupload: bool,
},
}
@@ -390,6 +414,7 @@ impl Layer {
for mesh in meshes {
let indices = mesh.indices();
+
let uniforms =
Uniforms::new(transformation * mesh.transformation());
@@ -448,7 +473,7 @@ impl Layer {
gradient: &'a gradient::Pipeline,
meshes: &Batch,
layer_bounds: Rectangle<u32>,
- scale_factor: f32,
+ transformation: Transformation,
render_pass: &mut wgpu::RenderPass<'a>,
) {
let mut num_solids = 0;
@@ -457,15 +482,12 @@ impl Layer {
for (index, mesh) in meshes.iter().enumerate() {
let Some(clip_bounds) = Rectangle::<f32>::from(layer_bounds)
- .intersection(&(mesh.clip_bounds() * scale_factor))
- .map(Rectangle::snap)
+ .intersection(&(mesh.clip_bounds() * transformation))
else {
continue;
};
- if clip_bounds.width < 1 || clip_bounds.height < 1 {
- continue;
- }
+ let clip_bounds = clip_bounds.snap();
render_pass.set_scissor_rect(
clip_bounds.x,