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