ltk_webkit/wpe/
software.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//! Copy path for GL implementations without a GPU: maps the DMA-BUF and uploads it instead of importing an EGLImage.

use std::ffi::{ c_int, c_ulong, c_void };
use std::ptr;

use glow::HasContext;
use gobject_sys::*;
use wpe_platform_sys::*;

const PROT_READ:  c_int = 1;
const MAP_SHARED: c_int = 1;

const DMA_BUF_IOCTL_SYNC: c_ulong = 0x4008_6200;
const DMA_BUF_SYNC_READ:  u64     = 1;
const DMA_BUF_SYNC_START: u64     = 0;
const DMA_BUF_SYNC_END:   u64     = 4;

const DRM_FORMAT_XRGB8888: u32 = 0x3432_5258;
const DRM_FORMAT_ARGB8888: u32 = 0x3432_5241;
const DRM_FORMAT_XBGR8888: u32 = 0x3432_4258;
const DRM_FORMAT_ABGR8888: u32 = 0x3432_4241;

const DRM_FORMAT_MOD_LINEAR:  u64 = 0;
const DRM_FORMAT_MOD_INVALID: u64 = 0x00ff_ffff_ffff_ffff;

#[ repr( C ) ]
struct DmaBufSync
{
	flags: u64,
}

extern "C"
{
	fn mmap( addr: *mut c_void, len: usize, prot: c_int, flags: c_int, fd: c_int, offset: i64 ) -> *mut c_void;
	fn munmap( addr: *mut c_void, len: usize ) -> c_int;
	fn ioctl( fd: c_int, request: c_ulong, ... ) -> c_int;
}

/// Must run with LTK's GLES context current.
pub fn wanted( gl: &glow::Context ) -> bool
{
	if std::env::var_os( "LTK_WEBKIT_COPY" ).is_some()
	{
		return true;
	}
	// SAFETY: the context is current, as the caller guarantees.
	let renderer = unsafe { gl.get_parameter_string( glow::RENDERER ) };
	renderer.contains( "llvmpipe" ) || renderer.contains( "softpipe" )
}

/// Uploads `buffer` to `texture`, creating the texture on first use and
/// reallocating it when the buffer size changes. Must run with LTK's GLES
/// context current.
pub fn upload(
	gl:      &glow::Context,
	texture: &mut Option<glow::Texture>,
	size:    &mut ( i32, i32 ),
	buffer:  *mut WPEBuffer,
) -> Result<(), String>
{
	// SAFETY: the caller guarantees `buffer` is a live WPEBuffer pointer.
	let is_dmabuf = unsafe
	{
		g_type_check_instance_is_a( buffer as *mut GTypeInstance, wpe_buffer_dma_buf_get_type() )
	} != 0;
	if !is_dmabuf
	{
		return Err( "buffer is not WPEBufferDMABuf".into() );
	}
	let dmabuf = buffer as *mut WPEBufferDMABuf;

	// SAFETY: `dmabuf` was just checked to be a WPEBufferDMABuf.
	let ( width, height, fourcc, planes, modifier, fd, offset, pitch ) = unsafe
	{
		(
			wpe_buffer_get_width( buffer ) as i32,
			wpe_buffer_get_height( buffer ) as i32,
			wpe_buffer_dma_buf_get_format( dmabuf ) as u32,
			wpe_buffer_dma_buf_get_n_planes( dmabuf ),
			wpe_buffer_dma_buf_get_modifier( dmabuf ) as u64,
			wpe_buffer_dma_buf_get_fd( dmabuf, 0 ) as c_int,
			wpe_buffer_dma_buf_get_offset( dmabuf, 0 ) as usize,
			wpe_buffer_dma_buf_get_stride( dmabuf, 0 ) as usize,
		)
	};

	super::dbg_log( &format!(
		"dmabuf {}x{} fourcc=0x{:08x} modifier={:#x} planes={} pitch={}",
		width, height, fourcc, modifier, planes, pitch,
	) );

	let ( swap_rb, opaque ) = match fourcc
	{
		DRM_FORMAT_ARGB8888 => ( true,  false ),
		DRM_FORMAT_XRGB8888 => ( true,  true ),
		DRM_FORMAT_ABGR8888 => ( false, false ),
		DRM_FORMAT_XBGR8888 => ( false, true ),
		_ => return Err( format!( "unsupported fourcc 0x{:08x}", fourcc ) ),
	};
	if planes != 1
	{
		return Err( format!( "unsupported plane count {}", planes ) );
	}
	if modifier != DRM_FORMAT_MOD_LINEAR && modifier != DRM_FORMAT_MOD_INVALID && modifier != u64::MAX
	{
		return Err( format!( "unsupported modifier {:#x}", modifier ) );
	}
	if width <= 0 || height <= 0 || pitch % 4 != 0 || pitch < width as usize * 4
	{
		return Err( format!( "unsupported geometry {}x{} pitch {}", width, height, pitch ) );
	}

	let rows = pitch * height as usize;
	let len  = offset + rows;
	// SAFETY: a read-only shared mapping of a DMA-BUF fd owned by the buffer.
	let map = unsafe { mmap( ptr::null_mut(), len, PROT_READ, MAP_SHARED, fd, 0 ) };
	if map as isize == -1
	{
		return Err( format!( "mmap failed: {}", std::io::Error::last_os_error() ) );
	}

	let sync = | flags: u64 |
	{
		let arg = DmaBufSync { flags };
		// SAFETY: DMA_BUF_IOCTL_SYNC takes a pointer to a struct dma_buf_sync.
		unsafe { ioctl( fd, DMA_BUF_IOCTL_SYNC, &arg as *const DmaBufSync ); }
	};
	sync( DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ );

	// SAFETY: the mapping is `len` bytes long and stays alive until munmap
	// below; `pixels` does not escape this function.
	let pixels = unsafe { std::slice::from_raw_parts( ( map as *const u8 ).add( offset ), rows ) };

	let result = unsafe { upload_pixels( gl, texture, size, pixels, width, height, pitch, swap_rb, opaque ) };

	sync( DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ );
	// SAFETY: `map` and `len` are the mapping created above.
	unsafe { munmap( map, len ); }
	result
}

#[ allow( clippy::too_many_arguments ) ]
unsafe fn upload_pixels(
	gl:      &glow::Context,
	texture: &mut Option<glow::Texture>,
	size:    &mut ( i32, i32 ),
	pixels:  &[u8],
	width:   i32,
	height:  i32,
	pitch:   usize,
	swap_rb: bool,
	opaque:  bool,
) -> Result<(), String>
{
	let tex = match *texture
	{
		Some( tex ) => tex,
		None =>
		{
			let tex     = unsafe { gl.create_texture() }?;
			let swizzle = [
				if swap_rb { glow::BLUE } else { glow::RED },
				if swap_rb { glow::RED } else { glow::BLUE },
				if opaque { glow::ONE } else { glow::ALPHA },
			];
			unsafe
			{
				gl.bind_texture( glow::TEXTURE_2D, Some( tex ) );
				gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32 );
				gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32 );
				gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_S,     glow::CLAMP_TO_EDGE as i32 );
				gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_T,     glow::CLAMP_TO_EDGE as i32 );
				gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_SWIZZLE_R,  swizzle[ 0 ] as i32 );
				gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_SWIZZLE_B,  swizzle[ 1 ] as i32 );
				gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_SWIZZLE_A,  swizzle[ 2 ] as i32 );
			}
			*texture = Some( tex );
			*size    = ( 0, 0 );
			tex
		}
	};

	unsafe
	{
		gl.bind_texture( glow::TEXTURE_2D, Some( tex ) );
		gl.pixel_store_i32( glow::UNPACK_ALIGNMENT, 4 );
		gl.pixel_store_i32( glow::UNPACK_ROW_LENGTH, ( pitch / 4 ) as i32 );
		if *size == ( width, height )
		{
			gl.tex_sub_image_2d(
				glow::TEXTURE_2D, 0, 0, 0, width, height,
				glow::RGBA, glow::UNSIGNED_BYTE, glow::PixelUnpackData::Slice( Some( pixels ) ),
			);
		} else {
			gl.tex_image_2d(
				glow::TEXTURE_2D, 0, glow::RGBA8 as i32, width, height, 0,
				glow::RGBA, glow::UNSIGNED_BYTE, glow::PixelUnpackData::Slice( Some( pixels ) ),
			);
			*size = ( width, height );
		}
		gl.pixel_store_i32( glow::UNPACK_ROW_LENGTH, 0 );
	}
	Ok( () )
}