hdr: per-document Clip vs Highlight-rolloff output mode (Stage A pt 2)
Add a per-document HDR→SDR mapping applied at the final linear→sRGB encode, so
super-white (HDR) video highlights can be recovered instead of hard-clipped:
- core: HdrOutputMode {Clip (default), HighlightRolloff} on Document (serde
default), with a SetDocumentPropertiesAction variant for undoable edits.
- shaders: a fs_main_rolloff entry point (preview linear_to_srgb.wgsl + the export
inline shader) applying a C1 highlight knee — identity below 0.8, smooth rolloff
[0.8,∞)→[0.8,1). SDR below the knee is untouched; Clip stays the historical path.
- preview (stage.rs) and both export encodes (video_exporter.rs) pick the pipeline
variant from document.hdr_output_mode — one value per frame, so no per-pixel
uniform; mirrors the existing fs_main_straight pattern.
- UI: an "HDR output" dropdown in the Document section of the info panel.
Default (Clip) is bit-identical to previous behaviour. Completes Stage A:
HDR-correct input (pt 1) + SDR-safe output mapping. HDR export (10-bit P010/PQ)
and HDR display remain Stages B/C.
This commit is contained in:
parent
ff490ab9ae
commit
b76eefb404
|
|
@ -4,7 +4,7 @@
|
||||||
//! with undo/redo support.
|
//! with undo/redo support.
|
||||||
|
|
||||||
use crate::action::Action;
|
use crate::action::Action;
|
||||||
use crate::document::Document;
|
use crate::document::{Document, HdrOutputMode};
|
||||||
use crate::shape::ShapeColor;
|
use crate::shape::ShapeColor;
|
||||||
|
|
||||||
/// Individual property change for a document
|
/// Individual property change for a document
|
||||||
|
|
@ -15,6 +15,7 @@ pub enum DocumentPropertyChange {
|
||||||
Duration(f64),
|
Duration(f64),
|
||||||
Framerate(f64),
|
Framerate(f64),
|
||||||
BackgroundColor(ShapeColor),
|
BackgroundColor(ShapeColor),
|
||||||
|
HdrOutputMode(HdrOutputMode),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stored old value for undo (either f64 or color)
|
/// Stored old value for undo (either f64 or color)
|
||||||
|
|
@ -22,6 +23,7 @@ pub enum DocumentPropertyChange {
|
||||||
enum OldValue {
|
enum OldValue {
|
||||||
F64(f64),
|
F64(f64),
|
||||||
Color(ShapeColor),
|
Color(ShapeColor),
|
||||||
|
Hdr(HdrOutputMode),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Action that sets a property on the document
|
/// Action that sets a property on the document
|
||||||
|
|
@ -72,6 +74,14 @@ impl SetDocumentPropertiesAction {
|
||||||
old_value: None,
|
old_value: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new action to set the HDR→SDR output mode
|
||||||
|
pub fn set_hdr_output_mode(mode: HdrOutputMode) -> Self {
|
||||||
|
Self {
|
||||||
|
property: DocumentPropertyChange::HdrOutputMode(mode),
|
||||||
|
old_value: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Action for SetDocumentPropertiesAction {
|
impl Action for SetDocumentPropertiesAction {
|
||||||
|
|
@ -83,6 +93,7 @@ impl Action for SetDocumentPropertiesAction {
|
||||||
DocumentPropertyChange::Duration(_) => OldValue::F64(document.duration),
|
DocumentPropertyChange::Duration(_) => OldValue::F64(document.duration),
|
||||||
DocumentPropertyChange::Framerate(_) => OldValue::F64(document.framerate),
|
DocumentPropertyChange::Framerate(_) => OldValue::F64(document.framerate),
|
||||||
DocumentPropertyChange::BackgroundColor(_) => OldValue::Color(document.background_color),
|
DocumentPropertyChange::BackgroundColor(_) => OldValue::Color(document.background_color),
|
||||||
|
DocumentPropertyChange::HdrOutputMode(_) => OldValue::Hdr(document.hdr_output_mode),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,6 +103,7 @@ impl Action for SetDocumentPropertiesAction {
|
||||||
DocumentPropertyChange::Duration(v) => document.duration = *v,
|
DocumentPropertyChange::Duration(v) => document.duration = *v,
|
||||||
DocumentPropertyChange::Framerate(v) => document.framerate = *v,
|
DocumentPropertyChange::Framerate(v) => document.framerate = *v,
|
||||||
DocumentPropertyChange::BackgroundColor(c) => document.background_color = *c,
|
DocumentPropertyChange::BackgroundColor(c) => document.background_color = *c,
|
||||||
|
DocumentPropertyChange::HdrOutputMode(m) => document.hdr_output_mode = *m,
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -106,11 +118,15 @@ impl Action for SetDocumentPropertiesAction {
|
||||||
DocumentPropertyChange::Duration(_) => document.duration = v,
|
DocumentPropertyChange::Duration(_) => document.duration = v,
|
||||||
DocumentPropertyChange::Framerate(_) => document.framerate = v,
|
DocumentPropertyChange::Framerate(_) => document.framerate = v,
|
||||||
DocumentPropertyChange::BackgroundColor(_) => {}
|
DocumentPropertyChange::BackgroundColor(_) => {}
|
||||||
|
DocumentPropertyChange::HdrOutputMode(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(OldValue::Color(c)) => {
|
Some(OldValue::Color(c)) => {
|
||||||
document.background_color = *c;
|
document.background_color = *c;
|
||||||
}
|
}
|
||||||
|
Some(OldValue::Hdr(m)) => {
|
||||||
|
document.hdr_output_mode = *m;
|
||||||
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -123,6 +139,7 @@ impl Action for SetDocumentPropertiesAction {
|
||||||
DocumentPropertyChange::Duration(_) => "duration",
|
DocumentPropertyChange::Duration(_) => "duration",
|
||||||
DocumentPropertyChange::Framerate(_) => "framerate",
|
DocumentPropertyChange::Framerate(_) => "framerate",
|
||||||
DocumentPropertyChange::BackgroundColor(_) => "background color",
|
DocumentPropertyChange::BackgroundColor(_) => "background color",
|
||||||
|
DocumentPropertyChange::HdrOutputMode(_) => "HDR output mode",
|
||||||
};
|
};
|
||||||
format!("Set {}", property_name)
|
format!("Set {}", property_name)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,26 @@ pub enum TimelineMode {
|
||||||
Frames,
|
Frames,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How super-white (HDR) values are mapped to the SDR display/export output at the final
|
||||||
|
/// linear→sRGB encode. SDR content sits in [0,1] and is unaffected by either mode.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
|
pub enum HdrOutputMode {
|
||||||
|
/// Hard-clip values above 1.0 (the historical behaviour). SDR-exact; HDR highlights blow out.
|
||||||
|
#[default]
|
||||||
|
Clip,
|
||||||
|
/// Roll highlights above a knee smoothly toward 1.0 to recover detail. Slightly dims near-white.
|
||||||
|
HighlightRolloff,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HdrOutputMode {
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
HdrOutputMode::Clip => "Clip",
|
||||||
|
HdrOutputMode::HighlightRolloff => "Highlight rolloff",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Asset category for folder tree access
|
/// Asset category for folder tree access
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum AssetCategory {
|
pub enum AssetCategory {
|
||||||
|
|
@ -244,6 +264,10 @@ pub struct Document {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub timeline_mode: TimelineMode,
|
pub timeline_mode: TimelineMode,
|
||||||
|
|
||||||
|
/// How HDR (super-white) values are mapped to the SDR output at the final encode.
|
||||||
|
#[serde(default)]
|
||||||
|
pub hdr_output_mode: HdrOutputMode,
|
||||||
|
|
||||||
/// Current UI layout state (serialized for save/load)
|
/// Current UI layout state (serialized for save/load)
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub ui_layout: Option<LayoutNode>,
|
pub ui_layout: Option<LayoutNode>,
|
||||||
|
|
@ -278,6 +302,7 @@ impl Default for Document {
|
||||||
ml
|
ml
|
||||||
},
|
},
|
||||||
duration: 10.0,
|
duration: 10.0,
|
||||||
|
hdr_output_mode: HdrOutputMode::default(),
|
||||||
root: GraphicsObject::default(),
|
root: GraphicsObject::default(),
|
||||||
vector_clips: HashMap::new(),
|
vector_clips: HashMap::new(),
|
||||||
video_clips: HashMap::new(),
|
video_clips: HashMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,8 @@ pub struct ExportGpuResources {
|
||||||
pub staging_buffer: wgpu::Buffer,
|
pub staging_buffer: wgpu::Buffer,
|
||||||
/// Linear to sRGB blit pipeline for final output
|
/// Linear to sRGB blit pipeline for final output
|
||||||
pub linear_to_srgb_pipeline: wgpu::RenderPipeline,
|
pub linear_to_srgb_pipeline: wgpu::RenderPipeline,
|
||||||
|
/// Variant with highlight rolloff (document HDR output mode = Highlight rolloff).
|
||||||
|
pub linear_to_srgb_pipeline_rolloff: wgpu::RenderPipeline,
|
||||||
/// Bind group layout for linear to sRGB blit
|
/// Bind group layout for linear to sRGB blit
|
||||||
pub linear_to_srgb_bind_group_layout: wgpu::BindGroupLayout,
|
pub linear_to_srgb_bind_group_layout: wgpu::BindGroupLayout,
|
||||||
/// Sampler for linear to sRGB conversion
|
/// Sampler for linear to sRGB conversion
|
||||||
|
|
@ -236,6 +238,41 @@ impl ExportGpuResources {
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Highlight-rolloff variant: identical but the `fs_main_rolloff` entry point.
|
||||||
|
let linear_to_srgb_pipeline_rolloff = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("linear_to_srgb_pipeline_rolloff"),
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("vs_main"),
|
||||||
|
buffers: &[],
|
||||||
|
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("fs_main_rolloff"),
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||||
|
blend: None,
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleStrip,
|
||||||
|
strip_index_format: None,
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: None,
|
||||||
|
polygon_mode: wgpu::PolygonMode::Fill,
|
||||||
|
unclipped_depth: false,
|
||||||
|
conservative: false,
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
multiview: None,
|
||||||
|
cache: None,
|
||||||
|
});
|
||||||
|
|
||||||
let linear_to_srgb_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
let linear_to_srgb_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
label: Some("linear_to_srgb_sampler"),
|
label: Some("linear_to_srgb_sampler"),
|
||||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
|
@ -263,6 +300,7 @@ impl ExportGpuResources {
|
||||||
yuv_texture_view,
|
yuv_texture_view,
|
||||||
staging_buffer,
|
staging_buffer,
|
||||||
linear_to_srgb_pipeline,
|
linear_to_srgb_pipeline,
|
||||||
|
linear_to_srgb_pipeline_rolloff,
|
||||||
linear_to_srgb_bind_group_layout,
|
linear_to_srgb_bind_group_layout,
|
||||||
linear_to_srgb_sampler,
|
linear_to_srgb_sampler,
|
||||||
canvas_blit,
|
canvas_blit,
|
||||||
|
|
@ -340,6 +378,32 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
|
||||||
return vec4<f32>(srgb, a);
|
return vec4<f32>(srgb, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Highlight rolloff: identity below the knee, smooth C1 rolloff [knee,∞)→[knee,1) above (recovers
|
||||||
|
// super-white HDR detail). SDR below the knee is untouched. Mirrors panes/shaders/linear_to_srgb.wgsl.
|
||||||
|
fn highlight_rolloff_ch(x: f32) -> f32 {
|
||||||
|
let knee = 0.8;
|
||||||
|
if x <= knee {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
let headroom = 1.0 - knee;
|
||||||
|
return knee + headroom * (1.0 - exp(-(x - knee) / headroom));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant of fs_main with highlight rolloff (document HDR output mode = Highlight rolloff).
|
||||||
|
@fragment
|
||||||
|
fn fs_main_rolloff(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let src = textureSample(source_tex, source_sampler, in.uv);
|
||||||
|
let a = src.a;
|
||||||
|
let straight = select(src.rgb / a, vec3<f32>(0.0), a <= 0.0);
|
||||||
|
let rolled = vec3<f32>(
|
||||||
|
highlight_rolloff_ch(straight.r),
|
||||||
|
highlight_rolloff_ch(straight.g),
|
||||||
|
highlight_rolloff_ch(straight.b),
|
||||||
|
);
|
||||||
|
let srgb = linear_to_srgb(rolled);
|
||||||
|
return vec4<f32>(srgb, a);
|
||||||
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
/// Convert RGBA8 pixels to YUV420p format using BT.709 color space
|
/// Convert RGBA8 pixels to YUV420p format using BT.709 color space
|
||||||
|
|
@ -1127,7 +1191,11 @@ pub fn render_frame_to_rgba_hdr(
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
render_pass.set_pipeline(&gpu_resources.linear_to_srgb_pipeline);
|
let final_pipeline = match document.hdr_output_mode {
|
||||||
|
lightningbeam_core::document::HdrOutputMode::HighlightRolloff => &gpu_resources.linear_to_srgb_pipeline_rolloff,
|
||||||
|
lightningbeam_core::document::HdrOutputMode::Clip => &gpu_resources.linear_to_srgb_pipeline,
|
||||||
|
};
|
||||||
|
render_pass.set_pipeline(final_pipeline);
|
||||||
render_pass.set_bind_group(0, &bind_group, &[]);
|
render_pass.set_bind_group(0, &bind_group, &[]);
|
||||||
render_pass.draw(0..4, 0..1);
|
render_pass.draw(0..4, 0..1);
|
||||||
}
|
}
|
||||||
|
|
@ -1394,7 +1462,11 @@ pub fn render_frame_to_gpu_rgba(
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
render_pass.set_pipeline(&gpu_resources.linear_to_srgb_pipeline);
|
let final_pipeline = match document.hdr_output_mode {
|
||||||
|
lightningbeam_core::document::HdrOutputMode::HighlightRolloff => &gpu_resources.linear_to_srgb_pipeline_rolloff,
|
||||||
|
lightningbeam_core::document::HdrOutputMode::Clip => &gpu_resources.linear_to_srgb_pipeline,
|
||||||
|
};
|
||||||
|
render_pass.set_pipeline(final_pipeline);
|
||||||
render_pass.set_bind_group(0, &bind_group, &[]);
|
render_pass.set_bind_group(0, &bind_group, &[]);
|
||||||
render_pass.draw(0..4, 0..1);
|
render_pass.draw(0..4, 0..1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -980,10 +980,10 @@ impl InfopanelPane {
|
||||||
|
|
||||||
// Extract all needed values up front, then drop the borrow before closures
|
// Extract all needed values up front, then drop the borrow before closures
|
||||||
// that need mutable access to shared or self.
|
// that need mutable access to shared or self.
|
||||||
let (mut width, mut height, mut duration, mut framerate, layer_count, background_color) = {
|
let (mut width, mut height, mut duration, mut framerate, layer_count, background_color, mut hdr_mode) = {
|
||||||
let document = shared.action_executor.document();
|
let document = shared.action_executor.document();
|
||||||
(document.width, document.height, document.duration, document.framerate,
|
(document.width, document.height, document.duration, document.framerate,
|
||||||
document.root.children.len(), document.background_color)
|
document.root.children.len(), document.background_color, document.hdr_output_mode)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Canvas width
|
// Canvas width
|
||||||
|
|
@ -1087,6 +1087,23 @@ impl InfopanelPane {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// HDR output mode (how super-white video highlights map to SDR output)
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
use lightningbeam_core::document::HdrOutputMode;
|
||||||
|
ui.label("HDR output:");
|
||||||
|
egui::ComboBox::from_id_salt(("hdr_output_mode", path))
|
||||||
|
.selected_text(hdr_mode.name())
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
let mut changed = false;
|
||||||
|
changed |= ui.selectable_value(&mut hdr_mode, HdrOutputMode::Clip, HdrOutputMode::Clip.name()).changed();
|
||||||
|
changed |= ui.selectable_value(&mut hdr_mode, HdrOutputMode::HighlightRolloff, HdrOutputMode::HighlightRolloff.name()).changed();
|
||||||
|
if changed {
|
||||||
|
let action = SetDocumentPropertiesAction::set_hdr_output_mode(hdr_mode);
|
||||||
|
shared.pending_actions.push(Box::new(action));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Layer count (read-only)
|
// Layer count (read-only)
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Layers:");
|
ui.label("Layers:");
|
||||||
|
|
|
||||||
|
|
@ -50,3 +50,34 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
a
|
a
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Highlight rolloff: identity below the knee, then a smooth C1 rolloff that maps [knee, ∞) → [knee, 1)
|
||||||
|
// so super-white (HDR) detail is recovered instead of hard-clipped. SDR below the knee is untouched.
|
||||||
|
fn highlight_rolloff(x: f32) -> f32 {
|
||||||
|
let knee = 0.8;
|
||||||
|
if x <= knee {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
let headroom = 1.0 - knee;
|
||||||
|
return knee + headroom * (1.0 - exp(-(x - knee) / headroom));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant of fs_main with highlight rolloff (document HDR output mode = Highlight rolloff).
|
||||||
|
@fragment
|
||||||
|
fn fs_main_rolloff(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let linear = textureSample(input_tex, input_sampler, in.uv);
|
||||||
|
let a = linear.a;
|
||||||
|
let straight = select(linear.rgb / a, vec3<f32>(0.0), a <= 0.0);
|
||||||
|
let rolled = vec3<f32>(
|
||||||
|
highlight_rolloff(straight.r),
|
||||||
|
highlight_rolloff(straight.g),
|
||||||
|
highlight_rolloff(straight.b),
|
||||||
|
);
|
||||||
|
|
||||||
|
return vec4<f32>(
|
||||||
|
linear_to_srgb_channel(rolled.r),
|
||||||
|
linear_to_srgb_channel(rolled.g),
|
||||||
|
linear_to_srgb_channel(rolled.b),
|
||||||
|
a
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,8 @@ struct SharedVelloResources {
|
||||||
blit_bind_group_layout: wgpu::BindGroupLayout,
|
blit_bind_group_layout: wgpu::BindGroupLayout,
|
||||||
/// HDR to sRGB blit pipeline (linear→sRGB conversion for display)
|
/// HDR to sRGB blit pipeline (linear→sRGB conversion for display)
|
||||||
hdr_blit_pipeline: wgpu::RenderPipeline,
|
hdr_blit_pipeline: wgpu::RenderPipeline,
|
||||||
|
/// Variant of `hdr_blit_pipeline` with highlight rolloff (document HDR output mode).
|
||||||
|
hdr_blit_pipeline_rolloff: wgpu::RenderPipeline,
|
||||||
sampler: wgpu::Sampler,
|
sampler: wgpu::Sampler,
|
||||||
/// Shared image cache for avoiding re-decoding images every frame
|
/// Shared image cache for avoiding re-decoding images every frame
|
||||||
image_cache: Mutex<lightningbeam_core::renderer::ImageCache>,
|
image_cache: Mutex<lightningbeam_core::renderer::ImageCache>,
|
||||||
|
|
@ -346,6 +348,41 @@ impl SharedVelloResources {
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Variant with highlight rolloff (document HDR output mode); identical but `fs_main_rolloff`.
|
||||||
|
let hdr_blit_pipeline_rolloff = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("hdr_blit_pipeline_rolloff"),
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &hdr_shader,
|
||||||
|
entry_point: Some("vs_main"),
|
||||||
|
buffers: &[],
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &hdr_shader,
|
||||||
|
entry_point: Some("fs_main_rolloff"),
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||||
|
blend: None,
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleStrip,
|
||||||
|
strip_index_format: None,
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: None,
|
||||||
|
unclipped_depth: false,
|
||||||
|
polygon_mode: wgpu::PolygonMode::Fill,
|
||||||
|
conservative: false,
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
multiview: None,
|
||||||
|
cache: None,
|
||||||
|
});
|
||||||
|
|
||||||
// Create sampler
|
// Create sampler
|
||||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
label: Some("vello_blit_sampler"),
|
label: Some("vello_blit_sampler"),
|
||||||
|
|
@ -383,6 +420,7 @@ impl SharedVelloResources {
|
||||||
blit_pipeline,
|
blit_pipeline,
|
||||||
blit_bind_group_layout,
|
blit_bind_group_layout,
|
||||||
hdr_blit_pipeline,
|
hdr_blit_pipeline,
|
||||||
|
hdr_blit_pipeline_rolloff,
|
||||||
sampler,
|
sampler,
|
||||||
image_cache: Mutex::new(lightningbeam_core::renderer::ImageCache::new()),
|
image_cache: Mutex::new(lightningbeam_core::renderer::ImageCache::new()),
|
||||||
video_manager,
|
video_manager,
|
||||||
|
|
@ -2947,7 +2985,11 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
render_pass.set_pipeline(&shared.hdr_blit_pipeline);
|
let hdr_pipeline = match self.ctx.document.hdr_output_mode {
|
||||||
|
lightningbeam_core::document::HdrOutputMode::HighlightRolloff => &shared.hdr_blit_pipeline_rolloff,
|
||||||
|
lightningbeam_core::document::HdrOutputMode::Clip => &shared.hdr_blit_pipeline,
|
||||||
|
};
|
||||||
|
render_pass.set_pipeline(hdr_pipeline);
|
||||||
render_pass.set_bind_group(0, hdr_bind_group, &[]);
|
render_pass.set_bind_group(0, hdr_bind_group, &[]);
|
||||||
render_pass.draw(0..3, 0..1); // Full-screen triangle (3 vertices)
|
render_pass.draw(0..3, 0..1); // Full-screen triangle (3 vertices)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue