@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.
- package/dist/assets/preview.html +167 -160
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/dist/assets/preview.html
CHANGED
|
@@ -1,66 +1,71 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html>
|
|
3
|
-
<head>
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
],
|
|
125
|
-
});
|
|
107
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
108
|
+
if (!adapter) {
|
|
109
|
+
throw new Error("No GPU adapter found");
|
|
110
|
+
}
|
|
126
111
|
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
139
|
-
|
|
115
|
+
const format = navigator.gpu.getPreferredCanvasFormat();
|
|
116
|
+
context.configure({ device, format, alphaMode: "opaque" });
|
|
140
117
|
|
|
141
|
-
|
|
142
|
-
if (!device || !pipeline || !videoFrame) return;
|
|
118
|
+
const shaderModule = device.createShaderModule({ code: shaderCode });
|
|
143
119
|
|
|
144
|
-
|
|
145
|
-
|
|
120
|
+
sampler = device.createSampler({
|
|
121
|
+
magFilter: "linear",
|
|
122
|
+
minFilter: "linear",
|
|
123
|
+
});
|
|
146
124
|
|
|
147
|
-
|
|
148
|
-
layout: bindGroupLayout,
|
|
125
|
+
bindGroupLayout = device.createBindGroupLayout({
|
|
149
126
|
entries: [
|
|
150
|
-
{ binding: 0,
|
|
151
|
-
{ binding: 1,
|
|
127
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, externalTexture: {} },
|
|
128
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
|
|
152
129
|
],
|
|
153
130
|
});
|
|
154
131
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
renderPass.draw(3);
|
|
170
|
-
renderPass.end();
|
|
143
|
+
infoEl.textContent = "WebGPU ready, waiting for frames...";
|
|
144
|
+
}
|
|
171
145
|
|
|
172
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
212
|
-
|
|
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.
|
|
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.
|
|
28
|
+
"@napolab/texture-bridge-core": "0.4.1"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"electron": ">=40.0.0"
|