mirror of
https://github.com/d07RiV/diabloweb.git
synced 2026-06-03 21:41:38 +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}) {
|
||||
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();
|
||||
}
|
||||
updateBelt(belt) {
|
||||
if (belt) {
|
||||
const used = new Set();
|
||||
let pos = 3;
|
||||
@@ -185,7 +156,6 @@ class App extends React.Component {
|
||||
document.removeEventListener("dragleave", this.onDragLeave, true);
|
||||
this.setState({dropping: 0});
|
||||
|
||||
this.renderer = this.canvas.getContext("2d", {alpha: false});
|
||||
this.setState({loading: true});
|
||||
|
||||
load_game(this, file).then(game => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import SpawnModule from './DiabloSpawn.jscc';
|
||||
/* eslint-disable-next-line no-restricted-globals */
|
||||
const worker = self;
|
||||
|
||||
let canvas = null, context = null;
|
||||
let files = null;
|
||||
let renderBatch = null;
|
||||
let drawBelt = null;
|
||||
@@ -15,38 +16,6 @@ const DApi = {
|
||||
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) {
|
||||
const data = files.get(path.toLowerCase());
|
||||
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;
|
||||
["create_sound", "duplicate_sound"].forEach(func => {
|
||||
DApi[func] = function(...params) {
|
||||
if (audioBatch) {
|
||||
maxBatchId = params[0] + 1;
|
||||
audioBatch.push({func, params});
|
||||
if (func === "create_sound") {
|
||||
audioTransfer.push(params[1].buffer);
|
||||
}
|
||||
} else {
|
||||
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) {
|
||||
audioBatch = [];
|
||||
audioTransfer = [];
|
||||
wasm["_" + func](...params);
|
||||
if (audioBatch.length) {
|
||||
maxSoundId = maxBatchId;
|
||||
worker.postMessage({action: "audioBatch", batch: audioBatch});
|
||||
worker.postMessage({action: "audioBatch", batch: audioBatch}, audioTransfer);
|
||||
audioBatch = null;
|
||||
audioTransfer = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function init_game(mpq) {
|
||||
async function init_game(mpq, offscreen) {
|
||||
if (mpq) {
|
||||
/* eslint-disable-next-line no-undef */
|
||||
const reader = new FileReaderSync();
|
||||
@@ -127,6 +174,14 @@ async function init_game(mpq) {
|
||||
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)({
|
||||
locateFile(name) {
|
||||
if (name === 'DiabloSpawn.wasm') {
|
||||
@@ -142,7 +197,7 @@ async function init_game(mpq) {
|
||||
wasm._DApi_Init(Math.floor(performance.now()));
|
||||
|
||||
setInterval(() => {
|
||||
call_api("DApi_Render", Math.floor(performance.now()));
|
||||
call_api("DApi_Render", Math.floor(performance.now()));
|
||||
}, 50);
|
||||
}
|
||||
|
||||
@@ -150,9 +205,9 @@ worker.addEventListener("message", ({data}) => {
|
||||
switch (data.action) {
|
||||
case "init":
|
||||
files = data.files;
|
||||
init_game(data.mpq).then(
|
||||
init_game(data.mpq, data.offscreen).then(
|
||||
() => worker.postMessage({action: "loaded"}),
|
||||
e => worker.postMessage({action: "failed", error: e.message}));
|
||||
e => {debugger;worker.postMessage({action: "failed", error: e.message || e.name});});
|
||||
break;
|
||||
case "event":
|
||||
call_api(data.func, ...data.params);
|
||||
|
||||
@@ -2,6 +2,49 @@ import Worker from './game.worker.js';
|
||||
import init_sound from './sound';
|
||||
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) {
|
||||
const fs = await api.fs;
|
||||
if (mpq) {
|
||||
@@ -9,6 +52,14 @@ async function do_load_game(api, audio, mpq) {
|
||||
} else {
|
||||
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) => {
|
||||
try {
|
||||
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}));
|
||||
break;
|
||||
case "render":
|
||||
api.onRender(data.batch);
|
||||
onRender(api, context, data.batch);
|
||||
break;
|
||||
case "audio":
|
||||
audio[data.func](...data.params);
|
||||
@@ -46,7 +97,11 @@ async function do_load_game(api, audio, mpq) {
|
||||
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;
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
|
||||
Reference in New Issue
Block a user