@napolab/texture-bridge-renderer 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,66 +1,71 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html>
3
- <head>
4
- <meta charset="utf-8">
5
- <title>Preview (GPU Zero-Copy)</title>
6
- <style>
7
- * { margin: 0; padding: 0; }
8
- body {
9
- background: #111;
10
- display: flex;
11
- flex-direction: column;
12
- align-items: center;
13
- justify-content: center;
14
- height: 100vh;
15
- font-family: -apple-system, BlinkMacSystemFont, sans-serif;
16
- }
17
- #info {
18
- position: absolute;
19
- top: 10px;
20
- left: 10px;
21
- color: #0f0;
22
- font-family: 'SF Mono', Menlo, monospace;
23
- font-size: 12px;
24
- background: rgba(0, 0, 0, 0.7);
25
- padding: 8px 12px;
26
- border-radius: 4px;
27
- z-index: 100;
28
- }
29
- canvas {
30
- max-width: 100%;
31
- max-height: calc(100vh - 20px);
32
- object-fit: contain;
33
- background: #000;
34
- }
35
- .error { color: #f44; }
36
- </style>
37
- </head>
38
- <body>
39
- <div id="info">Initializing WebGPU...</div>
40
- <canvas id="preview"></canvas>
41
-
42
- <script type="module">
43
- const params = new URLSearchParams(location.search);
44
- const W = parseInt(params.get('w'), 10) || 1920;
45
- const H = parseInt(params.get('h'), 10) || 1080;
46
-
47
- const canvas = document.getElementById('preview');
48
- canvas.width = W;
49
- canvas.height = H;
50
-
51
- const infoEl = document.getElementById('info');
52
-
53
- let device = null;
54
- let context = null;
55
- let pipeline = null;
56
- let sampler = null;
57
- let bindGroupLayout = null;
58
-
59
- let frameCount = 0;
60
- let lastTime = performance.now();
61
- let fps = 0;
62
-
63
- const shaderCode = `
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Preview (GPU Zero-Copy)</title>
6
+ <style>
7
+ * {
8
+ margin: 0;
9
+ padding: 0;
10
+ }
11
+ body {
12
+ background: #111;
13
+ display: flex;
14
+ flex-direction: column;
15
+ align-items: center;
16
+ justify-content: center;
17
+ height: 100vh;
18
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
19
+ }
20
+ #info {
21
+ position: absolute;
22
+ top: 10px;
23
+ left: 10px;
24
+ color: #0f0;
25
+ font-family: "SF Mono", Menlo, monospace;
26
+ font-size: 12px;
27
+ background: rgba(0, 0, 0, 0.7);
28
+ padding: 8px 12px;
29
+ border-radius: 4px;
30
+ z-index: 100;
31
+ }
32
+ canvas {
33
+ max-width: 100%;
34
+ max-height: calc(100vh - 20px);
35
+ object-fit: contain;
36
+ background: #000;
37
+ }
38
+ .error {
39
+ color: #f44;
40
+ }
41
+ </style>
42
+ </head>
43
+ <body>
44
+ <div id="info">Initializing WebGPU...</div>
45
+ <canvas id="preview"></canvas>
46
+
47
+ <script type="module">
48
+ const params = new URLSearchParams(location.search);
49
+ const W = parseInt(params.get("w"), 10) || 1920;
50
+ const H = parseInt(params.get("h"), 10) || 1080;
51
+
52
+ const canvas = document.getElementById("preview");
53
+ canvas.width = W;
54
+ canvas.height = H;
55
+
56
+ const infoEl = document.getElementById("info");
57
+
58
+ let device = null;
59
+ let context = null;
60
+ let pipeline = null;
61
+ let sampler = null;
62
+ let bindGroupLayout = null;
63
+
64
+ let frameCount = 0;
65
+ let lastTime = performance.now();
66
+ let fps = 0;
67
+
68
+ const shaderCode = `
64
69
  struct VertexOutput {
65
70
  @builtin(position) position: vec4f,
66
71
  @location(0) texCoord: vec2f,
@@ -94,121 +99,123 @@
94
99
  }
95
100
  `;
96
101
 
97
- async function initWebGPU() {
98
- if (!navigator.gpu) {
99
- throw new Error('WebGPU not supported');
100
- }
101
-
102
- const adapter = await navigator.gpu.requestAdapter();
103
- if (!adapter) {
104
- throw new Error('No GPU adapter found');
105
- }
106
-
107
- device = await adapter.requestDevice();
108
- context = canvas.getContext('webgpu');
109
-
110
- const format = navigator.gpu.getPreferredCanvasFormat();
111
- context.configure({ device, format, alphaMode: 'opaque' });
112
-
113
- const shaderModule = device.createShaderModule({ code: shaderCode });
114
-
115
- sampler = device.createSampler({
116
- magFilter: 'linear',
117
- minFilter: 'linear',
118
- });
102
+ async function initWebGPU() {
103
+ if (!navigator.gpu) {
104
+ throw new Error("WebGPU not supported");
105
+ }
119
106
 
120
- bindGroupLayout = device.createBindGroupLayout({
121
- entries: [
122
- { binding: 0, visibility: GPUShaderStage.FRAGMENT, externalTexture: {} },
123
- { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
124
- ],
125
- });
107
+ const adapter = await navigator.gpu.requestAdapter();
108
+ if (!adapter) {
109
+ throw new Error("No GPU adapter found");
110
+ }
126
111
 
127
- pipeline = device.createRenderPipeline({
128
- layout: device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
129
- vertex: { module: shaderModule, entryPoint: 'vertexMain' },
130
- fragment: {
131
- module: shaderModule,
132
- entryPoint: 'fragmentMain',
133
- targets: [{ format }],
134
- },
135
- primitive: { topology: 'triangle-list' },
136
- });
112
+ device = await adapter.requestDevice();
113
+ context = canvas.getContext("webgpu");
137
114
 
138
- infoEl.textContent = 'WebGPU ready, waiting for frames...';
139
- }
115
+ const format = navigator.gpu.getPreferredCanvasFormat();
116
+ context.configure({ device, format, alphaMode: "opaque" });
140
117
 
141
- function renderFrame(videoFrame) {
142
- if (!device || !pipeline || !videoFrame) return;
118
+ const shaderModule = device.createShaderModule({ code: shaderCode });
143
119
 
144
- try {
145
- const externalTexture = device.importExternalTexture({ source: videoFrame });
120
+ sampler = device.createSampler({
121
+ magFilter: "linear",
122
+ minFilter: "linear",
123
+ });
146
124
 
147
- const bindGroup = device.createBindGroup({
148
- layout: bindGroupLayout,
125
+ bindGroupLayout = device.createBindGroupLayout({
149
126
  entries: [
150
- { binding: 0, resource: externalTexture },
151
- { binding: 1, resource: sampler },
127
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, externalTexture: {} },
128
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
152
129
  ],
153
130
  });
154
131
 
155
- const commandEncoder = device.createCommandEncoder();
156
- const textureView = context.getCurrentTexture().createView();
157
-
158
- const renderPass = commandEncoder.beginRenderPass({
159
- colorAttachments: [{
160
- view: textureView,
161
- clearValue: { r: 0, g: 0, b: 0, a: 1 },
162
- loadOp: 'clear',
163
- storeOp: 'store',
164
- }],
132
+ pipeline = device.createRenderPipeline({
133
+ layout: device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
134
+ vertex: { module: shaderModule, entryPoint: "vertexMain" },
135
+ fragment: {
136
+ module: shaderModule,
137
+ entryPoint: "fragmentMain",
138
+ targets: [{ format }],
139
+ },
140
+ primitive: { topology: "triangle-list" },
165
141
  });
166
142
 
167
- renderPass.setPipeline(pipeline);
168
- renderPass.setBindGroup(0, bindGroup);
169
- renderPass.draw(3);
170
- renderPass.end();
143
+ infoEl.textContent = "WebGPU ready, waiting for frames...";
144
+ }
171
145
 
172
- device.queue.submit([commandEncoder.finish()]);
146
+ function renderFrame(videoFrame) {
147
+ if (!device || !pipeline || !videoFrame) return;
148
+
149
+ try {
150
+ const externalTexture = device.importExternalTexture({ source: videoFrame });
151
+
152
+ const bindGroup = device.createBindGroup({
153
+ layout: bindGroupLayout,
154
+ entries: [
155
+ { binding: 0, resource: externalTexture },
156
+ { binding: 1, resource: sampler },
157
+ ],
158
+ });
159
+
160
+ const commandEncoder = device.createCommandEncoder();
161
+ const textureView = context.getCurrentTexture().createView();
162
+
163
+ const renderPass = commandEncoder.beginRenderPass({
164
+ colorAttachments: [
165
+ {
166
+ view: textureView,
167
+ clearValue: { r: 0, g: 0, b: 0, a: 1 },
168
+ loadOp: "clear",
169
+ storeOp: "store",
170
+ },
171
+ ],
172
+ });
173
+
174
+ renderPass.setPipeline(pipeline);
175
+ renderPass.setBindGroup(0, bindGroup);
176
+ renderPass.draw(3);
177
+ renderPass.end();
178
+
179
+ device.queue.submit([commandEncoder.finish()]);
180
+
181
+ frameCount++;
182
+ const now = performance.now();
183
+ if (now - lastTime >= 1000) {
184
+ fps = (frameCount * 1000) / (now - lastTime);
185
+ frameCount = 0;
186
+ lastTime = now;
187
+ }
173
188
 
174
- frameCount++;
175
- const now = performance.now();
176
- if (now - lastTime >= 1000) {
177
- fps = frameCount * 1000 / (now - lastTime);
178
- frameCount = 0;
179
- lastTime = now;
189
+ infoEl.textContent = `GPU Zero-Copy | FPS: ${fps.toFixed(1)} | ${videoFrame.displayWidth}x${videoFrame.displayHeight}`;
190
+ } catch (err) {
191
+ console.error("[preview] render error:", err);
180
192
  }
181
-
182
- infoEl.textContent = `GPU Zero-Copy | FPS: ${fps.toFixed(1)} | ${videoFrame.displayWidth}x${videoFrame.displayHeight}`;
183
- } catch (err) {
184
- console.error('[preview] render error:', err);
185
193
  }
186
- }
187
-
188
- async function main() {
189
- try {
190
- await initWebGPU();
191
-
192
- window.electronAPI.onTextureFrame((imported) => {
193
- try {
194
- const videoFrame = imported.getVideoFrame();
195
- renderFrame(videoFrame);
196
- videoFrame.close();
197
- imported.release();
198
- } catch (err) {
199
- console.error('[preview] texture error:', err);
200
- }
201
- });
202
194
 
203
- window.electronAPI.previewReady();
204
- } catch (err) {
205
- console.error('[preview] init error:', err);
206
- infoEl.className = 'error';
207
- infoEl.textContent = `WebGPU Error: ${err.message}`;
195
+ async function main() {
196
+ try {
197
+ await initWebGPU();
198
+
199
+ window.electronAPI.onTextureFrame((imported) => {
200
+ try {
201
+ const videoFrame = imported.getVideoFrame();
202
+ renderFrame(videoFrame);
203
+ videoFrame.close();
204
+ imported.release();
205
+ } catch (err) {
206
+ console.error("[preview] texture error:", err);
207
+ }
208
+ });
209
+
210
+ window.electronAPI.previewReady();
211
+ } catch (err) {
212
+ console.error("[preview] init error:", err);
213
+ infoEl.className = "error";
214
+ infoEl.textContent = `WebGPU Error: ${err.message}`;
215
+ }
208
216
  }
209
- }
210
217
 
211
- main();
212
- </script>
213
- </body>
218
+ main();
219
+ </script>
220
+ </body>
214
221
  </html>
package/dist/index.cjs CHANGED
@@ -216,7 +216,7 @@ async function createTextureBridge(options) {
216
216
  const bridge = new TextureBridgeImpl(renderWindow, sender, previewManager, options);
217
217
  renderWindow.webContents.on("paint", (event) => {
218
218
  const texture = event.texture;
219
- if (!texture) return;
219
+ if (!texture?.textureInfo) return;
220
220
  try {
221
221
  (0, _napolab_texture_bridge_core.sendTextureFromPaintEvent)(bridge.sender, texture.textureInfo);
222
222
  bridge.previewManager?.sendFrame(texture);
package/dist/index.mjs CHANGED
@@ -187,7 +187,7 @@ async function createTextureBridge(options) {
187
187
  const bridge = new TextureBridgeImpl(renderWindow, sender, previewManager, options);
188
188
  renderWindow.webContents.on("paint", (event) => {
189
189
  const texture = event.texture;
190
- if (!texture) return;
190
+ if (!texture?.textureInfo) return;
191
191
  try {
192
192
  sendTextureFromPaintEvent(bridge.sender, texture.textureInfo);
193
193
  bridge.previewManager?.sendFrame(texture);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@napolab/texture-bridge-renderer",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "High-level factory API for GPU texture bridge (BrowserWindow + Preview + Sender)",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -25,7 +25,7 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@napolab/texture-bridge-core": "0.4.0"
28
+ "@napolab/texture-bridge-core": "0.4.1"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "electron": ">=40.0.0"