mirror of
https://github.com/d07RiV/diabloweb.git
synced 2026-07-03 11:51:35 +00:00
offscreen render
This commit is contained in:
5828
package-lock.json
generated
5828
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
src/App.js
32
src/App.js
@@ -129,36 +129,7 @@ class App extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onRender({images, text, clip, belt}) {
|
updateBelt(belt) {
|
||||||
const ctx = this.renderer;
|
|
||||||
if (!ctx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (let {x, y, w, h, image, data} of images) {
|
|
||||||
if (!image) {
|
|
||||||
image = ctx.createImageData(w, h);
|
|
||||||
image.data.set(data);
|
|
||||||
}
|
|
||||||
ctx.putImageData(image, x, y);
|
|
||||||
}
|
|
||||||
if (text.length) {
|
|
||||||
ctx.save();
|
|
||||||
ctx.font = 'bold 13px Times New Roman';
|
|
||||||
if (clip) {
|
|
||||||
const {x0, y0, x1, y1} = clip;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.rect(x0, y0, x1 - x0, y1 - y0);
|
|
||||||
ctx.clip();
|
|
||||||
}
|
|
||||||
for (let {x, y, text: str, color} of text) {
|
|
||||||
const r = ((color >> 16) & 0xFF);
|
|
||||||
const g = ((color >> 8) & 0xFF);
|
|
||||||
const b = (color & 0xFF);
|
|
||||||
ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
|
|
||||||
ctx.fillText(str, x, y + 22);
|
|
||||||
}
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
if (belt) {
|
if (belt) {
|
||||||
const used = new Set();
|
const used = new Set();
|
||||||
let pos = 3;
|
let pos = 3;
|
||||||
@@ -185,7 +156,6 @@ class App extends React.Component {
|
|||||||
document.removeEventListener("dragleave", this.onDragLeave, true);
|
document.removeEventListener("dragleave", this.onDragLeave, true);
|
||||||
this.setState({dropping: 0});
|
this.setState({dropping: 0});
|
||||||
|
|
||||||
this.renderer = this.canvas.getContext("2d", {alpha: false});
|
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
|
|
||||||
load_game(this, file).then(game => {
|
load_game(this, file).then(game => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import SpawnModule from './DiabloSpawn.jscc';
|
|||||||
/* eslint-disable-next-line no-restricted-globals */
|
/* eslint-disable-next-line no-restricted-globals */
|
||||||
const worker = self;
|
const worker = self;
|
||||||
|
|
||||||
|
let canvas = null, context = null;
|
||||||
let files = null;
|
let files = null;
|
||||||
let renderBatch = null;
|
let renderBatch = null;
|
||||||
let drawBelt = null;
|
let drawBelt = null;
|
||||||
@@ -15,38 +16,6 @@ const DApi = {
|
|||||||
worker.postMessage({action: "error", error});
|
worker.postMessage({action: "error", error});
|
||||||
},
|
},
|
||||||
|
|
||||||
draw_begin() {
|
|
||||||
renderBatch = {
|
|
||||||
images: [],
|
|
||||||
text: [],
|
|
||||||
clip: null,
|
|
||||||
belt: drawBelt,
|
|
||||||
};
|
|
||||||
drawBelt = null;
|
|
||||||
},
|
|
||||||
draw_blit(x, y, w, h, data) {
|
|
||||||
if (ImageData.length) {
|
|
||||||
const image = new ImageData(w, h);
|
|
||||||
image.data.set(data);
|
|
||||||
renderBatch.images.push({x, y, image});
|
|
||||||
} else {
|
|
||||||
renderBatch.images.push({x, y, w, h, data: data.slice});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
draw_clip_text(x0, y0, x1, y1) {
|
|
||||||
renderBatch.clip = {x0, y0, x1, y1};
|
|
||||||
},
|
|
||||||
draw_text(x, y, text, color) {
|
|
||||||
renderBatch.text.push({x, y, text, color});
|
|
||||||
},
|
|
||||||
draw_end() {
|
|
||||||
worker.postMessage({action: "render", batch: renderBatch});
|
|
||||||
renderBatch = null;
|
|
||||||
},
|
|
||||||
draw_belt(items) {
|
|
||||||
drawBelt = items.slice();
|
|
||||||
},
|
|
||||||
|
|
||||||
get_file_size(path) {
|
get_file_size(path) {
|
||||||
const data = files.get(path.toLowerCase());
|
const data = files.get(path.toLowerCase());
|
||||||
return data ? data.byteLength : 0;
|
return data ? data.byteLength : 0;
|
||||||
@@ -82,16 +51,92 @@ const DApi = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let audioBatch = null;
|
const DApi_renderLegacy = {
|
||||||
|
draw_begin() {
|
||||||
|
renderBatch = {
|
||||||
|
images: [],
|
||||||
|
text: [],
|
||||||
|
clip: null,
|
||||||
|
belt: drawBelt,
|
||||||
|
};
|
||||||
|
drawBelt = null;
|
||||||
|
},
|
||||||
|
draw_blit(x, y, w, h, data) {
|
||||||
|
renderBatch.images.push({x, y, w, h, data: data.slice()});
|
||||||
|
},
|
||||||
|
draw_clip_text(x0, y0, x1, y1) {
|
||||||
|
renderBatch.clip = {x0, y0, x1, y1};
|
||||||
|
},
|
||||||
|
draw_text(x, y, text, color) {
|
||||||
|
renderBatch.text.push({x, y, text, color});
|
||||||
|
},
|
||||||
|
draw_end() {
|
||||||
|
const transfer = renderBatch.images.map(({data}) => data.buffer);
|
||||||
|
if (renderBatch.belt) {
|
||||||
|
transfer.push(renderBatch.belt.buffer);
|
||||||
|
}
|
||||||
|
worker.postMessage({action: "render", batch: renderBatch}, transfer);
|
||||||
|
renderBatch = null;
|
||||||
|
},
|
||||||
|
draw_belt(items) {
|
||||||
|
drawBelt = items.slice();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const DApi_renderOffscreen = {
|
||||||
|
draw_begin() {
|
||||||
|
context.save();
|
||||||
|
context.font = 'bold 13px Times New Roman';
|
||||||
|
},
|
||||||
|
draw_blit(x, y, w, h, data) {
|
||||||
|
const image = context.createImageData(w, h);
|
||||||
|
image.data.set(data);
|
||||||
|
context.putImageData(image, x, y);
|
||||||
|
},
|
||||||
|
draw_clip_text(x0, y0, x1, y1) {
|
||||||
|
context.beginPath();
|
||||||
|
context.rect(x0, y0, x1 - x0, y1 - y0);
|
||||||
|
context.clip();
|
||||||
|
},
|
||||||
|
draw_text(x, y, text, color) {
|
||||||
|
const r = ((color >> 16) & 0xFF);
|
||||||
|
const g = ((color >> 8) & 0xFF);
|
||||||
|
const b = (color & 0xFF);
|
||||||
|
context.fillStyle = `rgb(${r}, ${g}, ${b})`;
|
||||||
|
context.fillText(text, x, y + 22);
|
||||||
|
},
|
||||||
|
draw_end() {
|
||||||
|
context.restore();
|
||||||
|
const bitmap = canvas.transferToImageBitmap();
|
||||||
|
const transfer = [bitmap];
|
||||||
|
if (drawBelt) {
|
||||||
|
transfer.push(drawBelt.buffer);
|
||||||
|
}
|
||||||
|
worker.postMessage({action: "render", batch: {bitmap, belt: drawBelt}}, transfer);
|
||||||
|
drawBelt = null;
|
||||||
|
},
|
||||||
|
draw_belt(items) {
|
||||||
|
drawBelt = items.slice();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let audioBatch = null, audioTransfer = null;
|
||||||
let maxSoundId = 0, maxBatchId = 0;
|
let maxSoundId = 0, maxBatchId = 0;
|
||||||
["create_sound", "duplicate_sound"].forEach(func => {
|
["create_sound", "duplicate_sound"].forEach(func => {
|
||||||
DApi[func] = function(...params) {
|
DApi[func] = function(...params) {
|
||||||
if (audioBatch) {
|
if (audioBatch) {
|
||||||
maxBatchId = params[0] + 1;
|
maxBatchId = params[0] + 1;
|
||||||
audioBatch.push({func, params});
|
audioBatch.push({func, params});
|
||||||
|
if (func === "create_sound") {
|
||||||
|
audioTransfer.push(params[1].buffer);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
maxSoundId = params[0] + 1;
|
maxSoundId = params[0] + 1;
|
||||||
worker.postMessage({action: "audio", func, params});
|
const transfer = [];
|
||||||
|
if (func === "create_sound") {
|
||||||
|
transfer.push(params[1].buffer);
|
||||||
|
}
|
||||||
|
worker.postMessage({action: "audio", func, params}, transfer);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -111,15 +156,17 @@ let wasm = null;
|
|||||||
|
|
||||||
function call_api(func, ...params) {
|
function call_api(func, ...params) {
|
||||||
audioBatch = [];
|
audioBatch = [];
|
||||||
|
audioTransfer = [];
|
||||||
wasm["_" + func](...params);
|
wasm["_" + func](...params);
|
||||||
if (audioBatch.length) {
|
if (audioBatch.length) {
|
||||||
maxSoundId = maxBatchId;
|
maxSoundId = maxBatchId;
|
||||||
worker.postMessage({action: "audioBatch", batch: audioBatch});
|
worker.postMessage({action: "audioBatch", batch: audioBatch}, audioTransfer);
|
||||||
audioBatch = null;
|
audioBatch = null;
|
||||||
|
audioTransfer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init_game(mpq) {
|
async function init_game(mpq, offscreen) {
|
||||||
if (mpq) {
|
if (mpq) {
|
||||||
/* eslint-disable-next-line no-undef */
|
/* eslint-disable-next-line no-undef */
|
||||||
const reader = new FileReaderSync();
|
const reader = new FileReaderSync();
|
||||||
@@ -127,6 +174,14 @@ async function init_game(mpq) {
|
|||||||
files.set('diabdat.mpq', new Uint8Array(data));
|
files.set('diabdat.mpq', new Uint8Array(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (offscreen) {
|
||||||
|
canvas = new OffscreenCanvas(640, 480);
|
||||||
|
context = canvas.getContext("2d");
|
||||||
|
Object.assign(DApi, DApi_renderOffscreen);
|
||||||
|
} else {
|
||||||
|
Object.assign(DApi, DApi_renderLegacy);
|
||||||
|
}
|
||||||
|
|
||||||
wasm = await (mpq ? DiabloModule : SpawnModule)({
|
wasm = await (mpq ? DiabloModule : SpawnModule)({
|
||||||
locateFile(name) {
|
locateFile(name) {
|
||||||
if (name === 'DiabloSpawn.wasm') {
|
if (name === 'DiabloSpawn.wasm') {
|
||||||
@@ -150,9 +205,9 @@ worker.addEventListener("message", ({data}) => {
|
|||||||
switch (data.action) {
|
switch (data.action) {
|
||||||
case "init":
|
case "init":
|
||||||
files = data.files;
|
files = data.files;
|
||||||
init_game(data.mpq).then(
|
init_game(data.mpq, data.offscreen).then(
|
||||||
() => worker.postMessage({action: "loaded"}),
|
() => worker.postMessage({action: "loaded"}),
|
||||||
e => worker.postMessage({action: "failed", error: e.message}));
|
e => {debugger;worker.postMessage({action: "failed", error: e.message || e.name});});
|
||||||
break;
|
break;
|
||||||
case "event":
|
case "event":
|
||||||
call_api(data.func, ...data.params);
|
call_api(data.func, ...data.params);
|
||||||
|
|||||||
@@ -2,6 +2,49 @@ import Worker from './game.worker.js';
|
|||||||
import init_sound from './sound';
|
import init_sound from './sound';
|
||||||
import load_spawn from './load_spawn';
|
import load_spawn from './load_spawn';
|
||||||
|
|
||||||
|
function onRender(api, ctx, {bitmap, images, text, clip, belt}) {
|
||||||
|
if (bitmap) {
|
||||||
|
ctx.transferFromImageBitmap(bitmap);
|
||||||
|
} else {
|
||||||
|
for (let {x, y, w, h, data} of images) {
|
||||||
|
const image = ctx.createImageData(w, h);
|
||||||
|
image.data.set(data);
|
||||||
|
ctx.putImageData(image, x, y);
|
||||||
|
}
|
||||||
|
if (text.length) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.font = 'bold 13px Times New Roman';
|
||||||
|
if (clip) {
|
||||||
|
const {x0, y0, x1, y1} = clip;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.rect(x0, y0, x1 - x0, y1 - y0);
|
||||||
|
ctx.clip();
|
||||||
|
}
|
||||||
|
for (let {x, y, text: str, color} of text) {
|
||||||
|
const r = ((color >> 16) & 0xFF);
|
||||||
|
const g = ((color >> 8) & 0xFF);
|
||||||
|
const b = (color & 0xFF);
|
||||||
|
ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
|
||||||
|
ctx.fillText(str, x, y + 22);
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.updateBelt(belt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testOffscreen() {
|
||||||
|
try {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const offscreen = canvas.transferControlToOffscreen();
|
||||||
|
const context = offscreen.getContext("2d");
|
||||||
|
return context != null;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function do_load_game(api, audio, mpq) {
|
async function do_load_game(api, audio, mpq) {
|
||||||
const fs = await api.fs;
|
const fs = await api.fs;
|
||||||
if (mpq) {
|
if (mpq) {
|
||||||
@@ -9,6 +52,14 @@ async function do_load_game(api, audio, mpq) {
|
|||||||
} else {
|
} else {
|
||||||
await load_spawn(api, fs);
|
await load_spawn(api, fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let context = null, offscreen = false;
|
||||||
|
if (testOffscreen()) {
|
||||||
|
context = api.canvas.getContext("bitmaprenderer");
|
||||||
|
offscreen = true;
|
||||||
|
} else {
|
||||||
|
context = api.canvas.getContext("2d", {alpha: false});
|
||||||
|
}
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const worker = new Worker();
|
const worker = new Worker();
|
||||||
@@ -18,7 +69,7 @@ async function do_load_game(api, audio, mpq) {
|
|||||||
resolve((func, ...params) => worker.postMessage({action: "event", func, params}));
|
resolve((func, ...params) => worker.postMessage({action: "event", func, params}));
|
||||||
break;
|
break;
|
||||||
case "render":
|
case "render":
|
||||||
api.onRender(data.batch);
|
onRender(api, context, data.batch);
|
||||||
break;
|
break;
|
||||||
case "audio":
|
case "audio":
|
||||||
audio[data.func](...data.params);
|
audio[data.func](...data.params);
|
||||||
@@ -46,7 +97,11 @@ async function do_load_game(api, audio, mpq) {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
worker.postMessage({action: "init", files: fs.files, mpq});
|
const transfer= [];
|
||||||
|
for (let [name, file] of fs.files) {
|
||||||
|
transfer.push(file.buffer);
|
||||||
|
}
|
||||||
|
worker.postMessage({action: "init", files: fs.files, mpq, offscreen}, transfer);
|
||||||
delete fs.files;
|
delete fs.files;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
|
|||||||
Reference in New Issue
Block a user