Skip to content

Commit 8cd4bd6

Browse files
committed
[naga hlsl-out] Handle external texture color space conversion
This adds several fields to `ExternalTextureDescriptor`, specifying how to handle color space conversion for an external texture. These fields consist of transfer functions for the source and destination color spaces, and a matrix for converting between gamuts. This allows `ImageSample` and `ImageLoad` operations on external textures to return values in a desired destination color space rather than the source color space of the underlying planes. These fields are plumbed through to the `ExternalTextureParams` uniform buffer from which they are exposed to the shader. Following conversion from YUV to RGB after sampling/loading from the external texture planes, the shader uses them to gamma decode to linear RGB in the source color space, convert from source to destination gamut, then finally gamma encode to non-linear RGB in the destination color space.
1 parent 88a8147 commit 8cd4bd6

45 files changed

Lines changed: 604 additions & 98 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

naga/src/back/hlsl/help.rs

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,61 @@ impl<W: Write> super::Writer<'_, W> {
264264
Ok(())
265265
}
266266

267+
/// Helper function used by [`Self::write_wrapped_image_load_function`] and
268+
/// [`Self::write_wrapped_image_sample_function`] to write the shared YUV
269+
/// to RGB conversion code for external textures. Expects the preceding
270+
/// code to declare the Y component as a `float` variable of name `y`, the
271+
/// UV components as a `float2` variable of name `uv`, and the external
272+
/// texture params as a variable of name `params`. The emitted code will
273+
/// return the result.
274+
fn write_convert_yuv_to_rgb_and_return(
275+
&mut self,
276+
level: crate::back::Level,
277+
y: &str,
278+
uv: &str,
279+
params: &str,
280+
) -> BackendResult {
281+
let l1 = level;
282+
let l2 = l1.next();
283+
284+
// Convert from YUV to non-linear RGB in the source color space. We
285+
// declare our matrices as row_major in HLSL, therefore we must reverse
286+
// the order of this multiplication
287+
writeln!(
288+
self.out,
289+
"{l1}float3 srcGammaRgb = mul(float4({y}, {uv}, 1.0), {params}.yuv_conversion_matrix).rgb;"
290+
)?;
291+
292+
// Apply the inverse of the source transfer function to convert to
293+
// linear RGB in the source color space.
294+
writeln!(
295+
self.out,
296+
"{l1}float3 srcLinearRgb = srcGammaRgb < {params}.src_tf.k * {params}.src_tf.b ?"
297+
)?;
298+
writeln!(self.out, "{l2}srcGammaRgb / {params}.src_tf.k :")?;
299+
writeln!(self.out, "{l2}pow((srcGammaRgb + {params}.src_tf.a - 1.0) / {params}.src_tf.a, {params}.src_tf.g);")?;
300+
301+
// Multiply by the gamut conversion matrix to convert to linear RGB in
302+
// the destination color space. We declare our matrices as row_major in
303+
// HLSL, therefore we must reverse the order of this multiplication.
304+
writeln!(
305+
self.out,
306+
"{l1}float3 dstLinearRgb = mul(srcLinearRgb, {params}.gamut_conversion_matrix);"
307+
)?;
308+
309+
// Finally, apply the dest transfer function to convert to non-linear
310+
// RGB in the destination color space, and return the result.
311+
writeln!(
312+
self.out,
313+
"{l1}float3 dstGammaRgb = dstLinearRgb < {params}.dst_tf.b ?"
314+
)?;
315+
writeln!(self.out, "{l2}{params}.dst_tf.k * dstLinearRgb :")?;
316+
writeln!(self.out, "{l2}{params}.dst_tf.a * pow(dstLinearRgb, 1.0 / {params}.dst_tf.g) - ({params}.dst_tf.a - 1);")?;
317+
318+
writeln!(self.out, "{l1}return float4(dstGammaRgb, 1.0);")?;
319+
Ok(())
320+
}
321+
267322
pub(super) fn write_wrapped_image_load_function(
268323
&mut self,
269324
module: &crate::Module,
@@ -346,12 +401,7 @@ impl<W: Write> super::Writer<'_, W> {
346401
writeln!(self.out, "{l3}uv = float2(plane1.Load(uint3(plane1_coords, 0u)).x, plane2.Load(uint3(plane2_coords, 0u)).x);")?;
347402
writeln!(self.out, "{l2}}}")?;
348403

349-
// Convert from YUV to RGB. We declare our matrices as row_major in HLSL,
350-
// therefore we must reverse the order of this multiplication
351-
writeln!(
352-
self.out,
353-
"{l2}return mul(float4(y, uv, 1.0), params.yuv_conversion_matrix);"
354-
)?;
404+
self.write_convert_yuv_to_rgb_and_return(l2, "y", "uv", "params")?;
355405

356406
writeln!(self.out, "{l1}}}")?;
357407
writeln!(self.out, "}}")?;
@@ -478,12 +528,8 @@ impl<W: Write> super::Writer<'_, W> {
478528
writeln!(self.out, "{l3}uv = float2(plane1.SampleLevel(samp, plane1_coords, 0.0f).x, plane2.SampleLevel(samp, plane2_coords, 0.0f).x);")?;
479529
writeln!(self.out, "{l2}}}")?;
480530

481-
// Convert from YUV to RGB. We declare our matrices as row_major in HLSL,
482-
// therefore we must reverse the order of this multiplication
483-
writeln!(
484-
self.out,
485-
"{l2}return mul(float4(y, uv, 1.0), params.yuv_conversion_matrix);"
486-
)?;
531+
self.write_convert_yuv_to_rgb_and_return(l2, "y", "uv", "params")?;
532+
487533
writeln!(self.out, "{l1}}}")?;
488534
writeln!(self.out, "}}")?;
489535
writeln!(self.out)?;

naga/src/back/wgsl/writer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ impl<W: Write> Writer<W> {
131131
.values()
132132
.any(|t| *t == ty)
133133
|| Some(ty) == module.special_types.external_texture_params
134+
|| Some(ty) == module.special_types.external_texture_transfer_function
134135
}
135136

136137
pub fn write(&mut self, module: &Module, info: &valid::ModuleInfo) -> BackendResult {

naga/src/compact/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ impl<'module> ModuleTracer<'module> {
381381
ref ray_vertex_return,
382382
ref predeclared_types,
383383
ref external_texture_params,
384+
ref external_texture_transfer_function,
384385
} = *special_types;
385386

386387
if let Some(ray_desc) = *ray_desc {
@@ -398,6 +399,9 @@ impl<'module> ModuleTracer<'module> {
398399
if let Some(external_texture_params) = *external_texture_params {
399400
self.types_used.insert(external_texture_params);
400401
}
402+
if let Some(external_texture_transfer_function) = *external_texture_transfer_function {
403+
self.types_used.insert(external_texture_transfer_function);
404+
}
401405
for (_, &handle) in predeclared_types {
402406
self.types_used.insert(handle);
403407
}
@@ -540,6 +544,7 @@ impl ModuleMap {
540544
ref mut ray_vertex_return,
541545
ref mut predeclared_types,
542546
ref mut external_texture_params,
547+
ref mut external_texture_transfer_function,
543548
} = *special;
544549

545550
if let Some(ref mut ray_desc) = *ray_desc {
@@ -557,6 +562,12 @@ impl ModuleMap {
557562
self.types.adjust(external_texture_params);
558563
}
559564

565+
if let Some(ref mut external_texture_transfer_function) =
566+
*external_texture_transfer_function
567+
{
568+
self.types.adjust(external_texture_transfer_function);
569+
}
570+
560571
for handle in predeclared_types.values_mut() {
561572
self.types.adjust(handle);
562573
}

naga/src/front/type_gen.rs

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -276,14 +276,29 @@ impl crate::Module {
276276
handle
277277
}
278278

279-
/// Generate [`SpecialTypes::external_texture_params`].
279+
/// Generate [`SpecialTypes::external_texture_params`] and
280+
/// [`SpecialTypes::external_texture_transfer_function`].
281+
///
282+
/// Other than the WGSL backend, every backend that supports external
283+
/// textures does so by lowering them to a set of ordinary textures and
284+
/// some parameters saying how to sample from them. These types are used
285+
/// for said parameters. Note that they are not used by the IR, but
286+
/// generated purely as a convenience for the backends.
280287
///
281288
/// [`SpecialTypes::external_texture_params`]: crate::ir::SpecialTypes::external_texture_params
282-
pub fn generate_external_texture_params_type(&mut self) -> Handle<crate::Type> {
283-
if let Some(handle) = self.special_types.external_texture_params {
284-
return handle;
289+
/// [`SpecialTypes::external_texture_transfer_function`]: crate::ir::SpecialTypes::external_texture_transfer_function
290+
pub fn generate_external_texture_types(&mut self) {
291+
if self.special_types.external_texture_params.is_some() {
292+
return;
285293
}
286294

295+
let ty_f32 = self.types.insert(
296+
crate::Type {
297+
name: None,
298+
inner: crate::TypeInner::Scalar(crate::Scalar::F32),
299+
},
300+
Span::UNDEFINED,
301+
);
287302
let ty_u32 = self.types.insert(
288303
crate::Type {
289304
name: None,
@@ -312,6 +327,17 @@ impl crate::Module {
312327
},
313328
Span::UNDEFINED,
314329
);
330+
let ty_mat3x3f = self.types.insert(
331+
crate::Type {
332+
name: None,
333+
inner: crate::TypeInner::Matrix {
334+
columns: crate::VectorSize::Tri,
335+
rows: crate::VectorSize::Tri,
336+
scalar: crate::Scalar::F32,
337+
},
338+
},
339+
Span::UNDEFINED,
340+
);
315341
let ty_mat4x4f = self.types.insert(
316342
crate::Type {
317343
name: None,
@@ -324,7 +350,44 @@ impl crate::Module {
324350
Span::UNDEFINED,
325351
);
326352

327-
let handle = self.types.insert(
353+
let transfer_fn_handle = self.types.insert(
354+
crate::Type {
355+
name: Some("NagaExternalTextureTransferFn".to_string()),
356+
inner: crate::TypeInner::Struct {
357+
members: vec![
358+
crate::StructMember {
359+
name: Some("a".to_string()),
360+
ty: ty_f32,
361+
binding: None,
362+
offset: 0,
363+
},
364+
crate::StructMember {
365+
name: Some("b".to_string()),
366+
ty: ty_f32,
367+
binding: None,
368+
offset: 4,
369+
},
370+
crate::StructMember {
371+
name: Some("g".to_string()),
372+
ty: ty_f32,
373+
binding: None,
374+
offset: 8,
375+
},
376+
crate::StructMember {
377+
name: Some("k".to_string()),
378+
ty: ty_f32,
379+
binding: None,
380+
offset: 12,
381+
},
382+
],
383+
span: 16,
384+
},
385+
},
386+
Span::UNDEFINED,
387+
);
388+
self.special_types.external_texture_transfer_function = Some(transfer_fn_handle);
389+
390+
let params_handle = self.types.insert(
328391
crate::Type {
329392
name: Some("NagaExternalTextureParams".to_string()),
330393
inner: crate::TypeInner::Struct {
@@ -335,39 +398,55 @@ impl crate::Module {
335398
binding: None,
336399
offset: 0,
337400
},
401+
crate::StructMember {
402+
name: Some("gamut_conversion_matrix".to_string()),
403+
ty: ty_mat3x3f,
404+
binding: None,
405+
offset: 64,
406+
},
407+
crate::StructMember {
408+
name: Some("src_tf".to_string()),
409+
ty: transfer_fn_handle,
410+
binding: None,
411+
offset: 112,
412+
},
413+
crate::StructMember {
414+
name: Some("dst_tf".to_string()),
415+
ty: transfer_fn_handle,
416+
binding: None,
417+
offset: 128,
418+
},
338419
crate::StructMember {
339420
name: Some("sample_transform".to_string()),
340421
ty: ty_mat3x2f,
341422
binding: None,
342-
offset: 64,
423+
offset: 144,
343424
},
344425
crate::StructMember {
345426
name: Some("load_transform".to_string()),
346427
ty: ty_mat3x2f,
347428
binding: None,
348-
offset: 88,
429+
offset: 168,
349430
},
350431
crate::StructMember {
351432
name: Some("size".to_string()),
352433
ty: ty_vec2u,
353434
binding: None,
354-
offset: 112,
435+
offset: 192,
355436
},
356437
crate::StructMember {
357438
name: Some("num_planes".to_string()),
358439
ty: ty_u32,
359440
binding: None,
360-
offset: 120,
441+
offset: 200,
361442
},
362443
],
363-
span: 128,
444+
span: 208,
364445
},
365446
},
366447
Span::UNDEFINED,
367448
);
368-
369-
self.special_types.external_texture_params = Some(handle);
370-
handle
449+
self.special_types.external_texture_params = Some(params_handle);
371450
}
372451

373452
/// Populate this module's [`SpecialTypes::predeclared_types`] type and return the handle.

naga/src/front/wgsl/lower/mod.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3991,14 +3991,15 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
39913991
// sample from them. We don't know which backend will
39923992
// consume the `Module` we're building, but in case it's not
39933993
// WGSL, populate `SpecialTypes::external_texture_params`
3994-
// with the type the backend will use for the parameter
3994+
// and `SpecialTypes::external_texture_transfer_function`
3995+
// with the types the backend will use for the parameter
39953996
// buffer.
39963997
//
3997-
// This is *not* the type we are lowering here: that's an
3998-
// ordinary `TypeInner::Image`. But the fact we are
3999-
// lowering a `texture_external` implies the backends may
4000-
// need `SpecialTypes::external_texture_params` too.
4001-
ctx.module.generate_external_texture_params_type();
3998+
// Neither of these are the type we are lowering here:
3999+
// that's an ordinary `TypeInner::Image`. But the fact we
4000+
// are lowering a `texture_external` implies the backends
4001+
// may need these additional types too.
4002+
ctx.module.generate_external_texture_types();
40024003
}
40034004
ir::TypeInner::Image {
40044005
dim,

naga/src/ir/mod.rs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2369,19 +2369,42 @@ pub struct SpecialTypes {
23692369
/// In WGSL, this type would be:
23702370
///
23712371
/// ```ignore
2372-
/// struct NagaExternalTextureParams { // align size offset
2373-
/// yuv_conversion_matrix: mat4x4<f32>, // 16 64 0
2374-
/// sample_transform: mat3x2<f32>, // 8 24 64
2375-
/// load_transform: mat3x2<f32>, // 8 24 88
2376-
/// size: vec2<u32>, // 8 8 112
2377-
/// num_planes: u32, // 4 4 120
2378-
/// } // whole struct: 16 128
2372+
/// struct NagaExternalTextureParams { // align size offset
2373+
/// yuv_conversion_matrix: mat4x4<f32>, // 16 64 0
2374+
/// gamut_conversion_matrix: mat3x3<f32>, // 16 48 64
2375+
/// src_tf: NagaExternalTextureTransferFn, // 4 16 112
2376+
/// dst_tf: NagaExternalTextureTransferFn, // 4 16 128
2377+
/// sample_transform: mat3x2<f32>, // 8 24 144
2378+
/// load_transform: mat3x2<f32>, // 8 24 168
2379+
/// size: vec2<u32>, // 8 8 192
2380+
/// num_planes: u32, // 4 4 200
2381+
/// } // whole struct: 16 208
23792382
/// ```
23802383
///
2381-
/// Call [`Module::generate_external_texture_params_type`] to populate this
2382-
/// if needed and return the handle.
2384+
/// Call [`Module::generate_external_texture_types`] to populate this if
2385+
/// needed.
23832386
pub external_texture_params: Option<Handle<Type>>,
23842387

2388+
/// Struct describing a gamma encoding transfer function. Member of
2389+
/// `NagaExternalTextureParams`, describing how the backend should perform
2390+
/// color space conversion when sampling from [`ImageClass::External`]
2391+
/// textures.
2392+
///
2393+
/// In WGSL, this type would be:
2394+
///
2395+
/// ```ignore
2396+
/// struct NagaExternalTextureTransferFn { // align size offset
2397+
/// a: f32, // 4 4 0
2398+
/// b: f32, // 4 4 4
2399+
/// g: f32, // 4 4 8
2400+
/// k: f32, // 4 4 12
2401+
/// } // whole struct: 4 16
2402+
/// ```
2403+
///
2404+
/// Call [`Module::generate_external_texture_types`] to populate this if
2405+
/// needed.
2406+
pub external_texture_transfer_function: Option<Handle<Type>>,
2407+
23852408
/// Types for predeclared wgsl types instantiated on demand.
23862409
///
23872410
/// Call [`Module::generate_predeclared_type`] to populate this if

0 commit comments

Comments
 (0)