Desktop add viewport texture (#2953)

* Allow rendering viewport texture beneath ui texture

* Add viewport scale

* Update desktop/src/render/fullscreen_texture.wgsl

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
Timon 2025-07-28 19:10:27 +02:00 committed by GitHub
parent 83d39fb320
commit 6119dea58c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 94 additions and 18 deletions

1
Cargo.lock generated
View File

@ -1834,6 +1834,7 @@ dependencies = [
name = "graphite-desktop"
version = "0.1.0"
dependencies = [
"bytemuck",
"cef",
"dirs",
"futures",

View File

@ -28,4 +28,5 @@ cef = { workspace = true }
include_dir = { workspace = true }
tracing-subscriber = { workspace = true }
tracing = { workspace = true }
dirs = {workspace = true}
dirs = { workspace = true }
bytemuck = { workspace = true }

View File

@ -83,7 +83,7 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
match event {
CustomEvent::UiUpdate(texture) => {
if let Some(graphics_state) = self.graphics_state.as_mut() {
graphics_state.bind_texture(&texture);
graphics_state.bind_ui_texture(&texture);
graphics_state.resize(texture.width(), texture.height());
}
if let Some(window) = &self.window {

View File

@ -1,5 +1,6 @@
use std::sync::Arc;
use bytemuck::{Pod, Zeroable};
use thiserror::Error;
use winit::window::Window;
@ -79,13 +80,15 @@ impl WgpuContext {
.await
.unwrap();
let required_limits = adapter.limits();
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
label: None,
required_features: wgpu::Features::PUSH_CONSTANTS,
required_limits,
memory_hints: Default::default(),
..Default::default()
trace: wgpu::Trace::Off,
})
.await
.unwrap();
@ -99,10 +102,13 @@ pub(crate) struct GraphicsState {
surface: wgpu::Surface<'static>,
context: WgpuContext,
config: wgpu::SurfaceConfiguration,
texture: Option<wgpu::Texture>,
bind_group: Option<wgpu::BindGroup>,
render_pipeline: wgpu::RenderPipeline,
sampler: wgpu::Sampler,
viewport_scale: [f32; 2],
viewport_offset: [f32; 2],
viewport_texture: Option<wgpu::Texture>,
ui_texture: Option<wgpu::Texture>,
bind_group: Option<wgpu::BindGroup>,
}
impl GraphicsState {
@ -156,6 +162,16 @@ impl GraphicsState {
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
@ -166,7 +182,10 @@ impl GraphicsState {
let render_pipeline_layout = context.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[&texture_bind_group_layout],
push_constant_ranges: &[],
push_constant_ranges: &[wgpu::PushConstantRange {
stages: wgpu::ShaderStages::FRAGMENT,
range: 0..size_of::<Constants>() as u32,
}],
});
let render_pipeline = context.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@ -211,10 +230,13 @@ impl GraphicsState {
surface,
context,
config,
texture: None,
bind_group: None,
render_pipeline,
sampler,
viewport_scale: [1.0, 1.0],
viewport_offset: [0.0, 0.0],
viewport_texture: None,
ui_texture: None,
bind_group: None,
}
}
@ -226,25 +248,47 @@ impl GraphicsState {
}
}
pub(crate) fn bind_texture(&mut self, texture: &wgpu::Texture) {
let bind_group = self.create_bindgroup(texture);
self.texture = Some(texture.clone());
pub(crate) fn bind_ui_texture(&mut self, texture: &wgpu::Texture) {
let bind_group = self.create_bindgroup(texture, &self.viewport_texture.clone().unwrap_or(texture.clone()));
self.ui_texture = Some(texture.clone());
self.bind_group = Some(bind_group);
}
fn create_bindgroup(&self, texture: &wgpu::Texture) -> wgpu::BindGroup {
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
pub(crate) fn bind_viewport_texture(&mut self, texture: &wgpu::Texture) {
let bind_group = self.create_bindgroup(&self.ui_texture.clone().unwrap_or(texture.clone()), texture);
self.viewport_texture = Some(texture.clone());
self.bind_group = Some(bind_group);
}
pub(crate) fn set_viewport_scale(&mut self, scale: [f32; 2]) {
self.viewport_scale = scale;
}
pub(crate) fn set_viewport_offset(&mut self, offset: [f32; 2]) {
self.viewport_offset = offset;
}
fn create_bindgroup(&self, ui_texture: &wgpu::Texture, viewport_texture: &wgpu::Texture) -> wgpu::BindGroup {
let ui_texture_view = ui_texture.create_view(&wgpu::TextureViewDescriptor::default());
let viewport_texture_view = viewport_texture.create_view(&wgpu::TextureViewDescriptor::default());
self.context.device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.render_pipeline.get_bind_group_layout(0),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&texture_view),
resource: wgpu::BindingResource::TextureView(&ui_texture_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&viewport_texture_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
],
@ -275,6 +319,14 @@ impl GraphicsState {
});
render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_push_constants(
wgpu::ShaderStages::FRAGMENT,
0,
bytemuck::bytes_of(&Constants {
viewport_scale: self.viewport_scale,
viewport_offset: self.viewport_offset,
}),
);
if let Some(bind_group) = &self.bind_group {
render_pass.set_bind_group(0, bind_group, &[]);
render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle
@ -288,3 +340,10 @@ impl GraphicsState {
Ok(())
}
}
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct Constants {
viewport_scale: [f32; 2],
viewport_offset: [f32; 2],
}

View File

@ -25,12 +25,27 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
return out;
}
struct Constants {
viewport_scale: vec2<f32>,
viewport_offset: vec2<f32>,
};
var<push_constant> constants: Constants;
@group(0) @binding(0)
var t_diffuse: texture_2d<f32>;
var t_ui: texture_2d<f32>;
@group(0) @binding(1)
var t_viewport: texture_2d<f32>;
@group(0) @binding(2)
var s_diffuse: sampler;
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return textureSample(t_diffuse, s_diffuse, in.tex_coords);
let ui_color: vec4<f32> = textureSample(t_ui, s_diffuse, in.tex_coords);
if (ui_color.a == 1.0) {
return ui_color;
}
let viewport_tex_coords = (in.tex_coords - constants.viewport_offset) * constants.viewport_scale;
let viewport_color: vec4<f32> = textureSample(t_viewport, s_diffuse, viewport_tex_coords);
return ui_color * ui_color.a + viewport_color * (1.0 - ui_color.a);
}