Skip to content

Commit 52539ba

Browse files
ryanbreenclaude
andcommitted
feat: GPU-textured compositing via VirGL sampler view + paged backing
Fix three bugs that caused VirGL texture-sampling to produce black output: 1. Missing texture target in create_sampler_view format DWORD — Mesa protocol packs PIPE_TEXTURE_2D into bits [24:31] of the format field. Without it, the host creates a BUFFER-targeted sampler view on a 2D texture, causing GL sampling to return black. 2. Single-entry backing for composite texture — Parallels requires per-page (4KB) scatter-gather entries for correct DMA reads during texture sampling. Generalized virgl_attach_backing_paged() to accept any resource's backing pointer/length. 3. Object handle collisions — all VirGL objects in the textured batch now use unique handles (10-18) to avoid virglrenderer hash collisions. Added FS_COLOR0_WRITES_ALL_CBUFS property to textured fragment shader. BWM now composites windows through the full VirGL textured quad pipeline at ~38 FPS with bounce demo rendering at 36-42 FPS. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3729a05 commit 52539ba

3 files changed

Lines changed: 69 additions & 70 deletions

File tree

kernel/src/drivers/virtio/gpu_pci.rs

Lines changed: 46 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -589,31 +589,9 @@ fn init_composite_texture(width: u32, height: u32) -> Result<(), &'static str> {
589589
)
590590
})?;
591591

592-
// Attach backing memory
592+
// Attach backing memory (per-page scatter-gather, required by Parallels)
593593
with_device_state(|state| {
594-
unsafe {
595-
let cmd_ptr = &raw mut PCI_CMD_BUF;
596-
let attach = &mut *((*cmd_ptr).data.as_mut_ptr() as *mut PciAttachBackingCmd);
597-
*attach = PciAttachBackingCmd {
598-
cmd: VirtioGpuResourceAttachBacking {
599-
hdr: VirtioGpuCtrlHdr {
600-
type_: cmd::RESOURCE_ATTACH_BACKING,
601-
flags: 0,
602-
fence_id: 0,
603-
ctx_id: 0,
604-
padding: 0,
605-
},
606-
resource_id: RESOURCE_COMPOSITE_TEX_ID,
607-
nr_entries: 1,
608-
},
609-
entry: VirtioGpuMemEntry {
610-
addr: phys,
611-
length: size as u32,
612-
padding: 0,
613-
},
614-
};
615-
}
616-
send_command_expect_ok(state, core::mem::size_of::<PciAttachBackingCmd>() as u32)
594+
virgl_attach_backing_paged(state, RESOURCE_COMPOSITE_TEX_ID, unsafe { COMPOSITE_TEX_PTR }, size)
617595
})?;
618596

619597
// Attach to VirGL context
@@ -2001,20 +1979,19 @@ fn virgl_resource_create_3d_cmd(
20011979

20021980
/// Attach backing memory to a 3D resource using per-page scatter-gather entries.
20031981
///
2004-
/// Uses heap-allocated PCI_3D_FB_PTR as the backing store.
2005-
/// CRITICAL: Must NOT share backing with the 2D resource (PCI_FRAMEBUFFER).
2006-
/// Linux's Mesa/virgl creates independent GEM buffers for each resource.
2007-
///
20081982
/// KEY FIX: Linux kernel sends one VirtioGpuMemEntry per 4KB page (768 entries
2009-
/// for a 1024×768×4 framebuffer). Our previous approach sent 1 entry with the
2010-
/// entire 3MB range. The host (Parallels) may require per-page entries to
2011-
/// properly map backing for GL texture operations and TRANSFER_FROM_HOST_3D.
2012-
fn virgl_attach_backing_cmd(state: &mut GpuPciDeviceState, resource_id: u32) -> Result<(), &'static str> {
2013-
let fb_ptr = unsafe { PCI_3D_FB_PTR };
2014-
assert!(!fb_ptr.is_null(), "3D framebuffer not initialized");
2015-
let fb_base_phys = virt_to_phys(fb_ptr as u64);
2016-
let fb_len = unsafe { PCI_3D_FB_LEN };
2017-
let actual_len = (state.width as usize * state.height as usize * 4).min(fb_len);
1983+
/// for a 1024×768×4 framebuffer). The host (Parallels) requires per-page entries
1984+
/// to properly map backing for GL texture operations and TRANSFER_FROM_HOST_3D.
1985+
/// A single-entry approach causes the host to read zeros → BLACK texture sampling.
1986+
fn virgl_attach_backing_paged(
1987+
state: &mut GpuPciDeviceState,
1988+
resource_id: u32,
1989+
backing_ptr: *const u8,
1990+
backing_len: usize,
1991+
) -> Result<(), &'static str> {
1992+
assert!(!backing_ptr.is_null(), "backing pointer is null");
1993+
let fb_base_phys = virt_to_phys(backing_ptr as u64);
1994+
let actual_len = backing_len;
20181995

20191996
const PAGE_SIZE: usize = 4096;
20201997
let nr_pages = (actual_len + PAGE_SIZE - 1) / PAGE_SIZE;
@@ -3054,7 +3031,6 @@ pub fn virgl_composite_frame(
30543031
/// alpha blending, transforms). Currently not working on Parallels — texture
30553032
/// sampling produces black output despite successful SUBMIT_3D. Kept for future
30563033
/// debugging and enablement.
3057-
#[allow(dead_code)]
30583034
pub fn virgl_composite_frame_textured(
30593035
pixels: &[u32],
30603036
width: u32,
@@ -3095,41 +3071,45 @@ pub fn virgl_composite_frame_textured(
30953071
})?;
30963072

30973073
// Build VirGL batch: full pipeline + textured quad
3074+
// Object handles are unique per batch to avoid hash collisions in virglrenderer:
3075+
// 10=surface, 11=blend, 12=DSA, 13=rasterizer, 14=VS, 15=FS,
3076+
// 16=VE, 17=sampler_view, 18=sampler_state
30983077
let mut cmdbuf = CommandBuffer::new();
30993078
cmdbuf.create_sub_ctx(1);
31003079
cmdbuf.set_sub_ctx(1);
31013080
cmdbuf.set_tweaks(1, 1);
31023081
cmdbuf.set_tweaks(2, display_w);
31033082

3104-
cmdbuf.create_surface(1, RESOURCE_3D_ID, vfmt::B8G8R8X8_UNORM, 0, 0);
3105-
cmdbuf.set_framebuffer_state(0, &[1]);
3106-
cmdbuf.create_blend_simple(1);
3107-
cmdbuf.bind_object(1, super::virgl::OBJ_BLEND);
3108-
cmdbuf.create_dsa_default(1);
3109-
cmdbuf.bind_object(1, super::virgl::OBJ_DSA);
3110-
cmdbuf.create_rasterizer_default(1);
3111-
cmdbuf.bind_object(1, super::virgl::OBJ_RASTERIZER);
3083+
cmdbuf.create_surface(10, RESOURCE_3D_ID, vfmt::B8G8R8X8_UNORM, 0, 0);
3084+
cmdbuf.set_framebuffer_state(0, &[10]);
3085+
cmdbuf.create_blend_simple(11);
3086+
cmdbuf.bind_object(11, super::virgl::OBJ_BLEND);
3087+
cmdbuf.create_dsa_default(12);
3088+
cmdbuf.bind_object(12, super::virgl::OBJ_DSA);
3089+
cmdbuf.create_rasterizer_default(13);
3090+
cmdbuf.bind_object(13, super::virgl::OBJ_RASTERIZER);
31123091

3113-
// Texture shaders
3092+
// Texture shaders (num_tokens=300 required by Parallels)
31143093
let tex_vs = b"VERT\nDCL IN[0]\nDCL IN[1]\nDCL OUT[0], POSITION\nDCL OUT[1], GENERIC[0]\n 0: MOV OUT[0], IN[0]\n 1: MOV OUT[1], IN[1]\n 2: END\n";
3115-
cmdbuf.create_shader(1, pipe::SHADER_VERTEX, 300, tex_vs);
3116-
cmdbuf.bind_shader(1, pipe::SHADER_VERTEX);
3117-
let tex_fs = b"FRAG\nDCL IN[0], GENERIC[0], LINEAR\nDCL OUT[0], COLOR\nDCL SAMP[0]\nDCL SVIEW[0], 2D, FLOAT\n 0: TEX OUT[0], IN[0], SAMP[0], 2D\n 1: END\n";
3118-
cmdbuf.create_shader(2, pipe::SHADER_FRAGMENT, 300, tex_fs);
3119-
cmdbuf.bind_shader(2, pipe::SHADER_FRAGMENT);
3094+
cmdbuf.create_shader(14, pipe::SHADER_VERTEX, 300, tex_vs);
3095+
cmdbuf.bind_shader(14, pipe::SHADER_VERTEX);
3096+
let tex_fs = b"FRAG\nPROPERTY FS_COLOR0_WRITES_ALL_CBUFS 1\nDCL IN[0], GENERIC[0], LINEAR\nDCL OUT[0], COLOR\nDCL SAMP[0]\nDCL SVIEW[0], 2D, FLOAT\n 0: TEX OUT[0], IN[0], SAMP[0], 2D\n 1: END\n";
3097+
cmdbuf.create_shader(15, pipe::SHADER_FRAGMENT, 300, tex_fs);
3098+
cmdbuf.bind_shader(15, pipe::SHADER_FRAGMENT);
31203099

3121-
cmdbuf.create_vertex_elements(1, &[
3100+
cmdbuf.create_vertex_elements(16, &[
31223101
(0, 0, 0, vfmt::R32G32B32A32_FLOAT),
31233102
(16, 0, 0, vfmt::R32G32B32A32_FLOAT),
31243103
]);
3125-
cmdbuf.bind_object(1, super::virgl::OBJ_VERTEX_ELEMENTS);
3104+
cmdbuf.bind_object(16, super::virgl::OBJ_VERTEX_ELEMENTS);
31263105

3127-
cmdbuf.create_sampler_view(2, RESOURCE_COMPOSITE_TEX_ID, vfmt::B8G8R8X8_UNORM, 0, 0, swizzle::IDENTITY);
3128-
cmdbuf.create_sampler_state(3, pipe::TEX_WRAP_CLAMP_TO_EDGE, pipe::TEX_WRAP_CLAMP_TO_EDGE, pipe::TEX_WRAP_CLAMP_TO_EDGE, pipe::TEX_FILTER_NEAREST, pipe::TEX_MIPFILTER_NONE, pipe::TEX_FILTER_NEAREST);
3106+
// Sampler view: format DWORD must include texture target in bits [24:31]
3107+
cmdbuf.create_sampler_view(17, RESOURCE_COMPOSITE_TEX_ID, vfmt::B8G8R8X8_UNORM, pipe::TEXTURE_2D, 0, 0, 0, 0, swizzle::IDENTITY);
3108+
cmdbuf.create_sampler_state(18, pipe::TEX_WRAP_CLAMP_TO_EDGE, pipe::TEX_WRAP_CLAMP_TO_EDGE, pipe::TEX_WRAP_CLAMP_TO_EDGE, pipe::TEX_FILTER_NEAREST, pipe::TEX_MIPFILTER_NONE, pipe::TEX_FILTER_NEAREST);
31293109
cmdbuf.set_min_samples(1);
31303110
cmdbuf.set_viewport(display_w as f32, display_h as f32);
3131-
cmdbuf.set_sampler_views(pipe::SHADER_FRAGMENT, 0, &[2]);
3132-
cmdbuf.bind_sampler_states(pipe::SHADER_FRAGMENT, 0, &[3]);
3111+
cmdbuf.set_sampler_views(pipe::SHADER_FRAGMENT, 0, &[17]);
3112+
cmdbuf.bind_sampler_states(pipe::SHADER_FRAGMENT, 0, &[18]);
31333113
cmdbuf.clear_color(0.0, 0.0, 0.0, 1.0);
31343114

31353115
let u_max = copy_w as f32 / tex_w as f32;
@@ -3329,9 +3309,12 @@ pub fn virgl_init() -> Result<(), &'static str> {
33293309
crate::serial_println!("[virgl] Step 2: 3D resource created ({}x{}, bind=0x{:08x})",
33303310
width, height, bind_flags);
33313311

3332-
// Step 4: Attach backing memory
3312+
// Step 4: Attach backing memory (per-page scatter-gather)
33333313
with_device_state(|state| {
3334-
virgl_attach_backing_cmd(state, RESOURCE_3D_ID)
3314+
let fb_ptr = unsafe { PCI_3D_FB_PTR };
3315+
let fb_len = unsafe { PCI_3D_FB_LEN };
3316+
let actual_len = (state.width as usize * state.height as usize * 4).min(fb_len);
3317+
virgl_attach_backing_paged(state, RESOURCE_3D_ID, fb_ptr, actual_len)
33353318
})?;
33363319
crate::serial_println!("[virgl] Step 3: backing attached");
33373320

@@ -3585,8 +3568,9 @@ fn virgl_test_textured_quad() -> Result<(), &'static str> {
35853568
5, // handle
35863569
RESOURCE_TEX_ID, // resource
35873570
vfmt::B8G8R8A8_UNORM, // format
3588-
0, // first_level
3589-
0, // last_level
3571+
pipe::TEXTURE_2D, // target (packed into format bits [24:31])
3572+
0, 0, // first_layer, last_layer
3573+
0, 0, // first_level, last_level
35903574
swizzle::IDENTITY, // RGBA identity swizzle
35913575
);
35923576

kernel/src/drivers/virtio/virgl.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -520,25 +520,34 @@ impl CommandBuffer {
520520
/// Create a sampler view (binds a texture resource for shader sampling).
521521
///
522522
/// VirGL protocol: CREATE_OBJECT(SAMPLER_VIEW) with 6 payload DWORDs:
523-
/// `[handle, res_handle, format, first_level, last_level, swizzle_packed]`
523+
/// `[handle, res_handle, fmt_target, val0(layers), val1(levels), swizzle_packed]`
524524
///
525+
/// `fmt_target`: bits [0:23] = pipe format, bits [24:31] = pipe texture target.
526+
/// `val0`: `first_layer | (last_layer << 16)` (for TEXTURE_2D, both 0).
527+
/// `val1`: `first_level | (last_level << 8)` (for single-mip, both 0).
525528
/// `swizzle_packed` encodes channel mapping: `r | (g<<3) | (b<<6) | (a<<9)`
526529
/// using constants from `swizzle::*`. Use `swizzle::IDENTITY` for default.
527530
pub fn create_sampler_view(
528531
&mut self,
529532
handle: u32,
530533
res_handle: u32,
531534
format: u32,
535+
target: u32,
536+
first_layer: u32,
537+
last_layer: u32,
532538
first_level: u32,
533539
last_level: u32,
534540
swizzle_packed: u32,
535541
) {
542+
let fmt_target = (format & 0x00FF_FFFF) | ((target & 0xFF) << 24);
543+
let val0 = (first_layer & 0xFFFF) | ((last_layer & 0xFFFF) << 16);
544+
let val1 = (first_level & 0xFF) | ((last_level & 0xFF) << 8);
536545
self.push(Self::cmd0(ccmd::CREATE_OBJECT, obj::SAMPLER_VIEW, 6));
537546
self.push(handle);
538547
self.push(res_handle);
539-
self.push(format);
540-
self.push(first_level);
541-
self.push(last_level);
548+
self.push(fmt_target);
549+
self.push(val0);
550+
self.push(val1);
542551
self.push(swizzle_packed);
543552
}
544553

kernel/src/syscall/graphics.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -455,11 +455,17 @@ fn handle_virgl_op(cmd: &FbDrawCmd) -> SyscallResult {
455455
let pixels = unsafe {
456456
core::slice::from_raw_parts(buf_ptr as *const u32, pixel_count as usize)
457457
};
458-
match crate::drivers::virtio::gpu_pci::virgl_composite_frame(pixels, width, height) {
458+
// Try GPU-textured compositing first, fall back to direct blit
459+
match crate::drivers::virtio::gpu_pci::virgl_composite_frame_textured(pixels, width, height) {
459460
Ok(()) => SyscallResult::Ok(0),
460-
Err(e) => {
461-
crate::serial_println!("[virgl-syscall] composite_frame FAILED: {}", e);
462-
SyscallResult::Err(super::ErrorCode::InvalidArgument as u64)
461+
Err(_) => {
462+
match crate::drivers::virtio::gpu_pci::virgl_composite_frame(pixels, width, height) {
463+
Ok(()) => SyscallResult::Ok(0),
464+
Err(e) => {
465+
crate::serial_println!("[virgl-syscall] composite_frame FAILED: {}", e);
466+
SyscallResult::Err(super::ErrorCode::InvalidArgument as u64)
467+
}
468+
}
463469
}
464470
}
465471
}

0 commit comments

Comments
 (0)