-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Naga mesh shader WGSL writer #8481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Slightlyclueless
wants to merge
113
commits into
gfx-rs:trunk
Choose a base branch
from
Slightlyclueless:naga-mesh-wgpu-writer
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+288
−29
Open
Changes from all commits
Commits
Show all changes
113 commits
Select commit
Hold shift + click to select a range
1c90d19
Initial commit
inner-daemons 8c3e550
Other initial changes
inner-daemons 85bbc5a
Updated shader snapshots
inner-daemons ccf8467
Added new HLSL limitation
inner-daemons e55c02f
Moved error to global variable error
inner-daemons f3a31a4
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 0f6da75
Added docs to per_primitive
inner-daemons 3017214
Added a little bit more docs here and there in IR
inner-daemons 19b55b5
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 198437b
Adding validation to ensure that task shaders have a task payload
inner-daemons 64000e4
Updated spec to reflect the change to payload variables
inner-daemons 0575e98
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons b572ec7
Updated the mesh shading spec because it was goofy
inner-daemons 34d0411
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 02664e4
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 7bec4dd
some doc tweaks
jimblandy 2fcb853
Tried to clarify docs a little
inner-daemons 3009b5a
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 8bfe106
Tried to update spec
inner-daemons 6ccaeec
Removed a warning
inner-daemons 5b7ba11
Addressed comment about docs mistake
inner-daemons 29c6972
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 63fa8b5
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 26c8681
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons d9cac9c
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons c112cb4
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons e1ff67d
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 64644f7
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 739948b
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 7ca25a4
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 09ddbec
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 2d6a647
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 4657646
Review in progress
jimblandy 41b654c
mesh_shading.md: more tweaks
jimblandy 33ed0a6
Ran cargo fmt
inner-daemons 53ecb39
Small tweaks
inner-daemons 0ead329
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 07bfb1f
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons ba51fa2
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons c4e3eef
[naga] Move definition of `ShaderStage::compute_like` to `proc`.
jimblandy 8c9287d
Replace TODO comment with followup issue.
jimblandy 8f04d4f
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons 3a8399d
Update analyzer.rs
inner-daemons 879b79b
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons d92fe67
Removed stuff in accordance with Jim's recommendation
inner-daemons 2dc4090
minor changes for readability
jimblandy 1ec734b
Pull mesh shader output type validation out into its own function.
jimblandy 9ef0ed5
doc fixes
jimblandy 1173b0f
remove duplicated task payload validation
jimblandy 258e7e6
Quick little changes
inner-daemons 8885c5d
Another quick fix
inner-daemons 1cc3e85
Quick fix
inner-daemons 3be2c25
Removed unnecessary TODO statement
inner-daemons 21d3cc7
A
inner-daemons d5c11d3
Tried to be more expressive
inner-daemons 82ec9c2
Merge branch 'trunk' into mesh-shading/naga-ir
inner-daemons e7faff6
Made functions only work in mesh shader entry points
inner-daemons 385535a
Various validation fix attempts
inner-daemons c3f9acd
Undid capabilities resize
inner-daemons d15ba19
WGSL PR is up :)
inner-daemons f14e0f0
Update naga/src/ir/mod.rs
inner-daemons 7e12d30
Update naga/src/front/wgsl/error.rs
inner-daemons ce517bb
Update naga/src/ir/mod.rs
inner-daemons 083959e
Other Erich suggestion
inner-daemons 16aa7d0
Updated docs & validation for some builtins
inner-daemons 76bfca0
Added some docs & removed contentious "// TODO"
inner-daemons e68d0d2
Merge branch 'trunk' into mesh-shading/wgsl-parse
inner-daemons 0fd0fdd
Merge remote-tracking branch 'upstream/trunk' into mesh-shading/wgsl-…
inner-daemons e100034
Fixed bad validation, formatted mesh shader wgsl
inner-daemons edea07e
Rewrote the IR and parser significantly
inner-daemons 3905ae8
Improved validation slightly, remvoed obselete crap, fixed bug in com…
inner-daemons 8f6332d
Merge remote-tracking branch 'upstream/trunk' into mesh-shading/wgsl-…
inner-daemons 64798dd
Added changelog entry
inner-daemons c8dccd3
Merge branch 'trunk' into mesh-shading/wgsl-parse
inner-daemons bd923cd
Made parser respect enable extension
inner-daemons d95070a
Updated mesh shader spec
inner-daemons ace7e17
Cleaned up the mesh shader analyzer function
inner-daemons f55dbd9
Merge branch 'trunk' into mesh-shading/wgsl-parse
inner-daemons 5bd8385
Initial Commit
Slightlyclueless 3de9940
cargo fmt
Slightlyclueless 8fd04d4
Yeet unused import
Slightlyclueless 3b7ad04
I Forgor
Slightlyclueless 74ba2fc
Fix merge conflicts
Slightlyclueless d69df5e
Fix per primitive stuff
Slightlyclueless b797538
Add task payload storage class
Slightlyclueless e5438e3
Change snapshot
Slightlyclueless 694a188
Write the mesh output variable, in quite possibly the laziest way
Slightlyclueless c131238
Correct feature detection. I hope...
Slightlyclueless 250ae47
Merge branch 'trunk' into naga-mesh-wgpu-writer
Slightlyclueless 9d78bc5
setting the variable got dropped in merge conflicts...
Slightlyclueless 03445d2
Commit snapshots
Slightlyclueless f0b9a9e
Add space to mesh stage output attr
Slightlyclueless e19131d
Didn't need to change that
Slightlyclueless a1008bf
Chage to pushing to mutable vecs instead of clunky if-else blocks
Slightlyclueless 93d051a
Change TOMLs
Slightlyclueless fdb3abb
Change feature name
Slightlyclueless c3522c4
Custom WGSL attribute for Mesh Stage
Slightlyclueless 7ef6a46
Delete random newline
Slightlyclueless 6be03c5
Cargo FMT
Slightlyclueless 8f23552
cargo xtask test output
Slightlyclueless acf055f
Change mesh shader feature name
Slightlyclueless ade61e9
Remove shader stage check, it's no longer needed
Slightlyclueless c445f71
Revert "Cargo FMT"
Slightlyclueless c9c9910
cargo xtask test
Slightlyclueless 42465ae
Missed these two builtins
Slightlyclueless 95b8923
Cargo Xtask Test
Slightlyclueless dfd5f9d
cargo + taplo format
Slightlyclueless 78aeb58
Taplo format, but for real this time
Slightlyclueless cdf5ddf
Add task var checking
Slightlyclueless cef35d2
redo how imported
Slightlyclueless f2ee2ec
Change to unreachable
Slightlyclueless 86d75b0
Give Builtins a sensible order
Slightlyclueless b236293
Changelog entry
Slightlyclueless File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,9 @@ enum Attribute { | |
| BlendSrc(u32), | ||
| Stage(ShaderStage), | ||
| WorkGroupSize([u32; 3]), | ||
| MeshStage(String), | ||
| TaskPayload(String), | ||
| PerPrimitive, | ||
| } | ||
|
|
||
| /// The WGSL form that `write_expr_with_indirection` should use to render a Naga | ||
|
|
@@ -207,9 +210,37 @@ impl<W: Write> Writer<W> { | |
| Attribute::Stage(ShaderStage::Compute), | ||
| Attribute::WorkGroupSize(ep.workgroup_size), | ||
| ], | ||
| ShaderStage::Mesh | ShaderStage::Task => unreachable!(), | ||
| ShaderStage::Mesh => { | ||
| let mesh_output_name = module.global_variables | ||
| [ep.mesh_info.as_ref().unwrap().output_variable] | ||
| .name | ||
| .clone() | ||
| .unwrap(); | ||
| let mut mesh_attrs = vec![ | ||
| Attribute::MeshStage(mesh_output_name), | ||
| Attribute::WorkGroupSize(ep.workgroup_size), | ||
| ]; | ||
| if ep.task_payload.is_some() { | ||
| let payload_name = module.global_variables[ep.task_payload.unwrap()] | ||
| .name | ||
| .clone() | ||
| .unwrap(); | ||
| mesh_attrs.push(Attribute::TaskPayload(payload_name)); | ||
| } | ||
| mesh_attrs | ||
| } | ||
| ShaderStage::Task => { | ||
| let payload_name = module.global_variables[ep.task_payload.unwrap()] | ||
| .name | ||
| .clone() | ||
| .unwrap(); | ||
| vec![ | ||
| Attribute::Stage(ShaderStage::Task), | ||
| Attribute::TaskPayload(payload_name), | ||
| Attribute::WorkGroupSize(ep.workgroup_size), | ||
| ] | ||
| } | ||
| }; | ||
|
|
||
| self.write_attributes(&attributes)?; | ||
| // Add a newline after attribute | ||
| writeln!(self.out)?; | ||
|
|
@@ -243,6 +274,7 @@ impl<W: Write> Writer<W> { | |
| let mut needs_f16 = false; | ||
| let mut needs_dual_source_blending = false; | ||
| let mut needs_clip_distances = false; | ||
| let mut needs_mesh_shaders = false; | ||
|
|
||
| // Determine which `enable` declarations are needed | ||
| for (_, ty) in module.types.iter() { | ||
|
|
@@ -263,6 +295,25 @@ impl<W: Write> Writer<W> { | |
| crate::Binding::BuiltIn(crate::BuiltIn::ClipDistance) => { | ||
| needs_clip_distances = true; | ||
| } | ||
| crate::Binding::Location { | ||
| per_primitive: true, | ||
| .. | ||
| } => { | ||
| needs_mesh_shaders = true; | ||
| } | ||
| crate::Binding::BuiltIn( | ||
| crate::BuiltIn::MeshTaskSize | ||
| | crate::BuiltIn::CullPrimitive | ||
| | crate::BuiltIn::PointIndex | ||
| | crate::BuiltIn::LineIndices | ||
| | crate::BuiltIn::TriangleIndices | ||
| | crate::BuiltIn::VertexCount | ||
| | crate::BuiltIn::Vertices | ||
| | crate::BuiltIn::PrimitiveCount | ||
| | crate::BuiltIn::Primitives, | ||
| ) => { | ||
| needs_mesh_shaders = true; | ||
| } | ||
| _ => {} | ||
| } | ||
| } | ||
|
|
@@ -271,6 +322,22 @@ impl<W: Write> Writer<W> { | |
| } | ||
| } | ||
|
|
||
| if module | ||
| .entry_points | ||
| .iter() | ||
| .any(|ep| matches!(ep.stage, ShaderStage::Mesh | ShaderStage::Task)) | ||
| { | ||
| needs_mesh_shaders = true; | ||
| } | ||
|
|
||
| if module | ||
| .global_variables | ||
| .iter() | ||
| .any(|gv| gv.1.space == crate::AddressSpace::TaskPayload) | ||
| { | ||
| needs_mesh_shaders = true; | ||
| } | ||
|
Comment on lines
+333
to
+339
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good stuff
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😄 I literally wrote that about 5 minutes before rushing out the door to go to work, was surprised it even compiled |
||
|
|
||
| // Write required declarations | ||
| let mut any_written = false; | ||
| if needs_f16 { | ||
|
|
@@ -285,6 +352,10 @@ impl<W: Write> Writer<W> { | |
| writeln!(self.out, "enable clip_distances;")?; | ||
| any_written = true; | ||
| } | ||
| if needs_mesh_shaders { | ||
| writeln!(self.out, "enable wgpu_mesh_shader;")?; | ||
| any_written = true; | ||
| } | ||
| if any_written { | ||
| // Empty line for readability | ||
| writeln!(self.out)?; | ||
|
|
@@ -403,8 +474,10 @@ impl<W: Write> Writer<W> { | |
| ShaderStage::Vertex => "vertex", | ||
| ShaderStage::Fragment => "fragment", | ||
| ShaderStage::Compute => "compute", | ||
| ShaderStage::Task | ShaderStage::Mesh => unreachable!(), | ||
| ShaderStage::Task => "task", | ||
| ShaderStage::Mesh => unreachable!(), | ||
| }; | ||
|
|
||
| write!(self.out, "@{stage_str} ")?; | ||
| } | ||
| Attribute::WorkGroupSize(size) => { | ||
|
|
@@ -433,6 +506,13 @@ impl<W: Write> Writer<W> { | |
| write!(self.out, "@interpolate({interpolation}) ")?; | ||
| } | ||
| } | ||
| Attribute::MeshStage(ref name) => { | ||
| write!(self.out, "@mesh({name}) ")?; | ||
| } | ||
| Attribute::TaskPayload(ref payload_name) => { | ||
| write!(self.out, "@payload({payload_name}) ")?; | ||
| } | ||
| Attribute::PerPrimitive => write!(self.out, "@per_primitive ")?, | ||
| }; | ||
| } | ||
| Ok(()) | ||
|
|
@@ -1822,21 +1902,33 @@ fn map_binding_to_attribute(binding: &crate::Binding) -> Vec<Attribute> { | |
| interpolation, | ||
| sampling, | ||
| blend_src: None, | ||
| per_primitive: _, | ||
| } => vec![ | ||
| Attribute::Location(location), | ||
| Attribute::Interpolate(interpolation, sampling), | ||
| ], | ||
| per_primitive, | ||
| } => { | ||
| let mut attrs = vec![ | ||
| Attribute::Location(location), | ||
| Attribute::Interpolate(interpolation, sampling), | ||
| ]; | ||
| if per_primitive { | ||
| attrs.push(Attribute::PerPrimitive); | ||
| } | ||
| attrs | ||
| } | ||
| crate::Binding::Location { | ||
| location, | ||
| interpolation, | ||
| sampling, | ||
| blend_src: Some(blend_src), | ||
| per_primitive: _, | ||
| } => vec![ | ||
| Attribute::Location(location), | ||
| Attribute::BlendSrc(blend_src), | ||
| Attribute::Interpolate(interpolation, sampling), | ||
| ], | ||
| per_primitive, | ||
| } => { | ||
| let mut attrs = vec![ | ||
| Attribute::Location(location), | ||
| Attribute::BlendSrc(blend_src), | ||
| Attribute::Interpolate(interpolation, sampling), | ||
| ]; | ||
| if per_primitive { | ||
| attrs.push(Attribute::PerPrimitive); | ||
| } | ||
| attrs | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,2 @@ | ||
| god_mode = true | ||
| targets = "IR | ANALYSIS" | ||
| targets = "IR | ANALYSIS | WGSL" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,2 @@ | ||
| god_mode = true | ||
| targets = "IR | ANALYSIS" | ||
| targets = "IR | ANALYSIS | WGSL" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,2 @@ | ||
| god_mode = true | ||
| targets = "IR | ANALYSIS" | ||
| targets = "IR | ANALYSIS | WGSL" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,2 @@ | ||
| god_mode = true | ||
| targets = "IR | ANALYSIS" | ||
| targets = "IR | ANALYSIS | WGSL" | ||
Slightlyclueless marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| enable wgpu_mesh_shader; | ||
|
|
||
| struct TaskPayload { | ||
| dummy: u32, | ||
| } | ||
|
|
||
| struct VertexOutput { | ||
| @builtin(position) position: vec4<f32>, | ||
| } | ||
|
|
||
| struct PrimitiveOutput { | ||
| @builtin(triangle_indices) indices: vec3<u32>, | ||
| } | ||
|
|
||
| struct MeshOutput { | ||
| @builtin(vertices) vertices: array<VertexOutput, 3>, | ||
| @builtin(primitives) primitives: array<PrimitiveOutput, 1>, | ||
| @builtin(vertex_count) vertex_count: u32, | ||
| @builtin(primitive_count) primitive_count: u32, | ||
| } | ||
|
|
||
| var<task_payload> taskPayload: TaskPayload; | ||
| var<workgroup> mesh_output: MeshOutput; | ||
|
|
||
| @task @payload(taskPayload) @workgroup_size(1, 1, 1) | ||
| fn ts_main() -> @builtin(mesh_task_size) vec3<u32> { | ||
| return vec3<u32>(1u, 1u, 1u); | ||
| } | ||
|
|
||
| @mesh(mesh_output) @workgroup_size(1, 1, 1) @payload(taskPayload) | ||
| fn ms_main() { | ||
| return; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| enable wgpu_mesh_shader; | ||
|
|
||
| struct TaskPayload { | ||
| dummy: u32, | ||
| } | ||
|
|
||
| struct VertexOutput { | ||
| @builtin(position) position: vec4<f32>, | ||
| } | ||
|
|
||
| struct PrimitiveOutput { | ||
| @builtin(line_indices) indices: vec2<u32>, | ||
| } | ||
|
|
||
| struct MeshOutput { | ||
| @builtin(vertices) vertices: array<VertexOutput, 2>, | ||
| @builtin(primitives) primitives: array<PrimitiveOutput, 1>, | ||
| @builtin(vertex_count) vertex_count: u32, | ||
| @builtin(primitive_count) primitive_count: u32, | ||
| } | ||
|
|
||
| var<task_payload> taskPayload: TaskPayload; | ||
| var<workgroup> mesh_output: MeshOutput; | ||
|
|
||
| @task @payload(taskPayload) @workgroup_size(1, 1, 1) | ||
| fn ts_main() -> @builtin(mesh_task_size) vec3<u32> { | ||
| return vec3<u32>(1u, 1u, 1u); | ||
| } | ||
|
|
||
| @mesh(mesh_output) @workgroup_size(1, 1, 1) @payload(taskPayload) | ||
| fn ms_main() { | ||
| return; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| enable wgpu_mesh_shader; | ||
|
|
||
| struct TaskPayload { | ||
| dummy: u32, | ||
| } | ||
|
|
||
| struct VertexOutput { | ||
| @builtin(position) position: vec4<f32>, | ||
| } | ||
|
|
||
| struct PrimitiveOutput { | ||
| @builtin(point_index) indices: u32, | ||
| } | ||
|
|
||
| struct MeshOutput { | ||
| @builtin(vertices) vertices: array<VertexOutput, 1>, | ||
| @builtin(primitives) primitives: array<PrimitiveOutput, 1>, | ||
| @builtin(vertex_count) vertex_count: u32, | ||
| @builtin(primitive_count) primitive_count: u32, | ||
| } | ||
|
|
||
| var<task_payload> taskPayload: TaskPayload; | ||
| var<workgroup> mesh_output: MeshOutput; | ||
|
|
||
| @task @payload(taskPayload) @workgroup_size(1, 1, 1) | ||
| fn ts_main() -> @builtin(mesh_task_size) vec3<u32> { | ||
| return vec3<u32>(1u, 1u, 1u); | ||
| } | ||
|
|
||
| @mesh(mesh_output) @workgroup_size(1, 1, 1) @payload(taskPayload) | ||
| fn ms_main() { | ||
| return; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.