piet_wgpu/
lib.rs

1// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0
2// This file is a part of `piet-hardware`.
3//
4// `piet-hardware` is free software: you can redistribute it and/or modify it under the
5// terms of either:
6//
7// * GNU Lesser General Public License as published by the Free Software Foundation, either
8//   version 3 of the License, or (at your option) any later version.
9// * Mozilla Public License as published by the Mozilla Foundation, version 2.
10//
11// `piet-hardware` is distributed in the hope that it will be useful, but WITHOUT ANY
12// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
13// PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more
14// details.
15//
16// You should have received a copy of the GNU Lesser General Public License and the Mozilla
17// Public License along with `piet-hardware`. If not, see <https://www.gnu.org/licenses/>.
18
19//! A GPU-accelerated 2D graphics backend for [`piet`] that uses the [`wgpu`] crate.
20//!
21//! This crate follows the [`wgpu` middleware pattern], but in a somewhat unique way.
22//!
23//! - The user creates the [`WgpuContext`] by calling `new()` with a device and expected texture
24//!   format.
25//! - Before rendering, the user creates a [`RenderContext`] by calling `prepare()` on the
26//!   [`WgpuContext`] with a [`Device`] and a [`Queue`]. `prepare()` returns the context,
27//!   which is expected to be written to.
28//! - Finally, by calling `render` on the [`WgpuContext`], the user renders all of the material
29//!   that was written to the [`RenderContext`] using the [`piet`] API.
30//!
31//! [`piet`]: https://crates.io/crates/piet
32//! [`wgpu`]: https://crates.io/crates/wgpu
33//! [`wgpu` middleware pattern]: https://github.com/gfx-rs/wgpu/wiki/Encapsulating-Graphics-Work
34//! [`WgpuContext`]: struct.WgpuContext.html
35//! [`RenderContext`]: struct.RenderContext.html
36//! [`Device`]: wgpu::Device
37//! [`Queue`]: wgpu::Queue
38
39#![forbid(unsafe_code, rust_2018_idioms)]
40
41pub use piet_hardware::piet;
42pub use wgpu;
43
44use piet_hardware::piet::kurbo::Affine;
45use piet_hardware::piet::{Color, Error as Pierror, ImageFormat, InterpolationMode};
46
47mod buffer;
48mod context;
49mod texture;
50
51use context::GpuContext;
52
53/// A wrapper around internal cached state.
54#[derive(Debug)]
55pub struct WgpuContext {
56    /// The internal context.
57    source: piet_hardware::Source<GpuContext>,
58
59    /// The text.
60    text: Text,
61}
62
63impl WgpuContext {
64    /// Create a new [`wgpu`]-based drawing context.
65    pub fn new(
66        device: &wgpu::Device,
67        queue: &wgpu::Queue,
68        format: wgpu::TextureFormat,
69        depth_format: Option<wgpu::TextureFormat>,
70        samples: u32,
71    ) -> Self {
72        let source = piet_hardware::Source::new(
73            GpuContext::new(device, queue, format, depth_format, samples),
74            device,
75            queue,
76        )
77        .expect("failed to create GPU context");
78        let text = Text(source.text().clone());
79        Self { source, text }
80    }
81
82    /// Prepare rendering by drawing to a [`RenderContext`].
83    ///
84    /// After this method is called, drawing is expected to be done to the returned context.
85    pub fn prepare<'this, 'dev, 'que>(
86        &'this mut self,
87        device: &'dev wgpu::Device,
88        queue: &'que wgpu::Queue,
89        width: u32,
90        height: u32,
91    ) -> RenderContext<'this, 'dev, 'que> {
92        RenderContext {
93            context: self.source.render_context(device, queue, width, height),
94            text: &mut self.text,
95        }
96    }
97
98    /// Render the contents of the [`RenderContext`] to the provided render pass.
99    pub fn render<'this>(&'this self, pass: &mut wgpu::RenderPass<'this>) {
100        self.source.context().render(pass);
101    }
102
103    /// Call this function after you call `wgpu::Queue::submit` to free up resources.
104    pub fn after_submit(&mut self, device: &wgpu::Device) {
105        self.source.gpu_flushed();
106        self.source.context_mut().gpu_flushed(device);
107    }
108}
109
110/// The whole point.
111#[derive(Debug)]
112pub struct RenderContext<'context, 'device, 'queue> {
113    context: piet_hardware::RenderContext<'context, 'device, 'queue, GpuContext>,
114    text: &'context mut Text,
115}
116
117impl RenderContext<'_, '_, '_> {
118    /// Get the flattening tolerance.
119    #[inline]
120    pub fn tolerance(&self) -> f64 {
121        self.context.tolerance()
122    }
123
124    /// Set the flattening tolerance.
125    #[inline]
126    pub fn set_tolerance(&mut self, tolerance: f64) {
127        self.context.set_tolerance(tolerance)
128    }
129
130    /// Get the bitmap scale.
131    #[inline]
132    pub fn bitmap_scale(&self) -> f64 {
133        self.context.bitmap_scale()
134    }
135
136    /// Set the bitmap scale.
137    #[inline]
138    pub fn set_bitmap_scale(&mut self, scale: f64) {
139        self.context.set_bitmap_scale(scale)
140    }
141}
142
143impl piet::RenderContext for RenderContext<'_, '_, '_> {
144    type Brush = Brush;
145    type Image = Image;
146    type Text = Text;
147    type TextLayout = TextLayout;
148
149    fn blurred_rect(
150        &mut self,
151        rect: piet::kurbo::Rect,
152        blur_radius: f64,
153        brush: &impl piet::IntoBrush<Self>,
154    ) {
155        let brush = brush.make_brush(self, || rect);
156        self.context
157            .blurred_rect(rect, blur_radius, &brush.as_ref().0)
158    }
159
160    fn capture_image_area(
161        &mut self,
162        src_rect: impl Into<piet::kurbo::Rect>,
163    ) -> Result<Self::Image, Pierror> {
164        self.context.capture_image_area(src_rect).map(Image)
165    }
166
167    fn clear(&mut self, region: impl Into<Option<piet::kurbo::Rect>>, color: Color) {
168        self.context.clear(region, color)
169    }
170
171    fn clip(&mut self, shape: impl piet::kurbo::Shape) {
172        self.context.clip(shape)
173    }
174
175    fn current_transform(&self) -> Affine {
176        self.context.current_transform()
177    }
178
179    fn draw_image(
180        &mut self,
181        image: &Self::Image,
182        dst_rect: impl Into<piet::kurbo::Rect>,
183        interp: InterpolationMode,
184    ) {
185        self.context.draw_image(&image.0, dst_rect, interp)
186    }
187
188    fn draw_image_area(
189        &mut self,
190        image: &Self::Image,
191        src_rect: impl Into<piet::kurbo::Rect>,
192        dst_rect: impl Into<piet::kurbo::Rect>,
193        interp: InterpolationMode,
194    ) {
195        self.context
196            .draw_image_area(&image.0, src_rect, dst_rect, interp)
197    }
198
199    fn draw_text(&mut self, layout: &Self::TextLayout, pos: impl Into<piet::kurbo::Point>) {
200        self.context.draw_text(&layout.0, pos)
201    }
202
203    fn fill(&mut self, shape: impl piet::kurbo::Shape, brush: &impl piet::IntoBrush<Self>) {
204        let brush = brush.make_brush(self, || shape.bounding_box());
205        self.context.fill(shape, &brush.as_ref().0)
206    }
207
208    fn fill_even_odd(
209        &mut self,
210        shape: impl piet::kurbo::Shape,
211        brush: &impl piet::IntoBrush<Self>,
212    ) {
213        let brush = brush.make_brush(self, || shape.bounding_box());
214        self.context.fill_even_odd(shape, &brush.as_ref().0)
215    }
216
217    fn finish(&mut self) -> Result<(), Pierror> {
218        self.context.finish()
219    }
220
221    fn gradient(
222        &mut self,
223        gradient: impl Into<piet::FixedGradient>,
224    ) -> Result<Self::Brush, Pierror> {
225        self.context.gradient(gradient).map(Brush)
226    }
227
228    fn make_image(
229        &mut self,
230        width: usize,
231        height: usize,
232        buf: &[u8],
233        format: ImageFormat,
234    ) -> Result<Self::Image, Pierror> {
235        self.context
236            .make_image(width, height, buf, format)
237            .map(Image)
238    }
239
240    fn restore(&mut self) -> Result<(), Pierror> {
241        self.context.restore()
242    }
243
244    fn save(&mut self) -> Result<(), Pierror> {
245        self.context.save()
246    }
247
248    fn solid_brush(&mut self, color: Color) -> Self::Brush {
249        Brush(self.context.solid_brush(color))
250    }
251
252    fn status(&mut self) -> Result<(), Pierror> {
253        self.context.status()
254    }
255
256    fn stroke(
257        &mut self,
258        shape: impl piet::kurbo::Shape,
259        brush: &impl piet::IntoBrush<Self>,
260        width: f64,
261    ) {
262        let brush = brush.make_brush(self, || shape.bounding_box());
263        self.context.stroke(shape, &brush.as_ref().0, width)
264    }
265
266    fn stroke_styled(
267        &mut self,
268        shape: impl piet::kurbo::Shape,
269        brush: &impl piet::IntoBrush<Self>,
270        width: f64,
271        style: &piet::StrokeStyle,
272    ) {
273        let brush = brush.make_brush(self, || shape.bounding_box());
274        self.context
275            .stroke_styled(shape, &brush.as_ref().0, width, style)
276    }
277
278    fn text(&mut self) -> &mut Self::Text {
279        self.text
280    }
281
282    fn transform(&mut self, transform: Affine) {
283        self.context.transform(transform)
284    }
285}
286
287/// The brush type.
288#[derive(Debug)]
289pub struct Brush(piet_hardware::Brush<GpuContext>);
290
291impl Clone for Brush {
292    fn clone(&self) -> Self {
293        Self(self.0.clone())
294    }
295}
296
297impl piet::IntoBrush<RenderContext<'_, '_, '_>> for Brush {
298    fn make_brush<'a>(
299        &'a self,
300        _piet: &mut RenderContext<'_, '_, '_>,
301        _bbox: impl FnOnce() -> piet::kurbo::Rect,
302    ) -> std::borrow::Cow<'a, Brush> {
303        std::borrow::Cow::Borrowed(self)
304    }
305}
306
307/// The image type.
308#[derive(Debug)]
309pub struct Image(piet_hardware::Image<GpuContext>);
310
311impl Clone for Image {
312    fn clone(&self) -> Self {
313        Self(self.0.clone())
314    }
315}
316
317impl piet::Image for Image {
318    fn size(&self) -> piet::kurbo::Size {
319        self.0.size()
320    }
321}
322
323/// The text layout type.
324#[derive(Debug, Clone)]
325pub struct TextLayout(piet_hardware::TextLayout);
326
327impl piet::TextLayout for TextLayout {
328    fn size(&self) -> piet::kurbo::Size {
329        self.0.size()
330    }
331
332    fn line_text(&self, line_number: usize) -> Option<&str> {
333        self.0.line_text(line_number)
334    }
335
336    fn line_metric(&self, line_number: usize) -> Option<piet::LineMetric> {
337        self.0.line_metric(line_number)
338    }
339
340    fn line_count(&self) -> usize {
341        self.0.line_count()
342    }
343
344    fn hit_test_point(&self, point: piet::kurbo::Point) -> piet::HitTestPoint {
345        self.0.hit_test_point(point)
346    }
347
348    fn trailing_whitespace_width(&self) -> f64 {
349        self.0.trailing_whitespace_width()
350    }
351
352    fn image_bounds(&self) -> piet::kurbo::Rect {
353        self.0.image_bounds()
354    }
355
356    fn text(&self) -> &str {
357        self.0.text()
358    }
359
360    fn hit_test_text_position(&self, idx: usize) -> piet::HitTestPosition {
361        self.0.hit_test_text_position(idx)
362    }
363}
364
365/// The text layout builder type.
366#[derive(Debug)]
367pub struct TextLayoutBuilder(piet_hardware::TextLayoutBuilder);
368
369impl piet::TextLayoutBuilder for TextLayoutBuilder {
370    type Out = TextLayout;
371
372    fn max_width(self, width: f64) -> Self {
373        Self(self.0.max_width(width))
374    }
375
376    fn alignment(self, alignment: piet::TextAlignment) -> Self {
377        Self(self.0.alignment(alignment))
378    }
379
380    fn default_attribute(self, attribute: impl Into<piet::TextAttribute>) -> Self {
381        Self(self.0.default_attribute(attribute))
382    }
383
384    fn range_attribute(
385        self,
386        range: impl std::ops::RangeBounds<usize>,
387        attribute: impl Into<piet::TextAttribute>,
388    ) -> Self {
389        Self(self.0.range_attribute(range, attribute))
390    }
391
392    fn build(self) -> Result<Self::Out, Pierror> {
393        Ok(TextLayout(self.0.build()?))
394    }
395}
396
397/// The text engine type.
398#[derive(Debug, Clone)]
399pub struct Text(piet_hardware::Text);
400
401impl Text {
402    /// Get the DPI scale.
403    pub fn dpi(&self) -> f64 {
404        self.0.dpi()
405    }
406
407    /// Set the DPI scale.
408    pub fn set_dpi(&mut self, dpi: f64) {
409        self.0.set_dpi(dpi)
410    }
411}
412
413impl piet::Text for Text {
414    type TextLayoutBuilder = TextLayoutBuilder;
415    type TextLayout = TextLayout;
416
417    fn font_family(&mut self, family_name: &str) -> Option<piet::FontFamily> {
418        self.0.font_family(family_name)
419    }
420
421    fn load_font(&mut self, data: &[u8]) -> Result<piet::FontFamily, Pierror> {
422        self.0.load_font(data)
423    }
424
425    fn new_text_layout(&mut self, text: impl piet::TextStorage) -> Self::TextLayoutBuilder {
426        TextLayoutBuilder(self.0.new_text_layout(text))
427    }
428}