Skip to content

Commit 53c8a9f

Browse files
committed
[add] high level texture view and frame implementations.
1 parent 3103b09 commit 53c8a9f

3 files changed

Lines changed: 193 additions & 87 deletions

File tree

crates/lambda-rs/src/render/color_attachments.rs

Lines changed: 36 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@
66
77
use lambda_platform::wgpu as platform;
88

9-
use super::{
10-
render_pass::RenderPass as RenderPassDesc,
11-
RenderContext,
12-
};
9+
use super::surface::TextureView;
1310

1411
#[derive(Debug, Default)]
1512
/// High‑level color attachments collection used when beginning a render pass.
@@ -30,20 +27,26 @@ impl<'view> RenderColorAttachments<'view> {
3027
}
3128

3229
/// Append a color attachment targeting the provided texture view.
33-
pub(crate) fn push_color(
34-
&mut self,
35-
view: platform::surface::TextureViewRef<'view>,
36-
) {
37-
self.inner.push_color(view);
30+
///
31+
/// Accepts a high-level `TextureView` and converts it internally to the
32+
/// platform type.
33+
pub(crate) fn push_color(&mut self, view: TextureView<'view>) {
34+
self.inner.push_color(view.to_platform());
3835
}
3936

4037
/// Append a multi‑sampled color attachment with a resolve target view.
38+
///
39+
/// The `msaa_view` is the multi-sampled render target, and `resolve_view`
40+
/// is the single-sample target that receives the resolved output.
41+
/// Both accept high-level `TextureView` types and convert internally.
4142
pub(crate) fn push_msaa_color(
4243
&mut self,
43-
msaa_view: platform::surface::TextureViewRef<'view>,
44-
resolve_view: platform::surface::TextureViewRef<'view>,
44+
msaa_view: TextureView<'view>,
45+
resolve_view: TextureView<'view>,
4546
) {
46-
self.inner.push_msaa_color(msaa_view, resolve_view);
47+
self
48+
.inner
49+
.push_msaa_color(msaa_view.to_platform(), resolve_view.to_platform());
4750
}
4851

4952
/// Borrow the underlying platform attachments mutably for pass creation.
@@ -53,48 +56,35 @@ impl<'view> RenderColorAttachments<'view> {
5356
return &mut self.inner;
5457
}
5558

56-
/// Build color attachments for a surfacebacked render pass.
59+
/// Build color attachments for a surface-backed render pass.
5760
///
58-
/// This helper encapsulates the logic for configuring single‑sample and
59-
/// multi‑sample color attachments targeting the presentation surface,
60-
/// including creation and reuse of the MSAA resolve target stored on the
61-
/// `RenderContext`.
61+
/// This helper configures single-sample or multi-sample color attachments
62+
/// targeting the presentation surface. The MSAA view is optional and should
63+
/// be provided when multi-sampling is enabled.
64+
///
65+
/// # Arguments
66+
/// * `uses_color` - Whether the render pass uses color output.
67+
/// * `sample_count` - The MSAA sample count (1 for no MSAA).
68+
/// * `msaa_view` - Optional high-level MSAA texture view (required when
69+
/// `sample_count > 1`).
70+
/// * `surface_view` - The high-level surface texture view (resolve target
71+
/// for MSAA, or direct target for single-sample).
6272
pub(crate) fn for_surface_pass(
63-
render_context: &mut RenderContext,
64-
pass: &RenderPassDesc,
65-
surface_view: platform::surface::TextureViewRef<'view>,
73+
uses_color: bool,
74+
sample_count: u32,
75+
msaa_view: Option<TextureView<'view>>,
76+
surface_view: TextureView<'view>,
6677
) -> Self {
6778
let mut attachments = RenderColorAttachments::new();
68-
if !pass.uses_color() {
79+
80+
if !uses_color {
6981
return attachments;
7082
}
7183

72-
let sample_count = pass.sample_count();
7384
if sample_count > 1 {
74-
let need_recreate = match &render_context.msaa_color {
75-
Some(_existing) => render_context.msaa_sample_count != sample_count,
76-
None => true,
77-
};
78-
79-
if need_recreate {
80-
render_context.msaa_color = Some(
81-
platform::texture::ColorAttachmentTextureBuilder::new(
82-
render_context.config.format,
83-
)
84-
.with_size(render_context.size.0.max(1), render_context.size.1.max(1))
85-
.with_sample_count(sample_count)
86-
.with_label("lambda-msaa-color")
87-
.build(render_context.gpu()),
88-
);
89-
render_context.msaa_sample_count = sample_count;
90-
}
91-
92-
let msaa_view = render_context
93-
.msaa_color
94-
.as_ref()
95-
.expect("MSAA color attachment should be created")
96-
.view_ref();
97-
attachments.push_msaa_color(msaa_view, surface_view);
85+
let msaa =
86+
msaa_view.expect("MSAA view must be provided when sample_count > 1");
87+
attachments.push_msaa_color(msaa, surface_view);
9888
} else {
9989
attachments.push_color(surface_view);
10090
}

crates/lambda-rs/src/render/mod.rs

Lines changed: 79 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ pub mod vertex;
4444
pub mod viewport;
4545
pub mod window;
4646

47+
// Internal modules
48+
mod color_attachments;
49+
4750
use std::{
4851
collections::HashSet,
4952
iter,
@@ -398,6 +401,40 @@ impl RenderContext {
398401
return self.gpu.limits().min_uniform_buffer_offset_alignment;
399402
}
400403

404+
/// Ensure the MSAA color attachment texture exists with the given sample
405+
/// count, recreating it if necessary. Returns the texture view reference.
406+
///
407+
/// This method manages the lifecycle of the internal MSAA texture, creating
408+
/// or recreating it when the sample count changes.
409+
fn ensure_msaa_color_texture(
410+
&mut self,
411+
sample_count: u32,
412+
) -> platform::surface::TextureViewRef<'_> {
413+
let need_recreate = match &self.msaa_color {
414+
Some(_) => self.msaa_sample_count != sample_count,
415+
None => true,
416+
};
417+
418+
if need_recreate {
419+
self.msaa_color = Some(
420+
platform::texture::ColorAttachmentTextureBuilder::new(
421+
self.config.format.to_platform(),
422+
)
423+
.with_size(self.size.0.max(1), self.size.1.max(1))
424+
.with_sample_count(sample_count)
425+
.with_label("lambda-msaa-color")
426+
.build(self.gpu()),
427+
);
428+
self.msaa_sample_count = sample_count;
429+
}
430+
431+
return self
432+
.msaa_color
433+
.as_ref()
434+
.expect("MSAA color attachment should exist")
435+
.view_ref();
436+
}
437+
401438
/// Encode and submit GPU work for a single frame.
402439
fn render_internal(
403440
&mut self,
@@ -407,16 +444,18 @@ impl RenderContext {
407444
return Ok(());
408445
}
409446

410-
let mut frame = match self.surface.acquire_next_frame() {
411-
Ok(frame) => frame,
447+
let frame = match self.surface.acquire_next_frame() {
448+
Ok(frame) => surface::Frame::from_platform(frame),
412449
Err(err) => {
413450
let high_level_err = surface::SurfaceError::from(err);
414451
match high_level_err {
415452
surface::SurfaceError::Lost | surface::SurfaceError::Outdated => {
416453
self.reconfigure_surface(self.size)?;
417-
self.surface.acquire_next_frame().map_err(|e| {
418-
RenderError::Surface(surface::SurfaceError::from(e))
419-
})?
454+
let platform_frame =
455+
self.surface.acquire_next_frame().map_err(|e| {
456+
RenderError::Surface(surface::SurfaceError::from(e))
457+
})?;
458+
surface::Frame::from_platform(platform_frame)
420459
}
421460
_ => return Err(RenderError::Surface(high_level_err)),
422461
}
@@ -436,11 +475,17 @@ impl RenderContext {
436475
render_pass,
437476
viewport,
438477
} => {
439-
let pass = self.render_passes.get(render_pass).ok_or_else(|| {
440-
RenderError::Configuration(format!(
441-
"Unknown render pass {render_pass}"
442-
))
443-
})?;
478+
// Clone the render pass descriptor to avoid borrowing self while we
479+
// need mutable access for MSAA texture creation.
480+
let pass = self
481+
.render_passes
482+
.get(render_pass)
483+
.ok_or_else(|| {
484+
RenderError::Configuration(format!(
485+
"Unknown render pass {render_pass}"
486+
))
487+
})?
488+
.clone();
444489

445490
// Build (begin) the platform render pass using the builder API.
446491
let mut rp_builder = platform::render_pass::RenderPassBuilder::new();
@@ -464,39 +509,32 @@ impl RenderContext {
464509
rp_builder.with_store_op(platform::render_pass::StoreOp::Discard)
465510
}
466511
};
467-
// Create variably sized color attachments and begin the pass.
468-
let mut color_attachments =
469-
platform::render_pass::RenderColorAttachments::new();
512+
513+
// Ensure MSAA texture exists if needed.
470514
let sample_count = pass.sample_count();
471-
if pass.uses_color() {
472-
if sample_count > 1 {
473-
let need_recreate = match &self.msaa_color {
474-
Some(_) => self.msaa_sample_count != sample_count,
475-
None => true,
476-
};
477-
if need_recreate {
478-
self.msaa_color = Some(
479-
platform::texture::ColorAttachmentTextureBuilder::new(
480-
self.config.format.to_platform(),
481-
)
482-
.with_size(self.size.0.max(1), self.size.1.max(1))
483-
.with_sample_count(sample_count)
484-
.with_label("lambda-msaa-color")
485-
.build(self.gpu()),
486-
);
487-
self.msaa_sample_count = sample_count;
488-
}
489-
let msaa_view = self
490-
.msaa_color
491-
.as_ref()
492-
.expect("MSAA color attachment should be created")
493-
.view_ref();
494-
color_attachments.push_msaa_color(msaa_view, view);
495-
} else {
496-
color_attachments.push_color(view);
497-
}
515+
let uses_color = pass.uses_color();
516+
if uses_color && sample_count > 1 {
517+
self.ensure_msaa_color_texture(sample_count);
498518
}
499519

520+
// Create color attachments for the surface pass. The MSAA view is
521+
// retrieved here after the mutable borrow for texture creation ends.
522+
let msaa_view = if sample_count > 1 {
523+
self
524+
.msaa_color
525+
.as_ref()
526+
.map(|t| surface::TextureView::from_platform(t.view_ref()))
527+
} else {
528+
None
529+
};
530+
let mut color_attachments =
531+
color_attachments::RenderColorAttachments::for_surface_pass(
532+
uses_color,
533+
sample_count,
534+
msaa_view,
535+
view,
536+
);
537+
500538
// Depth/stencil attachment when either depth or stencil requested.
501539
let want_depth_attachment = Self::has_depth_attachment(
502540
pass.depth_operations(),
@@ -583,7 +621,7 @@ impl RenderContext {
583621

584622
let mut pass_encoder = rp_builder.build(
585623
&mut encoder,
586-
&mut color_attachments,
624+
color_attachments.as_platform_attachments_mut(),
587625
depth_view,
588626
depth_ops,
589627
stencil_ops,

crates/lambda-rs/src/render/surface.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,84 @@ use super::texture::{
55
TextureUsages,
66
};
77

8+
// ---------------------------------------------------------------------------
9+
// TextureView
10+
// ---------------------------------------------------------------------------
11+
12+
/// High-level reference to a texture view for render pass attachments.
13+
///
14+
/// This type wraps the platform `TextureViewRef` and provides a stable
15+
/// engine-level API for referencing texture views without exposing `wgpu`
16+
/// types at call sites.
17+
#[derive(Clone, Copy)]
18+
pub struct TextureView<'a> {
19+
inner: platform_surface::TextureViewRef<'a>,
20+
}
21+
22+
impl<'a> TextureView<'a> {
23+
/// Create a high-level texture view from a platform texture view reference.
24+
#[inline]
25+
pub(crate) fn from_platform(
26+
view: platform_surface::TextureViewRef<'a>,
27+
) -> Self {
28+
return TextureView { inner: view };
29+
}
30+
31+
/// Convert to the platform texture view reference for internal use.
32+
#[inline]
33+
pub(crate) fn to_platform(&self) -> platform_surface::TextureViewRef<'a> {
34+
return self.inner;
35+
}
36+
}
37+
38+
impl<'a> std::fmt::Debug for TextureView<'a> {
39+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40+
return f.debug_struct("TextureView").finish_non_exhaustive();
41+
}
42+
}
43+
44+
// ---------------------------------------------------------------------------
45+
// Frame
46+
// ---------------------------------------------------------------------------
47+
48+
/// A single acquired frame from the presentation surface.
49+
///
50+
/// This type wraps the platform `Frame` and provides access to its texture
51+
/// view for rendering. The frame must be presented after rendering is complete
52+
/// by calling `present()`.
53+
pub struct Frame {
54+
inner: platform_surface::Frame,
55+
}
56+
57+
impl Frame {
58+
/// Create a high-level frame from a platform frame.
59+
#[inline]
60+
pub(crate) fn from_platform(frame: platform_surface::Frame) -> Self {
61+
return Frame { inner: frame };
62+
}
63+
64+
/// Borrow the default texture view for rendering to this frame.
65+
#[inline]
66+
pub fn texture_view(&self) -> TextureView<'_> {
67+
return TextureView::from_platform(self.inner.texture_view());
68+
}
69+
70+
/// Present the frame to the swapchain.
71+
///
72+
/// This consumes the frame and submits it for display. After calling this
73+
/// method, the frame's texture is no longer valid for rendering.
74+
#[inline]
75+
pub fn present(self) {
76+
self.inner.present();
77+
}
78+
}
79+
80+
impl std::fmt::Debug for Frame {
81+
fn fmt(&self, frame: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82+
return frame.debug_struct("Frame").finish_non_exhaustive();
83+
}
84+
}
85+
886
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
987
pub enum PresentMode {
1088
/// Vsync enabled; frames wait for vertical blanking interval.

0 commit comments

Comments
 (0)