@@ -101,6 +101,36 @@ pub enum OffscreenTargetError {
101101 DeviceError ( String ) ,
102102}
103103
104+ impl std:: fmt:: Display for OffscreenTargetError {
105+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
106+ return match self {
107+ OffscreenTargetError :: MissingColorAttachment => {
108+ write ! ( f, "Missing color attachment configuration" )
109+ }
110+ OffscreenTargetError :: InvalidSize { width, height } => write ! (
111+ f,
112+ "Invalid offscreen target size {}x{} (width and height must be > 0)" ,
113+ width, height
114+ ) ,
115+ OffscreenTargetError :: UnsupportedSampleCount { requested } => {
116+ write ! (
117+ f,
118+ "Unsupported sample count {} (allowed: 1, 2, 4, 8)" ,
119+ requested
120+ )
121+ }
122+ OffscreenTargetError :: UnsupportedFormat { message } => {
123+ write ! ( f, "Unsupported format: {}" , message)
124+ }
125+ OffscreenTargetError :: DeviceError ( message) => {
126+ write ! ( f, "Device error: {}" , message)
127+ }
128+ } ;
129+ }
130+ }
131+
132+ impl std:: error:: Error for OffscreenTargetError { }
133+
104134/// Builder for creating an `OffscreenTarget`.
105135pub struct OffscreenTargetBuilder {
106136 label : Option < String > ,
@@ -207,7 +237,20 @@ impl OffscreenTargetBuilder {
207237 let resolve_texture = match color_builder. build ( gpu) {
208238 Ok ( texture) => texture,
209239 Err ( message) => {
210- return Err ( OffscreenTargetError :: DeviceError ( message. to_string ( ) ) ) ;
240+ const UNSUPPORTED_FORMAT_MESSAGE : & str =
241+ "Texture format does not support bytes_per_pixel calculation" ;
242+ let error_message = message. to_string ( ) ;
243+ if message == UNSUPPORTED_FORMAT_MESSAGE {
244+ return Err ( OffscreenTargetError :: UnsupportedFormat {
245+ message : error_message,
246+ } ) ;
247+ }
248+
249+ let label = self . label . as_deref ( ) . unwrap_or ( "unnamed offscreen target" ) ;
250+ return Err ( OffscreenTargetError :: DeviceError ( format ! (
251+ "Failed to build resolve color texture for '{}': {}" ,
252+ label, error_message
253+ ) ) ) ;
211254 }
212255 } ;
213256
@@ -264,24 +307,15 @@ impl OffscreenTargetBuilder {
264307 }
265308}
266309
267- #[ deprecated(
268- note = "Use `lambda::render::targets::offscreen::OffscreenTarget` to avoid confusion with `lambda::render::targets::surface::RenderTarget`."
269- ) ]
270- pub type RenderTarget = OffscreenTarget ;
271-
272- #[ deprecated(
273- note = "Use `lambda::render::targets::offscreen::OffscreenTargetBuilder` to avoid confusion with `lambda::render::targets::surface::RenderTarget`."
274- ) ]
275- pub type RenderTargetBuilder = OffscreenTargetBuilder ;
276-
277- #[ deprecated(
278- note = "Use `lambda::render::targets::offscreen::OffscreenTargetError`."
279- ) ]
280- pub type RenderTargetError = OffscreenTargetError ;
281-
282310#[ cfg( test) ]
283311mod tests {
312+ use lambda_platform:: wgpu as platform;
313+
284314 use super :: * ;
315+ use crate :: render:: {
316+ gpu:: GpuBuilder ,
317+ instance:: InstanceBuilder ,
318+ } ;
285319
286320 /// Fails when the builder has a zero dimension.
287321 #[ test]
@@ -308,4 +342,145 @@ mod tests {
308342 let builder = OffscreenTargetBuilder :: new ( ) . with_multi_sample ( 0 ) ;
309343 assert_eq ! ( builder. sample_count, 1 ) ;
310344 }
345+
346+ fn create_test_gpu ( ) -> Option < Gpu > {
347+ let instance = InstanceBuilder :: new ( )
348+ . with_label ( "lambda-offscreen-target-test-instance" )
349+ . build ( ) ;
350+ return GpuBuilder :: new ( )
351+ . with_label ( "lambda-offscreen-target-test-gpu" )
352+ . build ( & instance, None )
353+ . ok ( ) ;
354+ }
355+
356+ #[ test]
357+ fn build_rejects_missing_color_attachment ( ) {
358+ let gpu = match create_test_gpu ( ) {
359+ Some ( gpu) => gpu,
360+ None => return ,
361+ } ;
362+
363+ let built = OffscreenTargetBuilder :: new ( ) . build ( & gpu) ;
364+ assert_eq ! (
365+ built. unwrap_err( ) ,
366+ OffscreenTargetError :: MissingColorAttachment
367+ ) ;
368+ }
369+
370+ #[ test]
371+ fn build_rejects_unsupported_sample_count ( ) {
372+ let gpu = match create_test_gpu ( ) {
373+ Some ( gpu) => gpu,
374+ None => return ,
375+ } ;
376+
377+ let built = OffscreenTargetBuilder :: new ( )
378+ . with_color ( texture:: TextureFormat :: Rgba8Unorm , 1 , 1 )
379+ . with_multi_sample ( 3 )
380+ . build ( & gpu) ;
381+
382+ assert_eq ! (
383+ built. unwrap_err( ) ,
384+ OffscreenTargetError :: UnsupportedSampleCount { requested: 3 }
385+ ) ;
386+ }
387+
388+ #[ test]
389+ fn resolve_texture_supports_sampling_and_render_attachment ( ) {
390+ let gpu = match create_test_gpu ( ) {
391+ Some ( gpu) => gpu,
392+ None => return ,
393+ } ;
394+
395+ let target = OffscreenTargetBuilder :: new ( )
396+ . with_color ( texture:: TextureFormat :: Rgba8Unorm , 4 , 4 )
397+ . with_label ( "offscreen-usage-test" )
398+ . build ( & gpu)
399+ . expect ( "build offscreen target" ) ;
400+
401+ let resolve_platform_texture = target. color_texture ( ) . platform_texture ( ) ;
402+
403+ let sampler = platform:: texture:: SamplerBuilder :: new ( )
404+ . nearest_clamp ( )
405+ . with_label ( "offscreen-usage-sampler" )
406+ . build ( gpu. platform ( ) ) ;
407+
408+ let layout = platform:: bind:: BindGroupLayoutBuilder :: new ( )
409+ . with_sampled_texture_2d ( 1 , platform:: bind:: Visibility :: Fragment )
410+ . with_sampler ( 2 , platform:: bind:: Visibility :: Fragment )
411+ . build ( gpu. platform ( ) ) ;
412+
413+ let _group = platform:: bind:: BindGroupBuilder :: new ( )
414+ . with_layout ( & layout)
415+ . with_texture ( 1 , resolve_platform_texture. as_ref ( ) )
416+ . with_sampler ( 2 , & sampler)
417+ . build ( gpu. platform ( ) ) ;
418+
419+ let mut encoder = platform:: command:: CommandEncoder :: new (
420+ gpu. platform ( ) ,
421+ Some ( "offscreen-usage-encoder" ) ,
422+ ) ;
423+ {
424+ let mut attachments =
425+ platform:: render_pass:: RenderColorAttachments :: new ( ) ;
426+ attachments. push_color ( target. resolve_view ( ) . to_platform ( ) ) ;
427+ let _pass = platform:: render_pass:: RenderPassBuilder :: new ( )
428+ . with_clear_color ( [ 0.0 , 0.0 , 0.0 , 1.0 ] )
429+ . build ( & mut encoder, & mut attachments, None , None , None , None ) ;
430+ }
431+
432+ let buffer = encoder. finish ( ) ;
433+ gpu. platform ( ) . submit ( std:: iter:: once ( buffer) ) ;
434+ }
435+
436+ #[ test]
437+ fn msaa_target_depth_attachment_matches_sample_count ( ) {
438+ let gpu = match create_test_gpu ( ) {
439+ Some ( gpu) => gpu,
440+ None => return ,
441+ } ;
442+
443+ let target = OffscreenTargetBuilder :: new ( )
444+ . with_color ( texture:: TextureFormat :: Rgba8Unorm , 4 , 4 )
445+ . with_depth ( texture:: DepthFormat :: Depth32Float )
446+ . with_multi_sample ( 4 )
447+ . with_label ( "offscreen-msaa-depth-test" )
448+ . build ( & gpu)
449+ . expect ( "build offscreen target" ) ;
450+
451+ let msaa_view = target. msaa_view ( ) . expect ( "MSAA view" ) ;
452+ let resolve_view = target. resolve_view ( ) ;
453+ let depth_view = target
454+ . depth_texture ( )
455+ . expect ( "depth texture" )
456+ . platform_view_ref ( ) ;
457+
458+ let mut encoder = platform:: command:: CommandEncoder :: new (
459+ gpu. platform ( ) ,
460+ Some ( "offscreen-msaa-depth-encoder" ) ,
461+ ) ;
462+ {
463+ let mut attachments =
464+ platform:: render_pass:: RenderColorAttachments :: new ( ) ;
465+ attachments
466+ . push_msaa_color ( msaa_view. to_platform ( ) , resolve_view. to_platform ( ) ) ;
467+ let depth_ops = Some ( platform:: render_pass:: DepthOperations {
468+ load : platform:: render_pass:: DepthLoadOp :: Clear ( 1.0 ) ,
469+ store : platform:: render_pass:: StoreOp :: Store ,
470+ } ) ;
471+ let _pass = platform:: render_pass:: RenderPassBuilder :: new ( )
472+ . with_clear_color ( [ 0.0 , 0.0 , 0.0 , 1.0 ] )
473+ . build (
474+ & mut encoder,
475+ & mut attachments,
476+ Some ( depth_view) ,
477+ depth_ops,
478+ None ,
479+ None ,
480+ ) ;
481+ }
482+
483+ let buffer = encoder. finish ( ) ;
484+ gpu. platform ( ) . submit ( std:: iter:: once ( buffer) ) ;
485+ }
311486}
0 commit comments