Skip to main content

euv_engine/renderer/
struct.rs

1use super::*;
2
3/// A 2D camera that defines the viewport into the game world.
4#[derive(Clone, Copy, Data, Debug, New, PartialEq, PartialOrd)]
5pub struct Camera2D {
6    /// The world-space position of the camera center.
7    #[get(type(copy))]
8    pub(crate) position: Vector2D,
9    /// The zoom factor (1.0 = no zoom, 2.0 = 2x magnification).
10    #[get(type(copy))]
11    pub(crate) zoom: f64,
12    /// The rotation angle in radians.
13    #[get(type(copy))]
14    pub(crate) rotation: f64,
15    /// The viewport width in screen pixels.
16    #[get(type(copy))]
17    pub(crate) viewport_width: f64,
18    /// The viewport height in screen pixels.
19    #[get(type(copy))]
20    pub(crate) viewport_height: f64,
21}
22
23/// A 3D camera that defines the viewport into a 3D world using perspective
24/// or orthographic projection.
25#[derive(Clone, Copy, Data, Debug, New, PartialEq, PartialOrd)]
26pub struct Camera3D {
27    /// The world-space position of the camera (eye).
28    #[get(type(copy))]
29    pub(crate) position: Vector3D,
30    /// The point the camera is looking at (target).
31    #[get(type(copy))]
32    pub(crate) target: Vector3D,
33    /// The up direction for the camera.
34    #[get(type(copy))]
35    #[new(skip)]
36    pub(crate) up: Vector3D,
37    /// The vertical field of view in radians.
38    #[get(type(copy))]
39    #[new(skip)]
40    pub(crate) fov: f64,
41    /// The near clipping plane distance.
42    #[get(type(copy))]
43    #[new(skip)]
44    pub(crate) near: f64,
45    /// The far clipping plane distance.
46    #[get(type(copy))]
47    #[new(skip)]
48    pub(crate) far: f64,
49    /// The viewport width in pixels.
50    #[get(type(copy))]
51    pub(crate) viewport_width: f64,
52    /// The viewport height in pixels.
53    #[get(type(copy))]
54    pub(crate) viewport_height: f64,
55}
56
57/// A wrapper around `CanvasRenderingContext2d` providing convenience
58/// drawing methods and camera management for the game engine.
59#[derive(Clone, Data, New)]
60pub struct CanvasRenderer {
61    /// The underlying canvas 2D rendering context.
62    pub(crate) context: CanvasRenderingContext2d,
63    /// The active camera controlling the viewport.
64    #[get(type(copy))]
65    pub(crate) camera: Camera2D,
66    /// The active rendering quality preset.
67    ///
68    /// Controls `imageSmoothingEnabled`, `imageSmoothingQuality`, and
69    /// `textRendering` on the underlying context. Defaults to `Medium`.
70    #[get(type(copy))]
71    pub(crate) quality: RenderQuality,
72}
73
74/// A linear gradient defined by two endpoints and a list of color stops.
75///
76/// Used to create smooth color transitions along a straight line
77/// for fill or stroke operations on the canvas.
78#[derive(Clone, Data, Debug, New, PartialEq)]
79pub struct LinearGradient {
80    /// The starting point of the gradient in world space.
81    #[get(type(copy))]
82    pub(crate) start: Vector2D,
83    /// The ending point of the gradient in world space.
84    #[get(type(copy))]
85    pub(crate) end: Vector2D,
86    /// The ordered list of color stops, each containing a position (0.0 to 1.0) and a CSS color string.
87    pub(crate) stops: Vec<(f64, String)>,
88}
89
90/// A radial gradient defined by inner and outer circles and a list of color stops.
91///
92/// Used to create smooth color transitions radiating outward from a center point
93/// for fill or stroke operations on the canvas.
94#[derive(Clone, Data, Debug, New, PartialEq)]
95pub struct RadialGradient {
96    /// The center of the inner circle of the gradient.
97    #[get(type(copy))]
98    pub(crate) inner_center: Vector2D,
99    /// The radius of the inner circle.
100    #[get(type(copy))]
101    pub(crate) inner_radius: f64,
102    /// The center of the outer circle of the gradient.
103    #[get(type(copy))]
104    pub(crate) outer_center: Vector2D,
105    /// The radius of the outer circle.
106    #[get(type(copy))]
107    pub(crate) outer_radius: f64,
108    /// The ordered list of color stops, each containing a position (0.0 to 1.0) and a CSS color string.
109    pub(crate) stops: Vec<(f64, String)>,
110}
111
112/// Shadow rendering configuration for drop shadow effects on canvas primitives.
113///
114/// When applied, all subsequent fill, stroke, and draw operations will cast
115/// a shadow with the specified color, blur radius, and offset.
116#[derive(Clone, Data, Debug, New, PartialEq, PartialOrd)]
117pub struct ShadowConfig {
118    /// The CSS color string of the shadow (e.g., `"rgba(0,0,0,0.5)"`).
119    #[get(type(clone))]
120    pub(crate) color: String,
121    /// The blur radius of the shadow in pixels.
122    #[get(type(copy))]
123    pub(crate) blur: f64,
124    /// The horizontal offset of the shadow in pixels.
125    #[get(type(copy))]
126    pub(crate) offset_x: f64,
127    /// The vertical offset of the shadow in pixels.
128    #[get(type(copy))]
129    pub(crate) offset_y: f64,
130}
131
132/// Represents the rendering priority layer for draw call ordering.
133///
134/// Higher z-index values are drawn on top of lower values,
135/// enabling correct visual layering of game objects.
136#[derive(Clone, Copy, Data, Debug, Default, Eq, Hash, New, Ord, PartialEq, PartialOrd)]
137pub struct RenderLayer {
138    /// The z-index determining draw order. Higher values draw later (on top).
139    #[get(type(copy))]
140    pub(crate) z_index: i32,
141    /// Whether objects in this layer should be rendered.
142    #[get(type(copy))]
143    pub(crate) visible: bool,
144}
145
146/// An ordered buffer of deferred draw commands recorded during a frame.
147///
148/// Scenes and components push `DrawCommand`s into the list during `on_render`
149/// instead of drawing immediately. The engine then replays the whole list once
150/// per frame via `CanvasRenderer::replay`, which batches consecutive same-style
151/// shapes into a single path and skips redundant canvas state changes. The
152/// backing `Vec` is reused across frames via `clear()` to avoid reallocation.
153#[derive(Clone, Data, Debug, Default, New)]
154pub struct DrawList {
155    /// The recorded draw commands for the current frame.
156    #[get_mut(pub(crate))]
157    #[set(pub(crate))]
158    pub(crate) commands: Vec<DrawCommand>,
159}
160
161/// A supersampling anti-aliasing (SSAA) canvas wrapper that renders at a higher
162/// resolution on an offscreen canvas and downscales to the display canvas for
163/// smoother polygon edges in software-rendered 3D scenes.
164///
165/// The offscreen context is scaled by `scale_factor` so that all drawing
166/// code can use logical pixel coordinates without modification. After
167/// rendering, call `present()` to draw the high-resolution buffer onto the
168/// visible canvas with high-quality image smoothing.
169#[derive(Clone, Data, New)]
170pub struct SsaaCanvas {
171    /// The display canvas element visible to the user.
172    pub(crate) display_canvas: HtmlCanvasElement,
173    /// The 2D rendering context of the display canvas used for final presentation.
174    pub(crate) display_context: CanvasRenderingContext2d,
175    /// The offscreen canvas used for high-resolution rendering.
176    pub(crate) offscreen_canvas: HtmlCanvasElement,
177    /// The 2D rendering context of the offscreen canvas, pre-scaled by `scale_factor`.
178    pub(crate) offscreen_context: CanvasRenderingContext2d,
179    /// The supersampling scale factor (e.g., 2.0 means 4x SSAA).
180    #[get(type(copy))]
181    pub(crate) scale_factor: f64,
182    /// The rendering quality preset for the downscaling present step.
183    ///
184    /// Controls the smoothing strategy when the offscreen buffer is
185    /// downscaled onto the display canvas. Defaults to `Medium`.
186    #[new(skip)]
187    #[get(type(copy))]
188    pub(crate) quality: RenderQuality,
189    /// The logical display width in CSS pixels.
190    #[get(type(copy))]
191    pub(crate) width: f64,
192    /// The logical display height in CSS pixels.
193    #[get(type(copy))]
194    pub(crate) height: f64,
195}
196
197/// A WebGPU rendering backend wrapping the GPU device, queue, and canvas context
198/// for GPU-accelerated rendering on the web.
199///
200/// Created asynchronously via `WebGpuRenderer::init` because adapter and
201/// device acquisition returns JavaScript Promises that must be awaited.
202/// Once initialized, the renderer provides methods to create GPU resources
203/// (buffers, shader modules, command encoders) and execute render passes.
204///
205/// WebGPU types are stored as `JsValue` to avoid feature-gated import issues
206/// with `web_sys`. Method calls are performed via `Reflect` and `JsCast`.
207#[derive(Clone, Data)]
208pub struct WebGpuRenderer {
209    /// The WebGPU device (`GpuDevice`) used to create GPU resources.
210    pub(crate) device: JsValue,
211    /// The device's command queue (`GpuQueue`) for submitting command buffers.
212    pub(crate) queue: JsValue,
213    /// The WebGPU canvas rendering context (`GpuCanvasContext`).
214    pub(crate) context: JsValue,
215    /// The HTML canvas element backing the WebGPU context.
216    pub(crate) canvas: HtmlCanvasElement,
217    /// The texture format string used by the canvas's swap chain (e.g., `"bgra8unorm"`).
218    #[get(type(clone))]
219    pub(crate) format: String,
220    /// The physical pixel width of the canvas backing store.
221    #[get(type(copy))]
222    pub(crate) width: u32,
223    /// The physical pixel height of the canvas backing store.
224    #[get(type(copy))]
225    pub(crate) height: u32,
226    /// Whether MSAA anti-aliasing is enabled for render pipelines.
227    ///
228    /// When `true`, the renderer allocates a multisampled intermediate texture
229    /// (`sampleCount: 4`) and resolves into the swap chain each frame; when
230    /// `false`, render passes attach directly to the swap chain view at
231    /// `sampleCount: 1`.
232    #[get(type(copy))]
233    pub(crate) antialias: bool,
234    /// The multisampled color texture used when `antialias` is `true`.
235    ///
236    /// `None` when MSAA is disabled. Rebuilt on every resize because the
237    /// `width`/`height` are immutable for a given `GpuTexture`.
238    #[get(type(clone))]
239    pub(crate) multisample_texture: Option<JsValue>,
240    /// The default `GpuTextureView` into `multisample_texture`.
241    ///
242    /// Cached at texture-create time so `begin_render_pass` does not have to
243    /// recreate the view each frame. `None` when MSAA is disabled.
244    #[get(type(clone))]
245    pub(crate) multisample_view: Option<JsValue>,
246    /// The depth-stencil texture used for depth-tested passes.
247    ///
248    /// Created lazily on the first call to [`WebGpuRenderer::begin_render_pass`]
249    /// that includes a `depthStencil` attachment. Rebuilt on every resize
250    /// because the dimensions are immutable for a given `GpuTexture`. The
251    /// matching default view is cached in `depth_view`.
252    ///
253    /// `None` until the first depth-tested render pass is opened.
254    #[get(type(clone))]
255    pub(crate) depth_texture: Option<JsValue>,
256    /// The default `GpuTextureView` into `depth_texture`.
257    ///
258    /// `None` when no depth texture has been allocated.
259    #[get(type(clone))]
260    pub(crate) depth_view: Option<JsValue>,
261    /// The depth-stencil format used for `depth_texture`.
262    ///
263    /// Stored so subsequent render-pass openers can pass the same format
264    /// to the pipeline layout without having to remember it externally.
265    /// `None` until the first depth texture is allocated.
266    #[get(type(clone))]
267    pub(crate) depth_format: Option<String>,
268    /// User-supplied closure fired when the underlying `GpuDevice` enters
269    /// the `lost` state (browser-initiated context loss, OS driver crash,
270    /// `device.destroy()`, ...).
271    ///
272    /// `None` until the caller calls [`WebGpuRenderer::on_device_lost`].
273    /// The renderer also stores a separate `device_lost_handle` that
274    /// forwards the `GPUDeviceLostInfo` JS value into this callback.
275    #[get(type(clone))]
276    pub(crate) device_lost_callback: Option<js_sys::Function>,
277    /// Whether the device is currently in the `lost` state.
278    ///
279    /// Once flipped to `true`, every GPU operation returns
280    /// `Err(WebGpuError::RendererDisposed)` until the caller destroys the
281    /// renderer and creates a new one (WebGPU has no "recover from lost
282    /// device" API).
283    #[get(type(copy))]
284    pub(crate) device_lost: bool,
285    /// Shared slot for the most recent popped error-scope value.
286    ///
287    /// `device.popErrorScope()` returns a `Promise<GPUError?>`; we
288    /// cannot `.await` it from a sync call site. Instead, every
289    /// `push_error_scope` + `pop_error_scope` pair registers a
290    /// microtask via `wasm_bindgen_futures::spawn_local` that stores
291    /// the resolved value here. Callers that want the error
292    /// synchronously call [`WebGpuRenderer::take_last_error`] to
293    /// drain the slot.
294    ///
295    /// Holding a `Rc<PendingErrorCell>` lets the spawn_local future
296    /// own its own handle independently of `&self`, so the
297    /// renderer's borrow checker stays happy. The slot is empty
298    /// (`None`) by default and after each successful take.
299    ///
300    /// The cell is intentionally `PendingErrorCell` (a `Sync`
301    /// `UnsafeCell` newtype, see [`crate::renderer::static`]) rather
302    /// than `Rc<RefCell<...>>` - the WASM single-threaded scheduler
303    /// makes the runtime borrow check `RefCell` provides unreachable
304    /// in practice, so we trade it for a raw `UnsafeCell` deref
305    /// confined to two call sites. This mirrors how euv-core
306    /// implements its global registries
307    /// (`core/src/renderer/registry/struct.rs:62`).
308    pub(crate) pending_error: Rc<PendingErrorCell>,
309    /// The currently-open `GpuCommandEncoder`, if any.
310    ///
311    /// WebGPU expects the application to encode all work for a
312    /// frame (clear, render passes, compute passes, copy ops) into
313    /// a single command encoder, then call `encoder.finish()` to
314    /// produce a `GpuCommandBuffer` and submit it to the queue.
315    /// The encoder is `None` after `submit()` finishes and must
316    /// be re-acquired via `device.createCommandEncoder()` before
317    /// the next frame.
318    #[get(type(clone))]
319    pub(crate) command_encoder: Option<JsValue>,
320    /// OPT 34: cached render-pass descriptor, allocated lazily on the
321    /// first call to [`WebGpuRenderer::begin_render_pass_full`].
322    ///
323    /// The pre-WebGPU-audit path allocated a fresh `Object` +
324    /// `Array` + 8-15 `Reflect::set` calls every frame. We keep the
325    /// descriptor Object alive for the renderer's lifetime, refreshing
326    /// the per-frame fields (`view`, `resolveTarget`, `clearValue`) in
327    /// place on every call. The cache invalidates itself automatically
328    /// when `load_op` / `store_op`, the depth-stencil shape, or the
329    /// resolve-target shape changes.
330    ///
331    /// `None` until the first `begin_render_pass_full` call; `Some(_)`
332    /// afterwards and persists for the lifetime of the renderer.
333    #[get(type(clone))]
334    pub(crate) render_pass_descriptor_cache: Option<RenderPassDescriptorCache>,
335}
336
337/// OPT 34: persistent render-pass descriptor and its inner
338/// attachments, reused across `begin_render_pass_full` calls.
339///
340/// # Why
341///
342/// `begin_render_pass_full` historically allocated a fresh descriptor
343/// `Object`, a `colorAttachments` `Array`, and one inner
344/// `color_attachment` `Object` (plus an optional `clearValue` `Object`)
345/// on every frame, then ran 8-15 `Reflect::set` calls to populate
346/// them. WebGPU re-validates the descriptor each call, but the JS-side
347/// `Object` / `Array` allocations and the per-property `Reflect::set`
348/// crossings are pure overhead — only the `clearValue` and (rarely)
349/// `view` / `loadOp` / `storeOp` fields change between frames.
350///
351/// # What we cache
352///
353/// - The top-level descriptor `Object` (the one passed to
354///   `beginRenderPass`).
355/// - The `colorAttachments` `Array` (always exactly one element —
356///   we keep the same `Array` reference and mutate its slot 0 in
357///   place).
358/// - The inner color attachment `Object` (slot 0 of
359///   `colorAttachments`).
360/// - The `clearValue` `Object` (the `{r, g, b, a}` dictionary that
361///   is the actual per-frame mutating field).
362/// - Last-applied `loadOp` / `storeOp` string slices, to detect when
363///   the caller switched ops and the cached descriptor must be
364///   rebuilt (rare; WebGPU does not hot-swap ops every frame).
365/// - Last-applied depth-stencil shape (present / absent), to detect
366///   when the depth-stencil shape changes.
367///
368/// # Invalidation
369///
370/// The cache is invalidated (rebuilt from scratch) when any of:
371/// - `load_op` changes between calls,
372/// - `store_op` changes between calls,
373/// - the depth-stencil shape changes (None → Some / Some → None),
374/// - the resolve-target shape changes (MSAA on/off).
375///
376/// `view` / `resolveTarget` / `clearValue` are refreshed on every call
377/// (the swap-chain view expires after each presented frame, so caching
378/// it across frames silently invalidates every subsequent render pass).
379///
380/// These are all `&'static str` (they come from `WEBGPU_*_OP_*`
381/// constants), so invalidation is a pointer-compare.
382///
383/// `Clone` is derived so the parent `WebGpuRenderer`'s `Data` derive
384/// (which adds a `Clone` bound on every field) keeps compiling;
385/// `js_sys::Object` and `js_sys::Array` both derive `Clone`, so the
386/// derived `Clone` impl just clones the inner JS-side references
387/// (cheap, no JS allocation).
388#[derive(Clone, Debug)]
389pub struct RenderPassDescriptorCache {
390    /// The cached top-level `GpuRenderPassDescriptor` Object.
391    /// Pass directly to `encoder.beginRenderPass(descriptor)`.
392    pub(crate) descriptor: Object,
393    /// The cached inner color attachment Object.
394    /// `descriptor.colorAttachments[0]` in JS terms.
395    pub(crate) attachment: Object,
396    /// The cached `clearValue` Object (the `{r, g, b, a}` dictionary
397    /// under `attachment.clearValue`). The hot-path field — only
398    /// this is mutated on most frames.
399    pub(crate) clear_value: Object,
400    /// Last applied `loadOp` (as a `&'static str`). Used to detect
401    /// op changes that invalidate the descriptor.
402    pub(crate) last_load_op: Option<&'static str>,
403    /// Last applied `storeOp` (as a `&'static str`). Used to detect
404    /// op changes that invalidate the descriptor.
405    pub(crate) last_store_op: Option<&'static str>,
406    /// Whether the last applied descriptor had a depth-stencil
407    /// attachment (`true`) or not (`false`). Used to detect shape
408    /// changes that invalidate the descriptor.
409    pub(crate) last_has_depth: bool,
410    /// Whether the last applied descriptor had a `resolveTarget`
411    /// (`true`, MSAA path) or not (`false`). Used to detect shape
412    /// changes that invalidate the descriptor, so a stale
413    /// `resolveTarget` never survives an MSAA -> non-MSAA switch.
414    pub(crate) last_has_resolve: bool,
415}
416
417/// Describes a 2D viewport rectangle plus optional depth range, in the same
418/// pixel space as the destination render target.
419///
420/// Used by [`WebGpuRenderer::set_viewport`] (and any future caller that needs
421/// to push a `GpuViewport`-shaped JS object through `Reflect::set`). The
422/// depth-range fields are omitted from the `::new` constructor via
423/// `#[new(skip)]`; they default to zero-initialised `f32` and are typically
424/// overwritten by [`WebGpuRenderer::set_viewport`] to the WebGPU spec
425/// defaults of `0.0` / `1.0`.
426#[derive(Clone, Copy, Data, Debug, New, PartialEq)]
427pub struct ViewportDescriptor {
428    /// X coordinate of the viewport's top-left in pixels.
429    pub(crate) x: f32,
430    /// Y coordinate of the viewport's top-left in pixels.
431    pub(crate) y: f32,
432    /// Viewport width in pixels.
433    pub(crate) width: f32,
434    /// Viewport height in pixels.
435    pub(crate) height: f32,
436    /// Minimum depth, clamped to `[0, 1]`. Set to `0.0` to disable.
437    #[new(skip)]
438    pub(crate) min_depth: f32,
439    /// Maximum depth, clamped to `[0, 1]`. Set to `1.0` to disable.
440    #[new(skip)]
441    pub(crate) max_depth: f32,
442}
443
444/// A WebGL 2 rendering backend wrapping the `WebGl2RenderingContext`.
445///
446/// Unlike `WebGpuRenderer`, which stores all GPU handles as opaque `JsValue`s,
447/// WebGL exposes concrete `web_sys` types, so the context and canvas are kept
448/// as strongly typed values. Shader programs created via
449/// [`WebGlRenderer::create_program`] are managed by the caller.
450///
451/// Construct via [`WebGlRenderer::init`], which resolves the canvas from the
452/// [`RenderConfig`], applies device-pixel-ratio scaling to the backing store,
453/// and acquires the `webgl2` context.
454#[derive(Clone, Data)]
455pub struct WebGlRenderer {
456    /// The WebGL 2 rendering context used for all GL calls.
457    pub(crate) context: WebGl2RenderingContext,
458    /// The HTML canvas element backing the WebGL context.
459    pub(crate) canvas: HtmlCanvasElement,
460    /// The physical pixel width of the canvas backing store.
461    #[get(type(copy))]
462    pub(crate) width: u32,
463    /// The physical pixel height of the canvas backing store.
464    #[get(type(copy))]
465    pub(crate) height: u32,
466}
467
468// =====================================================================
469// WebGPU: descriptor & data structs (consumed by the WebGpuRenderer API)
470// =====================================================================
471
472/// A single vertex attribute within a vertex buffer layout.
473///
474/// Mirrors the fields of `GPUVertexAttribute` exactly. The shader location
475/// is the `@location(N)` qualifier in the WGSL source. The offset is in
476/// bytes from the start of the vertex, and `format` is one of the
477/// WGSL vertex format strings (e.g. `"float32x4"`, `"unorm8x4"`).
478#[derive(Clone, Copy, Debug, Eq, Getter, Hash, New, PartialEq)]
479pub struct VertexAttribute {
480    /// The shader location the attribute maps to.
481    #[get(type(copy))]
482    pub(crate) shader_location: u32,
483    /// The byte offset from the start of the vertex.
484    #[get(type(copy))]
485    pub(crate) offset: u64,
486    /// The WGSL vertex format (e.g. `"float32x4"`).
487    #[get(type(clone))]
488    pub(crate) format: &'static str,
489}
490
491/// The layout of a single vertex buffer, expressed as an array stride plus
492/// a list of attributes.
493///
494/// Mirrors `GPUVertexBufferLayout` from the WebGPU spec. The renderer
495/// passes the assembled descriptor straight to `createRenderPipeline` via
496/// `Reflect`.
497#[derive(Clone, Debug, Getter, New)]
498pub struct VertexBufferLayout {
499    /// The byte stride of one vertex in the buffer.
500    #[get(type(copy))]
501    pub(crate) array_stride: u64,
502    /// Whether the buffer should be advanced per-instance (`true`) or
503    /// per-vertex (`false`).
504    #[get(type(copy))]
505    pub(crate) step_mode: VertexStepMode,
506    /// The attributes that describe how to interpret the bytes of one
507    /// vertex.
508    pub(crate) attributes: Vec<VertexAttribute>,
509}
510
511/// A 2D texture descriptor for `create_texture_2d`.
512///
513/// Defaults produce a 1x1 RGBA8 texture with `TEXTURE_BINDING | COPY_DST
514/// | COPY_SRC` usage, which is the right baseline for a sampled color
515/// texture that is uploaded to via `queue.writeTexture`. Override fields
516/// after constructing to set `mip_level_count`, `sample_count`, or
517/// different `usage` flags.
518#[derive(Clone, Debug, Getter, New)]
519pub struct Texture2DDescriptor {
520    /// The texture width in pixels. Must be > 0.
521    #[get(type(copy))]
522    pub(crate) width: u32,
523    /// The texture height in pixels. Must be > 0.
524    #[get(type(copy))]
525    pub(crate) height: u32,
526    /// The WGSL texture format (e.g. `"rgba8unorm"`, `"bgra8unorm"`,
527    /// `"rgba16float"`, `"depth24plus-stencil8"`).
528    #[get(type(clone))]
529    pub(crate) format: &'static str,
530    /// The number of mip levels. `0` is treated as `1`.
531    #[get(type(copy))]
532    #[new(skip)]
533    pub(crate) mip_level_count: u32,
534    /// The number of samples per texel (`1` for non-MSAA, `4` for MSAA).
535    #[get(type(copy))]
536    #[new(skip)]
537    pub(crate) sample_count: u32,
538    /// The WGSL usage flags (e.g. `"RENDER_ATTACHMENT | TEXTURE_BINDING |
539    /// COPY_DST | COPY_SRC"`).
540    #[get(type(clone))]
541    #[new(skip)]
542    pub(crate) usage: &'static str,
543}
544
545/// A sampler descriptor for `create_sampler`.
546///
547/// Defaults produce a non-filtering clamp-to-edge sampler. Override
548/// fields after constructing to enable linear filtering, repeat
549/// addressing, or depth comparison.
550#[derive(Clone, Debug, Getter, New)]
551pub struct GpuSamplerDescriptor {
552    /// Minification filter.
553    #[get(type(clone))]
554    #[new(skip)]
555    pub(crate) mag_filter: &'static str,
556    /// Magnification filter.
557    #[get(type(clone))]
558    #[new(skip)]
559    pub(crate) min_filter: &'static str,
560    /// Mipmap filter.
561    #[get(type(clone))]
562    #[new(skip)]
563    pub(crate) mipmap_filter: &'static str,
564    /// U address mode.
565    #[get(type(clone))]
566    #[new(skip)]
567    pub(crate) address_mode_u: &'static str,
568    /// V address mode.
569    #[get(type(clone))]
570    #[new(skip)]
571    pub(crate) address_mode_v: &'static str,
572    /// W address mode.
573    #[get(type(clone))]
574    #[new(skip)]
575    pub(crate) address_mode_w: &'static str,
576    /// Whether the sampler is a comparison sampler.
577    #[get(type(copy))]
578    #[new(skip)]
579    pub(crate) compare: bool,
580}
581
582/// The descriptor for a single (color or depth-stencil) render pass
583/// attachment, used as input to `begin_render_pass` / `begin_render_pass_to_texture`.
584#[derive(Clone, Debug, Getter)]
585pub struct RenderPassColorAttachment {
586    /// The texture view to draw into.
587    ///
588    /// When `None`, the renderer uses the swap-chain view (or the MSAA
589    /// intermediate view if `antialias == true`).
590    pub view: Option<JsValue>,
591    /// An optional resolve target for MSAA.
592    ///
593    /// `None` when MSAA is disabled. The renderer fills in the default
594    /// resolve target (the swap-chain view) when MSAA is enabled and the
595    /// caller leaves this as `None`.
596    pub resolve_target: Option<JsValue>,
597    /// The clear color as `(r, g, b, a)` in `0.0..=1.0`. `None` means
598    /// `"load"` (keep the previous contents).
599    pub clear_value: Option<(f64, f64, f64, f64)>,
600    /// The load operation. `None` → `"clear"` when `clear_value` is
601    /// `Some`, otherwise `"load"`.
602    pub load_op: Option<&'static str>,
603    /// The store operation. `None` → `"store"`.
604    pub store_op: Option<&'static str>,
605}
606
607/// The depth-stencil portion of a `RenderPassDescriptor`, used as input to
608/// `begin_render_pass` / `begin_render_pass_to_texture`.
609#[derive(Clone, Debug, Getter)]
610pub struct RenderPassDepthStencilAttachment {
611    /// The depth-stencil texture view to use.
612    ///
613    /// When `None`, the renderer uses the default view into its
614    /// `depth_texture` field, allocating the depth texture lazily if
615    /// needed.
616    pub view: Option<JsValue>,
617    /// The depth clear value in `0.0..=1.0`. `None` means
618    /// `"load"` (keep previous depth).
619    pub depth_clear_value: Option<f32>,
620    /// The depth load op. `None` → `"clear"` when
621    /// `depth_clear_value` is `Some`, otherwise `"load"`.
622    pub depth_load_op: Option<&'static str>,
623    /// The depth store op. `None` → `"store"`.
624    pub depth_store_op: Option<&'static str>,
625    /// Whether depth reads should be enabled. `None` → `false`.
626    pub depth_read_only: Option<bool>,
627}
628
629/// Descriptor for `GpuTexture.createView(descriptor)`.
630///
631/// Sub-selects a single cube face / mip / array slice / depth-aspect of a
632/// texture. When you need the full texture as a 2D view (the common case),
633/// just call `create_view` without a descriptor; the new method accepts an
634/// `Option<&TextureViewDescriptor>` for callers that need the full
635/// flexibility of the WebGPU spec.
636#[derive(Clone, Debug, Getter, New)]
637pub struct TextureViewDescriptor {
638    /// View format override, or `None` to use the texture's own format.
639    #[get(type(clone))]
640    #[new(value = "None")]
641    pub(crate) format: Option<&'static str>,
642    /// View dimension (`"2d"`, `"2d-array"`, `"cube"`, `"cube-array"`, ...).
643    /// `None` means the dimension is inferred from the texture.
644    #[get(type(clone))]
645    #[new(value = "None")]
646    pub(crate) dimension: Option<&'static str>,
647    /// Most significant mip level (inclusive). `None` → `0`.
648    #[get(type(copy))]
649    #[new(value = "0")]
650    pub(crate) base_mip_level: u32,
651    /// Number of mip levels in the view. `0` → all the way to the top.
652    #[get(type(copy))]
653    #[new(value = "0")]
654    pub(crate) mip_level_count: u32,
655    /// First array layer (inclusive). `None` → `0`. Only meaningful for
656    /// `2d-array` / `cube` / `cube-array` views.
657    #[get(type(copy))]
658    #[new(value = "0")]
659    pub(crate) base_array_layer: u32,
660    /// Number of array layers. `0` → all remaining layers.
661    #[get(type(copy))]
662    #[new(value = "0")]
663    pub(crate) array_layer_count: u32,
664    /// Which aspect of the texture to expose. One of:
665    /// `"all"`, `"depth-only"`, `"stencil-only"`. `None` → `"all"`.
666    #[get(type(clone))]
667    #[new(value = "None")]
668    pub(crate) aspect: Option<&'static str>,
669}
670
671/// Descriptor for `queue.writeTexture(destination, data, dataLayout, size)`.
672///
673/// WebGPU's `writeTexture` lets you upload CPU-side pixel data directly to a
674/// texture without staging through a buffer. Use it for: ImGui font atlases,
675/// procedural noise textures, sprite sheets, `ImageBitmap` pixels, etc.
676#[derive(Clone, Debug, Getter, New)]
677pub struct TextureWriteDescriptor {
678    /// The pixel data to upload. Bytes are laid out according to
679    /// `bytes_per_row` / `rows_per_image`.
680    #[get(type(clone))]
681    pub(crate) data: Vec<u8>,
682    /// Bytes per row of the source data. Must be a multiple of 256.
683    #[get(type(copy))]
684    pub(crate) bytes_per_row: u32,
685    /// Number of rows per image. `0` for 2D textures without mip chains.
686    #[get(type(copy))]
687    pub(crate) rows_per_image: u32,
688    /// Destination mip level to write into.
689    #[get(type(copy))]
690    pub(crate) mip_level: u32,
691    /// Destination texture to write into.
692    #[get(type(clone))]
693    pub(crate) texture: JsValue,
694    /// Origin within the destination texture. `None` → `(0, 0, 0)`.
695    #[get(type(clone))]
696    #[new(value = "None")]
697    pub(crate) origin: Option<JsValue>,
698    /// Whether to flip the source data vertically before writing.
699    /// `true` is essential when uploading from `<img>` / `<canvas>` whose
700    /// rows are top-to-bottom but WebGPU textures are bottom-to-top.
701    #[get(type(copy))]
702    #[new(value = "false")]
703    pub(crate) flip_y: bool,
704}
705
706/// Interior-mutable slot for the renderer's pending error-scope value.
707///
708/// This is the `euv-engine` analog of euv-core's `HandlerRegistryCell`
709/// (`core/src/renderer/registry/struct.rs:62`): a single-element
710/// `Sync` wrapper that holds an `Option<JsValue>` behind an
711/// `UnsafeCell`.
712///
713/// # Why this type exists
714///
715/// `WebGpuRenderer::pending_error` needs interior mutability
716/// because:
717///
718/// 1. `pop_error_sync` takes `&self` (the WebGPU hot path cannot
719///    be `async`), but the spawned `wasm_bindgen_futures::spawn_local`
720///    future must mutate the slot to store the resolved
721///    `Promise<GPUError?>` value.
722/// 2. `take_last_error` also takes `&self` and drains the slot
723///    on the next render tick.
724///
725/// The first implementation used `Rc<RefCell<Option<JsValue>>>`,
726/// which works but pays for:
727///
728/// - a `RefCell::borrow_mut` runtime borrow check on every
729///   write (the panic path is unreachable in practice — only
730///   the spawn_local future and `take_last_error` ever touch
731///   the slot, and they never overlap because the future is
732///   a microtask drained before the next render tick).
733/// - a heap allocation for the `RefCell`'s borrow state.
734///
735/// The newtype keeps the interior-mutability primitive (`Rc`),
736/// because the spawn_local future needs its own owning handle,
737/// but swaps the inner cell from `RefCell` to `UnsafeCell`:
738///
739/// - zero runtime borrow check (the WASM single-threaded
740///   scheduler makes the borrow impossible to violate).
741/// - zero allocation (the cell is just a `*mut Option<JsValue>`
742///   sitting inside the `Rc`-managed box).
743///
744/// # Sync safety
745///
746/// `PendingErrorCell` is **not** `Sync` by default (`UnsafeCell`
747/// explicitly opts out). We hand-implement `Sync` for it because
748/// the renderer is only ever used in the WASM single-threaded
749/// runtime; the `Rc` ensures the same instance is never shared
750/// across threads (it is not `Send`/`Sync` either), and the
751/// WASM main thread is the only place that ever touches the
752/// slot. This matches euv-core's pattern
753/// (`unsafe impl Sync for HandlerRegistryCell {}`).
754///
755/// If the engine is ever compiled for a multi-threaded target
756/// (native, `wasm-bindgen-rayon`), this `unsafe impl Sync` is
757/// unsound and must be removed.
758pub struct PendingErrorCell(
759    /// Interior-mutable storage for the optional `JsValue`.
760    ///
761    /// Marked `pub(crate)` (not just `pub`) because the field is
762    /// only meant to be touched from inside the renderer module —
763    /// specifically from the `impl PendingErrorCell` block in
764    /// `impl.rs`. The struct itself stays `pub` so external code
765    /// can name the type, but the raw `UnsafeCell` is an
766    /// implementation detail.
767    pub(crate) UnsafeCell<Option<JsValue>>,
768);
769
770/// A single entry inside a `GpuBindGroupLayoutDescriptor`.
771///
772/// Together these describe one slot of the bind group layout used by
773/// a render / compute pipeline. The visibility bitmask controls
774/// which shader stages can read the binding (`VERTEX = 0x1`,
775/// `FRAGMENT = 0x2`, `COMPUTE = 0x4`); `VERTEX | FRAGMENT = 0x3` and
776/// `VERTEX | FRAGMENT | COMPUTE = 0x7` are the most common values.
777#[derive(Clone, Debug)]
778pub struct BindGroupLayoutEntry {
779    /// The binding slot (matches `@binding(N)` in the shader).
780    pub binding: u32,
781    /// Visibility bitmask (`VERTEX = 0x1`, `FRAGMENT = 0x2`, `COMPUTE = 0x4`).
782    pub visibility: u32,
783    /// The resource kind bound at this slot.
784    pub ty: BindGroupEntryType,
785}