fix color space for raster editing
This commit is contained in:
parent
885c52c02a
commit
759e41d84a
|
|
@ -94,6 +94,7 @@ impl CanvasPair {
|
|||
/// in `raw_pixels` / PNG files). The values are decoded to linear premultiplied
|
||||
/// before being written to the canvas, which operates entirely in linear space.
|
||||
pub fn upload(&self, queue: &wgpu::Queue, pixels: &[u8]) {
|
||||
eprintln!("[CANVAS] upload: {}x{} pixels={}", self.width, self.height, pixels.len());
|
||||
// Decode sRGB-premultiplied → linear premultiplied for the GPU canvas.
|
||||
let linear: Vec<u8> = pixels.chunks_exact(4).flat_map(|p| {
|
||||
let r = (srgb_to_linear(p[0] as f32 / 255.0) * 255.0 + 0.5) as u8;
|
||||
|
|
@ -268,59 +269,68 @@ impl GpuBrushEngine {
|
|||
let needs_new = self.canvases.get(&keyframe_id)
|
||||
.map_or(true, |c| c.width != width || c.height != height);
|
||||
if needs_new {
|
||||
eprintln!("[CANVAS] ensure_canvas: creating new CanvasPair for kf={:?} {}x{}", keyframe_id, width, height);
|
||||
self.canvases.insert(keyframe_id, CanvasPair::new(device, width, height));
|
||||
} else {
|
||||
eprintln!("[CANVAS] ensure_canvas: reusing existing CanvasPair for kf={:?}", keyframe_id);
|
||||
}
|
||||
self.canvases.get_mut(&keyframe_id).unwrap()
|
||||
}
|
||||
|
||||
/// Dispatch the brush compute shader for `dabs` onto the canvas of `keyframe_id`.
|
||||
///
|
||||
/// * Pre-fills `dst` from `src` so untouched pixels are preserved.
|
||||
/// * Dispatches the compute shader.
|
||||
/// * Swaps src/dst so the just-written texture becomes the new source.
|
||||
/// Each dab is dispatched as a separate copy+compute+swap so that every dab
|
||||
/// reads the result of the previous one. This is required for the smudge tool:
|
||||
/// if all dabs were batched into one dispatch they would all read the pre-batch
|
||||
/// canvas state, breaking the carry-forward that makes smudge drag pixels along.
|
||||
///
|
||||
/// `dab_bbox` is `(x0, y0, x1, y1)` — the union bounding box of all dabs.
|
||||
/// If `dabs` is empty or the bbox is invalid, does nothing.
|
||||
/// `dab_bbox` is the union bounding box (unused here; kept for API compat).
|
||||
/// If `dabs` is empty, does nothing.
|
||||
pub fn render_dabs(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
keyframe_id: Uuid,
|
||||
dabs: &[GpuDab],
|
||||
bbox: (i32, i32, i32, i32),
|
||||
_bbox: (i32, i32, i32, i32),
|
||||
canvas_w: u32,
|
||||
canvas_h: u32,
|
||||
) {
|
||||
if dabs.is_empty() || bbox.0 == i32::MAX { return; }
|
||||
if dabs.is_empty() { return; }
|
||||
|
||||
let canvas = match self.canvases.get_mut(&keyframe_id) {
|
||||
Some(c) => c,
|
||||
None => return,
|
||||
if !self.canvases.contains_key(&keyframe_id) { return; }
|
||||
|
||||
let full_extent = wgpu::Extent3d {
|
||||
width: self.canvases[&keyframe_id].width,
|
||||
height: self.canvases[&keyframe_id].height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
// Clamp bbox to canvas bounds
|
||||
let x0 = bbox.0.max(0) as u32;
|
||||
let y0 = bbox.1.max(0) as u32;
|
||||
let x1 = (bbox.2.min(canvas_w as i32 - 1)).max(0) as u32;
|
||||
let y1 = (bbox.3.min(canvas_h as i32 - 1)).max(0) as u32;
|
||||
if x1 < x0 || y1 < y0 { return; }
|
||||
eprintln!("[DAB] render_dabs keyframe={:?} count={}", keyframe_id, dabs.len());
|
||||
for dab in dabs {
|
||||
// Per-dab bounding box
|
||||
let r_fringe = dab.radius + 1.0;
|
||||
let dx0 = (dab.x - r_fringe).floor() as i32;
|
||||
let dy0 = (dab.y - r_fringe).floor() as i32;
|
||||
let dx1 = (dab.x + r_fringe).ceil() as i32;
|
||||
let dy1 = (dab.y + r_fringe).ceil() as i32;
|
||||
|
||||
let x0 = dx0.max(0) as u32;
|
||||
let y0 = dy0.max(0) as u32;
|
||||
let x1 = (dx1.min(canvas_w as i32 - 1)).max(0) as u32;
|
||||
let y1 = (dy1.min(canvas_h as i32 - 1)).max(0) as u32;
|
||||
if x1 < x0 || y1 < y0 { continue; }
|
||||
|
||||
let bbox_w = x1 - x0 + 1;
|
||||
let bbox_h = y1 - y0 + 1;
|
||||
|
||||
// --- Pre-fill dst from src: copy the ENTIRE canvas so every pixel outside
|
||||
// the dab bounding box is preserved across the ping-pong swap.
|
||||
// Copying only the bbox would leave dst with data from two frames ago
|
||||
// in all other regions, causing missing dabs on alternating frames. ---
|
||||
let mut copy_encoder = device.create_command_encoder(
|
||||
let canvas = self.canvases.get_mut(&keyframe_id).unwrap();
|
||||
|
||||
// Pre-fill dst from src so pixels outside this dab's bbox are preserved.
|
||||
let mut copy_enc = device.create_command_encoder(
|
||||
&wgpu::CommandEncoderDescriptor { label: Some("canvas_copy_encoder") },
|
||||
);
|
||||
let full_extent = wgpu::Extent3d {
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
copy_encoder.copy_texture_to_texture(
|
||||
copy_enc.copy_texture_to_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: canvas.src(),
|
||||
mip_level: 0,
|
||||
|
|
@ -335,10 +345,10 @@ impl GpuBrushEngine {
|
|||
},
|
||||
full_extent,
|
||||
);
|
||||
queue.submit(Some(copy_encoder.finish()));
|
||||
queue.submit(Some(copy_enc.finish()));
|
||||
|
||||
// --- Upload dab data and params ---
|
||||
let dab_bytes = bytemuck::cast_slice(dabs);
|
||||
// Upload single-dab buffer and params
|
||||
let dab_bytes = bytemuck::bytes_of(dab);
|
||||
let dab_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("dab_storage_buf"),
|
||||
size: dab_bytes.len() as u64,
|
||||
|
|
@ -352,7 +362,7 @@ impl GpuBrushEngine {
|
|||
bbox_y0: y0 as i32,
|
||||
bbox_w,
|
||||
bbox_h,
|
||||
num_dabs: dabs.len() as u32,
|
||||
num_dabs: 1,
|
||||
canvas_w,
|
||||
canvas_h,
|
||||
_pad: 0,
|
||||
|
|
@ -388,12 +398,11 @@ impl GpuBrushEngine {
|
|||
],
|
||||
});
|
||||
|
||||
// --- Dispatch ---
|
||||
let mut compute_encoder = device.create_command_encoder(
|
||||
let mut compute_enc = device.create_command_encoder(
|
||||
&wgpu::CommandEncoderDescriptor { label: Some("brush_dab_encoder") },
|
||||
);
|
||||
{
|
||||
let mut pass = compute_encoder.begin_compute_pass(
|
||||
let mut pass = compute_enc.begin_compute_pass(
|
||||
&wgpu::ComputePassDescriptor {
|
||||
label: Some("brush_dab_pass"),
|
||||
timestamp_writes: None,
|
||||
|
|
@ -401,14 +410,15 @@ impl GpuBrushEngine {
|
|||
);
|
||||
pass.set_pipeline(&self.compute_pipeline);
|
||||
pass.set_bind_group(0, &bg, &[]);
|
||||
let wg_x = bbox_w.div_ceil(8);
|
||||
let wg_y = bbox_h.div_ceil(8);
|
||||
pass.dispatch_workgroups(wg_x, wg_y, 1);
|
||||
pass.dispatch_workgroups(bbox_w.div_ceil(8), bbox_h.div_ceil(8), 1);
|
||||
}
|
||||
queue.submit(Some(compute_encoder.finish()));
|
||||
queue.submit(Some(compute_enc.finish()));
|
||||
|
||||
// Swap: dst is now the authoritative source
|
||||
// Swap: the just-written dst becomes src for the next dab.
|
||||
eprintln!("[DAB] dispatched bbox=({},{},{},{}) current_before_swap={}", x0, y0, x1, y1, canvas.current);
|
||||
canvas.swap();
|
||||
eprintln!("[DAB] after swap: current={}", canvas.current);
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the current canvas back to a CPU `Vec<u8>` (raw RGBA, row-major).
|
||||
|
|
@ -613,7 +623,7 @@ impl CanvasBlitPipeline {
|
|||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||
format: wgpu::TextureFormat::Rgba16Float,
|
||||
blend: None, // canvas already stores premultiplied alpha
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
|
|
@ -655,7 +665,7 @@ impl CanvasBlitPipeline {
|
|||
Self { pipeline, bg_layout, sampler, mask_sampler }
|
||||
}
|
||||
|
||||
/// Render the canvas texture into `target_view` (Rgba8Unorm) with the given camera.
|
||||
/// Render the canvas texture into `target_view` (Rgba16Float) with the given camera.
|
||||
///
|
||||
/// `target_view` is cleared to transparent before writing.
|
||||
/// `mask_view` is an R8Unorm texture in canvas-pixel space: 255 = keep, 0 = discard.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
// Canvas blit shader.
|
||||
//
|
||||
// Renders a GPU raster canvas (at document resolution) into the layer's sRGB
|
||||
// render buffer (at viewport resolution), applying the camera transform
|
||||
// (pan + zoom) to map document-space pixels to viewport-space pixels.
|
||||
// Renders a GPU raster canvas (at document resolution) into an Rgba16Float HDR
|
||||
// buffer (at viewport resolution), applying the camera transform (pan + zoom)
|
||||
// to map document-space pixels to viewport-space pixels.
|
||||
//
|
||||
// The canvas stores premultiplied linear RGBA. We output it as-is so the HDR
|
||||
// compositor sees the same premultiplied-linear format it always works with,
|
||||
// bypassing the sRGB intermediate used for Vello layers.
|
||||
//
|
||||
// Any viewport pixel whose corresponding document coordinate falls outside
|
||||
// [0, canvas_w) × [0, canvas_h) outputs transparent black.
|
||||
|
|
@ -42,17 +46,6 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
|||
return out;
|
||||
}
|
||||
|
||||
// Linear → sRGB encoding for a single channel.
|
||||
// Applied to premultiplied linear values so the downstream srgb_to_linear
|
||||
// pass round-trips correctly without darkening semi-transparent edges.
|
||||
fn linear_to_srgb(c: f32) -> f32 {
|
||||
return select(
|
||||
1.055 * pow(max(c, 0.0), 1.0 / 2.4) - 0.055,
|
||||
c * 12.92,
|
||||
c <= 0.0031308,
|
||||
);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// Map viewport UV [0,1] → viewport pixel
|
||||
|
|
@ -71,23 +64,11 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|||
}
|
||||
|
||||
// The canvas stores premultiplied linear RGBA.
|
||||
// The downstream pipeline (srgb_to_linear → compositor) expects the sRGB
|
||||
// buffer to contain straight-alpha sRGB, i.e. the same format Vello outputs:
|
||||
// sRGB buffer: srgb(r_straight), srgb(g_straight), srgb(b_straight), a
|
||||
// srgb_to_linear: r_straight, g_straight, b_straight, a (linear straight)
|
||||
// compositor: r_straight * a * opacity (premultiplied, correct)
|
||||
//
|
||||
// Without unpremultiplying, the compositor would double-premultiply:
|
||||
// src = (premul_r, premul_g, premul_b, a) → output = premul_r * a = r * a²
|
||||
// which produces a dark halo over transparent regions.
|
||||
// The compositor expects straight-alpha linear (it premultiplies by src_alpha itself),
|
||||
// so unpremultiply here. No sRGB conversion — the HDR buffer is linear throughout.
|
||||
let c = textureSample(canvas_tex, canvas_sampler, canvas_uv);
|
||||
let mask = textureSample(mask_tex, mask_sampler, canvas_uv).r;
|
||||
let masked_a = c.a * mask;
|
||||
let inv_a = select(0.0, 1.0 / c.a, c.a > 1e-6);
|
||||
return vec4<f32>(
|
||||
linear_to_srgb(c.r * inv_a),
|
||||
linear_to_srgb(c.g * inv_a),
|
||||
linear_to_srgb(c.b * inv_a),
|
||||
masked_a,
|
||||
);
|
||||
return vec4<f32>(c.r * inv_a, c.g * inv_a, c.b * inv_a, masked_a);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -508,6 +508,7 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
if let Some(ref float_sel) = self.ctx.selection.raster_floating {
|
||||
if let Ok(mut gpu_brush) = shared.gpu_brush.lock() {
|
||||
if !gpu_brush.canvases.contains_key(&float_sel.canvas_id) {
|
||||
eprintln!("[CANVAS] lazy-init float canvas id={:?}", float_sel.canvas_id);
|
||||
gpu_brush.ensure_canvas(device, float_sel.canvas_id, float_sel.width, float_sel.height);
|
||||
if let Some(canvas) = gpu_brush.canvases.get(&float_sel.canvas_id) {
|
||||
let pixels = if float_sel.pixels.is_empty() {
|
||||
|
|
@ -536,6 +537,7 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
);
|
||||
// On stroke start, upload the pre-stroke pixel data to both textures
|
||||
if let Some(ref pixels) = pending.initial_pixels {
|
||||
eprintln!("[STROKE] uploading initial_pixels for kf={:?} painting_float={}", pending.keyframe_id, self.ctx.painting_float);
|
||||
if let Some(canvas) = gpu_brush.canvases.get(&pending.keyframe_id) {
|
||||
canvas.upload(queue, pixels);
|
||||
}
|
||||
|
|
@ -592,6 +594,14 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
);
|
||||
drop(image_cache);
|
||||
|
||||
// Debug frame counter (only active during strokes)
|
||||
static FRAME: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||
let dbg_frame = FRAME.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
let dbg_stroke = self.ctx.painting_canvas.is_some();
|
||||
if dbg_stroke {
|
||||
eprintln!("[FRAME {}] painting_canvas={:?} painting_float={}", dbg_frame, self.ctx.painting_canvas, self.ctx.painting_float);
|
||||
}
|
||||
|
||||
// Get buffer pool for layer rendering
|
||||
let mut buffer_pool = shared.buffer_pool.lock().unwrap();
|
||||
|
||||
|
|
@ -631,6 +641,7 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
antialiasing_method: vello::AaConfig::Msaa16,
|
||||
};
|
||||
|
||||
if dbg_stroke { eprintln!("[DRAW] background Vello render"); }
|
||||
if let Ok(mut renderer) = shared.renderer.lock() {
|
||||
renderer.render_to_texture(device, queue, &composite_result.background, bg_srgb_view, &bg_render_params).ok();
|
||||
}
|
||||
|
|
@ -650,6 +661,7 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
// Clear to dark gray (stage background outside document bounds)
|
||||
// Note: stage_bg values are already in linear space for HDR compositing
|
||||
let stage_bg = [45.0 / 255.0, 45.0 / 255.0, 48.0 / 255.0, 1.0];
|
||||
if dbg_stroke { eprintln!("[COMPOSITE] background onto HDR"); }
|
||||
shared.compositor.composite(
|
||||
device,
|
||||
queue,
|
||||
|
|
@ -757,12 +769,14 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
&instance_resources.hdr_texture_view,
|
||||
) {
|
||||
// GPU canvas blit path: if a live GPU canvas exists for this
|
||||
// raster layer, sample it directly instead of rendering the Vello
|
||||
// scene (which lags until raw_pixels is updated after readback).
|
||||
// raster layer, blit it directly into the HDR buffer (premultiplied
|
||||
// linear → Rgba16Float), bypassing the sRGB intermediate entirely.
|
||||
// Vello path: render to sRGB buffer → srgb_to_linear → HDR buffer.
|
||||
let used_gpu_canvas = if let Some(kf_id) = gpu_canvas_kf {
|
||||
let mut used = false;
|
||||
if let Ok(gpu_brush) = shared.gpu_brush.lock() {
|
||||
if let Some(canvas) = gpu_brush.canvases.get(&kf_id) {
|
||||
if dbg_stroke { eprintln!("[DRAW] GPU canvas blit layer={:?} kf={:?} canvas.current={}", rendered_layer.layer_id, kf_id, canvas.current); }
|
||||
let camera = crate::gpu_brush::CameraParams {
|
||||
pan_x: self.ctx.pan_offset.x,
|
||||
pan_y: self.ctx.pan_offset.y,
|
||||
|
|
@ -776,7 +790,7 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
shared.canvas_blit.blit(
|
||||
device, queue,
|
||||
canvas.src_view(),
|
||||
srgb_view,
|
||||
hdr_layer_view, // blit directly to HDR
|
||||
&camera,
|
||||
None, // no mask on layer canvas blit
|
||||
);
|
||||
|
|
@ -789,18 +803,17 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
};
|
||||
|
||||
if !used_gpu_canvas {
|
||||
// Render layer scene to sRGB buffer
|
||||
// Render layer scene to sRGB buffer, then convert to HDR
|
||||
if dbg_stroke { eprintln!("[DRAW] Vello render layer={:?} opacity={}", rendered_layer.layer_id, rendered_layer.opacity); }
|
||||
if let Ok(mut renderer) = shared.renderer.lock() {
|
||||
renderer.render_to_texture(device, queue, &rendered_layer.scene, srgb_view, &layer_render_params).ok();
|
||||
}
|
||||
}
|
||||
|
||||
// Convert sRGB to linear HDR
|
||||
let mut convert_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("layer_srgb_to_linear_encoder"),
|
||||
});
|
||||
shared.srgb_to_linear.convert(device, &mut convert_encoder, srgb_view, hdr_layer_view);
|
||||
queue.submit(Some(convert_encoder.finish()));
|
||||
}
|
||||
|
||||
// Composite this layer onto the HDR accumulator with its opacity
|
||||
let compositor_layer = lightningbeam_core::gpu::CompositorLayer::new(
|
||||
|
|
@ -809,6 +822,7 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
rendered_layer.blend_mode,
|
||||
);
|
||||
|
||||
if dbg_stroke { eprintln!("[COMPOSITE] layer={:?} opacity={} blend={:?} used_gpu_canvas={}", rendered_layer.layer_id, rendered_layer.opacity, rendered_layer.blend_mode, used_gpu_canvas); }
|
||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("layer_composite_encoder"),
|
||||
});
|
||||
|
|
@ -1003,6 +1017,7 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
|
||||
// Blit the float GPU canvas on top of all composited layers.
|
||||
// The float_mask_view clips to the selection shape (None = full float visible).
|
||||
if dbg_stroke { eprintln!("[FRAME {}] float blit section: raster_floating={}", dbg_frame, self.ctx.selection.raster_floating.is_some()); }
|
||||
if let Some(ref float_sel) = self.ctx.selection.raster_floating {
|
||||
let float_canvas_id = float_sel.canvas_id;
|
||||
let float_x = float_sel.x;
|
||||
|
|
@ -1011,10 +1026,9 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
let float_h = float_sel.height;
|
||||
if let Ok(gpu_brush) = shared.gpu_brush.lock() {
|
||||
if let Some(canvas) = gpu_brush.canvases.get(&float_canvas_id) {
|
||||
let float_srgb_handle = buffer_pool.acquire(device, layer_spec);
|
||||
if dbg_stroke { eprintln!("[DRAW] float canvas blit canvas_id={:?} canvas.current={}", float_canvas_id, canvas.current); }
|
||||
let float_hdr_handle = buffer_pool.acquire(device, hdr_spec);
|
||||
if let (Some(fsrgb_view), Some(fhdr_view), Some(hdr_view)) = (
|
||||
buffer_pool.get_view(float_srgb_handle),
|
||||
if let (Some(fhdr_view), Some(hdr_view)) = (
|
||||
buffer_pool.get_view(float_hdr_handle),
|
||||
&instance_resources.hdr_texture_view,
|
||||
) {
|
||||
|
|
@ -1028,27 +1042,22 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
viewport_h: height as f32,
|
||||
_pad: 0.0,
|
||||
};
|
||||
// Blit directly to HDR (straight-alpha linear, no sRGB step)
|
||||
shared.canvas_blit.blit(
|
||||
device, queue,
|
||||
canvas.src_view(),
|
||||
fsrgb_view,
|
||||
fhdr_view,
|
||||
&fcamera,
|
||||
float_mask_view.as_ref(),
|
||||
);
|
||||
let mut enc = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("float_canvas_srgb_to_linear"),
|
||||
});
|
||||
shared.srgb_to_linear.convert(device, &mut enc, fsrgb_view, fhdr_view);
|
||||
queue.submit(Some(enc.finish()));
|
||||
|
||||
let float_layer = lightningbeam_core::gpu::CompositorLayer::normal(float_hdr_handle, 1.0);
|
||||
if dbg_stroke { eprintln!("[COMPOSITE] float canvas onto HDR"); }
|
||||
let mut enc = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("float_canvas_composite"),
|
||||
});
|
||||
shared.compositor.composite(device, queue, &mut enc, &[float_layer], &buffer_pool, hdr_view, None);
|
||||
queue.submit(Some(enc.finish()));
|
||||
}
|
||||
buffer_pool.release(float_srgb_handle);
|
||||
buffer_pool.release(float_hdr_handle);
|
||||
}
|
||||
}
|
||||
|
|
@ -2180,6 +2189,7 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
antialiasing_method: vello::AaConfig::Msaa16,
|
||||
};
|
||||
|
||||
if self.ctx.painting_canvas.is_some() { eprintln!("[DRAW] overlay Vello render"); }
|
||||
if let Ok(mut renderer) = shared.renderer.lock() {
|
||||
renderer.render_to_texture(device, queue, &scene, overlay_srgb_view, &overlay_params).ok();
|
||||
}
|
||||
|
|
@ -2193,6 +2203,7 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
|||
|
||||
// Composite overlay onto HDR texture
|
||||
let overlay_layer = lightningbeam_core::gpu::CompositorLayer::normal(overlay_hdr_handle, 1.0);
|
||||
if self.ctx.painting_canvas.is_some() { eprintln!("[COMPOSITE] overlay onto HDR"); }
|
||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("overlay_composite_encoder"),
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue