Skip to main content

euv_engine/renderer/
enum.rs

1use super::*;
2
3/// Defines how new pixels are composited with existing pixels on the canvas.
4///
5/// Maps directly to the CSS `globalCompositeOperation` property.
6#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
7pub enum BlendMode {
8    /// The source is drawn over the destination (default alpha blending).
9    #[default]
10    Normal,
11    /// The source color is multiplied with the destination, producing a darker result.
12    Multiply,
13    /// The source and destination are inverted, multiplied, then inverted again.
14    Screen,
15    /// The source and destination colors are added together, clamped to maximum brightness.
16    Lighter,
17    /// Combines `Multiply` and `Screen` based on the destination color.
18    Overlay,
19    /// Keeps the darker of the source and destination per channel.
20    Darken,
21    /// Keeps the lighter of the source and destination per channel.
22    Lighten,
23    /// Dodges the destination color brightening it based on the source.
24    ColorDodge,
25    /// Burns the destination color darkening it based on the source.
26    ColorBurn,
27    /// A harsher version of `Overlay` using the source color as the filter.
28    HardLight,
29    /// A softer version of `Overlay` using the source color as the filter.
30    SoftLight,
31    /// Subtracts the darker color from the lighter color per channel.
32    Difference,
33    /// Similar to `Difference` but with lower contrast.
34    Exclusion,
35    /// Uses the hue of the source with the saturation and luminosity of the destination.
36    Hue,
37    /// Uses the saturation of the source with the hue and luminosity of the destination.
38    Saturation,
39    /// Uses the hue and saturation of the source with the luminosity of the destination.
40    Color,
41    /// Uses the luminosity of the source with the hue and saturation of the destination.
42    Luminosity,
43}
44
45/// A single deferred draw operation recorded into a `DrawList`.
46///
47/// Commands carry the resolved style for the operation (fill/stroke color, line
48/// width) so that replay can group consecutive same-style shapes into a single
49/// path and skip redundant canvas state changes. Colors are stored as `Color`
50/// and converted to CSS strings only at replay time.
51#[derive(Clone, Debug, PartialEq)]
52pub enum DrawCommand {
53    /// Fills a rectangle. Carries the fill color.
54    FillRect {
55        /// The top-left position in world space.
56        position: Vector2D,
57        /// The width in pixels.
58        width: f64,
59        /// The height in pixels.
60        height: f64,
61        /// The fill color.
62        color: Color,
63    },
64    /// Strokes the outline of a rectangle. Carries stroke color and line width.
65    StrokeRect {
66        /// The top-left position in world space.
67        position: Vector2D,
68        /// The width in pixels.
69        width: f64,
70        /// The height in pixels.
71        height: f64,
72        /// The stroke color.
73        color: Color,
74        /// The stroke line width in pixels.
75        line_width: f64,
76    },
77    /// Fills a circle. Carries the fill color.
78    FillCircle {
79        /// The center in world space.
80        center: Vector2D,
81        /// The radius in pixels.
82        radius: f64,
83        /// The fill color.
84        color: Color,
85    },
86    /// Strokes the outline of a circle. Carries stroke color and line width.
87    StrokeCircle {
88        /// The center in world space.
89        center: Vector2D,
90        /// The radius in pixels.
91        radius: f64,
92        /// The stroke color.
93        color: Color,
94        /// The stroke line width in pixels.
95        line_width: f64,
96    },
97    /// Draws a line segment. Carries stroke color and line width.
98    Line {
99        /// The start point in world space.
100        start: Vector2D,
101        /// The end point in world space.
102        end: Vector2D,
103        /// The stroke color.
104        color: Color,
105        /// The stroke line width in pixels.
106        line_width: f64,
107    },
108    /// Fills text at a position. Carries the fill color and font.
109    FillText {
110        /// The text to draw.
111        text: String,
112        /// The position in world space.
113        position: Vector2D,
114        /// The fill color.
115        color: Color,
116        /// The CSS font string.
117        font: String,
118    },
119    /// Draws a transformed sprite sub-region (image, source rect, TRS transform).
120    DrawSprite {
121        /// The image to draw.
122        image: HtmlImageElement,
123        /// The source rectangle within the image.
124        source: Rect,
125        /// The world-space transform (position, rotation, scale). Scale signs flip.
126        transform: Transform2D,
127    },
128    /// Draws an image sub-region at a destination rect (no rotation).
129    DrawImageRect {
130        /// The image to draw.
131        image: HtmlImageElement,
132        /// The source rectangle within the image.
133        source: Rect,
134        /// The destination top-left position in world space.
135        dest_position: Vector2D,
136        /// The destination width in pixels.
137        dest_width: f64,
138        /// The destination height in pixels.
139        dest_height: f64,
140    },
141    /// Applies a global alpha to all subsequent commands until changed.
142    SetGlobalAlpha {
143        /// The alpha value in the range 0.0 to 1.0.
144        alpha: f64,
145    },
146    /// Applies a blend mode to all subsequent commands until changed.
147    SetBlendMode {
148        /// The blend mode to apply.
149        mode: BlendMode,
150    },
151}
152
153/// Rendering quality preset controlling anti-aliasing smoothing strategy.
154///
155/// Maps to the canvas `imageSmoothingQuality` value plus an explicit
156/// `imageSmoothingEnabled` toggle. Combined with a CSS `image-rendering:
157/// pixelated` rule on the consumer side, `Low` produces crisp pixel-art
158/// rendering while `High` produces smooth vector-style rendering.
159#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
160pub enum RenderQuality {
161    /// Fastest rendering, pixelated scaling.
162    ///
163    /// Disables `imageSmoothingEnabled` on the canvas context and sets
164    /// `imageSmoothingQuality = "low"`. Pair with CSS `image-rendering:
165    /// pixelated` for sharp nearest-neighbour scaling.
166    Low,
167    /// Balanced rendering with default smoothing quality.
168    ///
169    /// Sets `imageSmoothingQuality = "medium"`.
170    Medium,
171    /// Highest fidelity rendering with smooth edges and high-quality scaling.
172    ///
173    /// Sets `imageSmoothingQuality = "high"`. Best for vector-style content
174    /// on HiDPI displays. This is the default — when no explicit quality is
175    /// requested, the engine errs on the side of visual fidelity rather than
176    /// performance, since users typically notice aliasing artifacts before
177    /// they notice a few extra milliseconds of GPU time.
178    #[default]
179    High,
180}
181
182/// Errors that can occur while asynchronously initializing a `WebGpuRenderer`.
183///
184/// Each variant maps to one specific failure mode that the WebGPU init
185/// pipeline can encounter when calling into the browser's GPU API. The
186/// underlying JS error (when available) is carried as a `JsValue` so callers
187/// can surface the exact diagnostic string without losing fidelity.
188///
189/// Instead of logging diagnostics inside the engine, `WebGpuRenderer::init`
190/// returns `Result<WebGpuRenderer, WebGpuInitError>` and lets the caller
191/// decide how to react — typically via `Console::error` on the example side
192/// or by falling back to the Canvas 2D backend.
193#[derive(Clone, Debug)]
194pub enum WebGpuInitError {
195    /// `Reflect::get(navigator, "webgpu")` threw an exception.
196    ///
197    /// Surfaced when the JavaScript binding lookup itself fails rather than
198    /// simply returning `undefined`/`null`. Carries the original JS error.
199    NavigatorLookup(JsValue),
200    /// `navigator.gpu` is `undefined` or `null`.
201    ///
202    /// The browser does not expose WebGPU on the current origin. The most
203    /// common causes are serving over an insecure origin (must be HTTPS or
204    /// `localhost`) or running in a browser that lacks the WebGPU feature.
205    NavigatorGpuMissing,
206    /// `Reflect::get(gpu, "requestAdapter")` threw an exception.
207    ///
208    /// Carries the original JS error returned by the reflect call.
209    RequestAdapterLookup(JsValue),
210    /// `gpu.requestAdapter()` threw an exception synchronously.
211    ///
212    /// Carries the thrown JS error or value.
213    RequestAdapterCall(JsValue),
214    /// The adapter promise rejected, or the `INIT_PROMISE_TIMEOUT_MILLIS`
215    /// race timer fired before the adapter was produced.
216    ///
217    /// Carries the rejection value, which may be a string, an error object,
218    /// or `undefined` when the timeout won the race.
219    AdapterPromise(JsValue),
220    /// `requestAdapter()` resolved to `null` or `undefined`.
221    ///
222    /// No compatible GPU adapter exists for the requested `powerPreference`.
223    AdapterUnavailable,
224    /// `Reflect::get(adapter, "requestDevice")` threw an exception.
225    RequestDeviceLookup(JsValue),
226    /// `adapter.requestDevice()` threw an exception synchronously.
227    RequestDeviceCall(JsValue),
228    /// The device promise rejected, or the `INIT_PROMISE_TIMEOUT_MILLIS`
229    /// race timer fired before the device was produced.
230    DevicePromise(JsValue),
231    /// `requestDevice()` resolved to `null` or `undefined`.
232    ///
233    /// The adapter could not allocate a device, typically because the
234    /// adapter is in a `device-lost` state.
235    DeviceUnavailable,
236    /// `document.querySelector(canvas_selector)` returned `None`.
237    ///
238    /// The canvas element is not in the DOM yet (or its selector is wrong).
239    /// Carries the selector string that was queried.
240    CanvasNotFound(String),
241    /// `document.querySelector(canvas_selector)` threw an exception.
242    CanvasQuery(JsValue),
243    /// `canvas.get_context("webgpu")` returned `None`.
244    ///
245    /// The canvas is already using a different context type, or WebGPU is
246    /// disabled for this canvas.
247    CanvasContextUnavailable,
248    /// `Reflect::get(gpu, "getPreferredCanvasFormat")` threw an exception.
249    PreferredFormatLookup(JsValue),
250    /// `gpu.getPreferredCanvasFormat()` threw an exception synchronously.
251    PreferredFormatCall(JsValue),
252    /// `getPreferredCanvasFormat()` resolved to a value that is not a string.
253    ///
254    /// Carries the offending JS value so callers can log its type/name.
255    PreferredFormatType(JsValue),
256    /// `Reflect::get(context, "configure")` threw an exception.
257    ConfigureLookup(JsValue),
258    /// `Reflect::get(device, "queue")` threw an exception.
259    QueueLookup(JsValue),
260}
261
262/// Errors that can occur while initializing a `WebGlRenderer`.
263///
264/// WebGL context acquisition is synchronous, so the failure modes are far
265/// fewer than `WebGpuInitError`: the canvas must resolve and the browser
266/// must hand back a `WebGl2RenderingContext`. Each variant maps to one
267/// specific failure mode; the caller decides how to surface it (typically
268/// via `Console::error` on the example side).
269#[derive(Clone, Debug)]
270pub enum WebGlInitError {
271    /// `document.querySelector(canvas_selector)` returned `None`.
272    ///
273    /// The canvas element is not in the DOM yet (or its selector is wrong).
274    /// Carries the selector string that was queried.
275    CanvasNotFound(String),
276    /// `document.querySelector(canvas_selector)` threw an exception.
277    CanvasQuery(JsValue),
278    /// `canvas.get_context("webgl2")` returned `None`.
279    ///
280    /// The browser does not support WebGL 2, or the canvas is already bound
281    /// to a different context type.
282    ContextUnavailable,
283    /// `canvas.get_context("webgl2")` threw an exception.
284    ContextLookup(JsValue),
285    /// The object returned by `canvas.get_context("webgl2")` could not be
286    /// cast to `WebGl2RenderingContext`.
287    ContextCast,
288}
289
290/// Errors that can occur while building a WebGL shader program.
291///
292/// Each variant carries the browser-provided info log so the caller can
293/// surface the exact GLSL diagnostic without losing fidelity.
294#[derive(Clone, Debug)]
295pub enum WebGlProgramError {
296    /// Vertex or fragment shader compilation failed.
297    ///
298    /// Carries the shader info log returned by `getShaderInfoLog`.
299    ShaderCompile(String),
300    /// Program linking failed (or `createProgram` returned `None`).
301    ///
302    /// Carries the program info log returned by `getProgramInfoLog`.
303    ProgramLink(String),
304}
305
306/// Whether a vertex buffer is consumed per-vertex or per-instance.
307#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
308pub enum VertexStepMode {
309    /// Advance the buffer one vertex at a time.
310    #[default]
311    Vertex,
312    /// Advance the buffer one entry at a time, for all vertices of an
313    /// instance.
314    Instance,
315}
316
317/// A single binding entry inside a `BindGroupDescriptor`.
318#[derive(Clone, Debug)]
319pub enum BindGroupEntry {
320    /// A uniform / storage buffer binding.
321    ///
322    /// In WGSL terms, the buffer's `usage` must include `UNIFORM` for
323    /// `var<uniform>` bindings and `STORAGE` for `var<storage>` bindings.
324    Buffer {
325        /// The binding slot (matches `@binding(N)` in the shader).
326        binding: u32,
327        /// The `GpuBuffer` handle.
328        buffer: JsValue,
329        /// The byte offset into the buffer where the binding starts.
330        offset: u64,
331        /// The size in bytes of the binding. `None` means "until the end
332        /// of the buffer".
333        size: Option<u64>,
334    },
335    /// A read-write storage texture binding.
336    ///
337    /// The `GpuTexture` must have been created with `STORAGE_BINDING`
338    /// in its `usage` flag. Combine with `view` (a `GpuTextureView`)
339    /// obtained from `GpuTexture.createView()`.
340    StorageTexture {
341        /// The binding slot.
342        binding: u32,
343        /// The `GpuTextureView` handle.
344        view: JsValue,
345        /// `true` for `texture_storage_2d<format, read>` bindings,
346        /// `false` for `texture_storage_2d<format, read_write>` bindings.
347        read_only: bool,
348    },
349    /// A sampled texture binding.
350    Texture {
351        /// The binding slot.
352        binding: u32,
353        /// The `GpuTextureView` handle.
354        view: JsValue,
355    },
356    /// A sampler binding.
357    Sampler {
358        /// The binding slot.
359        binding: u32,
360        /// The `GpuSampler` handle.
361        sampler: JsValue,
362    },
363}
364
365/// A single entry inside a `GpuBindGroupLayoutDescriptor`.
366///
367/// Together these describe one slot of the bind group layout used by
368/// a render / compute pipeline. The visibility bitmask controls
369/// which shader stages can read the binding (`VERTEX = 0x1`,
370/// `FRAGMENT = 0x2`, `COMPUTE = 0x4`); `VERTEX | FRAGMENT = 0x3` and
371/// `VERTEX | FRAGMENT | COMPUTE = 0x7` are the most common values.
372#[derive(Clone, Debug)]
373pub struct BindGroupLayoutEntry {
374    /// The binding slot (matches `@binding(N)` in the shader).
375    pub binding: u32,
376    /// Visibility bitmask (`VERTEX = 0x1`, `FRAGMENT = 0x2`, `COMPUTE = 0x4`).
377    pub visibility: u32,
378    /// The resource kind bound at this slot.
379    pub ty: BindGroupEntryType,
380}
381
382impl BindGroupLayoutEntry {
383    /// Convenience constructor for a uniform-buffer binding slot.
384    pub fn uniform(binding: u32, visibility: u32) -> Self {
385        Self {
386            binding,
387            visibility,
388            ty: BindGroupEntryType::UniformBuffer,
389        }
390    }
391    /// Convenience constructor for a storage-buffer binding slot.
392    ///
393    /// `read_only = true` selects `read-only-storage` (matches `var<storage, read>`);
394    /// `read_only = false` selects `storage` (matches `var<storage, read_write>`).
395    pub fn storage(binding: u32, visibility: u32, read_only: bool) -> Self {
396        Self {
397            binding,
398            visibility,
399            ty: BindGroupEntryType::StorageBuffer { read_only },
400        }
401    }
402    /// Convenience constructor for a sampled texture binding slot.
403    ///
404    /// `sample_type` must be one of `"float"`, `"unfilterable-float"`,
405    /// `"depth"`, `"sint"`, `"uint"`.
406    pub fn texture(binding: u32, visibility: u32, sample_type: &str) -> Self {
407        Self {
408            binding,
409            visibility,
410            ty: BindGroupEntryType::SampledTexture {
411                sample_type: sample_type.to_string(),
412                multisampled: false,
413            },
414        }
415    }
416    /// Convenience constructor for a multisampled sampled texture binding slot.
417    pub fn texture_multisampled(binding: u32, visibility: u32, sample_type: &str) -> Self {
418        Self {
419            binding,
420            visibility,
421            ty: BindGroupEntryType::SampledTexture {
422                sample_type: sample_type.to_string(),
423                multisampled: true,
424            },
425        }
426    }
427    /// Convenience constructor for a storage-texture binding slot.
428    ///
429    /// `format` is a GpuTextureFormat string such as `"rgba8unorm"` or `"r32float"`.
430    pub fn storage_texture(binding: u32, visibility: u32, format: &str, read_only: bool) -> Self {
431        Self {
432            binding,
433            visibility,
434            ty: BindGroupEntryType::StorageTexture {
435                read_only,
436                format: format.to_string(),
437            },
438        }
439    }
440    /// Convenience constructor for a filtering sampler binding slot.
441    pub fn sampler(binding: u32, visibility: u32) -> Self {
442        Self {
443            binding,
444            visibility,
445            ty: BindGroupEntryType::Sampler {
446                filtering: true,
447                comparison: false,
448            },
449        }
450    }
451    /// Convenience constructor for a non-filtering sampler binding slot.
452    pub fn sampler_non_filtering(binding: u32, visibility: u32) -> Self {
453        Self {
454            binding,
455            visibility,
456            ty: BindGroupEntryType::Sampler {
457                filtering: false,
458                comparison: false,
459            },
460        }
461    }
462    /// Convenience constructor for a comparison sampler binding slot.
463    pub fn sampler_comparison(binding: u32, visibility: u32) -> Self {
464        Self {
465            binding,
466            visibility,
467            ty: BindGroupEntryType::Sampler {
468                filtering: false,
469                comparison: true,
470            },
471        }
472    }
473}
474
475/// The resource kind bound at a single slot of a `BindGroupLayoutEntry`.
476#[derive(Clone, Debug)]
477pub enum BindGroupEntryType {
478    /// `GpuBufferBindingLayout { type: "uniform" }`.
479    UniformBuffer,
480    /// `GpuBufferBindingLayout { type: "storage" | "read-only-storage" }`.
481    StorageBuffer {
482        /// `true` → `"read-only-storage"`, `false` → `"storage"`.
483        read_only: bool,
484    },
485    /// `GpuTextureBindingLayout`.
486    SampledTexture {
487        /// One of `"float"`, `"unfilterable-float"`, `"depth"`, `"sint"`, `"uint"`.
488        sample_type: String,
489        /// `true` if the bound texture is multisampled (matches MSAA render-target sampling).
490        multisampled: bool,
491    },
492    /// `GpuStorageTextureBindingLayout`.
493    StorageTexture {
494        /// `true` → `"read-only"`, `false` → `"read-write"`.
495        read_only: bool,
496        /// Texture format string (e.g. `"rgba8unorm"`, `"r32float"`).
497        format: String,
498    },
499    /// `GpuSamplerBindingLayout`.
500    Sampler {
501        /// `true` for filtering samplers (linear interpolation).
502        filtering: bool,
503        /// `true` for comparison samplers (depth-texture sampling).
504        comparison: bool,
505    },
506}