Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ext/canvas): OffscreenCanvas #23773

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
work
  • Loading branch information
crowlKats committed May 8, 2024
commit 6d0ea2a41dc4896ebe3c2221410d45f31a2b50c2
54 changes: 45 additions & 9 deletions ext/canvas/02_canvas.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import webidl from "ext:deno_webidl/00_webidl.js";
import { DOMException } from "ext:deno_web/01_dom_exception.js";
import { op_image_encode_png } from "ext:core/ops";


const _width = Symbol("[[width]]");
const _height = Symbol("[[height]]");
Expand Down Expand Up @@ -39,6 +41,7 @@ class OffscreenCanvas extends EventTarget {
this[_width] = width;
this[_height] = height;


// TODO: internal bitmap
}

Expand All @@ -59,10 +62,8 @@ class OffscreenCanvas extends EventTarget {
const settings = webidl.converters.ImageBitmapRenderingContextSettings(options, prefix, "Argument 2");
const context = webidl.createBranded(ImageBitmapRenderingContext);
context[_canvas] = this;

// TODO Set context's output bitmap to the same bitmap as target's bitmap (so that they are shared).
// TODO Run the steps to set an ImageBitmapRenderingContext's output bitmap with context.

context; // TODO Set context's output bitmap to the same bitmap as target's bitmap (so that they are shared).
setOutputBitmap(context);
context[_alpha] = settings.alpha;

this[_contextMode] = "bitmaprenderer";
Expand All @@ -79,7 +80,7 @@ class OffscreenCanvas extends EventTarget {
} else if (contextId === "webgpu") {
switch (this[_contextMode]) {
case null: {
// TODO Let context be the result of following the instructions given in WebGPU's Canvas Rendering section. [WEBGPU]
// TODO Let context be the result of following the instructions given in WebGPU's Canvas Rendering section. [WEBGPU]
this[_contextMode] = "bitmaprenderer";
this[_context] = context;
return context;
Expand All @@ -98,21 +99,35 @@ class OffscreenCanvas extends EventTarget {

transferToImageBitmap() {
webidl.assertBranded(this, OffscreenCanvasPrototype);
// TODO: If the value of this OffscreenCanvas object's [[Detached]] internal slot is set to true, then throw an "InvalidStateError" DOMException.

// TODO
if (!this[_contextMode]) {
throw new DOMException("Cannot get bitmap from canvas without a context", "InvalidStateError");
}

// TODO: Let image be a newly created ImageBitmap object that references the same underlying bitmap data as this OffscreenCanvas object's bitmap.
// TODO: Set this OffscreenCanvas object's bitmap to reference a newly created bitmap of the same dimensions and color space as the previous bitmap, and with its pixels initialized to transparent black, or opaque black if the rendering context's alpha flag is set to false.
}

convertToBlob(options = {}) {
webidl.assertBranded(this, OffscreenCanvasPrototype);
const prefix = "Failed to call 'getContext' on 'OffscreenCanvas'";
options = webidl.converters.ImageEncodeOptions(options, prefix, "Argument 1");

// TODO
// TODO: If the value of this OffscreenCanvas object's [[Detached]] internal slot is set to true, then return a promise rejected with an "InvalidStateError" DOMException.
// TODO: If this OffscreenCanvas object's context mode is 2d and the rendering context's bitmap's origin-clean flag is set to false, then return a promise rejected with a "SecurityError" DOMException.
// TODO: If this OffscreenCanvas object's bitmap has no pixels (i.e., either its horizontal dimension or its vertical dimension is zero) then return a promise rejected with an "IndexSizeError" DOMException.
// TODO: Let bitmap be a copy of this OffscreenCanvas object's bitmap.


op_image_encode_png(, this[_width], this[_height]);

}
}
const OffscreenCanvasPrototype = OffscreenCanvas.prototype;

const _canvas = Symbol("[[canvas]]");
const _bitmapMode = Symbol("[[bitmapMode]]");
const _alpha = Symbol("[[alpha]]");
class ImageBitmapRenderingContext {
[_canvas];
Expand All @@ -121,20 +136,39 @@ class ImageBitmapRenderingContext {
return this[_canvas];
}

[_bitmapMode];

constructor() {
webidl.illegalConstructor();
}

transferFromImageBitmap(bitmap) {
webidl.assertBranded(this, ImageBitmapRenderingContextPrototype);
const prefix = "Failed to call 'getContext' on 'OffscreenCanvas'";
bitmap = webidl.converters.ImageEncodeOptions(bitmap, prefix, "Argument 1");
bitmap = webidl.converters["ImageBitmap?"](bitmap, prefix, "Argument 1");

// TODO
if (bitmap === null) {
setOutputBitmap(this);
} else {
// TODO: If the value of bitmap's [[Detached]] internal slot is set to true, then throw an "InvalidStateError" DOMException.
setOutputBitmap(this, bitmap);
// TODO: Set the value of bitmap's [[Detached]] internal slot to true.
// TODO: Unset bitmap's bitmap data.
}
}
}
const ImageBitmapRenderingContextPrototype = ImageBitmapRenderingContext.prototype;

function setOutputBitmap(context, data) {
if (!data) {
context[_bitmapMode] = "blank";
context[_canvas]; // TODO: Set context's output bitmap to be transparent black with a natural width equal to the numeric value of canvas's width attribute and a natural height equal to the numeric value of canvas's height attribute, those values being interpreted in CSS pixels.
} else {
context[_bitmapMode] = "valid";
context; // TODO: Set context's output bitmap to refer to the same underlying bitmap data as bitmap, without making a copy.
}
}

// ENUM: OffscreenRenderingContextId
webidl.converters["OffscreenRenderingContextId"] = webidl.createEnumConverter(
"OffscreenRenderingContextId",
Expand All @@ -157,6 +191,8 @@ webidl.converters["ImageEncodeOptions"] = webidl
dictImageEncodeOptions,
);

webidl.converters["ImageBitmap?"] = webidl.createNullableConverter(webidl.converters["ImageBitmap"]);

// DICT: ImageBitmapRenderingContextSettings
const dictImageBitmapRenderingContextSettings = [
{ key: "alpha", converter: webidl.converters.boolean, defaultValue: true },
Expand Down
16 changes: 15 additions & 1 deletion ext/canvas/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use deno_core::ToJsBuffer;
use image::imageops::FilterType;
use image::ColorType;
use image::ImageDecoder;
use image::ImageEncoder;
use image::Pixel;
use image::RgbaImage;
use serde::Deserialize;
Expand Down Expand Up @@ -141,10 +142,23 @@ fn op_image_decode_png(#[buffer] buf: &[u8]) -> Result<DecodedPng, AnyError> {
})
}

#[op2]
#[serde]
fn op_image_encode_png(
#[buffer] buf: &[u8],
width: u32,
height: u32,
) -> Result<ToJsBuffer, AnyError> {
let mut out = vec![];
let png = image::codecs::png::PngEncoder::new(&out);
png.write_image(buf, width, height, ColorType::Rgb8)?;
Ok(out.into())
}

deno_core::extension!(
deno_canvas,
deps = [deno_webidl, deno_web, deno_webgpu],
ops = [op_image_process, op_image_decode_png],
ops = [op_image_process, op_image_decode_png, op_image_encode_png],
lazy_loaded_esm = ["01_image.js"],
);

Expand Down
1 change: 1 addition & 0 deletions ext/webgpu/01_webgpu.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ class GPUAdapter {
requiredFeatures,
descriptor.requiredLimits,
);
core.close(this[_adapter].rid);

const inner = new InnerGPUDevice({
rid,
Expand Down