Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions examples/src/examples/misc/html-texture-configurator.example.mjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// @config
//
// 3D product configurator with an HTML UI panel rendered as a WebGL texture via
// the {accent:HTML-in-Canvas} API. Uses {accent:getElementTransform} for
// interactive hit testing — clicks and hovers on the 3D panel are handled by
// the browser's native DOM event system. {accent:Click} to switch shoe
// material variants.
// 3D product configurator with an HTML UI panel rendered as a GPU texture via
// the {accent:HTML-in-Canvas} API, supported on both {accent:WebGL} and
// {accent:WebGPU}. Uses {accent:getElementTransform} for interactive hit
// testing — clicks and hovers on the 3D panel are handled by the browser's
// native DOM event system. {accent:Click} to switch shoe material variants.

//
// This example demonstrates a 3D product configurator where an interactive HTML
// panel (styled with CSS glassmorphism) is rendered as a WebGL texture on a 3D
// panel (styled with CSS glassmorphism) is rendered as a GPU texture on a 3D
// plane next to a shoe model with glTF KHR_materials_variants. The HtmlSync
// class keeps the DOM element's CSS transform in sync with the 3D projection so
// the browser can hit-test clicks and hovers on the HTML buttons.
Expand Down Expand Up @@ -160,7 +160,7 @@ window.addEventListener('resize', resize);

// --- HTML UI Panel ---
// The UI panel is a regular HTML <div> styled with CSS. When HTML-in-Canvas is
// supported it gets appended to the canvas (so it's composited into the WebGL
// supported it gets appended to the canvas (so it's composited into the GPU
// surface and can be used as a texture). Otherwise it falls back to a fixed DOM
// overlay on top of the canvas.
const PANEL_WIDTH = 280;
Expand Down Expand Up @@ -262,9 +262,10 @@ const updatePanel = () => {
};
updatePanel();

// --- HTML-to-WebGL texture pipeline ---
// --- HTML-to-GPU texture pipeline ---
// When HTML-in-Canvas is available, the HTML panel is appended as a child of
// the canvas and captured into a WebGL texture via texElementImage2D. The
// the canvas and captured into a GPU texture (texElementImage2D on WebGL,
// copyElementImageToTexture on WebGPU). The
// browser fires a "paint" event whenever the panel's visual content changes;
// we respond by re-uploading the texture. The first paint uses setSource() to
// bind the element, subsequent paints just call upload().
Expand Down
10 changes: 6 additions & 4 deletions examples/src/examples/misc/html-texture.example.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// @config
//
// Renders live HTML content directly as a WebGL texture via the
// {accent:HTML-in-Canvas} API ({accent:texElementImage2D}). Includes animated
// CSS gradients, text glow, and a pulsing circle — all driven by standard CSS.
// Renders live HTML content directly as a GPU texture via the
// {accent:HTML-in-Canvas} API, supported on both {accent:WebGL} and
// {accent:WebGPU}. Includes animated CSS gradients, text glow, and a pulsing
// circle — all driven by standard CSS.

//
// This example demonstrates the HTML-in-Canvas API: a styled HTML element with
// CSS animations is appended to a canvas marked with the "layoutsubtree"
// attribute, then captured into a WebGL texture via texElementImage2D.
// attribute, then captured into a GPU texture (texElementImage2D on WebGL,
// copyElementImageToTexture on WebGPU).
//
// Fallback: when device.supportsHtmlTextures is false, a static 2D canvas with
// hand-drawn placeholder graphics is used as the texture source instead.
Expand Down
3 changes: 3 additions & 0 deletions src/platform/graphics/webgpu/webgpu-graphics-device.js
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,9 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
*/
this.wgpu = await this.gpuAdapter.requestDevice(deviceDescr);

// HTML-in-Canvas support (copyElementImageToTexture)
this.supportsHtmlTextures = typeof this.wgpu.queue?.copyElementImageToTexture === 'function';

// handle lost device
this.wgpu.lost?.then(this.handleDeviceLost.bind(this));

Expand Down
30 changes: 29 additions & 1 deletion src/platform/graphics/webgpu/webgpu-texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,13 @@ class WebgpuTexture {

} else { // 2d texture

if (this.isExternalImage(mipObject)) {
if (device._isHTMLElementInterface(mipObject) && device.supportsHtmlTextures) {

// generic HTML element via the HTML-in-Canvas API
this.uploadElementImage(device, mipObject, mipLevel, 0);
anyUploads = true;

} else if (this.isExternalImage(mipObject)) {

this.uploadExternalImage(device, mipObject, mipLevel, 0);
anyUploads = true;
Expand Down Expand Up @@ -516,6 +522,28 @@ class WebgpuTexture {
device.wgpu.queue.copyExternalImageToTexture(src, dst, copySize);
}

// upload a generic HTML element via the HTML-in-Canvas API (copyElementImageToTexture)
uploadElementImage(device, element, mipLevel, index) {

Debug.assert(mipLevel < this.desc.mipLevelCount, `Accessing mip level ${mipLevel} of texture with ${this.desc.mipLevelCount} mip levels`, this);

const dst = {
texture: this.gpuTexture,
mipLevel: mipLevel,
origin: [0, 0, index],
aspect: 'all', // can be: "all", "stencil-only", "depth-only"
premultipliedAlpha: this.texture._premultiplyAlpha
};

// submit existing scheduled commands to the queue before copying to preserve the order
device.submit();

Debug.trace(TRACEID_RENDER_QUEUE, `ELEMENT-TO-TEX: mip:${mipLevel} index:${index} ${this.texture.name}`);

// scale the element's rendered image to the texture dimensions
device.wgpu.queue.copyElementImageToTexture(element, this.desc.size.width, this.desc.size.height, dst);
}
Comment thread
mvaligursky marked this conversation as resolved.
Outdated

uploadTypedArrayData(device, data, mipLevel, index) {

const texture = this.texture;
Expand Down