Skip to content

Commit f17673a

Browse files
authored
feat: OffscreenCanvas (#29357)
Progresses #5701 Closes #31381 --------- Signed-off-by: Leo Kettmeir <crowlkats@toaxl.com>
1 parent 426a0f8 commit f17673a

34 files changed

Lines changed: 2753 additions & 570 deletions

.github/workflows/ci.generated.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,7 +1422,7 @@ jobs:
14221422
if: '!startsWith(github.ref, ''refs/tags/'')'
14231423
env:
14241424
CARGO_PROFILE_DEV_DEBUG: 0
1425-
run: cargo test --locked --lib -p deno -p denort -p node_shim -p bsdiff_helper -p deno_lib -p deno_snapshots -p deno_bundle_runtime -p deno_cache -p deno_cron -p deno_crypto -p deno_fetch -p deno_ffi -p deno_fs -p deno_http -p deno_image -p deno_io -p deno_kv -p deno_napi -p napi_sym -p deno_net -p deno_node -p deno_node_crypto -p deno_node_sqlite -p denort_helper -p deno_signals -p deno_telemetry -p deno_url -p deno_web -p deno_webgpu -p deno_webidl -p deno_websocket -p deno_webstorage -p deno_cache_dir -p deno_config -p deno_crypto_provider -p deno_dotenv -p eszip -p deno_inspector_server -p deno_lockfile -p deno_maybe_sync -p napi_sys -p node_resolver -p deno_npm -p deno_npm_cache -p deno_npm_installer -p deno_npmrc -p deno_package_json -p deno_resolver -p deno_typescript_go_client_rust -p deno_runtime -p deno_features -p deno_permissions -p deno_subprocess_windows
1425+
run: cargo test --locked --lib -p deno -p denort -p node_shim -p bsdiff_helper -p deno_lib -p deno_snapshots -p deno_bundle_runtime -p deno_cache -p deno_canvas -p deno_cron -p deno_crypto -p deno_fetch -p deno_ffi -p deno_fs -p deno_http -p deno_image -p deno_io -p deno_kv -p deno_napi -p napi_sym -p deno_net -p deno_node -p deno_node_crypto -p deno_node_sqlite -p denort_helper -p deno_signals -p deno_telemetry -p deno_url -p deno_web -p deno_webgpu -p deno_webidl -p deno_websocket -p deno_webstorage -p deno_cache_dir -p deno_config -p deno_crypto_provider -p deno_dotenv -p eszip -p deno_inspector_server -p deno_lockfile -p deno_maybe_sync -p napi_sys -p node_resolver -p deno_npm -p deno_npm_cache -p deno_npm_installer -p deno_npmrc -p deno_package_json -p deno_resolver -p deno_typescript_go_client_rust -p deno_runtime -p deno_features -p deno_permissions -p deno_subprocess_windows
14261426
- name: Cache cargo home
14271427
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830
14281428
if: '!startsWith(github.ref, ''refs/tags/'') && github.ref == ''refs/heads/main'''
@@ -2261,7 +2261,7 @@ jobs:
22612261
if: '!startsWith(github.ref, ''refs/tags/'')'
22622262
env:
22632263
CARGO_PROFILE_DEV_DEBUG: 0
2264-
run: cargo test --locked --lib -p deno -p denort -p node_shim -p bsdiff_helper -p deno_lib -p deno_snapshots -p deno_bundle_runtime -p deno_cache -p deno_cron -p deno_crypto -p deno_fetch -p deno_ffi -p deno_fs -p deno_http -p deno_image -p deno_io -p deno_kv -p deno_napi -p napi_sym -p deno_net -p deno_node -p deno_node_crypto -p deno_node_sqlite -p denort_helper -p deno_signals -p deno_telemetry -p deno_url -p deno_web -p deno_webgpu -p deno_webidl -p deno_websocket -p deno_webstorage -p deno_cache_dir -p deno_config -p deno_crypto_provider -p deno_dotenv -p eszip -p deno_inspector_server -p deno_lockfile -p deno_maybe_sync -p napi_sys -p node_resolver -p deno_npm -p deno_npm_cache -p deno_npm_installer -p deno_npmrc -p deno_package_json -p deno_resolver -p deno_typescript_go_client_rust -p deno_runtime -p deno_features -p deno_permissions -p deno_subprocess_windows
2264+
run: cargo test --locked --lib -p deno -p denort -p node_shim -p bsdiff_helper -p deno_lib -p deno_snapshots -p deno_bundle_runtime -p deno_cache -p deno_canvas -p deno_cron -p deno_crypto -p deno_fetch -p deno_ffi -p deno_fs -p deno_http -p deno_image -p deno_io -p deno_kv -p deno_napi -p napi_sym -p deno_net -p deno_node -p deno_node_crypto -p deno_node_sqlite -p denort_helper -p deno_signals -p deno_telemetry -p deno_url -p deno_web -p deno_webgpu -p deno_webidl -p deno_websocket -p deno_webstorage -p deno_cache_dir -p deno_config -p deno_crypto_provider -p deno_dotenv -p eszip -p deno_inspector_server -p deno_lockfile -p deno_maybe_sync -p napi_sys -p node_resolver -p deno_npm -p deno_npm_cache -p deno_npm_installer -p deno_npmrc -p deno_package_json -p deno_resolver -p deno_typescript_go_client_rust -p deno_runtime -p deno_features -p deno_permissions -p deno_subprocess_windows
22652265
- name: Cache cargo home
22662266
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830
22672267
if: '!startsWith(github.ref, ''refs/tags/'') && github.ref == ''refs/heads/main'''
@@ -5150,7 +5150,7 @@ jobs:
51505150
if: '!startsWith(github.ref, ''refs/tags/'')'
51515151
env:
51525152
CARGO_PROFILE_DEV_DEBUG: 0
5153-
run: cargo test --locked --lib -p deno -p denort -p node_shim -p bsdiff_helper -p deno_lib -p deno_snapshots -p deno_bundle_runtime -p deno_cache -p deno_cron -p deno_crypto -p deno_fetch -p deno_ffi -p deno_fs -p deno_http -p deno_image -p deno_io -p deno_kv -p deno_napi -p napi_sym -p deno_net -p deno_node -p deno_node_crypto -p deno_node_sqlite -p denort_helper -p deno_signals -p deno_telemetry -p deno_url -p deno_web -p deno_webgpu -p deno_webidl -p deno_websocket -p deno_webstorage -p deno_cache_dir -p deno_config -p deno_crypto_provider -p deno_dotenv -p eszip -p deno_inspector_server -p deno_lockfile -p deno_maybe_sync -p napi_sys -p node_resolver -p deno_npm -p deno_npm_cache -p deno_npm_installer -p deno_npmrc -p deno_package_json -p deno_resolver -p deno_typescript_go_client_rust -p deno_runtime -p deno_features -p deno_permissions -p deno_subprocess_windows
5153+
run: cargo test --locked --lib -p deno -p denort -p node_shim -p bsdiff_helper -p deno_lib -p deno_snapshots -p deno_bundle_runtime -p deno_cache -p deno_canvas -p deno_cron -p deno_crypto -p deno_fetch -p deno_ffi -p deno_fs -p deno_http -p deno_image -p deno_io -p deno_kv -p deno_napi -p napi_sym -p deno_net -p deno_node -p deno_node_crypto -p deno_node_sqlite -p denort_helper -p deno_signals -p deno_telemetry -p deno_url -p deno_web -p deno_webgpu -p deno_webidl -p deno_websocket -p deno_webstorage -p deno_cache_dir -p deno_config -p deno_crypto_provider -p deno_dotenv -p eszip -p deno_inspector_server -p deno_lockfile -p deno_maybe_sync -p napi_sys -p node_resolver -p deno_npm -p deno_npm_cache -p deno_npm_installer -p deno_npmrc -p deno_package_json -p deno_resolver -p deno_typescript_go_client_rust -p deno_runtime -p deno_features -p deno_permissions -p deno_subprocess_windows
51545154
- name: Cache cargo home
51555155
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830
51565156
if: '!startsWith(github.ref, ''refs/tags/'') && github.ref == ''refs/heads/main'''

Cargo.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ members = [
99
"cli/snapshot",
1010
"ext/bundle",
1111
"ext/cache",
12+
"ext/canvas",
1213
"ext/cron",
1314
"ext/crypto",
1415
"ext/fetch",
@@ -111,6 +112,7 @@ denokv_sqlite = { default-features = false, version = "0.13.0" }
111112
# exts
112113
deno_bundle_runtime = { version = "0.34.0", path = "./ext/bundle" }
113114
deno_cache = { version = "0.180.0", path = "./ext/cache" }
115+
deno_canvas = { version = "0.109.0", path = "./ext/canvas" }
114116
deno_cron = { version = "0.127.0", path = "./ext/cron" }
115117
deno_crypto = { version = "0.261.0", path = "./ext/crypto" }
116118
deno_fetch = { version = "0.271.0", path = "./ext/fetch" }

cli/ops/jupyter.rs

Lines changed: 7 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -196,32 +196,6 @@ pub fn op_jupyter_create_png_from_texture(
196196
use deno_runtime::deno_webgpu::*;
197197
use texture::GPUTextureFormat;
198198

199-
// We only support the 8 bit per pixel formats with 4 channels
200-
// as such a pixel has 4 bytes
201-
const BYTES_PER_PIXEL: u32 = 4;
202-
203-
let unpadded_bytes_per_row = texture.size.width * BYTES_PER_PIXEL;
204-
let padded_bytes_per_row_padding = (wgpu_types::COPY_BYTES_PER_ROW_ALIGNMENT
205-
- (unpadded_bytes_per_row % wgpu_types::COPY_BYTES_PER_ROW_ALIGNMENT))
206-
% wgpu_types::COPY_BYTES_PER_ROW_ALIGNMENT;
207-
let padded_bytes_per_row =
208-
unpadded_bytes_per_row + padded_bytes_per_row_padding;
209-
210-
let (buffer, maybe_err) = texture.instance.device_create_buffer(
211-
texture.device_id,
212-
&wgpu_types::BufferDescriptor {
213-
label: None,
214-
size: (padded_bytes_per_row * texture.size.height) as _,
215-
usage: wgpu_types::BufferUsages::MAP_READ
216-
| wgpu_types::BufferUsages::COPY_DST,
217-
mapped_at_creation: false,
218-
},
219-
None,
220-
);
221-
if let Some(maybe_err) = maybe_err {
222-
return Err(JsErrorBox::from_err::<GPUError>(maybe_err.into()));
223-
}
224-
225199
let (command_encoder, maybe_err) =
226200
texture.instance.device_create_command_encoder(
227201
texture.device_id,
@@ -232,94 +206,14 @@ pub fn op_jupyter_create_png_from_texture(
232206
return Err(JsErrorBox::from_err::<GPUError>(maybe_err.into()));
233207
}
234208

235-
texture
236-
.instance
237-
.command_encoder_copy_texture_to_buffer(
238-
command_encoder,
239-
&wgpu_types::TexelCopyTextureInfo {
240-
texture: texture.id,
241-
mip_level: 0,
242-
origin: Default::default(),
243-
aspect: Default::default(),
244-
},
245-
&wgpu_types::TexelCopyBufferInfo {
246-
buffer,
247-
layout: wgpu_types::TexelCopyBufferLayout {
248-
offset: 0,
249-
bytes_per_row: Some(padded_bytes_per_row),
250-
rows_per_image: None,
251-
},
252-
},
253-
&texture.size,
254-
)
255-
.map_err(|e| JsErrorBox::from_err::<GPUError>(e.into()))?;
256-
257-
let (command_buffer, maybe_err) = texture.instance.command_encoder_finish(
209+
let data = canvas::copy_texture_to_vec(
210+
&texture.instance,
211+
texture.device_id,
212+
texture.queue_id,
258213
command_encoder,
259-
&wgpu_types::CommandBufferDescriptor { label: None },
260-
None,
261-
);
262-
if let Some((_, maybe_err)) = maybe_err {
263-
return Err(JsErrorBox::from_err::<GPUError>(maybe_err.into()));
264-
}
265-
266-
let maybe_err = texture
267-
.instance
268-
.queue_submit(texture.queue_id, &[command_buffer])
269-
.err();
270-
if let Some((_, maybe_err)) = maybe_err {
271-
return Err(JsErrorBox::from_err::<GPUError>(maybe_err.into()));
272-
}
273-
274-
let index = texture
275-
.instance
276-
.buffer_map_async(
277-
buffer,
278-
0,
279-
None,
280-
wgpu_core::resource::BufferMapOperation {
281-
host: wgpu_core::device::HostMap::Read,
282-
callback: None,
283-
},
284-
)
285-
.map_err(|e| JsErrorBox::from_err::<GPUError>(e.into()))?;
286-
287-
texture
288-
.instance
289-
.device_poll(
290-
texture.device_id,
291-
wgpu_types::PollType::Wait {
292-
submission_index: Some(index),
293-
timeout: None,
294-
},
295-
)
296-
.unwrap();
297-
298-
let (slice_pointer, range_size) = texture
299-
.instance
300-
.buffer_get_mapped_range(buffer, 0, None)
301-
.map_err(|e| JsErrorBox::from_err::<GPUError>(e.into()))?;
302-
303-
let data = {
304-
// SAFETY: creating a slice from pointer and length provided by wgpu and
305-
// then dropping it before unmapping
306-
let slice = unsafe {
307-
std::slice::from_raw_parts(slice_pointer.as_ptr(), range_size as usize)
308-
};
309-
310-
let mut unpadded =
311-
Vec::with_capacity((unpadded_bytes_per_row * texture.size.height) as _);
312-
313-
for i in 0..texture.size.height {
314-
unpadded.extend_from_slice(
315-
&slice[((i * padded_bytes_per_row) as usize)
316-
..(((i + 1) * padded_bytes_per_row) as usize)]
317-
[..(unpadded_bytes_per_row as usize)],
318-
);
319-
}
320-
321-
unpadded
322-
};
214+
texture.id,
215+
&texture.size,
216+
)?;
323217

324218
let color_type = match texture.format {
325219
GPUTextureFormat::Rgba8unorm => ExtendedColorType::Rgba8,
@@ -345,12 +239,6 @@ pub fn op_jupyter_create_png_from_texture(
345239
.write_image(&data, texture.size.width, texture.size.height, color_type)
346240
.map_err(|e| JsErrorBox::type_error(e.to_string()))?;
347241

348-
texture
349-
.instance
350-
.buffer_unmap(buffer)
351-
.map_err(|e| JsErrorBox::from_err::<GPUError>(e.into()))?;
352-
texture.instance.buffer_drop(buffer);
353-
354242
Ok(deno_runtime::deno_web::forgiving_base64_encode(&out))
355243
}
356244

cli/tsc/dts/lib.deno.unstable.d.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,11 @@ declare namespace Deno {
197197
* @experimental
198198
*/
199199
export class UnsafeWindowSurface {
200+
/** The height of the window. */
201+
height: number;
202+
/** The width of the window. */
203+
width: number;
204+
200205
constructor(
201206
options: {
202207
system: "cocoa" | "win32" | "x11" | "wayland";
@@ -206,12 +211,13 @@ declare namespace Deno {
206211
height: number;
207212
},
208213
);
209-
getContext(context: "webgpu"): GPUCanvasContext;
214+
215+
getContext(
216+
contextId: OffscreenRenderingContextId,
217+
options?: any,
218+
): OffscreenRenderingContext | null;
219+
210220
present(): void;
211-
/**
212-
* This method should be invoked when the size of the window changes.
213-
*/
214-
resize(width: number, height: number): void;
215221
}
216222

217223
/** **UNSTABLE**: New API, yet to be vetted.

cli/tsc/dts/lib.deno_canvas.d.ts

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ declare function createImageBitmap(
152152
* console.error("Failed to create ImageBitmap:", error);
153153
* }
154154
* ```
155-
* @see https://developer.mozilla.org/en-US/docs/Web/API/createImageBitmap/createImageBitmap
155+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/createImageBitmap
156156
*/
157157
declare function createImageBitmap(
158158
image: ImageBitmapSource,
@@ -192,3 +192,114 @@ declare var ImageBitmap: {
192192
prototype: ImageBitmap;
193193
new (): ImageBitmap;
194194
};
195+
196+
/** @category Canvas */
197+
type OffscreenRenderingContextId = "bitmaprenderer" | "webgpu";
198+
/** @category Canvas */
199+
type OffscreenRenderingContext = ImageBitmapRenderingContext | GPUCanvasContext;
200+
201+
/** @category Canvas */
202+
interface ImageEncodeOptions {
203+
quality?: number;
204+
type?: string;
205+
}
206+
207+
/** @category Canvas */
208+
type GPUCanvasAlphaMode = "opaque" | "premultiplied";
209+
210+
/** @category Canvas */
211+
type GPUPresentMode =
212+
| "auto-vsync"
213+
| "auto-no-vsync"
214+
| "fifo"
215+
| "fifo-relaxed"
216+
| "immediate"
217+
| "mailbox";
218+
219+
/** @category Canvas */
220+
interface GPUCanvasConfiguration {
221+
device: GPUDevice;
222+
format: GPUTextureFormat;
223+
usage?: GPUTextureUsageFlags;
224+
viewFormats?: GPUTextureFormat[];
225+
colorSpace?: "srgb" | "display-p3";
226+
alphaMode?: GPUCanvasAlphaMode;
227+
228+
// extended from spec
229+
presentMode?: GPUPresentMode;
230+
}
231+
232+
/** @category Canvas */
233+
interface GPUCanvasContext {
234+
/** The canvas that this context is bound to. */
235+
readonly canvas: OffscreenCanvas;
236+
237+
configure(configuration: GPUCanvasConfiguration): undefined;
238+
getConfiguration(): GPUCanvasConfiguration | null;
239+
unconfigure(): undefined;
240+
getCurrentTexture(): GPUTexture;
241+
}
242+
/** @category Canvas */
243+
declare var GPUCanvasContext: {
244+
prototype: GPUCanvasContext;
245+
};
246+
247+
/** @category Canvas */
248+
interface ImageBitmapRenderingContext {
249+
/** The canvas that this context is bound to. */
250+
readonly canvas: OffscreenCanvas;
251+
252+
transferFromImageBitmap(bitmap: ImageBitmap | null): undefined;
253+
}
254+
/** @category Canvas */
255+
declare var ImageBitmapRenderingContext: {
256+
prototype: ImageBitmapRenderingContext;
257+
};
258+
259+
/**
260+
* @category Canvas
261+
* @see https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
262+
*/
263+
interface OffscreenCanvas extends EventTarget {
264+
/** The height of the canvas. */
265+
height: number;
266+
/** The width of the canvas. */
267+
width: number;
268+
269+
/** Create a Blob object representing the image contained in the canvas. */
270+
convertToBlob(options?: ImageEncodeOptions): Promise<Blob>;
271+
272+
/**
273+
* Get a drawing context for the canvas.
274+
* If this was previously called, it will return the same context.
275+
*/
276+
getContext(
277+
contextId: "bitmaprenderer",
278+
options?: any,
279+
): ImageBitmapRenderingContext | null;
280+
getContext(contextId: "webgpu", options?: any): GPUCanvasContext | null;
281+
getContext(
282+
contextId: OffscreenRenderingContextId,
283+
options?: any,
284+
): OffscreenRenderingContext | null;
285+
// Spec also defines "2d", "webgl", and "webgl2" context ids; Deno does
286+
// not implement those and getContext returns null for them.
287+
getContext(
288+
contextId: "2d" | "webgl" | "webgl2",
289+
options?: any,
290+
): null;
291+
292+
/**
293+
* Create an ImageBitmap object representing the image contained in the canvas.
294+
*/
295+
transferToImageBitmap(): ImageBitmap;
296+
}
297+
298+
/**
299+
* @category Canvas
300+
* @see https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
301+
*/
302+
declare var OffscreenCanvas: {
303+
prototype: OffscreenCanvas;
304+
new (width: number, height: number): OffscreenCanvas;
305+
};

0 commit comments

Comments
 (0)